diff --git a/Java-base/maven-doxia-sitetools/Dockerfile b/Java-base/maven-doxia-sitetools/Dockerfile new file mode 100644 index 000000000..e208c4890 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/Dockerfile @@ -0,0 +1,28 @@ +FROM ubuntu:22.04 + +RUN export DEBIAN_FRONTEND=noninteractive \ + && apt-get update \ + && apt-get install -y software-properties-common \ + && add-apt-repository ppa:deadsnakes/ppa \ + && apt-get update \ + && apt-get install -y \ + build-essential \ + git \ + vim \ + jq \ + && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/list/* + +RUN apt-get -y install sudo \ + openjdk-8-jdk \ + maven + +RUN bash -c "echo 2 | update-alternatives --config java" + +COPY src /workspace +WORKDIR /workspace + +RUN mvn install -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false + +RUN mvn test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 + +ENV TZ=Asia/Seoul diff --git a/Java-base/maven-doxia-sitetools/bugs/AbstractDocumentRenderer_532/buggy.java b/Java-base/maven-doxia-sitetools/bugs/AbstractDocumentRenderer_532/buggy.java new file mode 100644 index 000000000..707694137 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/AbstractDocumentRenderer_532/buggy.java @@ -0,0 +1,713 @@ +package org.apache.maven.doxia.docrenderer; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.apache.maven.doxia.Doxia; +import org.apache.maven.doxia.document.DocumentModel; +import org.apache.maven.doxia.document.io.xpp3.DocumentXpp3Reader; +import org.apache.maven.doxia.sink.Sink; +import org.apache.maven.doxia.parser.ParseException; +import org.apache.maven.doxia.parser.Parser; +import org.apache.maven.doxia.parser.manager.ParserNotFoundException; +import org.apache.maven.doxia.logging.PlexusLoggerWrapper; +import org.apache.maven.doxia.parser.module.ParserModule; +import org.apache.maven.doxia.parser.module.ParserModuleManager; +import org.apache.maven.doxia.util.XmlValidator; + +import org.apache.velocity.VelocityContext; +import org.apache.velocity.context.Context; + +import org.codehaus.plexus.component.annotations.Requirement; +import org.codehaus.plexus.logging.AbstractLogEnabled; + +import org.codehaus.plexus.util.DirectoryScanner; +import org.codehaus.plexus.util.FileUtils; +import org.codehaus.plexus.util.IOUtil; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.xml.XmlStreamReader; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; +import org.codehaus.plexus.velocity.SiteResourceLoader; +import org.codehaus.plexus.velocity.VelocityComponent; + +/** + * Abstract document renderer. + * + * @author Vincent Siveton + * @author ltheussl + * @since 1.1 + */ +public abstract class AbstractDocumentRenderer + extends AbstractLogEnabled + implements DocumentRenderer +{ + @Requirement + protected ParserModuleManager parserModuleManager; + + @Requirement + protected Doxia doxia; + + @Requirement + private VelocityComponent velocity; + + /** + * The common base directory of source files. + */ + private String baseDir; + + //-------------------------------------------- + // + //-------------------------------------------- + + /** + * Render an aggregate document from the files found in a Map. + * + * @param filesToProcess the Map of Files to process. The Map should contain as keys the paths of the + * source files (relative to {@link #getBaseDir() baseDir}), and the corresponding ParserModule as values. + * @param outputDirectory the output directory where the aggregate document should be generated. + * @param documentModel the document model, containing all the metadata, etc. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + * @throws java.io.IOException if any + * @deprecated since 1.1.2, use {@link #render(Map, File, DocumentModel, DocumentRendererContext)} + */ + public abstract void render( Map filesToProcess, File outputDirectory, + DocumentModel documentModel ) + throws DocumentRendererException, IOException; + + //-------------------------------------------- + // + //-------------------------------------------- + + /** {@inheritDoc} */ + public void render( Collection files, File outputDirectory, DocumentModel documentModel ) + throws DocumentRendererException, IOException + { + render( getFilesToProcess( files ), outputDirectory, documentModel, null ); + } + + /** {@inheritDoc} */ + public void render( File baseDirectory, File outputDirectory, DocumentModel documentModel ) + throws DocumentRendererException, IOException + { + render( baseDirectory, outputDirectory, documentModel, null ); + } + + /** + * Render an aggregate document from the files found in a Map. + * + * @param filesToProcess the Map of Files to process. The Map should contain as keys the paths of the + * source files (relative to {@link #getBaseDir() baseDir}), and the corresponding ParserModule as values. + * @param outputDirectory the output directory where the aggregate document should be generated. + * @param documentModel the document model, containing all the metadata, etc. + * @param context the rendering context when processing files. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + * @throws java.io.IOException if any + */ + public void render( Map filesToProcess, File outputDirectory, DocumentModel documentModel, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + // nop + } + + /** + * Render a document from the files found in a source directory, depending on a rendering context. + * + * @param baseDirectory the directory containing the source files. + * This should follow the standard Maven convention, ie containing all the site modules. + * @param outputDirectory the output directory where the document should be generated. + * @param documentModel the document model, containing all the metadata, etc. + * If the model contains a TOC, only the files found in this TOC are rendered, + * otherwise all files found under baseDirectory will be processed. + * If the model is null, render all files from baseDirectory individually. + * @param context the rendering context when processing files. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + * @throws java.io.IOException if any + * @since 1.1.2 + */ + public void render( File baseDirectory, File outputDirectory, DocumentModel documentModel, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + render( getFilesToProcess( baseDirectory ), outputDirectory, documentModel, context ); + } + + /** + * Render a document from the files found in baseDirectory. This just forwards to + * {@link #render(File,File,DocumentModel)} with a new DocumentModel. + * + * @param baseDirectory the directory containing the source files. + * This should follow the standard Maven convention, ie containing all the site modules. + * @param outputDirectory the output directory where the document should be generated. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + * @throws java.io.IOException if any + * @see #render(File, File, DocumentModel) + */ + public void render( File baseDirectory, File outputDirectory ) + throws DocumentRendererException, IOException + { + render( baseDirectory, outputDirectory, (DocumentModel) null ); + } + + /** + * Render a document from the files found in baseDirectory. + * + * @param baseDirectory the directory containing the source files. + * This should follow the standard Maven convention, ie containing all the site modules. + * @param outputDirectory the output directory where the document should be generated. + * @param documentDescriptor a file containing the document model. + * If this file does not exist or is null, some default settings will be used. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + * @throws java.io.IOException if any + * @see #render(File, File) if documentDescriptor does not exist or is null + * @see #render(Map, File, DocumentModel) otherwise + */ + public void render( File baseDirectory, File outputDirectory, File documentDescriptor ) + throws DocumentRendererException, IOException + { + if ( ( documentDescriptor == null ) || ( !documentDescriptor.exists() ) ) + { + getLogger().warn( "No documentDescriptor found: using default settings!" ); + + render( baseDirectory, outputDirectory ); + } + else + { + render( getFilesToProcess( baseDirectory ), outputDirectory, readDocumentModel( documentDescriptor ), + null ); + } + } + + /** + * Render documents separately for each file found in a Map. + * + * @param filesToProcess the Map of Files to process. The Map should contain as keys the paths of the + * source files (relative to {@link #getBaseDir() baseDir}), and the corresponding ParserModule as values. + * @param outputDirectory the output directory where the documents should be generated. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + * @throws java.io.IOException if any + * @since 1.1.1 + * @deprecated since 1.1.2, use {@link #renderIndividual(Map, File, DocumentRendererContext)} + */ + public void renderIndividual( Map filesToProcess, File outputDirectory ) + throws DocumentRendererException, IOException + { + // nop + } + + /** + * Render documents separately for each file found in a Map. + * + * @param filesToProcess the Map of Files to process. The Map should contain as keys the paths of the + * source files (relative to {@link #getBaseDir() baseDir}), and the corresponding ParserModule as values. + * @param outputDirectory the output directory where the documents should be generated. + * @param context the rendering context. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + * @throws java.io.IOException if any + * @since 1.1.2 + */ + public void renderIndividual( Map filesToProcess, File outputDirectory, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + // nop + } + + /** + * Returns a Map of files to process. The Map contains as keys the paths of the source files + * (relative to {@link #getBaseDir() baseDir}), and the corresponding ParserModule as values. + * + * @param baseDirectory the directory containing the source files. + * This should follow the standard Maven convention, ie containing all the site modules. + * @return a Map of files to process. + * @throws java.io.IOException in case of a problem reading the files under baseDirectory. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + */ + public Map getFilesToProcess( File baseDirectory ) + throws IOException, DocumentRendererException + { + if ( !baseDirectory.isDirectory() ) + { + getLogger().warn( "No files found to process!" ); + + return new HashMap(); + } + + setBaseDir( baseDirectory.getAbsolutePath() ); + + Map filesToProcess = new LinkedHashMap(); + Map duplicatesFiles = new LinkedHashMap(); + + Collection modules = parserModuleManager.getParserModules(); + for ( ParserModule module : modules ) + { + File moduleBasedir = new File( baseDirectory, module.getSourceDirectory() ); + + if ( moduleBasedir.exists() ) + { + // TODO: handle in/excludes + List allFiles = FileUtils.getFileNames( moduleBasedir, "**/*.*", null, false ); + + String[] extensions = getExtensions( module ); + List docs = new LinkedList( allFiles ); + // Take care of extension case + for ( Iterator it = docs.iterator(); it.hasNext(); ) + { + String name = it.next().trim(); + + if ( !endsWithIgnoreCase( name, extensions ) ) + { + it.remove(); + } + } + + String[] vmExtensions = new String[extensions.length]; + for ( int i = 0; i < extensions.length; i++ ) + { + vmExtensions[i] = extensions[i] + ".vm"; + } + List velocityFiles = new LinkedList( allFiles ); + // *.xml.vm + for ( Iterator it = velocityFiles.iterator(); it.hasNext(); ) + { + String name = it.next().trim(); + + if ( !endsWithIgnoreCase( name, vmExtensions ) ) + { + it.remove(); + } + } + docs.addAll( velocityFiles ); + + for ( String filePath : docs ) + { + filePath = filePath.trim(); + + if ( filePath.lastIndexOf( '.' ) > 0 ) + { + String key = filePath.substring( 0, filePath.lastIndexOf( '.' ) ); + + if ( duplicatesFiles.containsKey( key ) ) + { + throw new DocumentRendererException( "Files '" + module.getSourceDirectory() + + File.separator + filePath + "' clashes with existing '" + + duplicatesFiles.get( key ) + "'." ); + } + + duplicatesFiles.put( key, module.getSourceDirectory() + File.separator + filePath ); + } + + filesToProcess.put( filePath, module ); + } + } + } + + return filesToProcess; + } + + protected static String[] getExtensions( ParserModule module ) + { + String[] extensions = new String[module.getExtensions().length]; + for ( int i = module.getExtensions().length - 1; i >= 0; i-- ) + { + extensions[i] = '.' + module.getExtensions()[i]; + } + return extensions; + } + + // TODO replace with StringUtils.endsWithIgnoreCase() from maven-shared-utils 0.7 + protected 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() ); + } + + protected static boolean endsWithIgnoreCase( String str, String[] searchStrs ) + { + for ( String searchStr : searchStrs ) + { + if ( endsWithIgnoreCase( str, searchStr ) ) + { + return true; + } + } + return false; + } + + /** + * Returns a Map of files to process. The Map contains as keys the paths of the source files + * (relative to {@link #getBaseDir() baseDir}), and the corresponding ParserModule as values. + * + * @param files The Collection of source files. + * @return a Map of files to process. + */ + public Map getFilesToProcess( Collection files ) + { + // ---------------------------------------------------------------------- + // Map all the file names to parser ids + // ---------------------------------------------------------------------- + + Map filesToProcess = new HashMap(); + + Collection modules = parserModuleManager.getParserModules(); + for ( ParserModule module : modules ) + { + String[] extensions = getExtensions( module ); + + String sourceDirectory = File.separator + module.getSourceDirectory() + File.separator; + + for ( String file : files ) + { + // first check if the file path contains one of the recognized source dir identifiers + // (there's trouble if a pathname contains 2 identifiers), then match file extensions (not unique). + + if ( file.indexOf( sourceDirectory ) != -1 ) + { + filesToProcess.put( file, module ); + } + else + { + // don't overwrite if it's there already + if ( endsWithIgnoreCase( file, extensions ) && !filesToProcess.containsKey( file ) ) + { + filesToProcess.put( file, module ); + } + } + } + } + + return filesToProcess; + } + + /** {@inheritDoc} */ + public DocumentModel readDocumentModel( File documentDescriptor ) + throws DocumentRendererException, IOException + { + DocumentModel documentModel; + + Reader reader = null; + try + { + reader = ReaderFactory.newXmlReader( documentDescriptor ); + documentModel = new DocumentXpp3Reader().read( reader ); + } + catch ( XmlPullParserException e ) + { + throw new DocumentRendererException( "Error parsing document descriptor", e ); + } + finally + { + IOUtil.close( reader ); + } + + return documentModel; + } + + /** + * Sets the current base directory. + * + * @param newDir the absolute path to the base directory to set. + */ + public void setBaseDir( String newDir ) + { + this.baseDir = newDir; + } + + /** + * Return the current base directory. + * + * @return the current base directory. + */ + public String getBaseDir() + { + return this.baseDir; + } + + //-------------------------------------------- + // + //-------------------------------------------- + + /** + * Parse a source document into a sink. + * + * @param fullDocPath absolute path to the source document. + * @param parserId determines the parser to use. + * @param sink the sink to receive the events. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException in case of a parsing error. + * @throws java.io.IOException if the source document cannot be opened. + * @deprecated since 1.1.2, use {@link #parse(String, String, Sink, DocumentRendererContext)} + */ + protected void parse( String fullDocPath, String parserId, Sink sink ) + throws DocumentRendererException, IOException + { + parse( fullDocPath, parserId, sink, null ); + } + + /** + * Parse a source document into a sink. + * + * @param fullDocPath absolute path to the source document. + * @param parserId determines the parser to use. + * @param sink the sink to receive the events. + * @param context the rendering context. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException in case of a parsing error. + * @throws java.io.IOException if the source document cannot be opened. + */ +/** + * Parse a source document into a sink. + * + * @param fullDocPath + * absolute path to the source document. + * @param parserId + * determines the parser to use. + * @param sink + * the sink to receive the events. + * @param context + * the rendering context. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException + * in case of a parsing error. + * @throws java.io.IOException + * if the source document cannot be opened. + */ +protected void parse(java.lang.String fullDocPath, java.lang.String parserId, org.apache.maven.doxia.sink.Sink sink, org.apache.maven.doxia.docrenderer.DocumentRendererContext context) throws org.apache.maven.doxia.docrenderer.DocumentRendererException, java.io.IOException { + if (getLogger().isDebugEnabled()) { + getLogger().debug("Parsing file " + fullDocPath); + } + java.io.Reader reader = null; + try { + java.io.File f = new java.io.File(fullDocPath); + org.apache.maven.doxia.parser.Parser parser = doxia.getParser(parserId); + switch (parser.getType()) { + case org.apache.maven.doxia.parser.Parser.XML_TYPE : + reader = org.codehaus.plexus.util.ReaderFactory.newXmlReader(f); + if (org.apache.maven.doxia.docrenderer.AbstractDocumentRenderer.isVelocityFile(f)) { + reader = getVelocityReader(f, ((org.codehaus.plexus.util.xml.XmlStreamReader) (reader)).getEncoding(), context); + } + if ((context != null) && java.lang.Boolean.TRUE.equals(((java.lang.Boolean) (context.get("validate"))))) { + reader = validate(reader, fullDocPath); + } + break; + case org.apache.maven.doxia.parser.Parser.TXT_TYPE : + case org.apache.maven.doxia.parser.Parser.UNKNOWN_TYPE : + default : + if (org.apache.maven.doxia.docrenderer.AbstractDocumentRenderer.isVelocityFile(f)) { + reader = getVelocityReader(f, context == null ? org.codehaus.plexus.util.ReaderFactory.FILE_ENCODING : context.getInputEncoding(), context); + } else { + { + reader = org.codehaus.plexus.util.ReaderFactory.newReader(f, /* NPEX_NULL_EXP */ + context.getInputEncoding()); + } + } + } + sink.enableLogging(new org.apache.maven.doxia.logging.PlexusLoggerWrapper(getLogger())); + doxia.parse(reader, parserId, sink); + } catch (org.apache.maven.doxia.parser.manager.ParserNotFoundException e) { + throw new org.apache.maven.doxia.docrenderer.DocumentRendererException((((("No parser '" + parserId) + "' found for ") + fullDocPath) + ": ") + e.getMessage(), e); + } catch (org.apache.maven.doxia.parser.ParseException e) { + throw new org.apache.maven.doxia.docrenderer.DocumentRendererException((("Error parsing " + fullDocPath) + ": ") + e.getMessage(), e); + } finally { + org.codehaus.plexus.util.IOUtil.close(reader); + sink.flush(); + } +} + + /** + * Copies the contents of the resource directory to an output folder. + * + * @param outputDirectory the destination folder. + * @throws java.io.IOException if any. + */ + protected void copyResources( File outputDirectory ) + throws IOException + { + File resourcesDirectory = new File( getBaseDir(), "resources" ); + + if ( !resourcesDirectory.isDirectory() ) + { + return; + } + + if ( !outputDirectory.exists() ) + { + outputDirectory.mkdirs(); + } + + copyDirectory( resourcesDirectory, outputDirectory ); + } + + /** + * Copy content of a directory, excluding scm-specific files. + * + * @param source directory that contains the files and sub-directories to be copied. + * @param destination destination folder. + * @throws java.io.IOException if any. + */ + protected void copyDirectory( File source, File destination ) + throws IOException + { + if ( source.isDirectory() && destination.isDirectory() ) + { + DirectoryScanner scanner = new DirectoryScanner(); + + String[] includedResources = {"**/**"}; + + scanner.setIncludes( includedResources ); + + scanner.addDefaultExcludes(); + + scanner.setBasedir( source ); + + scanner.scan(); + + List includedFiles = Arrays.asList( scanner.getIncludedFiles() ); + + for ( String name : includedFiles ) + { + File sourceFile = new File( source, name ); + + File destinationFile = new File( destination, name ); + + FileUtils.copyFile( sourceFile, destinationFile ); + } + } + } + + /** + * @param documentModel not null + * @return the output name defined in the documentModel without the output extension. If the output name is not + * defined, return target by default. + * @since 1.1.1 + * @see org.apache.maven.doxia.document.DocumentModel#getOutputName() + * @see #getOutputExtension() + */ + protected String getOutputName( DocumentModel documentModel ) + { + String outputName = documentModel.getOutputName(); + if ( outputName == null ) + { + getLogger().info( "No outputName is defined in the document descriptor. Using 'target'" ); + + documentModel.setOutputName( "target" ); + } + + outputName = outputName.trim(); + if ( outputName.toLowerCase( Locale.ENGLISH ).endsWith( "." + getOutputExtension() ) ) + { + outputName = + outputName.substring( 0, outputName.toLowerCase( Locale.ENGLISH ) + .lastIndexOf( "." + getOutputExtension() ) ); + } + documentModel.setOutputName( outputName ); + + return documentModel.getOutputName(); + } + + /** + * TODO: DOXIA-111: we need a general filter here that knows how to alter the context + * + * @param f the file to process, not null + * @param encoding the wanted encoding, not null + * @param context the current render document context not null + * @return a reader with + * @throws DocumentRendererException + */ + private Reader getVelocityReader( File f, String encoding, DocumentRendererContext context ) + throws DocumentRendererException + { + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Velocity render for " + f.getAbsolutePath() ); + } + + SiteResourceLoader.setResource( f.getAbsolutePath() ); + + Context velocityContext = new VelocityContext(); + + if ( context.getKeys() != null ) + { + for ( int i = 0; i < context.getKeys().length; i++ ) + { + String key = (String) context.getKeys()[i]; + + velocityContext.put( key, context.get( key ) ); + } + } + + StringWriter sw = new StringWriter(); + try + { + velocity.getEngine().mergeTemplate( f.getAbsolutePath(), encoding, velocityContext, sw ); + } + catch ( Exception e ) + { + throw new DocumentRendererException( "Error whenn parsing Velocity file " + f.getAbsolutePath() + ": " + + e.getMessage(), e ); + } + + return new StringReader( sw.toString() ); + } + + /** + * @param f not null + * @return true if file has a vm extension, false otherwise. + */ + private static boolean isVelocityFile( File f ) + { + return FileUtils.getExtension( f.getAbsolutePath() ).toLowerCase( Locale.ENGLISH ).endsWith( "vm" ); + } + + 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 ); + } + } +} diff --git a/Java-base/maven-doxia-sitetools/bugs/AbstractDocumentRenderer_532/npe.json b/Java-base/maven-doxia-sitetools/bugs/AbstractDocumentRenderer_532/npe.json new file mode 100644 index 000000000..e5d1ce951 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/AbstractDocumentRenderer_532/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/AbstractDocumentRenderer.java", + "line": 534, + "npe_method": "parse", + "deref_field": "context", + "npe_class": "AbstractDocumentRenderer" +} \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/bugs/DecorationUtils_113/buggy.java b/Java-base/maven-doxia-sitetools/bugs/DecorationUtils_113/buggy.java new file mode 100644 index 000000000..3aa767149 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/DecorationUtils_113/buggy.java @@ -0,0 +1,127 @@ +package org.apache.maven.doxia.site.decoration; + +/* + * 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 org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.util.xml.Xpp3Dom; + +/** + * Decoration model utilities. + * + * @since 1.7 + */ +public class DecorationUtils +{ + public static boolean isLink( String href ) + { + return StringUtils.isNotBlank( href ) + && ( startsWithAnyIgnoreCase( href, "http:/", "https:/", "ftp:/", "mailto:", "file:/" ) + || href.contains( "://" ) ); + } + + private static boolean startsWithIgnoreCase( String str, String prefix ) + { + if ( str == null || prefix == null ) + { + return ( str == null && prefix == null ); + } + if ( prefix.length() > str.length() ) + { + return false; + } + return str.regionMatches( true, 0, prefix, 0, prefix.length() ); + } + + public static boolean startsWithAnyIgnoreCase( String string, String... searchStrings ) + { + for ( int i = 0; i < searchStrings.length; i++ ) + { + String searchString = searchStrings[i]; + if ( startsWithIgnoreCase( string, searchString ) ) + { + return true; + } + } + return false; + } + + /** + * Helper to get decoration custom DOM element by simply specifying a dotted path. + * + * @param custom the custom DOM element + * @param path the dotted path to the child + * @return null if any element in the path does not exist + * @since 1.8 + */ + public static Xpp3Dom getCustomChild( Xpp3Dom custom, String path ) + { + String[] elements = path.split( "\\." ); + for ( String element : elements ) + { + if ( custom == null ) + { + return null; + } + custom = custom.getChild( element ); + } + return custom; + } + + /** + * Helper to get decoration custom DOM element value by simply specifying a dotted path. + * + * @param custom the custom DOM element + * @param path the dotted path to the child + * @return the element value or null if any element in the path does not exist + * @since 1.8 + */ + public static String getCustomValue( Xpp3Dom custom, String path ) + { + custom = getCustomChild( custom, path ); + return ( custom == null ) ? null : custom.getValue(); + } + + /** + * Helper to get decoration custom DOM element value by simply specifying a dotted path. + * + * @param custom the custom DOM element + * @param path the dotted path to the child + * @param defaultValue default value + * @return the element value or the default value if any element in the path does not exist + * @since 1.8 + */ +/** + * Helper to get decoration custom DOM element value by simply specifying a dotted path. + * + * @param custom + * the custom DOM element + * @param path + * the dotted path to the child + * @param defaultValue + * default value + * @return the element value or the default value if any element in the path does not exist + * @since 1.8 + */ +public static java.lang.String getCustomValue(org.codehaus.plexus.util.xml.Xpp3Dom custom, java.lang.String path, java.lang.String defaultValue) { + custom = org.apache.maven.doxia.site.decoration.DecorationUtils.getCustomChild(/* NPEX_NULL_EXP */ + custom, path); + return custom.getValue(); +} +} diff --git a/Java-base/maven-doxia-sitetools/bugs/DecorationUtils_113/npe.json b/Java-base/maven-doxia-sitetools/bugs/DecorationUtils_113/npe.json new file mode 100644 index 000000000..1bc58a283 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/DecorationUtils_113/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/DecorationUtils.java", + "line": 124, + "npe_method": "getCustomValue", + "deref_field": "custom", + "npe_class": "DecorationUtils" +} \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/bugs/DecorationUtils_78/buggy.java b/Java-base/maven-doxia-sitetools/bugs/DecorationUtils_78/buggy.java new file mode 100644 index 000000000..75dd00cef --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/DecorationUtils_78/buggy.java @@ -0,0 +1,122 @@ +package org.apache.maven.doxia.site.decoration; + +/* + * 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 org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.util.xml.Xpp3Dom; + +/** + * Decoration model utilities. + * + * @since 1.7 + */ +public class DecorationUtils +{ + public static boolean isLink( String href ) + { + return StringUtils.isNotBlank( href ) + && ( startsWithAnyIgnoreCase( href, "http:/", "https:/", "ftp:/", "mailto:", "file:/" ) + || href.contains( "://" ) ); + } + + private static boolean startsWithIgnoreCase( String str, String prefix ) + { + if ( str == null || prefix == null ) + { + return ( str == null && prefix == null ); + } + if ( prefix.length() > str.length() ) + { + return false; + } + return str.regionMatches( true, 0, prefix, 0, prefix.length() ); + } + + public static boolean startsWithAnyIgnoreCase( String string, String... searchStrings ) + { + for ( int i = 0; i < searchStrings.length; i++ ) + { + String searchString = searchStrings[i]; + if ( startsWithIgnoreCase( string, searchString ) ) + { + return true; + } + } + return false; + } + + /** + * Helper to get decoration custom DOM element by simply specifying a dotted path. + * + * @param custom the custom DOM element + * @param path the dotted path to the child + * @return null if any element in the path does not exist + * @since 1.8 + */ +/** + * Helper to get decoration custom DOM element by simply specifying a dotted path. + * + * @param custom + * the custom DOM element + * @param path + * the dotted path to the child + * @return null if any element in the path does not exist + * @since 1.8 + */ +public static org.codehaus.plexus.util.xml.Xpp3Dom getCustomChild(org.codehaus.plexus.util.xml.Xpp3Dom custom, java.lang.String path) { + java.lang.String[] elements = path.split("\\."); + for (java.lang.String element : elements) { + { + custom = /* NPEX_NULL_EXP */ + custom.getChild(element); + } + } + return custom; +} + + /** + * Helper to get decoration custom DOM element value by simply specifying a dotted path. + * + * @param custom the custom DOM element + * @param path the dotted path to the child + * @return the element value or null if any element in the path does not exist + * @since 1.8 + */ + public static String getCustomValue( Xpp3Dom custom, String path ) + { + custom = getCustomChild( custom, path ); + return ( custom == null ) ? null : custom.getValue(); + } + + /** + * Helper to get decoration custom DOM element value by simply specifying a dotted path. + * + * @param custom the custom DOM element + * @param path the dotted path to the child + * @param defaultValue default value + * @return the element value or the default value if any element in the path does not exist + * @since 1.8 + */ + public static String getCustomValue( Xpp3Dom custom, String path, String defaultValue ) + { + custom = getCustomChild( custom, path ); + return ( custom == null ) ? defaultValue : custom.getValue(); + } +} diff --git a/Java-base/maven-doxia-sitetools/bugs/DecorationUtils_78/npe.json b/Java-base/maven-doxia-sitetools/bugs/DecorationUtils_78/npe.json new file mode 100644 index 000000000..94ad4391a --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/DecorationUtils_78/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/DecorationUtils.java", + "line": 88, + "npe_method": "getCustomChild", + "deref_field": "custom", + "npe_class": "DecorationUtils" +} \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/bugs/DecorationUtils_98/buggy.java b/Java-base/maven-doxia-sitetools/bugs/DecorationUtils_98/buggy.java new file mode 100644 index 000000000..b829f8b02 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/DecorationUtils_98/buggy.java @@ -0,0 +1,125 @@ +package org.apache.maven.doxia.site.decoration; + +/* + * 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 org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.util.xml.Xpp3Dom; + +/** + * Decoration model utilities. + * + * @since 1.7 + */ +public class DecorationUtils +{ + public static boolean isLink( String href ) + { + return StringUtils.isNotBlank( href ) + && ( startsWithAnyIgnoreCase( href, "http:/", "https:/", "ftp:/", "mailto:", "file:/" ) + || href.contains( "://" ) ); + } + + private static boolean startsWithIgnoreCase( String str, String prefix ) + { + if ( str == null || prefix == null ) + { + return ( str == null && prefix == null ); + } + if ( prefix.length() > str.length() ) + { + return false; + } + return str.regionMatches( true, 0, prefix, 0, prefix.length() ); + } + + public static boolean startsWithAnyIgnoreCase( String string, String... searchStrings ) + { + for ( int i = 0; i < searchStrings.length; i++ ) + { + String searchString = searchStrings[i]; + if ( startsWithIgnoreCase( string, searchString ) ) + { + return true; + } + } + return false; + } + + /** + * Helper to get decoration custom DOM element by simply specifying a dotted path. + * + * @param custom the custom DOM element + * @param path the dotted path to the child + * @return null if any element in the path does not exist + * @since 1.8 + */ + public static Xpp3Dom getCustomChild( Xpp3Dom custom, String path ) + { + String[] elements = path.split( "\\." ); + for ( String element : elements ) + { + if ( custom == null ) + { + return null; + } + custom = custom.getChild( element ); + } + return custom; + } + + /** + * Helper to get decoration custom DOM element value by simply specifying a dotted path. + * + * @param custom the custom DOM element + * @param path the dotted path to the child + * @return the element value or null if any element in the path does not exist + * @since 1.8 + */ +/** + * Helper to get decoration custom DOM element value by simply specifying a dotted path. + * + * @param custom + * the custom DOM element + * @param path + * the dotted path to the child + * @return the element value or null if any element in the path does not exist + * @since 1.8 + */ +public static java.lang.String getCustomValue(org.codehaus.plexus.util.xml.Xpp3Dom custom, java.lang.String path) { + custom = org.apache.maven.doxia.site.decoration.DecorationUtils.getCustomChild(/* NPEX_NULL_EXP */ + custom, path); + return custom.getValue(); +} + + /** + * Helper to get decoration custom DOM element value by simply specifying a dotted path. + * + * @param custom the custom DOM element + * @param path the dotted path to the child + * @param defaultValue default value + * @return the element value or the default value if any element in the path does not exist + * @since 1.8 + */ + public static String getCustomValue( Xpp3Dom custom, String path, String defaultValue ) + { + custom = getCustomChild( custom, path ); + return ( custom == null ) ? defaultValue : custom.getValue(); + } +} diff --git a/Java-base/maven-doxia-sitetools/bugs/DecorationUtils_98/npe.json b/Java-base/maven-doxia-sitetools/bugs/DecorationUtils_98/npe.json new file mode 100644 index 000000000..43859cec3 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/DecorationUtils_98/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/DecorationUtils.java", + "line": 107, + "npe_method": "getCustomValue", + "deref_field": "custom", + "npe_class": "DecorationUtils" +} \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultDecorationModelInheritanceAssembler_126/buggy.java b/Java-base/maven-doxia-sitetools/bugs/DefaultDecorationModelInheritanceAssembler_126/buggy.java new file mode 100644 index 000000000..6f5f43f73 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/DefaultDecorationModelInheritanceAssembler_126/buggy.java @@ -0,0 +1,458 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.util.ArrayList; +import java.util.List; + +import org.apache.maven.doxia.site.decoration.Banner; +import org.apache.maven.doxia.site.decoration.Body; +import org.apache.maven.doxia.site.decoration.DecorationModel; +import org.apache.maven.doxia.site.decoration.LinkItem; +import org.apache.maven.doxia.site.decoration.Logo; +import org.apache.maven.doxia.site.decoration.Menu; +import org.apache.maven.doxia.site.decoration.MenuItem; + +import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.util.xml.Xpp3Dom; + +/** + * Manage inheritance of the decoration model. + * + * @author Brett Porter + * @author Henning P. Schmiedehausen + */ +@Component( role = DecorationModelInheritanceAssembler.class ) +public class DefaultDecorationModelInheritanceAssembler + implements DecorationModelInheritanceAssembler +{ + /** {@inheritDoc} */ + public void assembleModelInheritance( String name, DecorationModel child, DecorationModel parent, + String childBaseUrl, String parentBaseUrl ) + { + if ( parent == null || !child.isMergeParent() ) + { + return; + } + + child.setCombineSelf( parent.getCombineSelf() ); + + URLRebaser urlContainer = new URLRebaser( parentBaseUrl, childBaseUrl ); + + if ( child.getBannerLeft() == null && parent.getBannerLeft() != null ) + { + child.setBannerLeft( parent.getBannerLeft().clone() ); + rebaseBannerPaths( child.getBannerLeft(), urlContainer ); + } + + if ( child.getBannerRight() == null && parent.getBannerRight() != null ) + { + child.setBannerRight( parent.getBannerRight().clone() ); + rebaseBannerPaths( child.getBannerRight(), urlContainer ); + } + + if ( child.isDefaultPublishDate() && parent.getPublishDate() != null ) + { + child.setPublishDate( parent.getPublishDate().clone() ); + } + + if ( child.isDefaultVersion() && parent.getVersion() != null ) + { + child.setVersion( parent.getVersion().clone() ); + } + + if ( child.getEdit() == null && parent.getEdit() != null ) + { + child.setEdit( parent.getEdit() ); + } + + if ( child.getSkin() == null && parent.getSkin() != null ) + { + child.setSkin( parent.getSkin().clone() ); + } + + child.setPoweredBy( mergePoweredByLists( child.getPoweredBy(), parent.getPoweredBy(), urlContainer ) ); + + if ( parent.getLastModified() > child.getLastModified() ) + { + child.setLastModified( parent.getLastModified() ); + } + + if ( child.getGoogleAdSenseClient() == null && parent.getGoogleAdSenseClient() != null ) + { + child.setGoogleAdSenseClient( parent.getGoogleAdSenseClient() ); + } + + if ( child.getGoogleAdSenseSlot() == null && parent.getGoogleAdSenseSlot() != null ) + { + child.setGoogleAdSenseSlot( parent.getGoogleAdSenseSlot() ); + } + + if ( child.getGoogleAnalyticsAccountId() == null && parent.getGoogleAnalyticsAccountId() != null ) + { + child.setGoogleAnalyticsAccountId( parent.getGoogleAnalyticsAccountId() ); + } + + assembleBodyInheritance( name, child, parent, urlContainer ); + + assembleCustomInheritance( child, parent ); + } + + /** {@inheritDoc} */ +/** + * {@inheritDoc } + */ +public void resolvePaths(final org.apache.maven.doxia.site.decoration.DecorationModel decoration, final java.lang.String baseUrl) { + if (baseUrl == null) { + return; + } + { + relativizeBannerPaths(/* NPEX_NULL_EXP */ + decoration.getBannerLeft(), baseUrl); + } + if (decoration.getBannerRight() != null) { + relativizeBannerPaths(decoration.getBannerRight(), baseUrl); + } + for (org.apache.maven.doxia.site.decoration.Logo logo : decoration.getPoweredBy()) { + relativizeLogoPaths(logo, baseUrl); + } + if (decoration.getBody() != null) { + for (org.apache.maven.doxia.site.decoration.LinkItem linkItem : decoration.getBody().getLinks()) { + relativizeLinkItemPaths(linkItem, baseUrl); + } + for (org.apache.maven.doxia.site.decoration.LinkItem linkItem : decoration.getBody().getBreadcrumbs()) { + relativizeLinkItemPaths(linkItem, baseUrl); + } + for (org.apache.maven.doxia.site.decoration.Menu menu : decoration.getBody().getMenus()) { + relativizeMenuPaths(menu.getItems(), baseUrl); + } + } +} + + /** + * Resolves all relative paths between the elements in a banner. The banner element might contain relative paths + * to the oldBaseUrl, these are changed to the newBannerUrl. + * + * @param banner + * @param baseUrl + */ + private void relativizeBannerPaths( final Banner banner, final String baseUrl ) + { + // banner has been checked to be not null, both href and src may be empty or null + banner.setHref( relativizeLink( banner.getHref(), baseUrl ) ); + banner.setSrc( relativizeLink( banner.getSrc(), baseUrl ) ); + } + + private void rebaseBannerPaths( final Banner banner, final URLRebaser urlContainer ) + { + if ( banner.getHref() != null ) // it may be empty + { + banner.setHref( urlContainer.rebaseLink( banner.getHref() ) ); + } + + if ( banner.getSrc() != null ) + { + banner.setSrc( urlContainer.rebaseLink( banner.getSrc() ) ); + } + } + + private void assembleCustomInheritance( final DecorationModel child, final DecorationModel parent ) + { + if ( child.getCustom() == null ) + { + child.setCustom( parent.getCustom() ); + } + else + { + child.setCustom( Xpp3Dom.mergeXpp3Dom( (Xpp3Dom) child.getCustom(), (Xpp3Dom) parent.getCustom() ) ); + } + } + + private void assembleBodyInheritance( final String name, final DecorationModel child, final DecorationModel parent, + final URLRebaser urlContainer ) + { + Body cBody = child.getBody(); + Body pBody = parent.getBody(); + + if ( cBody != null || pBody != null ) + { + if ( cBody == null ) + { + cBody = new Body(); + child.setBody( cBody ); + } + + if ( pBody == null ) + { + pBody = new Body(); + } + + if ( cBody.getHead() == null && pBody.getHead() != null ) + { + cBody.setHead( pBody.getHead() ); + } + + cBody.setLinks( mergeLinkItemLists( cBody.getLinks(), pBody.getLinks(), urlContainer, false ) ); + + if ( cBody.getBreadcrumbs().isEmpty() && !pBody.getBreadcrumbs().isEmpty() ) + { + LinkItem breadcrumb = new LinkItem(); + breadcrumb.setName( name ); + breadcrumb.setHref( "index.html" ); + cBody.getBreadcrumbs().add( breadcrumb ); + } + cBody.setBreadcrumbs( mergeLinkItemLists( cBody.getBreadcrumbs(), pBody.getBreadcrumbs(), urlContainer, + true ) ); + + cBody.setMenus( mergeMenus( cBody.getMenus(), pBody.getMenus(), urlContainer ) ); + + if ( cBody.getFooter() == null && pBody.getFooter() != null ) + { + cBody.setFooter( pBody.getFooter() ); + } + } + } + + private List mergeMenus( final List childMenus, final List parentMenus, + final URLRebaser urlContainer ) + { + List menus = new ArrayList( childMenus.size() + parentMenus.size() ); + + for ( Menu menu : childMenus ) + { + menus.add( menu ); + } + + int topCounter = 0; + for ( Menu menu : parentMenus ) + { + if ( "top".equals( menu.getInherit() ) ) + { + final Menu clone = menu.clone(); + + rebaseMenuPaths( clone.getItems(), urlContainer ); + + menus.add( topCounter, clone ); + topCounter++; + } + else if ( "bottom".equals( menu.getInherit() ) ) + { + final Menu clone = menu.clone(); + + rebaseMenuPaths( clone.getItems(), urlContainer ); + + menus.add( clone ); + } + } + + return menus; + } + + private void relativizeMenuPaths( final List items, final String baseUrl ) + { + for ( MenuItem item : items ) + { + relativizeLinkItemPaths( item, baseUrl ); + relativizeMenuPaths( item.getItems(), baseUrl ); + } + } + + private void rebaseMenuPaths( final List items, final URLRebaser urlContainer ) + { + for ( MenuItem item : items ) + { + rebaseLinkItemPaths( item, urlContainer ); + rebaseMenuPaths( item.getItems(), urlContainer ); + } + } + + private void relativizeLinkItemPaths( final LinkItem item, final String baseUrl ) + { + item.setHref( relativizeLink( item.getHref(), baseUrl ) ); + } + + private void rebaseLinkItemPaths( final LinkItem item, final URLRebaser urlContainer ) + { + item.setHref( urlContainer.rebaseLink( item.getHref() ) ); + } + + private void relativizeLogoPaths( final Logo logo, final String baseUrl ) + { + logo.setImg( relativizeLink( logo.getImg(), baseUrl ) ); + relativizeLinkItemPaths( logo, baseUrl ); + } + + private void rebaseLogoPaths( final Logo logo, final URLRebaser urlContainer ) + { + logo.setImg( urlContainer.rebaseLink( logo.getImg() ) ); + rebaseLinkItemPaths( logo, urlContainer ); + } + + private List mergeLinkItemLists( final List childList, final List parentList, + final URLRebaser urlContainer, boolean cutParentAfterDuplicate ) + { + List items = new ArrayList( childList.size() + parentList.size() ); + + for ( LinkItem item : parentList ) + { + if ( !items.contains( item ) && !childList.contains( item ) ) + { + final LinkItem clone = item.clone(); + + rebaseLinkItemPaths( clone, urlContainer ); + + items.add( clone ); + } + else if ( cutParentAfterDuplicate ) + { + // if a parent item is found in child, ignore next items (case for breadcrumbs) + // merge ( "B > E", "A > B > C > D" ) -> "A > B > E" (notice missing "C > D") + // see https://issues.apache.org/jira/browse/DOXIASITETOOLS-62 + break; + } + } + + for ( LinkItem item : childList ) + { + if ( !items.contains( item ) ) + { + items.add( item ); + } + } + + return items; + } + + private List mergePoweredByLists( final List childList, final List parentList, + final URLRebaser urlContainer ) + { + List logos = new ArrayList( 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_126/npe.json b/Java-base/maven-doxia-sitetools/bugs/DefaultDecorationModelInheritanceAssembler_126/npe.json new file mode 100644 index 000000000..9b36f9053 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/DefaultDecorationModelInheritanceAssembler_126/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/DefaultDecorationModelInheritanceAssembler.java", + "line": 128, + "npe_method": "resolvePaths", + "deref_field": "getBannerLeft", + "npe_class": "DefaultDecorationModelInheritanceAssembler" +} \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultDecorationModelInheritanceAssembler_131/buggy.java b/Java-base/maven-doxia-sitetools/bugs/DefaultDecorationModelInheritanceAssembler_131/buggy.java new file mode 100644 index 000000000..31a154fe2 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/DefaultDecorationModelInheritanceAssembler_131/buggy.java @@ -0,0 +1,458 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.util.ArrayList; +import java.util.List; + +import org.apache.maven.doxia.site.decoration.Banner; +import org.apache.maven.doxia.site.decoration.Body; +import org.apache.maven.doxia.site.decoration.DecorationModel; +import org.apache.maven.doxia.site.decoration.LinkItem; +import org.apache.maven.doxia.site.decoration.Logo; +import org.apache.maven.doxia.site.decoration.Menu; +import org.apache.maven.doxia.site.decoration.MenuItem; + +import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.util.xml.Xpp3Dom; + +/** + * Manage inheritance of the decoration model. + * + * @author Brett Porter + * @author Henning P. Schmiedehausen + */ +@Component( role = DecorationModelInheritanceAssembler.class ) +public class DefaultDecorationModelInheritanceAssembler + implements DecorationModelInheritanceAssembler +{ + /** {@inheritDoc} */ + public void assembleModelInheritance( String name, DecorationModel child, DecorationModel parent, + String childBaseUrl, String parentBaseUrl ) + { + if ( parent == null || !child.isMergeParent() ) + { + return; + } + + child.setCombineSelf( parent.getCombineSelf() ); + + URLRebaser urlContainer = new URLRebaser( parentBaseUrl, childBaseUrl ); + + if ( child.getBannerLeft() == null && parent.getBannerLeft() != null ) + { + child.setBannerLeft( parent.getBannerLeft().clone() ); + rebaseBannerPaths( child.getBannerLeft(), urlContainer ); + } + + if ( child.getBannerRight() == null && parent.getBannerRight() != null ) + { + child.setBannerRight( parent.getBannerRight().clone() ); + rebaseBannerPaths( child.getBannerRight(), urlContainer ); + } + + if ( child.isDefaultPublishDate() && parent.getPublishDate() != null ) + { + child.setPublishDate( parent.getPublishDate().clone() ); + } + + if ( child.isDefaultVersion() && parent.getVersion() != null ) + { + child.setVersion( parent.getVersion().clone() ); + } + + if ( child.getEdit() == null && parent.getEdit() != null ) + { + child.setEdit( parent.getEdit() ); + } + + if ( child.getSkin() == null && parent.getSkin() != null ) + { + child.setSkin( parent.getSkin().clone() ); + } + + child.setPoweredBy( mergePoweredByLists( child.getPoweredBy(), parent.getPoweredBy(), urlContainer ) ); + + if ( parent.getLastModified() > child.getLastModified() ) + { + child.setLastModified( parent.getLastModified() ); + } + + if ( child.getGoogleAdSenseClient() == null && parent.getGoogleAdSenseClient() != null ) + { + child.setGoogleAdSenseClient( parent.getGoogleAdSenseClient() ); + } + + if ( child.getGoogleAdSenseSlot() == null && parent.getGoogleAdSenseSlot() != null ) + { + child.setGoogleAdSenseSlot( parent.getGoogleAdSenseSlot() ); + } + + if ( child.getGoogleAnalyticsAccountId() == null && parent.getGoogleAnalyticsAccountId() != null ) + { + child.setGoogleAnalyticsAccountId( parent.getGoogleAnalyticsAccountId() ); + } + + assembleBodyInheritance( name, child, parent, urlContainer ); + + assembleCustomInheritance( child, parent ); + } + + /** {@inheritDoc} */ +/** + * {@inheritDoc } + */ +public void resolvePaths(final org.apache.maven.doxia.site.decoration.DecorationModel decoration, final java.lang.String baseUrl) { + if (baseUrl == null) { + return; + } + if (decoration.getBannerLeft() != null) { + relativizeBannerPaths(decoration.getBannerLeft(), baseUrl); + } + { + relativizeBannerPaths(/* NPEX_NULL_EXP */ + decoration.getBannerRight(), baseUrl); + } + for (org.apache.maven.doxia.site.decoration.Logo logo : decoration.getPoweredBy()) { + relativizeLogoPaths(logo, baseUrl); + } + if (decoration.getBody() != null) { + for (org.apache.maven.doxia.site.decoration.LinkItem linkItem : decoration.getBody().getLinks()) { + relativizeLinkItemPaths(linkItem, baseUrl); + } + for (org.apache.maven.doxia.site.decoration.LinkItem linkItem : decoration.getBody().getBreadcrumbs()) { + relativizeLinkItemPaths(linkItem, baseUrl); + } + for (org.apache.maven.doxia.site.decoration.Menu menu : decoration.getBody().getMenus()) { + relativizeMenuPaths(menu.getItems(), baseUrl); + } + } +} + + /** + * Resolves all relative paths between the elements in a banner. The banner element might contain relative paths + * to the oldBaseUrl, these are changed to the newBannerUrl. + * + * @param banner + * @param baseUrl + */ + private void relativizeBannerPaths( final Banner banner, final String baseUrl ) + { + // banner has been checked to be not null, both href and src may be empty or null + banner.setHref( relativizeLink( banner.getHref(), baseUrl ) ); + banner.setSrc( relativizeLink( banner.getSrc(), baseUrl ) ); + } + + private void rebaseBannerPaths( final Banner banner, final URLRebaser urlContainer ) + { + if ( banner.getHref() != null ) // it may be empty + { + banner.setHref( urlContainer.rebaseLink( banner.getHref() ) ); + } + + if ( banner.getSrc() != null ) + { + banner.setSrc( urlContainer.rebaseLink( banner.getSrc() ) ); + } + } + + private void assembleCustomInheritance( final DecorationModel child, final DecorationModel parent ) + { + if ( child.getCustom() == null ) + { + child.setCustom( parent.getCustom() ); + } + else + { + child.setCustom( Xpp3Dom.mergeXpp3Dom( (Xpp3Dom) child.getCustom(), (Xpp3Dom) parent.getCustom() ) ); + } + } + + private void assembleBodyInheritance( final String name, final DecorationModel child, final DecorationModel parent, + final URLRebaser urlContainer ) + { + Body cBody = child.getBody(); + Body pBody = parent.getBody(); + + if ( cBody != null || pBody != null ) + { + if ( cBody == null ) + { + cBody = new Body(); + child.setBody( cBody ); + } + + if ( pBody == null ) + { + pBody = new Body(); + } + + if ( cBody.getHead() == null && pBody.getHead() != null ) + { + cBody.setHead( pBody.getHead() ); + } + + cBody.setLinks( mergeLinkItemLists( cBody.getLinks(), pBody.getLinks(), urlContainer, false ) ); + + if ( cBody.getBreadcrumbs().isEmpty() && !pBody.getBreadcrumbs().isEmpty() ) + { + LinkItem breadcrumb = new LinkItem(); + breadcrumb.setName( name ); + breadcrumb.setHref( "index.html" ); + cBody.getBreadcrumbs().add( breadcrumb ); + } + cBody.setBreadcrumbs( mergeLinkItemLists( cBody.getBreadcrumbs(), pBody.getBreadcrumbs(), urlContainer, + true ) ); + + cBody.setMenus( mergeMenus( cBody.getMenus(), pBody.getMenus(), urlContainer ) ); + + if ( cBody.getFooter() == null && pBody.getFooter() != null ) + { + cBody.setFooter( pBody.getFooter() ); + } + } + } + + private List mergeMenus( final List childMenus, final List parentMenus, + final URLRebaser urlContainer ) + { + List menus = new ArrayList( childMenus.size() + parentMenus.size() ); + + for ( Menu menu : childMenus ) + { + menus.add( menu ); + } + + int topCounter = 0; + for ( Menu menu : parentMenus ) + { + if ( "top".equals( menu.getInherit() ) ) + { + final Menu clone = menu.clone(); + + rebaseMenuPaths( clone.getItems(), urlContainer ); + + menus.add( topCounter, clone ); + topCounter++; + } + else if ( "bottom".equals( menu.getInherit() ) ) + { + final Menu clone = menu.clone(); + + rebaseMenuPaths( clone.getItems(), urlContainer ); + + menus.add( clone ); + } + } + + return menus; + } + + private void relativizeMenuPaths( final List items, final String baseUrl ) + { + for ( MenuItem item : items ) + { + relativizeLinkItemPaths( item, baseUrl ); + relativizeMenuPaths( item.getItems(), baseUrl ); + } + } + + private void rebaseMenuPaths( final List items, final URLRebaser urlContainer ) + { + for ( MenuItem item : items ) + { + rebaseLinkItemPaths( item, urlContainer ); + rebaseMenuPaths( item.getItems(), urlContainer ); + } + } + + private void relativizeLinkItemPaths( final LinkItem item, final String baseUrl ) + { + item.setHref( relativizeLink( item.getHref(), baseUrl ) ); + } + + private void rebaseLinkItemPaths( final LinkItem item, final URLRebaser urlContainer ) + { + item.setHref( urlContainer.rebaseLink( item.getHref() ) ); + } + + private void relativizeLogoPaths( final Logo logo, final String baseUrl ) + { + logo.setImg( relativizeLink( logo.getImg(), baseUrl ) ); + relativizeLinkItemPaths( logo, baseUrl ); + } + + private void rebaseLogoPaths( final Logo logo, final URLRebaser urlContainer ) + { + logo.setImg( urlContainer.rebaseLink( logo.getImg() ) ); + rebaseLinkItemPaths( logo, urlContainer ); + } + + private List mergeLinkItemLists( final List childList, final List parentList, + final URLRebaser urlContainer, boolean cutParentAfterDuplicate ) + { + List items = new ArrayList( childList.size() + parentList.size() ); + + for ( LinkItem item : parentList ) + { + if ( !items.contains( item ) && !childList.contains( item ) ) + { + final LinkItem clone = item.clone(); + + rebaseLinkItemPaths( clone, urlContainer ); + + items.add( clone ); + } + else if ( cutParentAfterDuplicate ) + { + // if a parent item is found in child, ignore next items (case for breadcrumbs) + // merge ( "B > E", "A > B > C > D" ) -> "A > B > E" (notice missing "C > D") + // see https://issues.apache.org/jira/browse/DOXIASITETOOLS-62 + break; + } + } + + for ( LinkItem item : childList ) + { + if ( !items.contains( item ) ) + { + items.add( item ); + } + } + + return items; + } + + private List mergePoweredByLists( final List childList, final List parentList, + final URLRebaser urlContainer ) + { + List logos = new ArrayList( 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_131/npe.json b/Java-base/maven-doxia-sitetools/bugs/DefaultDecorationModelInheritanceAssembler_131/npe.json new file mode 100644 index 000000000..a41cc7848 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/DefaultDecorationModelInheritanceAssembler_131/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/DefaultDecorationModelInheritanceAssembler.java", + "line": 131, + "npe_method": "resolvePaths", + "deref_field": "getBannerRight", + "npe_class": "DefaultDecorationModelInheritanceAssembler" +} \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultDecorationModelInheritanceAssembler_141/buggy.java b/Java-base/maven-doxia-sitetools/bugs/DefaultDecorationModelInheritanceAssembler_141/buggy.java new file mode 100644 index 000000000..e02a26aff --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/DefaultDecorationModelInheritanceAssembler_141/buggy.java @@ -0,0 +1,458 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.util.ArrayList; +import java.util.List; + +import org.apache.maven.doxia.site.decoration.Banner; +import org.apache.maven.doxia.site.decoration.Body; +import org.apache.maven.doxia.site.decoration.DecorationModel; +import org.apache.maven.doxia.site.decoration.LinkItem; +import org.apache.maven.doxia.site.decoration.Logo; +import org.apache.maven.doxia.site.decoration.Menu; +import org.apache.maven.doxia.site.decoration.MenuItem; + +import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.util.xml.Xpp3Dom; + +/** + * Manage inheritance of the decoration model. + * + * @author Brett Porter + * @author Henning P. Schmiedehausen + */ +@Component( role = DecorationModelInheritanceAssembler.class ) +public class DefaultDecorationModelInheritanceAssembler + implements DecorationModelInheritanceAssembler +{ + /** {@inheritDoc} */ + public void assembleModelInheritance( String name, DecorationModel child, DecorationModel parent, + String childBaseUrl, String parentBaseUrl ) + { + if ( parent == null || !child.isMergeParent() ) + { + return; + } + + child.setCombineSelf( parent.getCombineSelf() ); + + URLRebaser urlContainer = new URLRebaser( parentBaseUrl, childBaseUrl ); + + if ( child.getBannerLeft() == null && parent.getBannerLeft() != null ) + { + child.setBannerLeft( parent.getBannerLeft().clone() ); + rebaseBannerPaths( child.getBannerLeft(), urlContainer ); + } + + if ( child.getBannerRight() == null && parent.getBannerRight() != null ) + { + child.setBannerRight( parent.getBannerRight().clone() ); + rebaseBannerPaths( child.getBannerRight(), urlContainer ); + } + + if ( child.isDefaultPublishDate() && parent.getPublishDate() != null ) + { + child.setPublishDate( parent.getPublishDate().clone() ); + } + + if ( child.isDefaultVersion() && parent.getVersion() != null ) + { + child.setVersion( parent.getVersion().clone() ); + } + + if ( child.getEdit() == null && parent.getEdit() != null ) + { + child.setEdit( parent.getEdit() ); + } + + if ( child.getSkin() == null && parent.getSkin() != null ) + { + child.setSkin( parent.getSkin().clone() ); + } + + child.setPoweredBy( mergePoweredByLists( child.getPoweredBy(), parent.getPoweredBy(), urlContainer ) ); + + if ( parent.getLastModified() > child.getLastModified() ) + { + child.setLastModified( parent.getLastModified() ); + } + + if ( child.getGoogleAdSenseClient() == null && parent.getGoogleAdSenseClient() != null ) + { + child.setGoogleAdSenseClient( parent.getGoogleAdSenseClient() ); + } + + if ( child.getGoogleAdSenseSlot() == null && parent.getGoogleAdSenseSlot() != null ) + { + child.setGoogleAdSenseSlot( parent.getGoogleAdSenseSlot() ); + } + + if ( child.getGoogleAnalyticsAccountId() == null && parent.getGoogleAnalyticsAccountId() != null ) + { + child.setGoogleAnalyticsAccountId( parent.getGoogleAnalyticsAccountId() ); + } + + assembleBodyInheritance( name, child, parent, urlContainer ); + + assembleCustomInheritance( child, parent ); + } + + /** {@inheritDoc} */ +/** + * {@inheritDoc } + */ +public void resolvePaths(final org.apache.maven.doxia.site.decoration.DecorationModel decoration, final java.lang.String baseUrl) { + if (baseUrl == null) { + return; + } + if (decoration.getBannerLeft() != null) { + relativizeBannerPaths(decoration.getBannerLeft(), baseUrl); + } + if (decoration.getBannerRight() != null) { + relativizeBannerPaths(decoration.getBannerRight(), baseUrl); + } + for (org.apache.maven.doxia.site.decoration.Logo logo : decoration.getPoweredBy()) { + relativizeLogoPaths(logo, baseUrl); + } + { + for (org.apache.maven.doxia.site.decoration.LinkItem linkItem : /* NPEX_NULL_EXP */ + decoration.getBody().getLinks()) { + relativizeLinkItemPaths(linkItem, baseUrl); + } + for (org.apache.maven.doxia.site.decoration.LinkItem linkItem : decoration.getBody().getBreadcrumbs()) { + relativizeLinkItemPaths(linkItem, baseUrl); + } + for (org.apache.maven.doxia.site.decoration.Menu menu : decoration.getBody().getMenus()) { + relativizeMenuPaths(menu.getItems(), baseUrl); + } + } +} + + /** + * Resolves all relative paths between the elements in a banner. The banner element might contain relative paths + * to the oldBaseUrl, these are changed to the newBannerUrl. + * + * @param banner + * @param baseUrl + */ + private void relativizeBannerPaths( final Banner banner, final String baseUrl ) + { + // banner has been checked to be not null, both href and src may be empty or null + banner.setHref( relativizeLink( banner.getHref(), baseUrl ) ); + banner.setSrc( relativizeLink( banner.getSrc(), baseUrl ) ); + } + + private void rebaseBannerPaths( final Banner banner, final URLRebaser urlContainer ) + { + if ( banner.getHref() != null ) // it may be empty + { + banner.setHref( urlContainer.rebaseLink( banner.getHref() ) ); + } + + if ( banner.getSrc() != null ) + { + banner.setSrc( urlContainer.rebaseLink( banner.getSrc() ) ); + } + } + + private void assembleCustomInheritance( final DecorationModel child, final DecorationModel parent ) + { + if ( child.getCustom() == null ) + { + child.setCustom( parent.getCustom() ); + } + else + { + child.setCustom( Xpp3Dom.mergeXpp3Dom( (Xpp3Dom) child.getCustom(), (Xpp3Dom) parent.getCustom() ) ); + } + } + + private void assembleBodyInheritance( final String name, final DecorationModel child, final DecorationModel parent, + final URLRebaser urlContainer ) + { + Body cBody = child.getBody(); + Body pBody = parent.getBody(); + + if ( cBody != null || pBody != null ) + { + if ( cBody == null ) + { + cBody = new Body(); + child.setBody( cBody ); + } + + if ( pBody == null ) + { + pBody = new Body(); + } + + if ( cBody.getHead() == null && pBody.getHead() != null ) + { + cBody.setHead( pBody.getHead() ); + } + + cBody.setLinks( mergeLinkItemLists( cBody.getLinks(), pBody.getLinks(), urlContainer, false ) ); + + if ( cBody.getBreadcrumbs().isEmpty() && !pBody.getBreadcrumbs().isEmpty() ) + { + LinkItem breadcrumb = new LinkItem(); + breadcrumb.setName( name ); + breadcrumb.setHref( "index.html" ); + cBody.getBreadcrumbs().add( breadcrumb ); + } + cBody.setBreadcrumbs( mergeLinkItemLists( cBody.getBreadcrumbs(), pBody.getBreadcrumbs(), urlContainer, + true ) ); + + cBody.setMenus( mergeMenus( cBody.getMenus(), pBody.getMenus(), urlContainer ) ); + + if ( cBody.getFooter() == null && pBody.getFooter() != null ) + { + cBody.setFooter( pBody.getFooter() ); + } + } + } + + private List mergeMenus( final List childMenus, final List parentMenus, + final URLRebaser urlContainer ) + { + List menus = new ArrayList( childMenus.size() + parentMenus.size() ); + + for ( Menu menu : childMenus ) + { + menus.add( menu ); + } + + int topCounter = 0; + for ( Menu menu : parentMenus ) + { + if ( "top".equals( menu.getInherit() ) ) + { + final Menu clone = menu.clone(); + + rebaseMenuPaths( clone.getItems(), urlContainer ); + + menus.add( topCounter, clone ); + topCounter++; + } + else if ( "bottom".equals( menu.getInherit() ) ) + { + final Menu clone = menu.clone(); + + rebaseMenuPaths( clone.getItems(), urlContainer ); + + menus.add( clone ); + } + } + + return menus; + } + + private void relativizeMenuPaths( final List items, final String baseUrl ) + { + for ( MenuItem item : items ) + { + relativizeLinkItemPaths( item, baseUrl ); + relativizeMenuPaths( item.getItems(), baseUrl ); + } + } + + private void rebaseMenuPaths( final List items, final URLRebaser urlContainer ) + { + for ( MenuItem item : items ) + { + rebaseLinkItemPaths( item, urlContainer ); + rebaseMenuPaths( item.getItems(), urlContainer ); + } + } + + private void relativizeLinkItemPaths( final LinkItem item, final String baseUrl ) + { + item.setHref( relativizeLink( item.getHref(), baseUrl ) ); + } + + private void rebaseLinkItemPaths( final LinkItem item, final URLRebaser urlContainer ) + { + item.setHref( urlContainer.rebaseLink( item.getHref() ) ); + } + + private void relativizeLogoPaths( final Logo logo, final String baseUrl ) + { + logo.setImg( relativizeLink( logo.getImg(), baseUrl ) ); + relativizeLinkItemPaths( logo, baseUrl ); + } + + private void rebaseLogoPaths( final Logo logo, final URLRebaser urlContainer ) + { + logo.setImg( urlContainer.rebaseLink( logo.getImg() ) ); + rebaseLinkItemPaths( logo, urlContainer ); + } + + private List mergeLinkItemLists( final List childList, final List parentList, + final URLRebaser urlContainer, boolean cutParentAfterDuplicate ) + { + List items = new ArrayList( childList.size() + parentList.size() ); + + for ( LinkItem item : parentList ) + { + if ( !items.contains( item ) && !childList.contains( item ) ) + { + final LinkItem clone = item.clone(); + + rebaseLinkItemPaths( clone, urlContainer ); + + items.add( clone ); + } + else if ( cutParentAfterDuplicate ) + { + // if a parent item is found in child, ignore next items (case for breadcrumbs) + // merge ( "B > E", "A > B > C > D" ) -> "A > B > E" (notice missing "C > D") + // see https://issues.apache.org/jira/browse/DOXIASITETOOLS-62 + break; + } + } + + for ( LinkItem item : childList ) + { + if ( !items.contains( item ) ) + { + items.add( item ); + } + } + + return items; + } + + private List mergePoweredByLists( final List childList, final List parentList, + final URLRebaser urlContainer ) + { + List logos = new ArrayList( 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 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 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 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 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 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 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 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 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(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 = /* NPEX_NULL_EXP */ + 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 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_828/npe.json b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_828/npe.json new file mode 100644 index 000000000..0a6d2c958 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_828/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java", + "line": 817, + "npe_method": "createContextForSkin", + "deref_field": "getPrerequisites", + "npe_class": "DefaultSiteRenderer" +} \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_832/buggy.java b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_832/buggy.java new file mode 100644 index 000000000..e680c8ab9 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_832/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(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 = /* NPEX_NULL_EXP */ + 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 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_832/npe.json b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_832/npe.json new file mode 100644 index 000000000..3fb36cc22 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_832/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java", + "line": 819, + "npe_method": "createContextForSkin", + "deref_field": "p", + "npe_class": "DefaultSiteRenderer" +} \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_1036/buggy.java b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_1036/buggy.java new file mode 100644 index 000000000..2f4af0b05 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_1036/buggy.java @@ -0,0 +1,1539 @@ +package org.apache.maven.doxia.tools; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.StringTokenizer; + +import org.apache.commons.io.FilenameUtils; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.factory.ArtifactFactory; +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.artifact.resolver.ArtifactNotFoundException; +import org.apache.maven.artifact.resolver.ArtifactResolutionException; +import org.apache.maven.artifact.resolver.ArtifactResolver; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; +import org.apache.maven.artifact.versioning.VersionRange; +import org.apache.maven.doxia.site.decoration.Banner; +import org.apache.maven.doxia.site.decoration.DecorationModel; +import org.apache.maven.doxia.site.decoration.Menu; +import org.apache.maven.doxia.site.decoration.MenuItem; +import org.apache.maven.doxia.site.decoration.Skin; +import org.apache.maven.doxia.site.decoration.inheritance.DecorationModelInheritanceAssembler; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Reader; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Writer; +import org.apache.maven.model.DistributionManagement; +import org.apache.maven.model.Site; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.MavenProjectBuilder; +import org.apache.maven.project.ProjectBuildingException; +import org.apache.maven.reporting.MavenReport; +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.IOUtil; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.interpolation.EnvarBasedValueSource; +import org.codehaus.plexus.interpolation.InterpolationException; +import org.codehaus.plexus.interpolation.MapBasedValueSource; +import org.codehaus.plexus.interpolation.ObjectBasedValueSource; +import org.codehaus.plexus.interpolation.PrefixedObjectValueSource; +import org.codehaus.plexus.interpolation.PrefixedPropertiesValueSource; +import org.codehaus.plexus.interpolation.RegexBasedInterpolator; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +/** + * Default implementation of the site tool. + * + * @author Vincent Siveton + */ +@Component( role = SiteTool.class ) +public class DefaultSiteTool + extends AbstractLogEnabled + implements SiteTool +{ + // ---------------------------------------------------------------------- + // Components + // ---------------------------------------------------------------------- + + /** + * The component that is used to resolve additional artifacts required. + */ + @Requirement + private ArtifactResolver artifactResolver; + + /** + * The component used for creating artifact instances. + */ + @Requirement + private ArtifactFactory artifactFactory; + + /** + * Internationalization. + */ + @Requirement + protected I18N i18n; + + /** + * The component for assembling inheritance. + */ + @Requirement + protected DecorationModelInheritanceAssembler assembler; + + /** + * Project builder (deprecated in Maven 3: should use ProjectBuilder, which will avoid + * issues like DOXIASITETOOLS-166) + */ + @Requirement + protected MavenProjectBuilder mavenProjectBuilder; + + // ---------------------------------------------------------------------- + // Public methods + // ---------------------------------------------------------------------- + + public Artifact getSkinArtifactFromRepository( ArtifactRepository localRepository, + List remoteArtifactRepositories, + DecorationModel decoration ) + throws SiteToolException + { + checkNotNull( "localRepository", localRepository ); + checkNotNull( "remoteArtifactRepositories", remoteArtifactRepositories ); + checkNotNull( "decoration", decoration ); + + Skin skin = decoration.getSkin(); + + if ( skin == null ) + { + skin = Skin.getDefaultSkin(); + } + + String version = skin.getVersion(); + Artifact artifact; + try + { + if ( version == null ) + { + version = Artifact.RELEASE_VERSION; + } + VersionRange versionSpec = VersionRange.createFromVersionSpec( version ); + artifact = artifactFactory.createDependencyArtifact( skin.getGroupId(), skin.getArtifactId(), versionSpec, + "jar", null, null ); + + artifactResolver.resolve( artifact, remoteArtifactRepositories, localRepository ); + } + catch ( InvalidVersionSpecificationException e ) + { + throw new SiteToolException( "InvalidVersionSpecificationException: The skin version '" + version + + "' is not valid: " + e.getMessage(), e ); + } + catch ( ArtifactResolutionException e ) + { + throw new SiteToolException( "ArtifactResolutionException: Unable to find skin", e ); + } + catch ( ArtifactNotFoundException e ) + { + throw new SiteToolException( "ArtifactNotFoundException: The skin does not exist: " + e.getMessage(), e ); + } + + return artifact; + } + + public Artifact getDefaultSkinArtifact( ArtifactRepository localRepository, + List remoteArtifactRepositories ) + throws SiteToolException + { + return getSkinArtifactFromRepository( localRepository, remoteArtifactRepositories, new DecorationModel() ); + } + + /** + * This method is not implemented according to the URI specification and has many weird + * corner cases where it doesn't do the right thing. Please consider using a better + * implemented method from a different library such as org.apache.http.client.utils.URIUtils#resolve. + */ + @Deprecated + public String getRelativePath( String to, String from ) + { + checkNotNull( "to", to ); + checkNotNull( "from", from ); + + if ( to.contains( ":" ) && from.contains( ":" ) ) + { + String toScheme = to.substring( 0, to.lastIndexOf( ':' ) ); + String fromScheme = from.substring( 0, from.lastIndexOf( ':' ) ); + if ( !toScheme.equals( fromScheme ) ) + { + return to; + } + } + + URL toUrl = null; + URL fromUrl = null; + + String toPath = to; + String fromPath = from; + + try + { + toUrl = new URL( to ); + } + catch ( MalformedURLException e ) + { + try + { + toUrl = new File( getNormalizedPath( to ) ).toURI().toURL(); + } + catch ( MalformedURLException e1 ) + { + getLogger().warn( "Unable to load a URL for '" + to + "': " + e.getMessage() ); + return to; + } + } + + try + { + fromUrl = new URL( from ); + } + catch ( MalformedURLException e ) + { + try + { + fromUrl = new File( getNormalizedPath( from ) ).toURI().toURL(); + } + catch ( MalformedURLException e1 ) + { + getLogger().warn( "Unable to load a URL for '" + from + "': " + e.getMessage() ); + return to; + } + } + + if ( toUrl != null && fromUrl != null ) + { + // URLs, determine if they share protocol and domain info + + if ( ( toUrl.getProtocol().equalsIgnoreCase( fromUrl.getProtocol() ) ) + && ( toUrl.getHost().equalsIgnoreCase( fromUrl.getHost() ) ) + && ( toUrl.getPort() == fromUrl.getPort() ) ) + { + // shared URL domain details, use URI to determine relative path + + toPath = toUrl.getFile(); + fromPath = fromUrl.getFile(); + } + else + { + // don't share basic URL information, no relative available + + return to; + } + } + else if ( ( toUrl != null && fromUrl == null ) || ( toUrl == null && fromUrl != null ) ) + { + // one is a URL and the other isn't, no relative available. + + return to; + } + + // either the two locations are not URLs or if they are they + // share the common protocol and domain info and we are left + // with their URI information + + String relativePath = getRelativeFilePath( fromPath, toPath ); + + if ( relativePath == null ) + { + relativePath = to; + } + + if ( getLogger().isDebugEnabled() && !relativePath.toString().equals( to ) ) + { + getLogger().debug( "Mapped url: " + to + " to relative path: " + relativePath ); + } + + return relativePath; + } + + private static String getRelativeFilePath( final String oldPath, final String newPath ) + { + // normalize the path delimiters + + String fromPath = new File( oldPath ).getPath(); + String toPath = new File( newPath ).getPath(); + + // strip any leading slashes if its a windows path + if ( toPath.matches( "^\\[a-zA-Z]:" ) ) + { + toPath = toPath.substring( 1 ); + } + if ( fromPath.matches( "^\\[a-zA-Z]:" ) ) + { + fromPath = fromPath.substring( 1 ); + } + + // lowercase windows drive letters. + if ( fromPath.startsWith( ":", 1 ) ) + { + fromPath = Character.toLowerCase( fromPath.charAt( 0 ) ) + fromPath.substring( 1 ); + } + if ( toPath.startsWith( ":", 1 ) ) + { + toPath = Character.toLowerCase( toPath.charAt( 0 ) ) + toPath.substring( 1 ); + } + + // check for the presence of windows drives. No relative way of + // traversing from one to the other. + + if ( ( toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) + && ( !toPath.substring( 0, 1 ).equals( fromPath.substring( 0, 1 ) ) ) ) + { + // they both have drive path element but they don't match, no + // relative path + + return null; + } + + if ( ( toPath.startsWith( ":", 1 ) && !fromPath.startsWith( ":", 1 ) ) + || ( !toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) ) + { + + // one has a drive path element and the other doesn't, no relative + // path. + + return null; + + } + + final String relativePath = buildRelativePath( toPath, fromPath, File.separatorChar ); + + return relativePath.toString(); + } + + /** {@inheritDoc} */ + public File getSiteDescriptor( File siteDirectory, Locale locale ) + { + checkNotNull( "siteDirectory", siteDirectory ); + final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale; + + File siteDescriptor = new File( siteDirectory, "site_" + llocale.getLanguage() + ".xml" ); + + if ( !siteDescriptor.isFile() ) + { + siteDescriptor = new File( siteDirectory, "site.xml" ); + } + return siteDescriptor; + } + + /** + * Get a site descriptor from one of the repositories. + * + * @param project the Maven project, not null. + * @param localRepository the Maven local repository, not null. + * @param repositories the Maven remote repositories, not null. + * @param locale the locale wanted for the site descriptor. If not null, searching for + * site_localeLanguage.xml, otherwise searching for site.xml. + * @return the site descriptor into the local repository after download of it from repositories or null if not + * found in repositories. + * @throws SiteToolException if any + */ + File getSiteDescriptorFromRepository( MavenProject project, ArtifactRepository localRepository, + List repositories, Locale locale ) + throws SiteToolException + { + checkNotNull( "project", project ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "repositories", repositories ); + + final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale; + + try + { + return resolveSiteDescriptor( project, localRepository, repositories, llocale ); + } + catch ( ArtifactNotFoundException e ) + { + getLogger().debug( "ArtifactNotFoundException: Unable to locate site descriptor: " + e ); + return null; + } + catch ( ArtifactResolutionException e ) + { + throw new SiteToolException( "ArtifactResolutionException: Unable to locate site descriptor: " + + e.getMessage(), e ); + } + catch ( IOException e ) + { + throw new SiteToolException( "IOException: Unable to locate site descriptor: " + e.getMessage(), e ); + } + } + + /** + * Read site descriptor content from Reader, adding support for deprecated ${reports}, + * ${parentProject} and ${modules} tags. + * + * @param reader + * @return the input content interpolated with deprecated tags + * @throws IOException + */ + private String readSiteDescriptor( Reader reader, String projectId ) + throws IOException + { + String siteDescriptorContent = IOUtil.toString( reader ); + + // This is to support the deprecated ${reports}, ${parentProject} and ${modules} tags. + Properties props = new Properties(); + props.put( "reports", "
" ); + props.put( "modules", "" ); + props.put( "parentProject", "" ); + + // warn if interpolation required + for ( Object prop : props.keySet() ) + { + if ( siteDescriptorContent.contains( "$" + prop ) ) + { + getLogger().warn( "Site descriptor for " + projectId + " contains $" + prop + + ": should be replaced with " + props.getProperty( (String) prop ) ); + } + if ( siteDescriptorContent.contains( "${" + prop + "}" ) ) + { + getLogger().warn( "Site descriptor for " + projectId + " contains ${" + prop + + "}: should be replaced with " + props.getProperty( (String) prop ) ); + } + } + + return StringUtils.interpolate( siteDescriptorContent, props ); + } + + /** {@inheritDoc} */ + public DecorationModel getDecorationModel( File siteDirectory, Locale locale, MavenProject project, + List reactorProjects, ArtifactRepository localRepository, + List repositories ) + throws SiteToolException + { + checkNotNull( "project", project ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "repositories", repositories ); + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + getLogger().debug( "Computing decoration model of " + project.getId() + " for locale " + llocale ); + + Map.Entry result = + getDecorationModel( 0, siteDirectory, llocale, project, reactorProjects, localRepository, repositories ); + DecorationModel decorationModel = result.getKey(); + MavenProject parentProject = result.getValue(); + + if ( decorationModel == null ) + { + getLogger().debug( "Using default site descriptor" ); + + String siteDescriptorContent; + + Reader reader = null; + try + { + // Note the default is not a super class - it is used when nothing else is found + reader = ReaderFactory.newXmlReader( getClass().getResourceAsStream( "/default-site.xml" ) ); + siteDescriptorContent = readSiteDescriptor( reader, "default-site.xml" ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading default site descriptor: " + e.getMessage(), e ); + } + finally + { + IOUtil.close( reader ); + } + + decorationModel = readDecorationModel( siteDescriptorContent ); + } + + // DecorationModel back to String to interpolate, then go back to DecorationModel + String siteDescriptorContent = decorationModelToString( decorationModel ); + + // "classical" late interpolation, after full inheritance + siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, false ); + + decorationModel = readDecorationModel( siteDescriptorContent ); + + if ( parentProject != null ) + { + populateParentMenu( decorationModel, llocale, project, parentProject, true ); + } + + try + { + populateModulesMenu( decorationModel, llocale, project, reactorProjects, localRepository, true ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error while populating modules menu: " + e.getMessage(), e ); + } + + if ( decorationModel.getBannerLeft() == null ) + { + // extra default to set + Banner banner = new Banner(); + banner.setName( project.getName() ); + decorationModel.setBannerLeft( banner ); + } + + return decorationModel; + } + + /** {@inheritDoc} */ + public String getInterpolatedSiteDescriptorContent( Map props, MavenProject aProject, + String siteDescriptorContent ) + throws SiteToolException + { + checkNotNull( "props", props ); + + // "classical" late interpolation + return getInterpolatedSiteDescriptorContent( aProject, siteDescriptorContent, false ); + } + + private String getInterpolatedSiteDescriptorContent( MavenProject aProject, + String siteDescriptorContent, boolean isEarly ) + throws SiteToolException + { + checkNotNull( "aProject", aProject ); + checkNotNull( "siteDescriptorContent", siteDescriptorContent ); + + RegexBasedInterpolator interpolator = new RegexBasedInterpolator(); + + if ( isEarly ) + { + interpolator.addValueSource( new PrefixedObjectValueSource( "this.", aProject ) ); + interpolator.addValueSource( new PrefixedPropertiesValueSource( "this.", aProject.getProperties() ) ); + } + else + { + interpolator.addValueSource( new ObjectBasedValueSource( aProject ) ); + interpolator.addValueSource( new MapBasedValueSource( aProject.getProperties() ) ); + + try + { + interpolator.addValueSource( new EnvarBasedValueSource() ); + } + catch ( IOException e ) + { + // Prefer logging? + throw new SiteToolException( "IOException: cannot interpolate environment properties: " + + e.getMessage(), e ); + } + } + + try + { + // FIXME: this does not escape xml entities, see MSITE-226, PLXCOMP-118 + return interpolator.interpolate( siteDescriptorContent, isEarly ? null : "project" ); + } + catch ( InterpolationException e ) + { + throw new SiteToolException( "Cannot interpolate site descriptor: " + e.getMessage(), e ); + } + } + + /** {@inheritDoc} */ + public MavenProject getParentProject( MavenProject aProject, List reactorProjects, + ArtifactRepository localRepository ) + { + checkNotNull( "aProject", aProject ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + + if ( isMaven3OrMore() ) + { + // no need to make voodoo with Maven 3: job already done + return aProject.getParent(); + } + + MavenProject parentProject = null; + + MavenProject origParent = aProject.getParent(); + if ( origParent != null ) + { + for ( MavenProject reactorProject : reactorProjects ) + { + if ( reactorProject.getGroupId().equals( origParent.getGroupId() ) + && reactorProject.getArtifactId().equals( origParent.getArtifactId() ) + && reactorProject.getVersion().equals( origParent.getVersion() ) ) + { + parentProject = reactorProject; + + getLogger().debug( "Parent project " + origParent.getId() + " picked from reactor" ); + break; + } + } + + if ( parentProject == null && aProject.getBasedir() != null + && StringUtils.isNotEmpty( aProject.getModel().getParent().getRelativePath() ) ) + { + try + { + String relativePath = aProject.getModel().getParent().getRelativePath(); + + File pomFile = new File( aProject.getBasedir(), relativePath ); + + if ( pomFile.isDirectory() ) + { + pomFile = new File( pomFile, "pom.xml" ); + } + pomFile = new File( getNormalizedPath( pomFile.getPath() ) ); + + if ( pomFile.isFile() ) + { + MavenProject mavenProject = mavenProjectBuilder.build( pomFile, localRepository, null ); + + if ( mavenProject.getGroupId().equals( origParent.getGroupId() ) + && mavenProject.getArtifactId().equals( origParent.getArtifactId() ) + && mavenProject.getVersion().equals( origParent.getVersion() ) ) + { + parentProject = mavenProject; + + getLogger().debug( "Parent project " + origParent.getId() + " loaded from a relative path: " + + relativePath ); + } + } + } + catch ( ProjectBuildingException e ) + { + getLogger().info( "Unable to load parent project " + origParent.getId() + " from a relative path: " + + e.getMessage() ); + } + } + + if ( parentProject == null ) + { + try + { + parentProject = mavenProjectBuilder.buildFromRepository( aProject.getParentArtifact(), aProject + .getRemoteArtifactRepositories(), localRepository ); + + getLogger().debug( "Parent project " + origParent.getId() + " loaded from repository" ); + } + catch ( ProjectBuildingException e ) + { + getLogger().warn( "Unable to load parent project " + origParent.getId() + " from repository: " + + e.getMessage() ); + } + } + + if ( parentProject == null ) + { + // fallback to original parent, which may contain uninterpolated value (still need a unit test) + + parentProject = origParent; + + getLogger().debug( "Parent project " + origParent.getId() + " picked from original value" ); + } + } + return parentProject; + } + + /** + * Populate the pre-defined parent menu of the decoration model, + * if used through <menu ref="parent"/>. + * + * @param decorationModel the Doxia Sitetools DecorationModel, not null. + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project a Maven project, not null. + * @param parentProject a Maven parent project, not null. + * @param keepInheritedRefs used for inherited references. + */ + private void populateParentMenu( DecorationModel decorationModel, Locale locale, MavenProject project, + MavenProject parentProject, boolean keepInheritedRefs ) + { + checkNotNull( "decorationModel", decorationModel ); + checkNotNull( "project", project ); + checkNotNull( "parentProject", parentProject ); + + Menu menu = decorationModel.getMenuRef( "parent" ); + + if ( menu == null ) + { + return; + } + + if ( keepInheritedRefs && menu.isInheritAsRef() ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + String parentUrl = getDistMgmntSiteUrl( parentProject ); + + if ( parentUrl != null ) + { + if ( parentUrl.endsWith( "/" ) ) + { + parentUrl += "index.html"; + } + else + { + parentUrl += "/index.html"; + } + + parentUrl = getRelativePath( parentUrl, getDistMgmntSiteUrl( project ) ); + } + else + { + // parent has no url, assume relative path is given by site structure + File parentBasedir = parentProject.getBasedir(); + // First make sure that the parent is available on the file system + if ( parentBasedir != null ) + { + // Try to find the relative path to the parent via the file system + String parentPath = parentBasedir.getAbsolutePath(); + String projectPath = project.getBasedir().getAbsolutePath(); + parentUrl = getRelativePath( parentPath, projectPath ) + "/index.html"; + } + } + + // Only add the parent menu if we were able to find a URL for it + if ( parentUrl == null ) + { + getLogger().warn( "Unable to find a URL to the parent project. The parent menu will NOT be added." ); + } + else + { + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.parentproject" ) ); + } + + MenuItem item = new MenuItem(); + item.setName( parentProject.getName() ); + item.setHref( parentUrl ); + menu.addItem( item ); + } + } + + /** + * Populate the pre-defined modules menu of the decoration model, + * if used through <menu ref="modules"/>. + * + * @param decorationModel the Doxia Sitetools DecorationModel, not null. + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project a Maven project, not null. + * @param reactorProjects the Maven reactor projects, not null. + * @param localRepository the Maven local repository, not null. + * @param keepInheritedRefs used for inherited references. + * @throws SiteToolException if any + * @throws IOException + */ + private void populateModulesMenu( DecorationModel decorationModel, Locale locale, MavenProject project, + List reactorProjects, ArtifactRepository localRepository, + boolean keepInheritedRefs ) + throws SiteToolException, IOException + { + checkNotNull( "project", project ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "decorationModel", decorationModel ); + + Menu menu = decorationModel.getMenuRef( "modules" ); + + if ( menu == null ) + { + return; + } + + if ( keepInheritedRefs && menu.isInheritAsRef() ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale ; + + // we require child modules and reactors to process module menu + if ( project.getModules().size() > 0 ) + { + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectmodules" ) ); + } + + for ( String module : (List) project.getModules() ) + { + MavenProject moduleProject = getModuleFromReactor( project, reactorProjects, module ); + + if ( moduleProject == null ) + { + getLogger().warn( "Module " + module + + " not found in reactor: loading locally" ); + + File f = new File( project.getBasedir(), module + "/pom.xml" ); + if ( f.exists() ) + { + try + { + moduleProject = mavenProjectBuilder.build( f, localRepository, null ); + } + catch ( ProjectBuildingException e ) + { + throw new SiteToolException( "Unable to read local module-POM", e ); + } + } + else + { + getLogger().warn( "No filesystem module-POM available" ); + + moduleProject = new MavenProject(); + moduleProject.setName( module ); + moduleProject.setDistributionManagement( new DistributionManagement() ); + moduleProject.getDistributionManagement().setSite( new Site() ); + moduleProject.getDistributionManagement().getSite().setUrl( module ); + } + } + + String siteUrl = getDistMgmntSiteUrl( moduleProject ); + String itemName = + ( moduleProject.getName() == null ) ? moduleProject.getArtifactId() : moduleProject.getName(); + + appendMenuItem( project, menu, itemName, siteUrl, moduleProject.getArtifactId() ); + } + } + else if ( decorationModel.getMenuRef( "modules" ).getInherit() == null ) + { + // only remove if project has no modules AND menu is not inherited, see MSHARED-174 + decorationModel.removeMenuRef( "modules" ); + } + } + + private static MavenProject getModuleFromReactor( MavenProject project, List reactorProjects, + String module ) + throws IOException + { + File moduleBasedir = new File( project.getBasedir(), module ).getCanonicalFile(); + + for ( MavenProject reactorProject : reactorProjects ) + { + if ( moduleBasedir.equals( reactorProject.getBasedir() ) ) + { + return reactorProject; + } + } + + // module not found in reactor + return null; + } + + /** {@inheritDoc} */ + public void populateReportsMenu( DecorationModel decorationModel, Locale locale, + Map> categories ) + { + checkNotNull( "decorationModel", decorationModel ); + checkNotNull( "categories", categories ); + + Menu menu = decorationModel.getMenuRef( "reports" ); + + if ( menu == null ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectdocumentation" ) ); + } + + boolean found = false; + if ( menu.getItems().isEmpty() ) + { + List categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_INFORMATION ); + if ( !isEmptyList( categoryReports ) ) + { + MenuItem item = createCategoryMenu( + i18n.getString( "site-tool", llocale, + "decorationModel.menu.projectinformation" ), + "/project-info.html", categoryReports, llocale ); + menu.getItems().add( item ); + found = true; + } + + categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_REPORTS ); + if ( !isEmptyList( categoryReports ) ) + { + MenuItem item = + createCategoryMenu( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectreports" ), + "/project-reports.html", categoryReports, llocale ); + menu.getItems().add( item ); + found = true; + } + } + if ( !found ) + { + decorationModel.removeMenuRef( "reports" ); + } + } + + /** {@inheritDoc} */ + public List getSiteLocales( String locales ) + { + if ( locales == null ) + { + return Collections.singletonList( DEFAULT_LOCALE ); + } + + String[] localesArray = StringUtils.split( locales, "," ); + List localesList = new ArrayList( localesArray.length ); + + for ( String localeString : localesArray ) + { + Locale locale = codeToLocale( localeString ); + + if ( locale == null ) + { + continue; + } + + if ( !Arrays.asList( Locale.getAvailableLocales() ).contains( locale ) ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "The locale defined by '" + locale + + "' is not available in this Java Virtual Machine (" + + System.getProperty( "java.version" ) + + " from " + System.getProperty( "java.vendor" ) + ") - IGNORING" ); + } + continue; + } + + // Default bundles are in English + if ( ( !locale.getLanguage().equals( DEFAULT_LOCALE.getLanguage() ) ) + && ( !i18n.getBundle( "site-tool", locale ).getLocale().getLanguage() + .equals( locale.getLanguage() ) ) ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "The locale '" + locale + "' (" + locale.getDisplayName( Locale.ENGLISH ) + + ") is not currently supported by Maven Site - IGNORING." + + "\nContributions are welcome and greatly appreciated!" + + "\nIf you want to contribute a new translation, please visit " + + "http://maven.apache.org/plugins/localization.html for detailed instructions." ); + } + + continue; + } + + localesList.add( locale ); + } + + if ( localesList.isEmpty() ) + { + localesList = Collections.singletonList( DEFAULT_LOCALE ); + } + + return localesList; + } + + /** + * Converts a locale code like "en", "en_US" or "en_US_win" to a java.util.Locale + * object. + *

If localeCode = default, return the current value of the default locale for this instance + * of the Java Virtual Machine.

+ * + * @param localeCode the locale code string. + * @return a java.util.Locale object instanced or null if errors occurred + * @see java.util.Locale#getDefault() + */ + private Locale codeToLocale( String localeCode ) + { + if ( localeCode == null ) + { + return null; + } + + if ( "default".equalsIgnoreCase( localeCode ) ) + { + return Locale.getDefault(); + } + + String language = ""; + String country = ""; + String variant = ""; + + StringTokenizer tokenizer = new StringTokenizer( localeCode, "_" ); + final int maxTokens = 3; + if ( tokenizer.countTokens() > maxTokens ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "Invalid java.util.Locale format for '" + localeCode + "' entry - IGNORING" ); + } + return null; + } + + if ( tokenizer.hasMoreTokens() ) + { + language = tokenizer.nextToken(); + if ( tokenizer.hasMoreTokens() ) + { + country = tokenizer.nextToken(); + if ( tokenizer.hasMoreTokens() ) + { + variant = tokenizer.nextToken(); + } + } + } + + return new Locale( language, country, variant ); + } + + // ---------------------------------------------------------------------- + // Protected methods + // ---------------------------------------------------------------------- + + /** + * @param path could be null. + * @return the path normalized, i.e. by eliminating "/../" and "/./" in the path. + * @see FilenameUtils#normalize(String) + */ +// ---------------------------------------------------------------------- +// Protected methods +// ---------------------------------------------------------------------- +/** + * + * + * @param path + * could be null. + * @return the path normalized, i.e. by eliminating "/../" and "/./" in the path. + * @see FilenameUtils#normalize(String) + */ +protected static java.lang.String getNormalizedPath(java.lang.String path) { + java.lang.String normalized = org.apache.commons.io.FilenameUtils.normalize(path); + if (/* NPEX_NULL_EXP */ + normalized == null) { + normalized = path; + } + return normalized.replace('\\', '/'); +} + + // ---------------------------------------------------------------------- + // Private methods + // ---------------------------------------------------------------------- + + /** + * @param project not null + * @param localRepository not null + * @param repositories not null + * @param locale not null + * @return the resolved site descriptor + * @throws IOException if any + * @throws ArtifactResolutionException if any + * @throws ArtifactNotFoundException if any + */ + private File resolveSiteDescriptor( MavenProject project, ArtifactRepository localRepository, + List repositories, Locale locale ) + throws IOException, ArtifactResolutionException, ArtifactNotFoundException + { + File result; + + // TODO: this is a bit crude - proper type, or proper handling as metadata rather than an artifact in 2.1? + Artifact artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), + project.getArtifactId(), + project.getVersion(), "xml", + "site_" + locale.getLanguage() ); + + boolean found = false; + try + { + artifactResolver.resolve( artifact, repositories, localRepository ); + + result = artifact.getFile(); + + // we use zero length files to avoid re-resolution (see below) + if ( result.length() > 0 ) + { + found = true; + } + else + { + getLogger().debug( "No site descriptor found for " + project.getId() + " for locale " + + locale.getLanguage() + ", trying without locale..." ); + } + } + catch ( ArtifactNotFoundException e ) + { + getLogger().debug( "Unable to locate site descriptor for locale " + locale.getLanguage() + ": " + e ); + + // we can afford to write an empty descriptor here as we don't expect it to turn up later in the remote + // repository, because the parent was already released (and snapshots are updated automatically if changed) + result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); + result.getParentFile().mkdirs(); + result.createNewFile(); + } + + if ( !found ) + { + artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), project.getArtifactId(), + project.getVersion(), "xml", "site" ); + try + { + artifactResolver.resolve( artifact, repositories, localRepository ); + } + catch ( ArtifactNotFoundException e ) + { + // see above regarding this zero length file + result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); + result.getParentFile().mkdirs(); + result.createNewFile(); + + throw e; + } + + result = artifact.getFile(); + + // we use zero length files to avoid re-resolution (see below) + if ( result.length() == 0 ) + { + getLogger().debug( "No site descriptor found for " + project.getId() + " without locale." ); + result = null; + } + } + + return result; + } + + /** + * @param depth depth of project + * @param siteDirectory, can be null if project.basedir is null, ie POM from repository + * @param locale not null + * @param project not null + * @param reactorProjects not null + * @param localRepository not null + * @param repositories not null + * @param origProps not null + * @return the decoration model depending the locale and the parent project + * @throws SiteToolException if any + */ + private Map.Entry getDecorationModel( int depth, File siteDirectory, Locale locale, + MavenProject project, + List reactorProjects, + ArtifactRepository localRepository, + List repositories ) + throws SiteToolException + { + // 1. get site descriptor File + File siteDescriptor; + if ( project.getBasedir() == null ) + { + // POM is in the repository: look into the repository for site descriptor + try + { + siteDescriptor = getSiteDescriptorFromRepository( project, localRepository, repositories, locale ); + } + catch ( SiteToolException e ) + { + throw new SiteToolException( "The site descriptor cannot be resolved from the repository: " + + e.getMessage(), e ); + } + } + else + { + // POM is in build directory: look for site descriptor as local file + siteDescriptor = getSiteDescriptor( siteDirectory, locale ); + } + + // 2. read DecorationModel from site descriptor File and do early interpolation (${this.*}) + DecorationModel decoration = null; + Reader siteDescriptorReader = null; + try + { + if ( siteDescriptor != null && siteDescriptor.exists() ) + { + getLogger().debug( "Reading" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + + " site descriptor from " + siteDescriptor ); + + siteDescriptorReader = ReaderFactory.newXmlReader( siteDescriptor ); + + String siteDescriptorContent = readSiteDescriptor( siteDescriptorReader, project.getId() ); + + // interpolate ${this.*} = early interpolation + siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, true ); + + decoration = readDecorationModel( siteDescriptorContent ); + decoration.setLastModified( siteDescriptor.lastModified() ); + } + else + { + getLogger().debug( "No" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + " site descriptor." ); + } + } + catch ( IOException e ) + { + throw new SiteToolException( "The site descriptor for " + project.getId() + " cannot be read from " + + siteDescriptor, e ); + } + finally + { + IOUtil.close( siteDescriptorReader ); + } + + // 3. look for parent project + MavenProject parentProject = getParentProject( project, reactorProjects, localRepository ); + + // 4. merge with parent project DecorationModel + if ( parentProject != null && ( decoration == null || decoration.isMergeParent() ) ) + { + depth++; + getLogger().debug( "Looking for site descriptor of level " + depth + " parent project: " + + parentProject.getId() ); + + File parentSiteDirectory = null; + if ( parentProject.getBasedir() != null ) + { + // extrapolate parent project site directory + String siteRelativePath = getRelativeFilePath( project.getBasedir().getAbsolutePath(), + siteDescriptor.getParentFile().getAbsolutePath() ); + + parentSiteDirectory = new File( parentProject.getBasedir(), siteRelativePath ); + // notice: using same siteRelativePath for parent as current project; may be wrong if site plugin + // has different configuration. But this is a rare case (this only has impact if parent is from reactor) + } + + DecorationModel parentDecoration = + getDecorationModel( depth, parentSiteDirectory, locale, parentProject, reactorProjects, localRepository, + repositories ).getKey(); + + // MSHARED-116 requires an empty decoration model (instead of a null one) + // MSHARED-145 requires us to do this only if there is a parent to merge it with + if ( decoration == null && parentDecoration != null ) + { + // we have no site descriptor: merge the parent into an empty one + decoration = new DecorationModel(); + } + + String name = project.getName(); + if ( decoration != null && StringUtils.isNotEmpty( decoration.getName() ) ) + { + name = decoration.getName(); + } + + // Merge the parent and child DecorationModels + String projectDistMgmnt = getDistMgmntSiteUrl( project ); + String parentDistMgmnt = getDistMgmntSiteUrl( parentProject ); + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Site decoration model inheritance: assembling child with level " + depth + + " parent: distributionManagement.site.url child = " + projectDistMgmnt + " and parent = " + + parentDistMgmnt ); + } + assembler.assembleModelInheritance( name, decoration, parentDecoration, projectDistMgmnt, + parentDistMgmnt == null ? projectDistMgmnt : parentDistMgmnt ); + } + + return new AbstractMap.SimpleEntry( decoration, parentProject ); + } + + /** + * @param siteDescriptorContent not null + * @return the decoration model object + * @throws SiteToolException if any + */ + private DecorationModel readDecorationModel( String siteDescriptorContent ) + throws SiteToolException + { + try + { + return new DecorationXpp3Reader().read( new StringReader( siteDescriptorContent ) ); + } + catch ( XmlPullParserException e ) + { + throw new SiteToolException( "Error parsing site descriptor", e ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading site descriptor", e ); + } + } + + private String decorationModelToString( DecorationModel decoration ) + throws SiteToolException + { + StringWriter writer = new StringWriter(); + + try + { + new DecorationXpp3Writer().write( writer, decoration ); + return writer.toString(); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading site descriptor", e ); + } + finally + { + IOUtil.close( writer ); + } + } + + private static String buildRelativePath( final String toPath, final String fromPath, final char separatorChar ) + { + // use tokenizer to traverse paths and for lazy checking + StringTokenizer toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); + StringTokenizer fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); + + int count = 0; + + // walk along the to path looking for divergence from the from path + while ( toTokeniser.hasMoreTokens() && fromTokeniser.hasMoreTokens() ) + { + if ( separatorChar == '\\' ) + { + if ( !fromTokeniser.nextToken().equalsIgnoreCase( toTokeniser.nextToken() ) ) + { + break; + } + } + else + { + if ( !fromTokeniser.nextToken().equals( toTokeniser.nextToken() ) ) + { + break; + } + } + + count++; + } + + // reinitialize the tokenizers to count positions to retrieve the + // gobbled token + + toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); + fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); + + while ( count-- > 0 ) + { + fromTokeniser.nextToken(); + toTokeniser.nextToken(); + } + + StringBuilder relativePath = new StringBuilder(); + + // add back refs for the rest of from location. + while ( fromTokeniser.hasMoreTokens() ) + { + fromTokeniser.nextToken(); + + relativePath.append( ".." ); + + if ( fromTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + } + + if ( relativePath.length() != 0 && toTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + + // add fwd fills for whatever's left of to. + while ( toTokeniser.hasMoreTokens() ) + { + relativePath.append( toTokeniser.nextToken() ); + + if ( toTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + } + return relativePath.toString(); + } + + /** + * @param project not null + * @param menu not null + * @param name not null + * @param href could be null + * @param defaultHref not null + */ + private void appendMenuItem( MavenProject project, Menu menu, String name, String href, String defaultHref ) + { + String selectedHref = href; + + if ( selectedHref == null ) + { + selectedHref = defaultHref; + } + + MenuItem item = new MenuItem(); + item.setName( name ); + + String baseUrl = getDistMgmntSiteUrl( project ); + if ( baseUrl != null ) + { + selectedHref = getRelativePath( selectedHref, baseUrl ); + } + + if ( selectedHref.endsWith( "/" ) ) + { + item.setHref( selectedHref + "index.html" ); + } + else + { + item.setHref( selectedHref + "/index.html" ); + } + menu.addItem( item ); + } + + /** + * @param name not null + * @param href not null + * @param categoryReports not null + * @param locale not null + * @return the menu item object + */ + private MenuItem createCategoryMenu( String name, String href, List categoryReports, Locale locale ) + { + MenuItem item = new MenuItem(); + item.setName( name ); + item.setCollapse( true ); + item.setHref( href ); + + // MSHARED-172, allow reports to define their order in some other way? + //Collections.sort( categoryReports, new ReportComparator( locale ) ); + + for ( MavenReport report : categoryReports ) + { + MenuItem subitem = new MenuItem(); + subitem.setName( report.getName( locale ) ); + subitem.setHref( report.getOutputName() + ".html" ); + item.getItems().add( subitem ); + } + + return item; + } + + // ---------------------------------------------------------------------- + // static methods + // ---------------------------------------------------------------------- + + /** + * Convenience method. + * + * @param list could be null + * @return true if the list is null or empty + */ + private static boolean isEmptyList( List list ) + { + return list == null || list.isEmpty(); + } + + /** + * Return distributionManagement.site.url if defined, null otherwise. + * + * @param project not null + * @return could be null + */ + private static String getDistMgmntSiteUrl( MavenProject project ) + { + return getDistMgmntSiteUrl( project.getDistributionManagement() ); + } + + private static String getDistMgmntSiteUrl( DistributionManagement distMgmnt ) + { + if ( distMgmnt != null && distMgmnt.getSite() != null && distMgmnt.getSite().getUrl() != null ) + { + return urlEncode( distMgmnt.getSite().getUrl() ); + } + + return null; + } + + private static String urlEncode( final String url ) + { + if ( url == null ) + { + return null; + } + + try + { + return new File( url ).toURI().toURL().toExternalForm(); + } + catch ( MalformedURLException ex ) + { + return url; // this will then throw somewhere else + } + } + + private void checkNotNull( String name, Object value ) + { + if ( value == null ) + { + throw new IllegalArgumentException( "The parameter '" + name + "' cannot be null." ); + } + } + + /** + * Check the current Maven version to see if it's Maven 3.0 or newer. + */ + private static boolean isMaven3OrMore() + { + return new DefaultArtifactVersion( getMavenVersion() ).getMajorVersion() >= 3; + } + + private static String getMavenVersion() + { + // This relies on the fact that MavenProject is the in core classloader + // and that the core classloader is for the maven-core artifact + // and that should have a pom.properties file + // if this ever changes, we will have to revisit this code. + final Properties properties = new Properties(); + final String corePomProperties = "META-INF/maven/org.apache.maven/maven-core/pom.properties"; + final InputStream in = MavenProject.class.getClassLoader().getResourceAsStream( corePomProperties ); + try + { + properties.load( in ); + } + catch ( IOException ioe ) + { + return ""; + } + finally + { + IOUtil.close( in ); + } + + return properties.getProperty( "version" ).trim(); + } +} diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_1036/npe.json b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_1036/npe.json new file mode 100644 index 000000000..6ccc98492 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_1036/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/DefaultSiteTool.java", + "line": 1043, + "npe_method": "getNormalizedPath", + "deref_field": "normalized", + "npe_class": "DefaultSiteTool" +} \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_349/buggy.java b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_349/buggy.java new file mode 100644 index 000000000..0088db730 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_349/buggy.java @@ -0,0 +1,1529 @@ +package org.apache.maven.doxia.tools; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.StringTokenizer; + +import org.apache.commons.io.FilenameUtils; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.factory.ArtifactFactory; +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.artifact.resolver.ArtifactNotFoundException; +import org.apache.maven.artifact.resolver.ArtifactResolutionException; +import org.apache.maven.artifact.resolver.ArtifactResolver; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; +import org.apache.maven.artifact.versioning.VersionRange; +import org.apache.maven.doxia.site.decoration.Banner; +import org.apache.maven.doxia.site.decoration.DecorationModel; +import org.apache.maven.doxia.site.decoration.Menu; +import org.apache.maven.doxia.site.decoration.MenuItem; +import org.apache.maven.doxia.site.decoration.Skin; +import org.apache.maven.doxia.site.decoration.inheritance.DecorationModelInheritanceAssembler; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Reader; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Writer; +import org.apache.maven.model.DistributionManagement; +import org.apache.maven.model.Site; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.MavenProjectBuilder; +import org.apache.maven.project.ProjectBuildingException; +import org.apache.maven.reporting.MavenReport; +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.IOUtil; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.interpolation.EnvarBasedValueSource; +import org.codehaus.plexus.interpolation.InterpolationException; +import org.codehaus.plexus.interpolation.MapBasedValueSource; +import org.codehaus.plexus.interpolation.ObjectBasedValueSource; +import org.codehaus.plexus.interpolation.PrefixedObjectValueSource; +import org.codehaus.plexus.interpolation.PrefixedPropertiesValueSource; +import org.codehaus.plexus.interpolation.RegexBasedInterpolator; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +/** + * Default implementation of the site tool. + * + * @author Vincent Siveton + */ +@Component( role = SiteTool.class ) +public class DefaultSiteTool + extends AbstractLogEnabled + implements SiteTool +{ + // ---------------------------------------------------------------------- + // Components + // ---------------------------------------------------------------------- + + /** + * The component that is used to resolve additional artifacts required. + */ + @Requirement + private ArtifactResolver artifactResolver; + + /** + * The component used for creating artifact instances. + */ + @Requirement + private ArtifactFactory artifactFactory; + + /** + * Internationalization. + */ + @Requirement + protected I18N i18n; + + /** + * The component for assembling inheritance. + */ + @Requirement + protected DecorationModelInheritanceAssembler assembler; + + /** + * Project builder (deprecated in Maven 3: should use ProjectBuilder, which will avoid + * issues like DOXIASITETOOLS-166) + */ + @Requirement + protected MavenProjectBuilder mavenProjectBuilder; + + // ---------------------------------------------------------------------- + // Public methods + // ---------------------------------------------------------------------- + + public Artifact getSkinArtifactFromRepository( ArtifactRepository localRepository, + List remoteArtifactRepositories, + DecorationModel decoration ) + throws SiteToolException + { + checkNotNull( "localRepository", localRepository ); + checkNotNull( "remoteArtifactRepositories", remoteArtifactRepositories ); + checkNotNull( "decoration", decoration ); + + Skin skin = decoration.getSkin(); + + if ( skin == null ) + { + skin = Skin.getDefaultSkin(); + } + + String version = skin.getVersion(); + Artifact artifact; + try + { + if ( version == null ) + { + version = Artifact.RELEASE_VERSION; + } + VersionRange versionSpec = VersionRange.createFromVersionSpec( version ); + artifact = artifactFactory.createDependencyArtifact( skin.getGroupId(), skin.getArtifactId(), versionSpec, + "jar", null, null ); + + artifactResolver.resolve( artifact, remoteArtifactRepositories, localRepository ); + } + catch ( InvalidVersionSpecificationException e ) + { + throw new SiteToolException( "InvalidVersionSpecificationException: The skin version '" + version + + "' is not valid: " + e.getMessage(), e ); + } + catch ( ArtifactResolutionException e ) + { + throw new SiteToolException( "ArtifactResolutionException: Unable to find skin", e ); + } + catch ( ArtifactNotFoundException e ) + { + throw new SiteToolException( "ArtifactNotFoundException: The skin does not exist: " + e.getMessage(), e ); + } + + return artifact; + } + + public Artifact getDefaultSkinArtifact( ArtifactRepository localRepository, + List remoteArtifactRepositories ) + throws SiteToolException + { + return getSkinArtifactFromRepository( localRepository, remoteArtifactRepositories, new DecorationModel() ); + } + + /** + * This method is not implemented according to the URI specification and has many weird + * corner cases where it doesn't do the right thing. Please consider using a better + * implemented method from a different library such as org.apache.http.client.utils.URIUtils#resolve. + */ + @Deprecated + public String getRelativePath( String to, String from ) + { + checkNotNull( "to", to ); + checkNotNull( "from", from ); + + if ( to.contains( ":" ) && from.contains( ":" ) ) + { + String toScheme = to.substring( 0, to.lastIndexOf( ':' ) ); + String fromScheme = from.substring( 0, from.lastIndexOf( ':' ) ); + if ( !toScheme.equals( fromScheme ) ) + { + return to; + } + } + + URL toUrl = null; + URL fromUrl = null; + + String toPath = to; + String fromPath = from; + + try + { + toUrl = new URL( to ); + } + catch ( MalformedURLException e ) + { + try + { + toUrl = new File( getNormalizedPath( to ) ).toURI().toURL(); + } + catch ( MalformedURLException e1 ) + { + getLogger().warn( "Unable to load a URL for '" + to + "': " + e.getMessage() ); + return to; + } + } + + try + { + fromUrl = new URL( from ); + } + catch ( MalformedURLException e ) + { + try + { + fromUrl = new File( getNormalizedPath( from ) ).toURI().toURL(); + } + catch ( MalformedURLException e1 ) + { + getLogger().warn( "Unable to load a URL for '" + from + "': " + e.getMessage() ); + return to; + } + } + + if ( toUrl != null && fromUrl != null ) + { + // URLs, determine if they share protocol and domain info + + if ( ( toUrl.getProtocol().equalsIgnoreCase( fromUrl.getProtocol() ) ) + && ( toUrl.getHost().equalsIgnoreCase( fromUrl.getHost() ) ) + && ( toUrl.getPort() == fromUrl.getPort() ) ) + { + // shared URL domain details, use URI to determine relative path + + toPath = toUrl.getFile(); + fromPath = fromUrl.getFile(); + } + else + { + // don't share basic URL information, no relative available + + return to; + } + } + else if ( ( toUrl != null && fromUrl == null ) || ( toUrl == null && fromUrl != null ) ) + { + // one is a URL and the other isn't, no relative available. + + return to; + } + + // either the two locations are not URLs or if they are they + // share the common protocol and domain info and we are left + // with their URI information + + String relativePath = getRelativeFilePath( fromPath, toPath ); + + if ( relativePath == null ) + { + relativePath = to; + } + + if ( getLogger().isDebugEnabled() && !relativePath.toString().equals( to ) ) + { + getLogger().debug( "Mapped url: " + to + " to relative path: " + relativePath ); + } + + return relativePath; + } + + private static String getRelativeFilePath( final String oldPath, final String newPath ) + { + // normalize the path delimiters + + String fromPath = new File( oldPath ).getPath(); + String toPath = new File( newPath ).getPath(); + + // strip any leading slashes if its a windows path + if ( toPath.matches( "^\\[a-zA-Z]:" ) ) + { + toPath = toPath.substring( 1 ); + } + if ( fromPath.matches( "^\\[a-zA-Z]:" ) ) + { + fromPath = fromPath.substring( 1 ); + } + + // lowercase windows drive letters. + if ( fromPath.startsWith( ":", 1 ) ) + { + fromPath = Character.toLowerCase( fromPath.charAt( 0 ) ) + fromPath.substring( 1 ); + } + if ( toPath.startsWith( ":", 1 ) ) + { + toPath = Character.toLowerCase( toPath.charAt( 0 ) ) + toPath.substring( 1 ); + } + + // check for the presence of windows drives. No relative way of + // traversing from one to the other. + + if ( ( toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) + && ( !toPath.substring( 0, 1 ).equals( fromPath.substring( 0, 1 ) ) ) ) + { + // they both have drive path element but they don't match, no + // relative path + + return null; + } + + if ( ( toPath.startsWith( ":", 1 ) && !fromPath.startsWith( ":", 1 ) ) + || ( !toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) ) + { + + // one has a drive path element and the other doesn't, no relative + // path. + + return null; + + } + + final String relativePath = buildRelativePath( toPath, fromPath, File.separatorChar ); + + return relativePath.toString(); + } + + /** {@inheritDoc} */ +/** + * {@inheritDoc } + */ +public java.io.File getSiteDescriptor(java.io.File siteDirectory, java.util.Locale locale) { + checkNotNull("siteDirectory", siteDirectory); + final java.util.Locale llocale = /* NPEX_NULL_EXP */ + locale; + java.io.File siteDescriptor = new java.io.File(siteDirectory, ("site_" + llocale.getLanguage()) + ".xml"); + if (!siteDescriptor.isFile()) { + siteDescriptor = new java.io.File(siteDirectory, "site.xml"); + } + return siteDescriptor; +} + + /** + * Get a site descriptor from one of the repositories. + * + * @param project the Maven project, not null. + * @param localRepository the Maven local repository, not null. + * @param repositories the Maven remote repositories, not null. + * @param locale the locale wanted for the site descriptor. If not null, searching for + * site_localeLanguage.xml, otherwise searching for site.xml. + * @return the site descriptor into the local repository after download of it from repositories or null if not + * found in repositories. + * @throws SiteToolException if any + */ + File getSiteDescriptorFromRepository( MavenProject project, ArtifactRepository localRepository, + List repositories, Locale locale ) + throws SiteToolException + { + checkNotNull( "project", project ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "repositories", repositories ); + + final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale; + + try + { + return resolveSiteDescriptor( project, localRepository, repositories, llocale ); + } + catch ( ArtifactNotFoundException e ) + { + getLogger().debug( "ArtifactNotFoundException: Unable to locate site descriptor: " + e ); + return null; + } + catch ( ArtifactResolutionException e ) + { + throw new SiteToolException( "ArtifactResolutionException: Unable to locate site descriptor: " + + e.getMessage(), e ); + } + catch ( IOException e ) + { + throw new SiteToolException( "IOException: Unable to locate site descriptor: " + e.getMessage(), e ); + } + } + + /** + * Read site descriptor content from Reader, adding support for deprecated ${reports}, + * ${parentProject} and ${modules} tags. + * + * @param reader + * @return the input content interpolated with deprecated tags + * @throws IOException + */ + private String readSiteDescriptor( Reader reader, String projectId ) + throws IOException + { + String siteDescriptorContent = IOUtil.toString( reader ); + + // This is to support the deprecated ${reports}, ${parentProject} and ${modules} tags. + Properties props = new Properties(); + props.put( "reports", "
" ); + props.put( "modules", "" ); + props.put( "parentProject", "" ); + + // warn if interpolation required + for ( Object prop : props.keySet() ) + { + if ( siteDescriptorContent.contains( "$" + prop ) ) + { + getLogger().warn( "Site descriptor for " + projectId + " contains $" + prop + + ": should be replaced with " + props.getProperty( (String) prop ) ); + } + if ( siteDescriptorContent.contains( "${" + prop + "}" ) ) + { + getLogger().warn( "Site descriptor for " + projectId + " contains ${" + prop + + "}: should be replaced with " + props.getProperty( (String) prop ) ); + } + } + + return StringUtils.interpolate( siteDescriptorContent, props ); + } + + /** {@inheritDoc} */ + public DecorationModel getDecorationModel( File siteDirectory, Locale locale, MavenProject project, + List reactorProjects, ArtifactRepository localRepository, + List repositories ) + throws SiteToolException + { + checkNotNull( "project", project ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "repositories", repositories ); + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + getLogger().debug( "Computing decoration model of " + project.getId() + " for locale " + llocale ); + + Map.Entry result = + getDecorationModel( 0, siteDirectory, llocale, project, reactorProjects, localRepository, repositories ); + DecorationModel decorationModel = result.getKey(); + MavenProject parentProject = result.getValue(); + + if ( decorationModel == null ) + { + getLogger().debug( "Using default site descriptor" ); + + String siteDescriptorContent; + + Reader reader = null; + try + { + // Note the default is not a super class - it is used when nothing else is found + reader = ReaderFactory.newXmlReader( getClass().getResourceAsStream( "/default-site.xml" ) ); + siteDescriptorContent = readSiteDescriptor( reader, "default-site.xml" ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading default site descriptor: " + e.getMessage(), e ); + } + finally + { + IOUtil.close( reader ); + } + + decorationModel = readDecorationModel( siteDescriptorContent ); + } + + // DecorationModel back to String to interpolate, then go back to DecorationModel + String siteDescriptorContent = decorationModelToString( decorationModel ); + + // "classical" late interpolation, after full inheritance + siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, false ); + + decorationModel = readDecorationModel( siteDescriptorContent ); + + if ( parentProject != null ) + { + populateParentMenu( decorationModel, llocale, project, parentProject, true ); + } + + try + { + populateModulesMenu( decorationModel, llocale, project, reactorProjects, localRepository, true ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error while populating modules menu: " + e.getMessage(), e ); + } + + if ( decorationModel.getBannerLeft() == null ) + { + // extra default to set + Banner banner = new Banner(); + banner.setName( project.getName() ); + decorationModel.setBannerLeft( banner ); + } + + return decorationModel; + } + + /** {@inheritDoc} */ + public String getInterpolatedSiteDescriptorContent( Map props, MavenProject aProject, + String siteDescriptorContent ) + throws SiteToolException + { + checkNotNull( "props", props ); + + // "classical" late interpolation + return getInterpolatedSiteDescriptorContent( aProject, siteDescriptorContent, false ); + } + + private String getInterpolatedSiteDescriptorContent( MavenProject aProject, + String siteDescriptorContent, boolean isEarly ) + throws SiteToolException + { + checkNotNull( "aProject", aProject ); + checkNotNull( "siteDescriptorContent", siteDescriptorContent ); + + RegexBasedInterpolator interpolator = new RegexBasedInterpolator(); + + if ( isEarly ) + { + interpolator.addValueSource( new PrefixedObjectValueSource( "this.", aProject ) ); + interpolator.addValueSource( new PrefixedPropertiesValueSource( "this.", aProject.getProperties() ) ); + } + else + { + interpolator.addValueSource( new ObjectBasedValueSource( aProject ) ); + interpolator.addValueSource( new MapBasedValueSource( aProject.getProperties() ) ); + + try + { + interpolator.addValueSource( new EnvarBasedValueSource() ); + } + catch ( IOException e ) + { + // Prefer logging? + throw new SiteToolException( "IOException: cannot interpolate environment properties: " + + e.getMessage(), e ); + } + } + + try + { + // FIXME: this does not escape xml entities, see MSITE-226, PLXCOMP-118 + return interpolator.interpolate( siteDescriptorContent, isEarly ? null : "project" ); + } + catch ( InterpolationException e ) + { + throw new SiteToolException( "Cannot interpolate site descriptor: " + e.getMessage(), e ); + } + } + + /** {@inheritDoc} */ + public MavenProject getParentProject( MavenProject aProject, List reactorProjects, + ArtifactRepository localRepository ) + { + checkNotNull( "aProject", aProject ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + + if ( isMaven3OrMore() ) + { + // no need to make voodoo with Maven 3: job already done + return aProject.getParent(); + } + + MavenProject parentProject = null; + + MavenProject origParent = aProject.getParent(); + if ( origParent != null ) + { + for ( MavenProject reactorProject : reactorProjects ) + { + if ( reactorProject.getGroupId().equals( origParent.getGroupId() ) + && reactorProject.getArtifactId().equals( origParent.getArtifactId() ) + && reactorProject.getVersion().equals( origParent.getVersion() ) ) + { + parentProject = reactorProject; + + getLogger().debug( "Parent project " + origParent.getId() + " picked from reactor" ); + break; + } + } + + if ( parentProject == null && aProject.getBasedir() != null + && StringUtils.isNotEmpty( aProject.getModel().getParent().getRelativePath() ) ) + { + try + { + String relativePath = aProject.getModel().getParent().getRelativePath(); + + File pomFile = new File( aProject.getBasedir(), relativePath ); + + if ( pomFile.isDirectory() ) + { + pomFile = new File( pomFile, "pom.xml" ); + } + pomFile = new File( getNormalizedPath( pomFile.getPath() ) ); + + if ( pomFile.isFile() ) + { + MavenProject mavenProject = mavenProjectBuilder.build( pomFile, localRepository, null ); + + if ( mavenProject.getGroupId().equals( origParent.getGroupId() ) + && mavenProject.getArtifactId().equals( origParent.getArtifactId() ) + && mavenProject.getVersion().equals( origParent.getVersion() ) ) + { + parentProject = mavenProject; + + getLogger().debug( "Parent project " + origParent.getId() + " loaded from a relative path: " + + relativePath ); + } + } + } + catch ( ProjectBuildingException e ) + { + getLogger().info( "Unable to load parent project " + origParent.getId() + " from a relative path: " + + e.getMessage() ); + } + } + + if ( parentProject == null ) + { + try + { + parentProject = mavenProjectBuilder.buildFromRepository( aProject.getParentArtifact(), aProject + .getRemoteArtifactRepositories(), localRepository ); + + getLogger().debug( "Parent project " + origParent.getId() + " loaded from repository" ); + } + catch ( ProjectBuildingException e ) + { + getLogger().warn( "Unable to load parent project " + origParent.getId() + " from repository: " + + e.getMessage() ); + } + } + + if ( parentProject == null ) + { + // fallback to original parent, which may contain uninterpolated value (still need a unit test) + + parentProject = origParent; + + getLogger().debug( "Parent project " + origParent.getId() + " picked from original value" ); + } + } + return parentProject; + } + + /** + * Populate the pre-defined parent menu of the decoration model, + * if used through <menu ref="parent"/>. + * + * @param decorationModel the Doxia Sitetools DecorationModel, not null. + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project a Maven project, not null. + * @param parentProject a Maven parent project, not null. + * @param keepInheritedRefs used for inherited references. + */ + private void populateParentMenu( DecorationModel decorationModel, Locale locale, MavenProject project, + MavenProject parentProject, boolean keepInheritedRefs ) + { + checkNotNull( "decorationModel", decorationModel ); + checkNotNull( "project", project ); + checkNotNull( "parentProject", parentProject ); + + Menu menu = decorationModel.getMenuRef( "parent" ); + + if ( menu == null ) + { + return; + } + + if ( keepInheritedRefs && menu.isInheritAsRef() ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + String parentUrl = getDistMgmntSiteUrl( parentProject ); + + if ( parentUrl != null ) + { + if ( parentUrl.endsWith( "/" ) ) + { + parentUrl += "index.html"; + } + else + { + parentUrl += "/index.html"; + } + + parentUrl = getRelativePath( parentUrl, getDistMgmntSiteUrl( project ) ); + } + else + { + // parent has no url, assume relative path is given by site structure + File parentBasedir = parentProject.getBasedir(); + // First make sure that the parent is available on the file system + if ( parentBasedir != null ) + { + // Try to find the relative path to the parent via the file system + String parentPath = parentBasedir.getAbsolutePath(); + String projectPath = project.getBasedir().getAbsolutePath(); + parentUrl = getRelativePath( parentPath, projectPath ) + "/index.html"; + } + } + + // Only add the parent menu if we were able to find a URL for it + if ( parentUrl == null ) + { + getLogger().warn( "Unable to find a URL to the parent project. The parent menu will NOT be added." ); + } + else + { + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.parentproject" ) ); + } + + MenuItem item = new MenuItem(); + item.setName( parentProject.getName() ); + item.setHref( parentUrl ); + menu.addItem( item ); + } + } + + /** + * Populate the pre-defined modules menu of the decoration model, + * if used through <menu ref="modules"/>. + * + * @param decorationModel the Doxia Sitetools DecorationModel, not null. + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project a Maven project, not null. + * @param reactorProjects the Maven reactor projects, not null. + * @param localRepository the Maven local repository, not null. + * @param keepInheritedRefs used for inherited references. + * @throws SiteToolException if any + * @throws IOException + */ + private void populateModulesMenu( DecorationModel decorationModel, Locale locale, MavenProject project, + List reactorProjects, ArtifactRepository localRepository, + boolean keepInheritedRefs ) + throws SiteToolException, IOException + { + checkNotNull( "project", project ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "decorationModel", decorationModel ); + + Menu menu = decorationModel.getMenuRef( "modules" ); + + if ( menu == null ) + { + return; + } + + if ( keepInheritedRefs && menu.isInheritAsRef() ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale ; + + // we require child modules and reactors to process module menu + if ( project.getModules().size() > 0 ) + { + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectmodules" ) ); + } + + for ( String module : (List) project.getModules() ) + { + MavenProject moduleProject = getModuleFromReactor( project, reactorProjects, module ); + + if ( moduleProject == null ) + { + getLogger().warn( "Module " + module + + " not found in reactor: loading locally" ); + + File f = new File( project.getBasedir(), module + "/pom.xml" ); + if ( f.exists() ) + { + try + { + moduleProject = mavenProjectBuilder.build( f, localRepository, null ); + } + catch ( ProjectBuildingException e ) + { + throw new SiteToolException( "Unable to read local module-POM", e ); + } + } + else + { + getLogger().warn( "No filesystem module-POM available" ); + + moduleProject = new MavenProject(); + moduleProject.setName( module ); + moduleProject.setDistributionManagement( new DistributionManagement() ); + moduleProject.getDistributionManagement().setSite( new Site() ); + moduleProject.getDistributionManagement().getSite().setUrl( module ); + } + } + + String siteUrl = getDistMgmntSiteUrl( moduleProject ); + String itemName = + ( moduleProject.getName() == null ) ? moduleProject.getArtifactId() : moduleProject.getName(); + + appendMenuItem( project, menu, itemName, siteUrl, moduleProject.getArtifactId() ); + } + } + else if ( decorationModel.getMenuRef( "modules" ).getInherit() == null ) + { + // only remove if project has no modules AND menu is not inherited, see MSHARED-174 + decorationModel.removeMenuRef( "modules" ); + } + } + + private static MavenProject getModuleFromReactor( MavenProject project, List reactorProjects, + String module ) + throws IOException + { + File moduleBasedir = new File( project.getBasedir(), module ).getCanonicalFile(); + + for ( MavenProject reactorProject : reactorProjects ) + { + if ( moduleBasedir.equals( reactorProject.getBasedir() ) ) + { + return reactorProject; + } + } + + // module not found in reactor + return null; + } + + /** {@inheritDoc} */ + public void populateReportsMenu( DecorationModel decorationModel, Locale locale, + Map> categories ) + { + checkNotNull( "decorationModel", decorationModel ); + checkNotNull( "categories", categories ); + + Menu menu = decorationModel.getMenuRef( "reports" ); + + if ( menu == null ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectdocumentation" ) ); + } + + boolean found = false; + if ( menu.getItems().isEmpty() ) + { + List categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_INFORMATION ); + if ( !isEmptyList( categoryReports ) ) + { + MenuItem item = createCategoryMenu( + i18n.getString( "site-tool", llocale, + "decorationModel.menu.projectinformation" ), + "/project-info.html", categoryReports, llocale ); + menu.getItems().add( item ); + found = true; + } + + categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_REPORTS ); + if ( !isEmptyList( categoryReports ) ) + { + MenuItem item = + createCategoryMenu( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectreports" ), + "/project-reports.html", categoryReports, llocale ); + menu.getItems().add( item ); + found = true; + } + } + if ( !found ) + { + decorationModel.removeMenuRef( "reports" ); + } + } + + /** {@inheritDoc} */ + public List getSiteLocales( String locales ) + { + if ( locales == null ) + { + return Collections.singletonList( DEFAULT_LOCALE ); + } + + String[] localesArray = StringUtils.split( locales, "," ); + List localesList = new ArrayList( localesArray.length ); + + for ( String localeString : localesArray ) + { + Locale locale = codeToLocale( localeString ); + + if ( locale == null ) + { + continue; + } + + if ( !Arrays.asList( Locale.getAvailableLocales() ).contains( locale ) ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "The locale defined by '" + locale + + "' is not available in this Java Virtual Machine (" + + System.getProperty( "java.version" ) + + " from " + System.getProperty( "java.vendor" ) + ") - IGNORING" ); + } + continue; + } + + // Default bundles are in English + if ( ( !locale.getLanguage().equals( DEFAULT_LOCALE.getLanguage() ) ) + && ( !i18n.getBundle( "site-tool", locale ).getLocale().getLanguage() + .equals( locale.getLanguage() ) ) ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "The locale '" + locale + "' (" + locale.getDisplayName( Locale.ENGLISH ) + + ") is not currently supported by Maven Site - IGNORING." + + "\nContributions are welcome and greatly appreciated!" + + "\nIf you want to contribute a new translation, please visit " + + "http://maven.apache.org/plugins/localization.html for detailed instructions." ); + } + + continue; + } + + localesList.add( locale ); + } + + if ( localesList.isEmpty() ) + { + localesList = Collections.singletonList( DEFAULT_LOCALE ); + } + + return localesList; + } + + /** + * Converts a locale code like "en", "en_US" or "en_US_win" to a java.util.Locale + * object. + *

If localeCode = default, return the current value of the default locale for this instance + * of the Java Virtual Machine.

+ * + * @param localeCode the locale code string. + * @return a java.util.Locale object instanced or null if errors occurred + * @see java.util.Locale#getDefault() + */ + private Locale codeToLocale( String localeCode ) + { + if ( localeCode == null ) + { + return null; + } + + if ( "default".equalsIgnoreCase( localeCode ) ) + { + return Locale.getDefault(); + } + + String language = ""; + String country = ""; + String variant = ""; + + StringTokenizer tokenizer = new StringTokenizer( localeCode, "_" ); + final int maxTokens = 3; + if ( tokenizer.countTokens() > maxTokens ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "Invalid java.util.Locale format for '" + localeCode + "' entry - IGNORING" ); + } + return null; + } + + if ( tokenizer.hasMoreTokens() ) + { + language = tokenizer.nextToken(); + if ( tokenizer.hasMoreTokens() ) + { + country = tokenizer.nextToken(); + if ( tokenizer.hasMoreTokens() ) + { + variant = tokenizer.nextToken(); + } + } + } + + return new Locale( language, country, variant ); + } + + // ---------------------------------------------------------------------- + // Protected methods + // ---------------------------------------------------------------------- + + /** + * @param path could be null. + * @return the path normalized, i.e. by eliminating "/../" and "/./" in the path. + * @see FilenameUtils#normalize(String) + */ + protected static String getNormalizedPath( String path ) + { + String normalized = FilenameUtils.normalize( path ); + if ( normalized == null ) + { + normalized = path; + } + return ( normalized == null ) ? null : normalized.replace( '\\', '/' ); + } + + // ---------------------------------------------------------------------- + // Private methods + // ---------------------------------------------------------------------- + + /** + * @param project not null + * @param localRepository not null + * @param repositories not null + * @param locale not null + * @return the resolved site descriptor + * @throws IOException if any + * @throws ArtifactResolutionException if any + * @throws ArtifactNotFoundException if any + */ + private File resolveSiteDescriptor( MavenProject project, ArtifactRepository localRepository, + List repositories, Locale locale ) + throws IOException, ArtifactResolutionException, ArtifactNotFoundException + { + File result; + + // TODO: this is a bit crude - proper type, or proper handling as metadata rather than an artifact in 2.1? + Artifact artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), + project.getArtifactId(), + project.getVersion(), "xml", + "site_" + locale.getLanguage() ); + + boolean found = false; + try + { + artifactResolver.resolve( artifact, repositories, localRepository ); + + result = artifact.getFile(); + + // we use zero length files to avoid re-resolution (see below) + if ( result.length() > 0 ) + { + found = true; + } + else + { + getLogger().debug( "No site descriptor found for " + project.getId() + " for locale " + + locale.getLanguage() + ", trying without locale..." ); + } + } + catch ( ArtifactNotFoundException e ) + { + getLogger().debug( "Unable to locate site descriptor for locale " + locale.getLanguage() + ": " + e ); + + // we can afford to write an empty descriptor here as we don't expect it to turn up later in the remote + // repository, because the parent was already released (and snapshots are updated automatically if changed) + result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); + result.getParentFile().mkdirs(); + result.createNewFile(); + } + + if ( !found ) + { + artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), project.getArtifactId(), + project.getVersion(), "xml", "site" ); + try + { + artifactResolver.resolve( artifact, repositories, localRepository ); + } + catch ( ArtifactNotFoundException e ) + { + // see above regarding this zero length file + result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); + result.getParentFile().mkdirs(); + result.createNewFile(); + + throw e; + } + + result = artifact.getFile(); + + // we use zero length files to avoid re-resolution (see below) + if ( result.length() == 0 ) + { + getLogger().debug( "No site descriptor found for " + project.getId() + " without locale." ); + result = null; + } + } + + return result; + } + + /** + * @param depth depth of project + * @param siteDirectory, can be null if project.basedir is null, ie POM from repository + * @param locale not null + * @param project not null + * @param reactorProjects not null + * @param localRepository not null + * @param repositories not null + * @param origProps not null + * @return the decoration model depending the locale and the parent project + * @throws SiteToolException if any + */ + private Map.Entry getDecorationModel( int depth, File siteDirectory, Locale locale, + MavenProject project, + List reactorProjects, + ArtifactRepository localRepository, + List repositories ) + throws SiteToolException + { + // 1. get site descriptor File + File siteDescriptor; + if ( project.getBasedir() == null ) + { + // POM is in the repository: look into the repository for site descriptor + try + { + siteDescriptor = getSiteDescriptorFromRepository( project, localRepository, repositories, locale ); + } + catch ( SiteToolException e ) + { + throw new SiteToolException( "The site descriptor cannot be resolved from the repository: " + + e.getMessage(), e ); + } + } + else + { + // POM is in build directory: look for site descriptor as local file + siteDescriptor = getSiteDescriptor( siteDirectory, locale ); + } + + // 2. read DecorationModel from site descriptor File and do early interpolation (${this.*}) + DecorationModel decoration = null; + Reader siteDescriptorReader = null; + try + { + if ( siteDescriptor != null && siteDescriptor.exists() ) + { + getLogger().debug( "Reading" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + + " site descriptor from " + siteDescriptor ); + + siteDescriptorReader = ReaderFactory.newXmlReader( siteDescriptor ); + + String siteDescriptorContent = readSiteDescriptor( siteDescriptorReader, project.getId() ); + + // interpolate ${this.*} = early interpolation + siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, true ); + + decoration = readDecorationModel( siteDescriptorContent ); + decoration.setLastModified( siteDescriptor.lastModified() ); + } + else + { + getLogger().debug( "No" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + " site descriptor." ); + } + } + catch ( IOException e ) + { + throw new SiteToolException( "The site descriptor for " + project.getId() + " cannot be read from " + + siteDescriptor, e ); + } + finally + { + IOUtil.close( siteDescriptorReader ); + } + + // 3. look for parent project + MavenProject parentProject = getParentProject( project, reactorProjects, localRepository ); + + // 4. merge with parent project DecorationModel + if ( parentProject != null && ( decoration == null || decoration.isMergeParent() ) ) + { + depth++; + getLogger().debug( "Looking for site descriptor of level " + depth + " parent project: " + + parentProject.getId() ); + + File parentSiteDirectory = null; + if ( parentProject.getBasedir() != null ) + { + // extrapolate parent project site directory + String siteRelativePath = getRelativeFilePath( project.getBasedir().getAbsolutePath(), + siteDescriptor.getParentFile().getAbsolutePath() ); + + parentSiteDirectory = new File( parentProject.getBasedir(), siteRelativePath ); + // notice: using same siteRelativePath for parent as current project; may be wrong if site plugin + // has different configuration. But this is a rare case (this only has impact if parent is from reactor) + } + + DecorationModel parentDecoration = + getDecorationModel( depth, parentSiteDirectory, locale, parentProject, reactorProjects, localRepository, + repositories ).getKey(); + + // MSHARED-116 requires an empty decoration model (instead of a null one) + // MSHARED-145 requires us to do this only if there is a parent to merge it with + if ( decoration == null && parentDecoration != null ) + { + // we have no site descriptor: merge the parent into an empty one + decoration = new DecorationModel(); + } + + String name = project.getName(); + if ( decoration != null && StringUtils.isNotEmpty( decoration.getName() ) ) + { + name = decoration.getName(); + } + + // Merge the parent and child DecorationModels + String projectDistMgmnt = getDistMgmntSiteUrl( project ); + String parentDistMgmnt = getDistMgmntSiteUrl( parentProject ); + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Site decoration model inheritance: assembling child with level " + depth + + " parent: distributionManagement.site.url child = " + projectDistMgmnt + " and parent = " + + parentDistMgmnt ); + } + assembler.assembleModelInheritance( name, decoration, parentDecoration, projectDistMgmnt, + parentDistMgmnt == null ? projectDistMgmnt : parentDistMgmnt ); + } + + return new AbstractMap.SimpleEntry( decoration, parentProject ); + } + + /** + * @param siteDescriptorContent not null + * @return the decoration model object + * @throws SiteToolException if any + */ + private DecorationModel readDecorationModel( String siteDescriptorContent ) + throws SiteToolException + { + try + { + return new DecorationXpp3Reader().read( new StringReader( siteDescriptorContent ) ); + } + catch ( XmlPullParserException e ) + { + throw new SiteToolException( "Error parsing site descriptor", e ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading site descriptor", e ); + } + } + + private String decorationModelToString( DecorationModel decoration ) + throws SiteToolException + { + StringWriter writer = new StringWriter(); + + try + { + new DecorationXpp3Writer().write( writer, decoration ); + return writer.toString(); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading site descriptor", e ); + } + finally + { + IOUtil.close( writer ); + } + } + + private static String buildRelativePath( final String toPath, final String fromPath, final char separatorChar ) + { + // use tokenizer to traverse paths and for lazy checking + StringTokenizer toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); + StringTokenizer fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); + + int count = 0; + + // walk along the to path looking for divergence from the from path + while ( toTokeniser.hasMoreTokens() && fromTokeniser.hasMoreTokens() ) + { + if ( separatorChar == '\\' ) + { + if ( !fromTokeniser.nextToken().equalsIgnoreCase( toTokeniser.nextToken() ) ) + { + break; + } + } + else + { + if ( !fromTokeniser.nextToken().equals( toTokeniser.nextToken() ) ) + { + break; + } + } + + count++; + } + + // reinitialize the tokenizers to count positions to retrieve the + // gobbled token + + toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); + fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); + + while ( count-- > 0 ) + { + fromTokeniser.nextToken(); + toTokeniser.nextToken(); + } + + StringBuilder relativePath = new StringBuilder(); + + // add back refs for the rest of from location. + while ( fromTokeniser.hasMoreTokens() ) + { + fromTokeniser.nextToken(); + + relativePath.append( ".." ); + + if ( fromTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + } + + if ( relativePath.length() != 0 && toTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + + // add fwd fills for whatever's left of to. + while ( toTokeniser.hasMoreTokens() ) + { + relativePath.append( toTokeniser.nextToken() ); + + if ( toTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + } + return relativePath.toString(); + } + + /** + * @param project not null + * @param menu not null + * @param name not null + * @param href could be null + * @param defaultHref not null + */ + private void appendMenuItem( MavenProject project, Menu menu, String name, String href, String defaultHref ) + { + String selectedHref = href; + + if ( selectedHref == null ) + { + selectedHref = defaultHref; + } + + MenuItem item = new MenuItem(); + item.setName( name ); + + String baseUrl = getDistMgmntSiteUrl( project ); + if ( baseUrl != null ) + { + selectedHref = getRelativePath( selectedHref, baseUrl ); + } + + if ( selectedHref.endsWith( "/" ) ) + { + item.setHref( selectedHref + "index.html" ); + } + else + { + item.setHref( selectedHref + "/index.html" ); + } + menu.addItem( item ); + } + + /** + * @param name not null + * @param href not null + * @param categoryReports not null + * @param locale not null + * @return the menu item object + */ + private MenuItem createCategoryMenu( String name, String href, List categoryReports, Locale locale ) + { + MenuItem item = new MenuItem(); + item.setName( name ); + item.setCollapse( true ); + item.setHref( href ); + + // MSHARED-172, allow reports to define their order in some other way? + //Collections.sort( categoryReports, new ReportComparator( locale ) ); + + for ( MavenReport report : categoryReports ) + { + MenuItem subitem = new MenuItem(); + subitem.setName( report.getName( locale ) ); + subitem.setHref( report.getOutputName() + ".html" ); + item.getItems().add( subitem ); + } + + return item; + } + + // ---------------------------------------------------------------------- + // static methods + // ---------------------------------------------------------------------- + + /** + * Convenience method. + * + * @param list could be null + * @return true if the list is null or empty + */ + private static boolean isEmptyList( List list ) + { + return list == null || list.isEmpty(); + } + + /** + * Return distributionManagement.site.url if defined, null otherwise. + * + * @param project not null + * @return could be null + */ + private static String getDistMgmntSiteUrl( MavenProject project ) + { + return getDistMgmntSiteUrl( project.getDistributionManagement() ); + } + + private static String getDistMgmntSiteUrl( DistributionManagement distMgmnt ) + { + if ( distMgmnt != null && distMgmnt.getSite() != null && distMgmnt.getSite().getUrl() != null ) + { + return urlEncode( distMgmnt.getSite().getUrl() ); + } + + return null; + } + + private static String urlEncode( final String url ) + { + if ( url == null ) + { + return null; + } + + try + { + return new File( url ).toURI().toURL().toExternalForm(); + } + catch ( MalformedURLException ex ) + { + return url; // this will then throw somewhere else + } + } + + private void checkNotNull( String name, Object value ) + { + if ( value == null ) + { + throw new IllegalArgumentException( "The parameter '" + name + "' cannot be null." ); + } + } + + /** + * Check the current Maven version to see if it's Maven 3.0 or newer. + */ + private static boolean isMaven3OrMore() + { + return new DefaultArtifactVersion( getMavenVersion() ).getMajorVersion() >= 3; + } + + private static String getMavenVersion() + { + // This relies on the fact that MavenProject is the in core classloader + // and that the core classloader is for the maven-core artifact + // and that should have a pom.properties file + // if this ever changes, we will have to revisit this code. + final Properties properties = new Properties(); + final String corePomProperties = "META-INF/maven/org.apache.maven/maven-core/pom.properties"; + final InputStream in = MavenProject.class.getClassLoader().getResourceAsStream( corePomProperties ); + try + { + properties.load( in ); + } + catch ( IOException ioe ) + { + return ""; + } + finally + { + IOUtil.close( in ); + } + + return properties.getProperty( "version" ).trim(); + } +} diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_349/npe.json b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_349/npe.json new file mode 100644 index 000000000..a61ad53a0 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_349/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/DefaultSiteTool.java", + "line": 352, + "npe_method": "getSiteDescriptor", + "deref_field": "locale", + "npe_class": "DefaultSiteTool" +} \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_492/buggy.java b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_492/buggy.java new file mode 100644 index 000000000..26e43d495 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_492/buggy.java @@ -0,0 +1,1503 @@ +package org.apache.maven.doxia.tools; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.StringTokenizer; + +import org.apache.commons.io.FilenameUtils; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.factory.ArtifactFactory; +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.artifact.resolver.ArtifactNotFoundException; +import org.apache.maven.artifact.resolver.ArtifactResolutionException; +import org.apache.maven.artifact.resolver.ArtifactResolver; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; +import org.apache.maven.artifact.versioning.VersionRange; +import org.apache.maven.doxia.site.decoration.Banner; +import org.apache.maven.doxia.site.decoration.DecorationModel; +import org.apache.maven.doxia.site.decoration.Menu; +import org.apache.maven.doxia.site.decoration.MenuItem; +import org.apache.maven.doxia.site.decoration.Skin; +import org.apache.maven.doxia.site.decoration.inheritance.DecorationModelInheritanceAssembler; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Reader; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Writer; +import org.apache.maven.model.DistributionManagement; +import org.apache.maven.model.Site; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.MavenProjectBuilder; +import org.apache.maven.project.ProjectBuildingException; +import org.apache.maven.reporting.MavenReport; +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.IOUtil; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.interpolation.EnvarBasedValueSource; +import org.codehaus.plexus.interpolation.InterpolationException; +import org.codehaus.plexus.interpolation.MapBasedValueSource; +import org.codehaus.plexus.interpolation.ObjectBasedValueSource; +import org.codehaus.plexus.interpolation.PrefixedObjectValueSource; +import org.codehaus.plexus.interpolation.PrefixedPropertiesValueSource; +import org.codehaus.plexus.interpolation.RegexBasedInterpolator; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +/** + * Default implementation of the site tool. + * + * @author Vincent Siveton + */ +@Component( role = SiteTool.class ) +public class DefaultSiteTool + extends AbstractLogEnabled + implements SiteTool +{ + // ---------------------------------------------------------------------- + // Components + // ---------------------------------------------------------------------- + + /** + * The component that is used to resolve additional artifacts required. + */ + @Requirement + private ArtifactResolver artifactResolver; + + /** + * The component used for creating artifact instances. + */ + @Requirement + private ArtifactFactory artifactFactory; + + /** + * Internationalization. + */ + @Requirement + protected I18N i18n; + + /** + * The component for assembling inheritance. + */ + @Requirement + protected DecorationModelInheritanceAssembler assembler; + + /** + * Project builder (deprecated in Maven 3: should use ProjectBuilder, which will avoid + * issues like DOXIASITETOOLS-166) + */ + @Requirement + protected MavenProjectBuilder mavenProjectBuilder; + + // ---------------------------------------------------------------------- + // Public methods + // ---------------------------------------------------------------------- + + public Artifact getSkinArtifactFromRepository( ArtifactRepository localRepository, + List remoteArtifactRepositories, + DecorationModel decoration ) + throws SiteToolException + { + checkNotNull( "localRepository", localRepository ); + checkNotNull( "remoteArtifactRepositories", remoteArtifactRepositories ); + checkNotNull( "decoration", decoration ); + + Skin skin = decoration.getSkin(); + + if ( skin == null ) + { + skin = Skin.getDefaultSkin(); + } + + String version = skin.getVersion(); + Artifact artifact; + try + { + if ( version == null ) + { + version = Artifact.RELEASE_VERSION; + } + VersionRange versionSpec = VersionRange.createFromVersionSpec( version ); + artifact = artifactFactory.createDependencyArtifact( skin.getGroupId(), skin.getArtifactId(), versionSpec, + "jar", null, null ); + + artifactResolver.resolve( artifact, remoteArtifactRepositories, localRepository ); + } + catch ( InvalidVersionSpecificationException e ) + { + throw new SiteToolException( "InvalidVersionSpecificationException: The skin version '" + version + + "' is not valid: " + e.getMessage(), e ); + } + catch ( ArtifactResolutionException e ) + { + throw new SiteToolException( "ArtifactResolutionException: Unable to find skin", e ); + } + catch ( ArtifactNotFoundException e ) + { + throw new SiteToolException( "ArtifactNotFoundException: The skin does not exist: " + e.getMessage(), e ); + } + + return artifact; + } + + public Artifact getDefaultSkinArtifact( ArtifactRepository localRepository, + List remoteArtifactRepositories ) + throws SiteToolException + { + return getSkinArtifactFromRepository( localRepository, remoteArtifactRepositories, new DecorationModel() ); + } + + /** + * This method is not implemented according to the URI specification and has many weird + * corner cases where it doesn't do the right thing. Please consider using a better + * implemented method from a different library such as org.apache.http.client.utils.URIUtils#resolve. + */ + @Deprecated + public String getRelativePath( String to, String from ) + { + checkNotNull( "to", to ); + checkNotNull( "from", from ); + + if ( to.contains( ":" ) && from.contains( ":" ) ) + { + String toScheme = to.substring( 0, to.lastIndexOf( ':' ) ); + String fromScheme = from.substring( 0, from.lastIndexOf( ':' ) ); + if ( !toScheme.equals( fromScheme ) ) + { + return to; + } + } + + URL toUrl = null; + URL fromUrl = null; + + String toPath = to; + String fromPath = from; + + try + { + toUrl = new URL( to ); + } + catch ( MalformedURLException e ) + { + try + { + toUrl = new File( getNormalizedPath( to ) ).toURI().toURL(); + } + catch ( MalformedURLException e1 ) + { + getLogger().warn( "Unable to load a URL for '" + to + "': " + e.getMessage() ); + return to; + } + } + + try + { + fromUrl = new URL( from ); + } + catch ( MalformedURLException e ) + { + try + { + fromUrl = new File( getNormalizedPath( from ) ).toURI().toURL(); + } + catch ( MalformedURLException e1 ) + { + getLogger().warn( "Unable to load a URL for '" + from + "': " + e.getMessage() ); + return to; + } + } + + if ( toUrl != null && fromUrl != null ) + { + // URLs, determine if they share protocol and domain info + + if ( ( toUrl.getProtocol().equalsIgnoreCase( fromUrl.getProtocol() ) ) + && ( toUrl.getHost().equalsIgnoreCase( fromUrl.getHost() ) ) + && ( toUrl.getPort() == fromUrl.getPort() ) ) + { + // shared URL domain details, use URI to determine relative path + + toPath = toUrl.getFile(); + fromPath = fromUrl.getFile(); + } + else + { + // don't share basic URL information, no relative available + + return to; + } + } + else if ( ( toUrl != null && fromUrl == null ) || ( toUrl == null && fromUrl != null ) ) + { + // one is a URL and the other isn't, no relative available. + + return to; + } + + // either the two locations are not URLs or if they are they + // share the common protocol and domain info and we are left + // with their URI information + + String relativePath = getRelativeFilePath( fromPath, toPath ); + + if ( relativePath == null ) + { + relativePath = to; + } + + if ( getLogger().isDebugEnabled() && !relativePath.toString().equals( to ) ) + { + getLogger().debug( "Mapped url: " + to + " to relative path: " + relativePath ); + } + + return relativePath; + } + + private static String getRelativeFilePath( final String oldPath, final String newPath ) + { + // normalize the path delimiters + + String fromPath = new File( oldPath ).getPath(); + String toPath = new File( newPath ).getPath(); + + // strip any leading slashes if its a windows path + if ( toPath.matches( "^\\[a-zA-Z]:" ) ) + { + toPath = toPath.substring( 1 ); + } + if ( fromPath.matches( "^\\[a-zA-Z]:" ) ) + { + fromPath = fromPath.substring( 1 ); + } + + // lowercase windows drive letters. + if ( fromPath.startsWith( ":", 1 ) ) + { + fromPath = Character.toLowerCase( fromPath.charAt( 0 ) ) + fromPath.substring( 1 ); + } + if ( toPath.startsWith( ":", 1 ) ) + { + toPath = Character.toLowerCase( toPath.charAt( 0 ) ) + toPath.substring( 1 ); + } + + // check for the presence of windows drives. No relative way of + // traversing from one to the other. + + if ( ( toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) + && ( !toPath.substring( 0, 1 ).equals( fromPath.substring( 0, 1 ) ) ) ) + { + // they both have drive path element but they don't match, no + // relative path + + return null; + } + + if ( ( toPath.startsWith( ":", 1 ) && !fromPath.startsWith( ":", 1 ) ) + || ( !toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) ) + { + + // one has a drive path element and the other doesn't, no relative + // path. + + return null; + + } + + final String relativePath = buildRelativePath( toPath, fromPath, File.separatorChar ); + + return relativePath.toString(); + } + + /** {@inheritDoc} */ + public File getSiteDescriptor( File siteDirectory, Locale locale ) + { + checkNotNull( "siteDirectory", siteDirectory ); + final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale; + + File siteDescriptor = new File( siteDirectory, "site_" + llocale.getLanguage() + ".xml" ); + + if ( !siteDescriptor.isFile() ) + { + siteDescriptor = new File( siteDirectory, "site.xml" ); + } + return siteDescriptor; + } + + /** + * Get a site descriptor from one of the repositories. + * + * @param project the Maven project, not null. + * @param localRepository the Maven local repository, not null. + * @param repositories the Maven remote repositories, not null. + * @param locale the locale wanted for the site descriptor. If not null, searching for + * site_localeLanguage.xml, otherwise searching for site.xml. + * @return the site descriptor into the local repository after download of it from repositories or null if not + * found in repositories. + * @throws SiteToolException if any + */ + File getSiteDescriptorFromRepository( MavenProject project, ArtifactRepository localRepository, + List repositories, Locale locale ) + throws SiteToolException + { + checkNotNull( "project", project ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "repositories", repositories ); + + final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale; + + try + { + return resolveSiteDescriptor( project, localRepository, repositories, llocale ); + } + catch ( ArtifactNotFoundException e ) + { + getLogger().debug( "ArtifactNotFoundException: Unable to locate site descriptor: " + e ); + return null; + } + catch ( ArtifactResolutionException e ) + { + throw new SiteToolException( "ArtifactResolutionException: Unable to locate site descriptor: " + + e.getMessage(), e ); + } + catch ( IOException e ) + { + throw new SiteToolException( "IOException: Unable to locate site descriptor: " + e.getMessage(), e ); + } + } + + /** + * Read site descriptor content from Reader, adding support for deprecated ${reports}, + * ${parentProject} and ${modules} tags. + * + * @param reader + * @return the input content interpolated with deprecated tags + * @throws IOException + */ + private String readSiteDescriptor( Reader reader, String projectId ) + throws IOException + { + String siteDescriptorContent = IOUtil.toString( reader ); + + // This is to support the deprecated ${reports}, ${parentProject} and ${modules} tags. + Properties props = new Properties(); + props.put( "reports", "
" ); + props.put( "modules", "" ); + props.put( "parentProject", "" ); + + // warn if interpolation required + for ( Object prop : props.keySet() ) + { + if ( siteDescriptorContent.contains( "$" + prop ) ) + { + getLogger().warn( "Site descriptor for " + projectId + " contains $" + prop + + ": should be replaced with " + props.getProperty( (String) prop ) ); + } + if ( siteDescriptorContent.contains( "${" + prop + "}" ) ) + { + getLogger().warn( "Site descriptor for " + projectId + " contains ${" + prop + + "}: should be replaced with " + props.getProperty( (String) prop ) ); + } + } + + return StringUtils.interpolate( siteDescriptorContent, props ); + } + + /** {@inheritDoc} */ +/** + * {@inheritDoc } + */ +public org.apache.maven.doxia.site.decoration.DecorationModel getDecorationModel(java.io.File siteDirectory, java.util.Locale locale, org.apache.maven.project.MavenProject project, java.util.List reactorProjects, org.apache.maven.artifact.repository.ArtifactRepository localRepository, java.util.List repositories) throws org.apache.maven.doxia.tools.SiteToolException { + checkNotNull("project", project); + checkNotNull("reactorProjects", reactorProjects); + checkNotNull("localRepository", localRepository); + checkNotNull("repositories", repositories); + final java.util.Locale llocale = (locale == null) ? java.util.Locale.getDefault() : locale; + getLogger().debug((("Computing decoration model of " + project.getId()) + " for locale ") + llocale); + java.util.Map.Entry result = getDecorationModel(0, siteDirectory, llocale, project, reactorProjects, localRepository, repositories); + org.apache.maven.doxia.site.decoration.DecorationModel decorationModel = result.getKey(); + org.apache.maven.project.MavenProject parentProject = result.getValue(); + if (decorationModel == null) { + getLogger().debug("Using default site descriptor"); + java.lang.String siteDescriptorContent; + java.io.Reader reader = null; + try { + // Note the default is not a super class - it is used when nothing else is found + reader = org.codehaus.plexus.util.ReaderFactory.newXmlReader(getClass().getResourceAsStream("/default-site.xml")); + siteDescriptorContent = readSiteDescriptor(reader, "default-site.xml"); + } catch (java.io.IOException e) { + throw new org.apache.maven.doxia.tools.SiteToolException("Error reading default site descriptor: " + e.getMessage(), e); + } finally { + org.codehaus.plexus.util.IOUtil.close(reader); + } + decorationModel = readDecorationModel(siteDescriptorContent); + } + // DecorationModel back to String to interpolate, then go back to DecorationModel + java.lang.String siteDescriptorContent = decorationModelToString(decorationModel); + // "classical" late interpolation, after full inheritance + siteDescriptorContent = getInterpolatedSiteDescriptorContent(project, siteDescriptorContent, false); + decorationModel = readDecorationModel(siteDescriptorContent); + { + populateParentMenu(decorationModel, llocale, project, /* NPEX_NULL_EXP */ + parentProject, true); + } + try { + populateModulesMenu(decorationModel, llocale, project, reactorProjects, localRepository, true); + } catch (java.io.IOException e) { + throw new org.apache.maven.doxia.tools.SiteToolException("Error while populating modules menu: " + e.getMessage(), e); + } + if (decorationModel.getBannerLeft() == null) { + // extra default to set + org.apache.maven.doxia.site.decoration.Banner banner = new org.apache.maven.doxia.site.decoration.Banner(); + banner.setName(project.getName()); + decorationModel.setBannerLeft(banner); + } + return decorationModel; +} + + /** {@inheritDoc} */ + public String getInterpolatedSiteDescriptorContent( Map props, MavenProject aProject, + String siteDescriptorContent ) + throws SiteToolException + { + checkNotNull( "props", props ); + + // "classical" late interpolation + return getInterpolatedSiteDescriptorContent( aProject, siteDescriptorContent, false ); + } + + private String getInterpolatedSiteDescriptorContent( MavenProject aProject, + String siteDescriptorContent, boolean isEarly ) + throws SiteToolException + { + checkNotNull( "aProject", aProject ); + checkNotNull( "siteDescriptorContent", siteDescriptorContent ); + + RegexBasedInterpolator interpolator = new RegexBasedInterpolator(); + + if ( isEarly ) + { + interpolator.addValueSource( new PrefixedObjectValueSource( "this.", aProject ) ); + interpolator.addValueSource( new PrefixedPropertiesValueSource( "this.", aProject.getProperties() ) ); + } + else + { + interpolator.addValueSource( new ObjectBasedValueSource( aProject ) ); + interpolator.addValueSource( new MapBasedValueSource( aProject.getProperties() ) ); + + try + { + interpolator.addValueSource( new EnvarBasedValueSource() ); + } + catch ( IOException e ) + { + // Prefer logging? + throw new SiteToolException( "IOException: cannot interpolate environment properties: " + + e.getMessage(), e ); + } + } + + try + { + // FIXME: this does not escape xml entities, see MSITE-226, PLXCOMP-118 + return interpolator.interpolate( siteDescriptorContent, isEarly ? null : "project" ); + } + catch ( InterpolationException e ) + { + throw new SiteToolException( "Cannot interpolate site descriptor: " + e.getMessage(), e ); + } + } + + /** {@inheritDoc} */ + public MavenProject getParentProject( MavenProject aProject, List reactorProjects, + ArtifactRepository localRepository ) + { + checkNotNull( "aProject", aProject ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + + if ( isMaven3OrMore() ) + { + // no need to make voodoo with Maven 3: job already done + return aProject.getParent(); + } + + MavenProject parentProject = null; + + MavenProject origParent = aProject.getParent(); + if ( origParent != null ) + { + for ( MavenProject reactorProject : reactorProjects ) + { + if ( reactorProject.getGroupId().equals( origParent.getGroupId() ) + && reactorProject.getArtifactId().equals( origParent.getArtifactId() ) + && reactorProject.getVersion().equals( origParent.getVersion() ) ) + { + parentProject = reactorProject; + + getLogger().debug( "Parent project " + origParent.getId() + " picked from reactor" ); + break; + } + } + + if ( parentProject == null && aProject.getBasedir() != null + && StringUtils.isNotEmpty( aProject.getModel().getParent().getRelativePath() ) ) + { + try + { + String relativePath = aProject.getModel().getParent().getRelativePath(); + + File pomFile = new File( aProject.getBasedir(), relativePath ); + + if ( pomFile.isDirectory() ) + { + pomFile = new File( pomFile, "pom.xml" ); + } + pomFile = new File( getNormalizedPath( pomFile.getPath() ) ); + + if ( pomFile.isFile() ) + { + MavenProject mavenProject = mavenProjectBuilder.build( pomFile, localRepository, null ); + + if ( mavenProject.getGroupId().equals( origParent.getGroupId() ) + && mavenProject.getArtifactId().equals( origParent.getArtifactId() ) + && mavenProject.getVersion().equals( origParent.getVersion() ) ) + { + parentProject = mavenProject; + + getLogger().debug( "Parent project " + origParent.getId() + " loaded from a relative path: " + + relativePath ); + } + } + } + catch ( ProjectBuildingException e ) + { + getLogger().info( "Unable to load parent project " + origParent.getId() + " from a relative path: " + + e.getMessage() ); + } + } + + if ( parentProject == null ) + { + try + { + parentProject = mavenProjectBuilder.buildFromRepository( aProject.getParentArtifact(), aProject + .getRemoteArtifactRepositories(), localRepository ); + + getLogger().debug( "Parent project " + origParent.getId() + " loaded from repository" ); + } + catch ( ProjectBuildingException e ) + { + getLogger().warn( "Unable to load parent project " + origParent.getId() + " from repository: " + + e.getMessage() ); + } + } + + if ( parentProject == null ) + { + // fallback to original parent, which may contain uninterpolated value (still need a unit test) + + parentProject = origParent; + + getLogger().debug( "Parent project " + origParent.getId() + " picked from original value" ); + } + } + return parentProject; + } + + /** + * Populate the pre-defined parent menu of the decoration model, + * if used through <menu ref="parent"/>. + * + * @param decorationModel the Doxia Sitetools DecorationModel, not null. + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project a Maven project, not null. + * @param parentProject a Maven parent project, not null. + * @param keepInheritedRefs used for inherited references. + */ + private void populateParentMenu( DecorationModel decorationModel, Locale locale, MavenProject project, + MavenProject parentProject, boolean keepInheritedRefs ) + { + checkNotNull( "decorationModel", decorationModel ); + checkNotNull( "project", project ); + checkNotNull( "parentProject", parentProject ); + + Menu menu = decorationModel.getMenuRef( "parent" ); + + if ( menu == null ) + { + return; + } + + if ( keepInheritedRefs && menu.isInheritAsRef() ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + String parentUrl = getDistMgmntSiteUrl( parentProject ); + + if ( parentUrl != null ) + { + if ( parentUrl.endsWith( "/" ) ) + { + parentUrl += "index.html"; + } + else + { + parentUrl += "/index.html"; + } + + parentUrl = getRelativePath( parentUrl, getDistMgmntSiteUrl( project ) ); + } + else + { + // parent has no url, assume relative path is given by site structure + File parentBasedir = parentProject.getBasedir(); + // First make sure that the parent is available on the file system + if ( parentBasedir != null ) + { + // Try to find the relative path to the parent via the file system + String parentPath = parentBasedir.getAbsolutePath(); + String projectPath = project.getBasedir().getAbsolutePath(); + parentUrl = getRelativePath( parentPath, projectPath ) + "/index.html"; + } + } + + // Only add the parent menu if we were able to find a URL for it + if ( parentUrl == null ) + { + getLogger().warn( "Unable to find a URL to the parent project. The parent menu will NOT be added." ); + } + else + { + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.parentproject" ) ); + } + + MenuItem item = new MenuItem(); + item.setName( parentProject.getName() ); + item.setHref( parentUrl ); + menu.addItem( item ); + } + } + + /** + * Populate the pre-defined modules menu of the decoration model, + * if used through <menu ref="modules"/>. + * + * @param decorationModel the Doxia Sitetools DecorationModel, not null. + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project a Maven project, not null. + * @param reactorProjects the Maven reactor projects, not null. + * @param localRepository the Maven local repository, not null. + * @param keepInheritedRefs used for inherited references. + * @throws SiteToolException if any + * @throws IOException + */ + private void populateModulesMenu( DecorationModel decorationModel, Locale locale, MavenProject project, + List reactorProjects, ArtifactRepository localRepository, + boolean keepInheritedRefs ) + throws SiteToolException, IOException + { + checkNotNull( "project", project ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "decorationModel", decorationModel ); + + Menu menu = decorationModel.getMenuRef( "modules" ); + + if ( menu == null ) + { + return; + } + + if ( keepInheritedRefs && menu.isInheritAsRef() ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale ; + + // we require child modules and reactors to process module menu + if ( project.getModules().size() > 0 ) + { + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectmodules" ) ); + } + + for ( String module : (List) project.getModules() ) + { + MavenProject moduleProject = getModuleFromReactor( project, reactorProjects, module ); + + if ( moduleProject == null ) + { + getLogger().warn( "Module " + module + + " not found in reactor: loading locally" ); + + File f = new File( project.getBasedir(), module + "/pom.xml" ); + if ( f.exists() ) + { + try + { + moduleProject = mavenProjectBuilder.build( f, localRepository, null ); + } + catch ( ProjectBuildingException e ) + { + throw new SiteToolException( "Unable to read local module-POM", e ); + } + } + else + { + getLogger().warn( "No filesystem module-POM available" ); + + moduleProject = new MavenProject(); + moduleProject.setName( module ); + moduleProject.setDistributionManagement( new DistributionManagement() ); + moduleProject.getDistributionManagement().setSite( new Site() ); + moduleProject.getDistributionManagement().getSite().setUrl( module ); + } + } + + String siteUrl = getDistMgmntSiteUrl( moduleProject ); + String itemName = + ( moduleProject.getName() == null ) ? moduleProject.getArtifactId() : moduleProject.getName(); + + appendMenuItem( project, menu, itemName, siteUrl, moduleProject.getArtifactId() ); + } + } + else if ( decorationModel.getMenuRef( "modules" ).getInherit() == null ) + { + // only remove if project has no modules AND menu is not inherited, see MSHARED-174 + decorationModel.removeMenuRef( "modules" ); + } + } + + private static MavenProject getModuleFromReactor( MavenProject project, List reactorProjects, + String module ) + throws IOException + { + File moduleBasedir = new File( project.getBasedir(), module ).getCanonicalFile(); + + for ( MavenProject reactorProject : reactorProjects ) + { + if ( moduleBasedir.equals( reactorProject.getBasedir() ) ) + { + return reactorProject; + } + } + + // module not found in reactor + return null; + } + + /** {@inheritDoc} */ + public void populateReportsMenu( DecorationModel decorationModel, Locale locale, + Map> categories ) + { + checkNotNull( "decorationModel", decorationModel ); + checkNotNull( "categories", categories ); + + Menu menu = decorationModel.getMenuRef( "reports" ); + + if ( menu == null ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectdocumentation" ) ); + } + + boolean found = false; + if ( menu.getItems().isEmpty() ) + { + List categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_INFORMATION ); + if ( !isEmptyList( categoryReports ) ) + { + MenuItem item = createCategoryMenu( + i18n.getString( "site-tool", llocale, + "decorationModel.menu.projectinformation" ), + "/project-info.html", categoryReports, llocale ); + menu.getItems().add( item ); + found = true; + } + + categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_REPORTS ); + if ( !isEmptyList( categoryReports ) ) + { + MenuItem item = + createCategoryMenu( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectreports" ), + "/project-reports.html", categoryReports, llocale ); + menu.getItems().add( item ); + found = true; + } + } + if ( !found ) + { + decorationModel.removeMenuRef( "reports" ); + } + } + + /** {@inheritDoc} */ + public List getSiteLocales( String locales ) + { + if ( locales == null ) + { + return Collections.singletonList( DEFAULT_LOCALE ); + } + + String[] localesArray = StringUtils.split( locales, "," ); + List localesList = new ArrayList( localesArray.length ); + + for ( String localeString : localesArray ) + { + Locale locale = codeToLocale( localeString ); + + if ( locale == null ) + { + continue; + } + + if ( !Arrays.asList( Locale.getAvailableLocales() ).contains( locale ) ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "The locale defined by '" + locale + + "' is not available in this Java Virtual Machine (" + + System.getProperty( "java.version" ) + + " from " + System.getProperty( "java.vendor" ) + ") - IGNORING" ); + } + continue; + } + + // Default bundles are in English + if ( ( !locale.getLanguage().equals( DEFAULT_LOCALE.getLanguage() ) ) + && ( !i18n.getBundle( "site-tool", locale ).getLocale().getLanguage() + .equals( locale.getLanguage() ) ) ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "The locale '" + locale + "' (" + locale.getDisplayName( Locale.ENGLISH ) + + ") is not currently supported by Maven Site - IGNORING." + + "\nContributions are welcome and greatly appreciated!" + + "\nIf you want to contribute a new translation, please visit " + + "http://maven.apache.org/plugins/localization.html for detailed instructions." ); + } + + continue; + } + + localesList.add( locale ); + } + + if ( localesList.isEmpty() ) + { + localesList = Collections.singletonList( DEFAULT_LOCALE ); + } + + return localesList; + } + + /** + * Converts a locale code like "en", "en_US" or "en_US_win" to a java.util.Locale + * object. + *

If localeCode = default, return the current value of the default locale for this instance + * of the Java Virtual Machine.

+ * + * @param localeCode the locale code string. + * @return a java.util.Locale object instanced or null if errors occurred + * @see java.util.Locale#getDefault() + */ + private Locale codeToLocale( String localeCode ) + { + if ( localeCode == null ) + { + return null; + } + + if ( "default".equalsIgnoreCase( localeCode ) ) + { + return Locale.getDefault(); + } + + String language = ""; + String country = ""; + String variant = ""; + + StringTokenizer tokenizer = new StringTokenizer( localeCode, "_" ); + final int maxTokens = 3; + if ( tokenizer.countTokens() > maxTokens ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "Invalid java.util.Locale format for '" + localeCode + "' entry - IGNORING" ); + } + return null; + } + + if ( tokenizer.hasMoreTokens() ) + { + language = tokenizer.nextToken(); + if ( tokenizer.hasMoreTokens() ) + { + country = tokenizer.nextToken(); + if ( tokenizer.hasMoreTokens() ) + { + variant = tokenizer.nextToken(); + } + } + } + + return new Locale( language, country, variant ); + } + + // ---------------------------------------------------------------------- + // Protected methods + // ---------------------------------------------------------------------- + + /** + * @param path could be null. + * @return the path normalized, i.e. by eliminating "/../" and "/./" in the path. + * @see FilenameUtils#normalize(String) + */ + protected static String getNormalizedPath( String path ) + { + String normalized = FilenameUtils.normalize( path ); + if ( normalized == null ) + { + normalized = path; + } + return ( normalized == null ) ? null : normalized.replace( '\\', '/' ); + } + + // ---------------------------------------------------------------------- + // Private methods + // ---------------------------------------------------------------------- + + /** + * @param project not null + * @param localRepository not null + * @param repositories not null + * @param locale not null + * @return the resolved site descriptor + * @throws IOException if any + * @throws ArtifactResolutionException if any + * @throws ArtifactNotFoundException if any + */ + private File resolveSiteDescriptor( MavenProject project, ArtifactRepository localRepository, + List repositories, Locale locale ) + throws IOException, ArtifactResolutionException, ArtifactNotFoundException + { + File result; + + // TODO: this is a bit crude - proper type, or proper handling as metadata rather than an artifact in 2.1? + Artifact artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), + project.getArtifactId(), + project.getVersion(), "xml", + "site_" + locale.getLanguage() ); + + boolean found = false; + try + { + artifactResolver.resolve( artifact, repositories, localRepository ); + + result = artifact.getFile(); + + // we use zero length files to avoid re-resolution (see below) + if ( result.length() > 0 ) + { + found = true; + } + else + { + getLogger().debug( "No site descriptor found for " + project.getId() + " for locale " + + locale.getLanguage() + ", trying without locale..." ); + } + } + catch ( ArtifactNotFoundException e ) + { + getLogger().debug( "Unable to locate site descriptor for locale " + locale.getLanguage() + ": " + e ); + + // we can afford to write an empty descriptor here as we don't expect it to turn up later in the remote + // repository, because the parent was already released (and snapshots are updated automatically if changed) + result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); + result.getParentFile().mkdirs(); + result.createNewFile(); + } + + if ( !found ) + { + artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), project.getArtifactId(), + project.getVersion(), "xml", "site" ); + try + { + artifactResolver.resolve( artifact, repositories, localRepository ); + } + catch ( ArtifactNotFoundException e ) + { + // see above regarding this zero length file + result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); + result.getParentFile().mkdirs(); + result.createNewFile(); + + throw e; + } + + result = artifact.getFile(); + + // we use zero length files to avoid re-resolution (see below) + if ( result.length() == 0 ) + { + getLogger().debug( "No site descriptor found for " + project.getId() + " without locale." ); + result = null; + } + } + + return result; + } + + /** + * @param depth depth of project + * @param siteDirectory, can be null if project.basedir is null, ie POM from repository + * @param locale not null + * @param project not null + * @param reactorProjects not null + * @param localRepository not null + * @param repositories not null + * @param origProps not null + * @return the decoration model depending the locale and the parent project + * @throws SiteToolException if any + */ + private Map.Entry getDecorationModel( int depth, File siteDirectory, Locale locale, + MavenProject project, + List reactorProjects, + ArtifactRepository localRepository, + List repositories ) + throws SiteToolException + { + // 1. get site descriptor File + File siteDescriptor; + if ( project.getBasedir() == null ) + { + // POM is in the repository: look into the repository for site descriptor + try + { + siteDescriptor = getSiteDescriptorFromRepository( project, localRepository, repositories, locale ); + } + catch ( SiteToolException e ) + { + throw new SiteToolException( "The site descriptor cannot be resolved from the repository: " + + e.getMessage(), e ); + } + } + else + { + // POM is in build directory: look for site descriptor as local file + siteDescriptor = getSiteDescriptor( siteDirectory, locale ); + } + + // 2. read DecorationModel from site descriptor File and do early interpolation (${this.*}) + DecorationModel decoration = null; + Reader siteDescriptorReader = null; + try + { + if ( siteDescriptor != null && siteDescriptor.exists() ) + { + getLogger().debug( "Reading" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + + " site descriptor from " + siteDescriptor ); + + siteDescriptorReader = ReaderFactory.newXmlReader( siteDescriptor ); + + String siteDescriptorContent = readSiteDescriptor( siteDescriptorReader, project.getId() ); + + // interpolate ${this.*} = early interpolation + siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, true ); + + decoration = readDecorationModel( siteDescriptorContent ); + decoration.setLastModified( siteDescriptor.lastModified() ); + } + else + { + getLogger().debug( "No" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + " site descriptor." ); + } + } + catch ( IOException e ) + { + throw new SiteToolException( "The site descriptor for " + project.getId() + " cannot be read from " + + siteDescriptor, e ); + } + finally + { + IOUtil.close( siteDescriptorReader ); + } + + // 3. look for parent project + MavenProject parentProject = getParentProject( project, reactorProjects, localRepository ); + + // 4. merge with parent project DecorationModel + if ( parentProject != null && ( decoration == null || decoration.isMergeParent() ) ) + { + depth++; + getLogger().debug( "Looking for site descriptor of level " + depth + " parent project: " + + parentProject.getId() ); + + File parentSiteDirectory = null; + if ( parentProject.getBasedir() != null ) + { + // extrapolate parent project site directory + String siteRelativePath = getRelativeFilePath( project.getBasedir().getAbsolutePath(), + siteDescriptor.getParentFile().getAbsolutePath() ); + + parentSiteDirectory = new File( parentProject.getBasedir(), siteRelativePath ); + // notice: using same siteRelativePath for parent as current project; may be wrong if site plugin + // has different configuration. But this is a rare case (this only has impact if parent is from reactor) + } + + DecorationModel parentDecoration = + getDecorationModel( depth, parentSiteDirectory, locale, parentProject, reactorProjects, localRepository, + repositories ).getKey(); + + // MSHARED-116 requires an empty decoration model (instead of a null one) + // MSHARED-145 requires us to do this only if there is a parent to merge it with + if ( decoration == null && parentDecoration != null ) + { + // we have no site descriptor: merge the parent into an empty one + decoration = new DecorationModel(); + } + + String name = project.getName(); + if ( decoration != null && StringUtils.isNotEmpty( decoration.getName() ) ) + { + name = decoration.getName(); + } + + // Merge the parent and child DecorationModels + String projectDistMgmnt = getDistMgmntSiteUrl( project ); + String parentDistMgmnt = getDistMgmntSiteUrl( parentProject ); + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Site decoration model inheritance: assembling child with level " + depth + + " parent: distributionManagement.site.url child = " + projectDistMgmnt + " and parent = " + + parentDistMgmnt ); + } + assembler.assembleModelInheritance( name, decoration, parentDecoration, projectDistMgmnt, + parentDistMgmnt == null ? projectDistMgmnt : parentDistMgmnt ); + } + + return new AbstractMap.SimpleEntry( decoration, parentProject ); + } + + /** + * @param siteDescriptorContent not null + * @return the decoration model object + * @throws SiteToolException if any + */ + private DecorationModel readDecorationModel( String siteDescriptorContent ) + throws SiteToolException + { + try + { + return new DecorationXpp3Reader().read( new StringReader( siteDescriptorContent ) ); + } + catch ( XmlPullParserException e ) + { + throw new SiteToolException( "Error parsing site descriptor", e ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading site descriptor", e ); + } + } + + private String decorationModelToString( DecorationModel decoration ) + throws SiteToolException + { + StringWriter writer = new StringWriter(); + + try + { + new DecorationXpp3Writer().write( writer, decoration ); + return writer.toString(); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading site descriptor", e ); + } + finally + { + IOUtil.close( writer ); + } + } + + private static String buildRelativePath( final String toPath, final String fromPath, final char separatorChar ) + { + // use tokenizer to traverse paths and for lazy checking + StringTokenizer toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); + StringTokenizer fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); + + int count = 0; + + // walk along the to path looking for divergence from the from path + while ( toTokeniser.hasMoreTokens() && fromTokeniser.hasMoreTokens() ) + { + if ( separatorChar == '\\' ) + { + if ( !fromTokeniser.nextToken().equalsIgnoreCase( toTokeniser.nextToken() ) ) + { + break; + } + } + else + { + if ( !fromTokeniser.nextToken().equals( toTokeniser.nextToken() ) ) + { + break; + } + } + + count++; + } + + // reinitialize the tokenizers to count positions to retrieve the + // gobbled token + + toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); + fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); + + while ( count-- > 0 ) + { + fromTokeniser.nextToken(); + toTokeniser.nextToken(); + } + + StringBuilder relativePath = new StringBuilder(); + + // add back refs for the rest of from location. + while ( fromTokeniser.hasMoreTokens() ) + { + fromTokeniser.nextToken(); + + relativePath.append( ".." ); + + if ( fromTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + } + + if ( relativePath.length() != 0 && toTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + + // add fwd fills for whatever's left of to. + while ( toTokeniser.hasMoreTokens() ) + { + relativePath.append( toTokeniser.nextToken() ); + + if ( toTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + } + return relativePath.toString(); + } + + /** + * @param project not null + * @param menu not null + * @param name not null + * @param href could be null + * @param defaultHref not null + */ + private void appendMenuItem( MavenProject project, Menu menu, String name, String href, String defaultHref ) + { + String selectedHref = href; + + if ( selectedHref == null ) + { + selectedHref = defaultHref; + } + + MenuItem item = new MenuItem(); + item.setName( name ); + + String baseUrl = getDistMgmntSiteUrl( project ); + if ( baseUrl != null ) + { + selectedHref = getRelativePath( selectedHref, baseUrl ); + } + + if ( selectedHref.endsWith( "/" ) ) + { + item.setHref( selectedHref + "index.html" ); + } + else + { + item.setHref( selectedHref + "/index.html" ); + } + menu.addItem( item ); + } + + /** + * @param name not null + * @param href not null + * @param categoryReports not null + * @param locale not null + * @return the menu item object + */ + private MenuItem createCategoryMenu( String name, String href, List categoryReports, Locale locale ) + { + MenuItem item = new MenuItem(); + item.setName( name ); + item.setCollapse( true ); + item.setHref( href ); + + // MSHARED-172, allow reports to define their order in some other way? + //Collections.sort( categoryReports, new ReportComparator( locale ) ); + + for ( MavenReport report : categoryReports ) + { + MenuItem subitem = new MenuItem(); + subitem.setName( report.getName( locale ) ); + subitem.setHref( report.getOutputName() + ".html" ); + item.getItems().add( subitem ); + } + + return item; + } + + // ---------------------------------------------------------------------- + // static methods + // ---------------------------------------------------------------------- + + /** + * Convenience method. + * + * @param list could be null + * @return true if the list is null or empty + */ + private static boolean isEmptyList( List list ) + { + return list == null || list.isEmpty(); + } + + /** + * Return distributionManagement.site.url if defined, null otherwise. + * + * @param project not null + * @return could be null + */ + private static String getDistMgmntSiteUrl( MavenProject project ) + { + return getDistMgmntSiteUrl( project.getDistributionManagement() ); + } + + private static String getDistMgmntSiteUrl( DistributionManagement distMgmnt ) + { + if ( distMgmnt != null && distMgmnt.getSite() != null && distMgmnt.getSite().getUrl() != null ) + { + return urlEncode( distMgmnt.getSite().getUrl() ); + } + + return null; + } + + private static String urlEncode( final String url ) + { + if ( url == null ) + { + return null; + } + + try + { + return new File( url ).toURI().toURL().toExternalForm(); + } + catch ( MalformedURLException ex ) + { + return url; // this will then throw somewhere else + } + } + + private void checkNotNull( String name, Object value ) + { + if ( value == null ) + { + throw new IllegalArgumentException( "The parameter '" + name + "' cannot be null." ); + } + } + + /** + * Check the current Maven version to see if it's Maven 3.0 or newer. + */ + private static boolean isMaven3OrMore() + { + return new DefaultArtifactVersion( getMavenVersion() ).getMajorVersion() >= 3; + } + + private static String getMavenVersion() + { + // This relies on the fact that MavenProject is the in core classloader + // and that the core classloader is for the maven-core artifact + // and that should have a pom.properties file + // if this ever changes, we will have to revisit this code. + final Properties properties = new Properties(); + final String corePomProperties = "META-INF/maven/org.apache.maven/maven-core/pom.properties"; + final InputStream in = MavenProject.class.getClassLoader().getResourceAsStream( corePomProperties ); + try + { + properties.load( in ); + } + catch ( IOException ioe ) + { + return ""; + } + finally + { + IOUtil.close( in ); + } + + return properties.getProperty( "version" ).trim(); + } +} diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_492/npe.json b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_492/npe.json new file mode 100644 index 000000000..119293c86 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_492/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/DefaultSiteTool.java", + "line": 475, + "npe_method": "getDecorationModel", + "deref_field": "parentProject", + "npe_class": "DefaultSiteTool" +} \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_587/buggy.java b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_587/buggy.java new file mode 100644 index 000000000..d79a3fb55 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_587/buggy.java @@ -0,0 +1,1491 @@ +package org.apache.maven.doxia.tools; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.StringTokenizer; + +import org.apache.commons.io.FilenameUtils; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.factory.ArtifactFactory; +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.artifact.resolver.ArtifactNotFoundException; +import org.apache.maven.artifact.resolver.ArtifactResolutionException; +import org.apache.maven.artifact.resolver.ArtifactResolver; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; +import org.apache.maven.artifact.versioning.VersionRange; +import org.apache.maven.doxia.site.decoration.Banner; +import org.apache.maven.doxia.site.decoration.DecorationModel; +import org.apache.maven.doxia.site.decoration.Menu; +import org.apache.maven.doxia.site.decoration.MenuItem; +import org.apache.maven.doxia.site.decoration.Skin; +import org.apache.maven.doxia.site.decoration.inheritance.DecorationModelInheritanceAssembler; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Reader; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Writer; +import org.apache.maven.model.DistributionManagement; +import org.apache.maven.model.Site; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.MavenProjectBuilder; +import org.apache.maven.project.ProjectBuildingException; +import org.apache.maven.reporting.MavenReport; +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.IOUtil; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.interpolation.EnvarBasedValueSource; +import org.codehaus.plexus.interpolation.InterpolationException; +import org.codehaus.plexus.interpolation.MapBasedValueSource; +import org.codehaus.plexus.interpolation.ObjectBasedValueSource; +import org.codehaus.plexus.interpolation.PrefixedObjectValueSource; +import org.codehaus.plexus.interpolation.PrefixedPropertiesValueSource; +import org.codehaus.plexus.interpolation.RegexBasedInterpolator; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +/** + * Default implementation of the site tool. + * + * @author Vincent Siveton + */ +@Component( role = SiteTool.class ) +public class DefaultSiteTool + extends AbstractLogEnabled + implements SiteTool +{ + // ---------------------------------------------------------------------- + // Components + // ---------------------------------------------------------------------- + + /** + * The component that is used to resolve additional artifacts required. + */ + @Requirement + private ArtifactResolver artifactResolver; + + /** + * The component used for creating artifact instances. + */ + @Requirement + private ArtifactFactory artifactFactory; + + /** + * Internationalization. + */ + @Requirement + protected I18N i18n; + + /** + * The component for assembling inheritance. + */ + @Requirement + protected DecorationModelInheritanceAssembler assembler; + + /** + * Project builder (deprecated in Maven 3: should use ProjectBuilder, which will avoid + * issues like DOXIASITETOOLS-166) + */ + @Requirement + protected MavenProjectBuilder mavenProjectBuilder; + + // ---------------------------------------------------------------------- + // Public methods + // ---------------------------------------------------------------------- + + public Artifact getSkinArtifactFromRepository( ArtifactRepository localRepository, + List remoteArtifactRepositories, + DecorationModel decoration ) + throws SiteToolException + { + checkNotNull( "localRepository", localRepository ); + checkNotNull( "remoteArtifactRepositories", remoteArtifactRepositories ); + checkNotNull( "decoration", decoration ); + + Skin skin = decoration.getSkin(); + + if ( skin == null ) + { + skin = Skin.getDefaultSkin(); + } + + String version = skin.getVersion(); + Artifact artifact; + try + { + if ( version == null ) + { + version = Artifact.RELEASE_VERSION; + } + VersionRange versionSpec = VersionRange.createFromVersionSpec( version ); + artifact = artifactFactory.createDependencyArtifact( skin.getGroupId(), skin.getArtifactId(), versionSpec, + "jar", null, null ); + + artifactResolver.resolve( artifact, remoteArtifactRepositories, localRepository ); + } + catch ( InvalidVersionSpecificationException e ) + { + throw new SiteToolException( "InvalidVersionSpecificationException: The skin version '" + version + + "' is not valid: " + e.getMessage(), e ); + } + catch ( ArtifactResolutionException e ) + { + throw new SiteToolException( "ArtifactResolutionException: Unable to find skin", e ); + } + catch ( ArtifactNotFoundException e ) + { + throw new SiteToolException( "ArtifactNotFoundException: The skin does not exist: " + e.getMessage(), e ); + } + + return artifact; + } + + public Artifact getDefaultSkinArtifact( ArtifactRepository localRepository, + List remoteArtifactRepositories ) + throws SiteToolException + { + return getSkinArtifactFromRepository( localRepository, remoteArtifactRepositories, new DecorationModel() ); + } + + /** + * This method is not implemented according to the URI specification and has many weird + * corner cases where it doesn't do the right thing. Please consider using a better + * implemented method from a different library such as org.apache.http.client.utils.URIUtils#resolve. + */ + @Deprecated + public String getRelativePath( String to, String from ) + { + checkNotNull( "to", to ); + checkNotNull( "from", from ); + + if ( to.contains( ":" ) && from.contains( ":" ) ) + { + String toScheme = to.substring( 0, to.lastIndexOf( ':' ) ); + String fromScheme = from.substring( 0, from.lastIndexOf( ':' ) ); + if ( !toScheme.equals( fromScheme ) ) + { + return to; + } + } + + URL toUrl = null; + URL fromUrl = null; + + String toPath = to; + String fromPath = from; + + try + { + toUrl = new URL( to ); + } + catch ( MalformedURLException e ) + { + try + { + toUrl = new File( getNormalizedPath( to ) ).toURI().toURL(); + } + catch ( MalformedURLException e1 ) + { + getLogger().warn( "Unable to load a URL for '" + to + "': " + e.getMessage() ); + return to; + } + } + + try + { + fromUrl = new URL( from ); + } + catch ( MalformedURLException e ) + { + try + { + fromUrl = new File( getNormalizedPath( from ) ).toURI().toURL(); + } + catch ( MalformedURLException e1 ) + { + getLogger().warn( "Unable to load a URL for '" + from + "': " + e.getMessage() ); + return to; + } + } + + if ( toUrl != null && fromUrl != null ) + { + // URLs, determine if they share protocol and domain info + + if ( ( toUrl.getProtocol().equalsIgnoreCase( fromUrl.getProtocol() ) ) + && ( toUrl.getHost().equalsIgnoreCase( fromUrl.getHost() ) ) + && ( toUrl.getPort() == fromUrl.getPort() ) ) + { + // shared URL domain details, use URI to determine relative path + + toPath = toUrl.getFile(); + fromPath = fromUrl.getFile(); + } + else + { + // don't share basic URL information, no relative available + + return to; + } + } + else if ( ( toUrl != null && fromUrl == null ) || ( toUrl == null && fromUrl != null ) ) + { + // one is a URL and the other isn't, no relative available. + + return to; + } + + // either the two locations are not URLs or if they are they + // share the common protocol and domain info and we are left + // with their URI information + + String relativePath = getRelativeFilePath( fromPath, toPath ); + + if ( relativePath == null ) + { + relativePath = to; + } + + if ( getLogger().isDebugEnabled() && !relativePath.toString().equals( to ) ) + { + getLogger().debug( "Mapped url: " + to + " to relative path: " + relativePath ); + } + + return relativePath; + } + + private static String getRelativeFilePath( final String oldPath, final String newPath ) + { + // normalize the path delimiters + + String fromPath = new File( oldPath ).getPath(); + String toPath = new File( newPath ).getPath(); + + // strip any leading slashes if its a windows path + if ( toPath.matches( "^\\[a-zA-Z]:" ) ) + { + toPath = toPath.substring( 1 ); + } + if ( fromPath.matches( "^\\[a-zA-Z]:" ) ) + { + fromPath = fromPath.substring( 1 ); + } + + // lowercase windows drive letters. + if ( fromPath.startsWith( ":", 1 ) ) + { + fromPath = Character.toLowerCase( fromPath.charAt( 0 ) ) + fromPath.substring( 1 ); + } + if ( toPath.startsWith( ":", 1 ) ) + { + toPath = Character.toLowerCase( toPath.charAt( 0 ) ) + toPath.substring( 1 ); + } + + // check for the presence of windows drives. No relative way of + // traversing from one to the other. + + if ( ( toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) + && ( !toPath.substring( 0, 1 ).equals( fromPath.substring( 0, 1 ) ) ) ) + { + // they both have drive path element but they don't match, no + // relative path + + return null; + } + + if ( ( toPath.startsWith( ":", 1 ) && !fromPath.startsWith( ":", 1 ) ) + || ( !toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) ) + { + + // one has a drive path element and the other doesn't, no relative + // path. + + return null; + + } + + final String relativePath = buildRelativePath( toPath, fromPath, File.separatorChar ); + + return relativePath.toString(); + } + + /** {@inheritDoc} */ + public File getSiteDescriptor( File siteDirectory, Locale locale ) + { + checkNotNull( "siteDirectory", siteDirectory ); + final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale; + + File siteDescriptor = new File( siteDirectory, "site_" + llocale.getLanguage() + ".xml" ); + + if ( !siteDescriptor.isFile() ) + { + siteDescriptor = new File( siteDirectory, "site.xml" ); + } + return siteDescriptor; + } + + /** + * Get a site descriptor from one of the repositories. + * + * @param project the Maven project, not null. + * @param localRepository the Maven local repository, not null. + * @param repositories the Maven remote repositories, not null. + * @param locale the locale wanted for the site descriptor. If not null, searching for + * site_localeLanguage.xml, otherwise searching for site.xml. + * @return the site descriptor into the local repository after download of it from repositories or null if not + * found in repositories. + * @throws SiteToolException if any + */ + File getSiteDescriptorFromRepository( MavenProject project, ArtifactRepository localRepository, + List repositories, Locale locale ) + throws SiteToolException + { + checkNotNull( "project", project ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "repositories", repositories ); + + final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale; + + try + { + return resolveSiteDescriptor( project, localRepository, repositories, llocale ); + } + catch ( ArtifactNotFoundException e ) + { + getLogger().debug( "ArtifactNotFoundException: Unable to locate site descriptor: " + e ); + return null; + } + catch ( ArtifactResolutionException e ) + { + throw new SiteToolException( "ArtifactResolutionException: Unable to locate site descriptor: " + + e.getMessage(), e ); + } + catch ( IOException e ) + { + throw new SiteToolException( "IOException: Unable to locate site descriptor: " + e.getMessage(), e ); + } + } + + /** + * Read site descriptor content from Reader, adding support for deprecated ${reports}, + * ${parentProject} and ${modules} tags. + * + * @param reader + * @return the input content interpolated with deprecated tags + * @throws IOException + */ + private String readSiteDescriptor( Reader reader, String projectId ) + throws IOException + { + String siteDescriptorContent = IOUtil.toString( reader ); + + // This is to support the deprecated ${reports}, ${parentProject} and ${modules} tags. + Properties props = new Properties(); + props.put( "reports", "
" ); + props.put( "modules", "" ); + props.put( "parentProject", "" ); + + // warn if interpolation required + for ( Object prop : props.keySet() ) + { + if ( siteDescriptorContent.contains( "$" + prop ) ) + { + getLogger().warn( "Site descriptor for " + projectId + " contains $" + prop + + ": should be replaced with " + props.getProperty( (String) prop ) ); + } + if ( siteDescriptorContent.contains( "${" + prop + "}" ) ) + { + getLogger().warn( "Site descriptor for " + projectId + " contains ${" + prop + + "}: should be replaced with " + props.getProperty( (String) prop ) ); + } + } + + return StringUtils.interpolate( siteDescriptorContent, props ); + } + + /** {@inheritDoc} */ + public DecorationModel getDecorationModel( File siteDirectory, Locale locale, MavenProject project, + List reactorProjects, ArtifactRepository localRepository, + List repositories ) + throws SiteToolException + { + checkNotNull( "project", project ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "repositories", repositories ); + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + getLogger().debug( "Computing decoration model of " + project.getId() + " for locale " + llocale ); + + Map.Entry result = + getDecorationModel( 0, siteDirectory, llocale, project, reactorProjects, localRepository, repositories ); + DecorationModel decorationModel = result.getKey(); + MavenProject parentProject = result.getValue(); + + if ( decorationModel == null ) + { + getLogger().debug( "Using default site descriptor" ); + + String siteDescriptorContent; + + Reader reader = null; + try + { + // Note the default is not a super class - it is used when nothing else is found + reader = ReaderFactory.newXmlReader( getClass().getResourceAsStream( "/default-site.xml" ) ); + siteDescriptorContent = readSiteDescriptor( reader, "default-site.xml" ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading default site descriptor: " + e.getMessage(), e ); + } + finally + { + IOUtil.close( reader ); + } + + decorationModel = readDecorationModel( siteDescriptorContent ); + } + + // DecorationModel back to String to interpolate, then go back to DecorationModel + String siteDescriptorContent = decorationModelToString( decorationModel ); + + // "classical" late interpolation, after full inheritance + siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, false ); + + decorationModel = readDecorationModel( siteDescriptorContent ); + + if ( parentProject != null ) + { + populateParentMenu( decorationModel, llocale, project, parentProject, true ); + } + + try + { + populateModulesMenu( decorationModel, llocale, project, reactorProjects, localRepository, true ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error while populating modules menu: " + e.getMessage(), e ); + } + + if ( decorationModel.getBannerLeft() == null ) + { + // extra default to set + Banner banner = new Banner(); + banner.setName( project.getName() ); + decorationModel.setBannerLeft( banner ); + } + + return decorationModel; + } + + /** {@inheritDoc} */ + public String getInterpolatedSiteDescriptorContent( Map props, MavenProject aProject, + String siteDescriptorContent ) + throws SiteToolException + { + checkNotNull( "props", props ); + + // "classical" late interpolation + return getInterpolatedSiteDescriptorContent( aProject, siteDescriptorContent, false ); + } + + private String getInterpolatedSiteDescriptorContent( MavenProject aProject, + String siteDescriptorContent, boolean isEarly ) + throws SiteToolException + { + checkNotNull( "aProject", aProject ); + checkNotNull( "siteDescriptorContent", siteDescriptorContent ); + + RegexBasedInterpolator interpolator = new RegexBasedInterpolator(); + + if ( isEarly ) + { + interpolator.addValueSource( new PrefixedObjectValueSource( "this.", aProject ) ); + interpolator.addValueSource( new PrefixedPropertiesValueSource( "this.", aProject.getProperties() ) ); + } + else + { + interpolator.addValueSource( new ObjectBasedValueSource( aProject ) ); + interpolator.addValueSource( new MapBasedValueSource( aProject.getProperties() ) ); + + try + { + interpolator.addValueSource( new EnvarBasedValueSource() ); + } + catch ( IOException e ) + { + // Prefer logging? + throw new SiteToolException( "IOException: cannot interpolate environment properties: " + + e.getMessage(), e ); + } + } + + try + { + // FIXME: this does not escape xml entities, see MSITE-226, PLXCOMP-118 + return interpolator.interpolate( siteDescriptorContent, isEarly ? null : "project" ); + } + catch ( InterpolationException e ) + { + throw new SiteToolException( "Cannot interpolate site descriptor: " + e.getMessage(), e ); + } + } + + /** {@inheritDoc} */ +/** + * {@inheritDoc } + */ +public org.apache.maven.project.MavenProject getParentProject(org.apache.maven.project.MavenProject aProject, java.util.List reactorProjects, org.apache.maven.artifact.repository.ArtifactRepository localRepository) { + checkNotNull("aProject", aProject); + checkNotNull("reactorProjects", reactorProjects); + checkNotNull("localRepository", localRepository); + if (org.apache.maven.doxia.tools.DefaultSiteTool.isMaven3OrMore()) { + // no need to make voodoo with Maven 3: job already done + return aProject.getParent(); + } + org.apache.maven.project.MavenProject parentProject = null; + org.apache.maven.project.MavenProject origParent = aProject.getParent(); + { + for (org.apache.maven.project.MavenProject reactorProject : reactorProjects) { + if ((reactorProject.getGroupId().equals(/* NPEX_NULL_EXP */ + origParent.getGroupId()) && reactorProject.getArtifactId().equals(origParent.getArtifactId())) && reactorProject.getVersion().equals(origParent.getVersion())) { + parentProject = reactorProject; + getLogger().debug(("Parent project " + origParent.getId()) + " picked from reactor"); + break; + } + } + if (((parentProject == null) && (aProject.getBasedir() != null)) && org.codehaus.plexus.util.StringUtils.isNotEmpty(aProject.getModel().getParent().getRelativePath())) { + try { + java.lang.String relativePath = aProject.getModel().getParent().getRelativePath(); + java.io.File pomFile = new java.io.File(aProject.getBasedir(), relativePath); + if (pomFile.isDirectory()) { + pomFile = new java.io.File(pomFile, "pom.xml"); + } + pomFile = new java.io.File(org.apache.maven.doxia.tools.DefaultSiteTool.getNormalizedPath(pomFile.getPath())); + if (pomFile.isFile()) { + org.apache.maven.project.MavenProject mavenProject = mavenProjectBuilder.build(pomFile, localRepository, null); + if ((mavenProject.getGroupId().equals(origParent.getGroupId()) && mavenProject.getArtifactId().equals(origParent.getArtifactId())) && mavenProject.getVersion().equals(origParent.getVersion())) { + parentProject = mavenProject; + getLogger().debug((("Parent project " + origParent.getId()) + " loaded from a relative path: ") + relativePath); + } + } + } catch (org.apache.maven.project.ProjectBuildingException e) { + getLogger().info((("Unable to load parent project " + origParent.getId()) + " from a relative path: ") + e.getMessage()); + } + } + if (parentProject == null) { + try { + parentProject = mavenProjectBuilder.buildFromRepository(aProject.getParentArtifact(), aProject.getRemoteArtifactRepositories(), localRepository); + getLogger().debug(("Parent project " + origParent.getId()) + " loaded from repository"); + } catch (org.apache.maven.project.ProjectBuildingException e) { + getLogger().warn((("Unable to load parent project " + origParent.getId()) + " from repository: ") + e.getMessage()); + } + } + if (parentProject == null) { + // fallback to original parent, which may contain uninterpolated value (still need a unit test) + parentProject = origParent; + getLogger().debug(("Parent project " + origParent.getId()) + " picked from original value"); + } + } + return parentProject; +} + + /** + * Populate the pre-defined parent menu of the decoration model, + * if used through <menu ref="parent"/>. + * + * @param decorationModel the Doxia Sitetools DecorationModel, not null. + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project a Maven project, not null. + * @param parentProject a Maven parent project, not null. + * @param keepInheritedRefs used for inherited references. + */ + private void populateParentMenu( DecorationModel decorationModel, Locale locale, MavenProject project, + MavenProject parentProject, boolean keepInheritedRefs ) + { + checkNotNull( "decorationModel", decorationModel ); + checkNotNull( "project", project ); + checkNotNull( "parentProject", parentProject ); + + Menu menu = decorationModel.getMenuRef( "parent" ); + + if ( menu == null ) + { + return; + } + + if ( keepInheritedRefs && menu.isInheritAsRef() ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + String parentUrl = getDistMgmntSiteUrl( parentProject ); + + if ( parentUrl != null ) + { + if ( parentUrl.endsWith( "/" ) ) + { + parentUrl += "index.html"; + } + else + { + parentUrl += "/index.html"; + } + + parentUrl = getRelativePath( parentUrl, getDistMgmntSiteUrl( project ) ); + } + else + { + // parent has no url, assume relative path is given by site structure + File parentBasedir = parentProject.getBasedir(); + // First make sure that the parent is available on the file system + if ( parentBasedir != null ) + { + // Try to find the relative path to the parent via the file system + String parentPath = parentBasedir.getAbsolutePath(); + String projectPath = project.getBasedir().getAbsolutePath(); + parentUrl = getRelativePath( parentPath, projectPath ) + "/index.html"; + } + } + + // Only add the parent menu if we were able to find a URL for it + if ( parentUrl == null ) + { + getLogger().warn( "Unable to find a URL to the parent project. The parent menu will NOT be added." ); + } + else + { + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.parentproject" ) ); + } + + MenuItem item = new MenuItem(); + item.setName( parentProject.getName() ); + item.setHref( parentUrl ); + menu.addItem( item ); + } + } + + /** + * Populate the pre-defined modules menu of the decoration model, + * if used through <menu ref="modules"/>. + * + * @param decorationModel the Doxia Sitetools DecorationModel, not null. + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project a Maven project, not null. + * @param reactorProjects the Maven reactor projects, not null. + * @param localRepository the Maven local repository, not null. + * @param keepInheritedRefs used for inherited references. + * @throws SiteToolException if any + * @throws IOException + */ + private void populateModulesMenu( DecorationModel decorationModel, Locale locale, MavenProject project, + List reactorProjects, ArtifactRepository localRepository, + boolean keepInheritedRefs ) + throws SiteToolException, IOException + { + checkNotNull( "project", project ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "decorationModel", decorationModel ); + + Menu menu = decorationModel.getMenuRef( "modules" ); + + if ( menu == null ) + { + return; + } + + if ( keepInheritedRefs && menu.isInheritAsRef() ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale ; + + // we require child modules and reactors to process module menu + if ( project.getModules().size() > 0 ) + { + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectmodules" ) ); + } + + for ( String module : (List) project.getModules() ) + { + MavenProject moduleProject = getModuleFromReactor( project, reactorProjects, module ); + + if ( moduleProject == null ) + { + getLogger().warn( "Module " + module + + " not found in reactor: loading locally" ); + + File f = new File( project.getBasedir(), module + "/pom.xml" ); + if ( f.exists() ) + { + try + { + moduleProject = mavenProjectBuilder.build( f, localRepository, null ); + } + catch ( ProjectBuildingException e ) + { + throw new SiteToolException( "Unable to read local module-POM", e ); + } + } + else + { + getLogger().warn( "No filesystem module-POM available" ); + + moduleProject = new MavenProject(); + moduleProject.setName( module ); + moduleProject.setDistributionManagement( new DistributionManagement() ); + moduleProject.getDistributionManagement().setSite( new Site() ); + moduleProject.getDistributionManagement().getSite().setUrl( module ); + } + } + + String siteUrl = getDistMgmntSiteUrl( moduleProject ); + String itemName = + ( moduleProject.getName() == null ) ? moduleProject.getArtifactId() : moduleProject.getName(); + + appendMenuItem( project, menu, itemName, siteUrl, moduleProject.getArtifactId() ); + } + } + else if ( decorationModel.getMenuRef( "modules" ).getInherit() == null ) + { + // only remove if project has no modules AND menu is not inherited, see MSHARED-174 + decorationModel.removeMenuRef( "modules" ); + } + } + + private static MavenProject getModuleFromReactor( MavenProject project, List reactorProjects, + String module ) + throws IOException + { + File moduleBasedir = new File( project.getBasedir(), module ).getCanonicalFile(); + + for ( MavenProject reactorProject : reactorProjects ) + { + if ( moduleBasedir.equals( reactorProject.getBasedir() ) ) + { + return reactorProject; + } + } + + // module not found in reactor + return null; + } + + /** {@inheritDoc} */ + public void populateReportsMenu( DecorationModel decorationModel, Locale locale, + Map> categories ) + { + checkNotNull( "decorationModel", decorationModel ); + checkNotNull( "categories", categories ); + + Menu menu = decorationModel.getMenuRef( "reports" ); + + if ( menu == null ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectdocumentation" ) ); + } + + boolean found = false; + if ( menu.getItems().isEmpty() ) + { + List categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_INFORMATION ); + if ( !isEmptyList( categoryReports ) ) + { + MenuItem item = createCategoryMenu( + i18n.getString( "site-tool", llocale, + "decorationModel.menu.projectinformation" ), + "/project-info.html", categoryReports, llocale ); + menu.getItems().add( item ); + found = true; + } + + categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_REPORTS ); + if ( !isEmptyList( categoryReports ) ) + { + MenuItem item = + createCategoryMenu( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectreports" ), + "/project-reports.html", categoryReports, llocale ); + menu.getItems().add( item ); + found = true; + } + } + if ( !found ) + { + decorationModel.removeMenuRef( "reports" ); + } + } + + /** {@inheritDoc} */ + public List getSiteLocales( String locales ) + { + if ( locales == null ) + { + return Collections.singletonList( DEFAULT_LOCALE ); + } + + String[] localesArray = StringUtils.split( locales, "," ); + List localesList = new ArrayList( localesArray.length ); + + for ( String localeString : localesArray ) + { + Locale locale = codeToLocale( localeString ); + + if ( locale == null ) + { + continue; + } + + if ( !Arrays.asList( Locale.getAvailableLocales() ).contains( locale ) ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "The locale defined by '" + locale + + "' is not available in this Java Virtual Machine (" + + System.getProperty( "java.version" ) + + " from " + System.getProperty( "java.vendor" ) + ") - IGNORING" ); + } + continue; + } + + // Default bundles are in English + if ( ( !locale.getLanguage().equals( DEFAULT_LOCALE.getLanguage() ) ) + && ( !i18n.getBundle( "site-tool", locale ).getLocale().getLanguage() + .equals( locale.getLanguage() ) ) ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "The locale '" + locale + "' (" + locale.getDisplayName( Locale.ENGLISH ) + + ") is not currently supported by Maven Site - IGNORING." + + "\nContributions are welcome and greatly appreciated!" + + "\nIf you want to contribute a new translation, please visit " + + "http://maven.apache.org/plugins/localization.html for detailed instructions." ); + } + + continue; + } + + localesList.add( locale ); + } + + if ( localesList.isEmpty() ) + { + localesList = Collections.singletonList( DEFAULT_LOCALE ); + } + + return localesList; + } + + /** + * Converts a locale code like "en", "en_US" or "en_US_win" to a java.util.Locale + * object. + *

If localeCode = default, return the current value of the default locale for this instance + * of the Java Virtual Machine.

+ * + * @param localeCode the locale code string. + * @return a java.util.Locale object instanced or null if errors occurred + * @see java.util.Locale#getDefault() + */ + private Locale codeToLocale( String localeCode ) + { + if ( localeCode == null ) + { + return null; + } + + if ( "default".equalsIgnoreCase( localeCode ) ) + { + return Locale.getDefault(); + } + + String language = ""; + String country = ""; + String variant = ""; + + StringTokenizer tokenizer = new StringTokenizer( localeCode, "_" ); + final int maxTokens = 3; + if ( tokenizer.countTokens() > maxTokens ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "Invalid java.util.Locale format for '" + localeCode + "' entry - IGNORING" ); + } + return null; + } + + if ( tokenizer.hasMoreTokens() ) + { + language = tokenizer.nextToken(); + if ( tokenizer.hasMoreTokens() ) + { + country = tokenizer.nextToken(); + if ( tokenizer.hasMoreTokens() ) + { + variant = tokenizer.nextToken(); + } + } + } + + return new Locale( language, country, variant ); + } + + // ---------------------------------------------------------------------- + // Protected methods + // ---------------------------------------------------------------------- + + /** + * @param path could be null. + * @return the path normalized, i.e. by eliminating "/../" and "/./" in the path. + * @see FilenameUtils#normalize(String) + */ + protected static String getNormalizedPath( String path ) + { + String normalized = FilenameUtils.normalize( path ); + if ( normalized == null ) + { + normalized = path; + } + return ( normalized == null ) ? null : normalized.replace( '\\', '/' ); + } + + // ---------------------------------------------------------------------- + // Private methods + // ---------------------------------------------------------------------- + + /** + * @param project not null + * @param localRepository not null + * @param repositories not null + * @param locale not null + * @return the resolved site descriptor + * @throws IOException if any + * @throws ArtifactResolutionException if any + * @throws ArtifactNotFoundException if any + */ + private File resolveSiteDescriptor( MavenProject project, ArtifactRepository localRepository, + List repositories, Locale locale ) + throws IOException, ArtifactResolutionException, ArtifactNotFoundException + { + File result; + + // TODO: this is a bit crude - proper type, or proper handling as metadata rather than an artifact in 2.1? + Artifact artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), + project.getArtifactId(), + project.getVersion(), "xml", + "site_" + locale.getLanguage() ); + + boolean found = false; + try + { + artifactResolver.resolve( artifact, repositories, localRepository ); + + result = artifact.getFile(); + + // we use zero length files to avoid re-resolution (see below) + if ( result.length() > 0 ) + { + found = true; + } + else + { + getLogger().debug( "No site descriptor found for " + project.getId() + " for locale " + + locale.getLanguage() + ", trying without locale..." ); + } + } + catch ( ArtifactNotFoundException e ) + { + getLogger().debug( "Unable to locate site descriptor for locale " + locale.getLanguage() + ": " + e ); + + // we can afford to write an empty descriptor here as we don't expect it to turn up later in the remote + // repository, because the parent was already released (and snapshots are updated automatically if changed) + result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); + result.getParentFile().mkdirs(); + result.createNewFile(); + } + + if ( !found ) + { + artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), project.getArtifactId(), + project.getVersion(), "xml", "site" ); + try + { + artifactResolver.resolve( artifact, repositories, localRepository ); + } + catch ( ArtifactNotFoundException e ) + { + // see above regarding this zero length file + result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); + result.getParentFile().mkdirs(); + result.createNewFile(); + + throw e; + } + + result = artifact.getFile(); + + // we use zero length files to avoid re-resolution (see below) + if ( result.length() == 0 ) + { + getLogger().debug( "No site descriptor found for " + project.getId() + " without locale." ); + result = null; + } + } + + return result; + } + + /** + * @param depth depth of project + * @param siteDirectory, can be null if project.basedir is null, ie POM from repository + * @param locale not null + * @param project not null + * @param reactorProjects not null + * @param localRepository not null + * @param repositories not null + * @param origProps not null + * @return the decoration model depending the locale and the parent project + * @throws SiteToolException if any + */ + private Map.Entry getDecorationModel( int depth, File siteDirectory, Locale locale, + MavenProject project, + List reactorProjects, + ArtifactRepository localRepository, + List repositories ) + throws SiteToolException + { + // 1. get site descriptor File + File siteDescriptor; + if ( project.getBasedir() == null ) + { + // POM is in the repository: look into the repository for site descriptor + try + { + siteDescriptor = getSiteDescriptorFromRepository( project, localRepository, repositories, locale ); + } + catch ( SiteToolException e ) + { + throw new SiteToolException( "The site descriptor cannot be resolved from the repository: " + + e.getMessage(), e ); + } + } + else + { + // POM is in build directory: look for site descriptor as local file + siteDescriptor = getSiteDescriptor( siteDirectory, locale ); + } + + // 2. read DecorationModel from site descriptor File and do early interpolation (${this.*}) + DecorationModel decoration = null; + Reader siteDescriptorReader = null; + try + { + if ( siteDescriptor != null && siteDescriptor.exists() ) + { + getLogger().debug( "Reading" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + + " site descriptor from " + siteDescriptor ); + + siteDescriptorReader = ReaderFactory.newXmlReader( siteDescriptor ); + + String siteDescriptorContent = readSiteDescriptor( siteDescriptorReader, project.getId() ); + + // interpolate ${this.*} = early interpolation + siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, true ); + + decoration = readDecorationModel( siteDescriptorContent ); + decoration.setLastModified( siteDescriptor.lastModified() ); + } + else + { + getLogger().debug( "No" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + " site descriptor." ); + } + } + catch ( IOException e ) + { + throw new SiteToolException( "The site descriptor for " + project.getId() + " cannot be read from " + + siteDescriptor, e ); + } + finally + { + IOUtil.close( siteDescriptorReader ); + } + + // 3. look for parent project + MavenProject parentProject = getParentProject( project, reactorProjects, localRepository ); + + // 4. merge with parent project DecorationModel + if ( parentProject != null && ( decoration == null || decoration.isMergeParent() ) ) + { + depth++; + getLogger().debug( "Looking for site descriptor of level " + depth + " parent project: " + + parentProject.getId() ); + + File parentSiteDirectory = null; + if ( parentProject.getBasedir() != null ) + { + // extrapolate parent project site directory + String siteRelativePath = getRelativeFilePath( project.getBasedir().getAbsolutePath(), + siteDescriptor.getParentFile().getAbsolutePath() ); + + parentSiteDirectory = new File( parentProject.getBasedir(), siteRelativePath ); + // notice: using same siteRelativePath for parent as current project; may be wrong if site plugin + // has different configuration. But this is a rare case (this only has impact if parent is from reactor) + } + + DecorationModel parentDecoration = + getDecorationModel( depth, parentSiteDirectory, locale, parentProject, reactorProjects, localRepository, + repositories ).getKey(); + + // MSHARED-116 requires an empty decoration model (instead of a null one) + // MSHARED-145 requires us to do this only if there is a parent to merge it with + if ( decoration == null && parentDecoration != null ) + { + // we have no site descriptor: merge the parent into an empty one + decoration = new DecorationModel(); + } + + String name = project.getName(); + if ( decoration != null && StringUtils.isNotEmpty( decoration.getName() ) ) + { + name = decoration.getName(); + } + + // Merge the parent and child DecorationModels + String projectDistMgmnt = getDistMgmntSiteUrl( project ); + String parentDistMgmnt = getDistMgmntSiteUrl( parentProject ); + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Site decoration model inheritance: assembling child with level " + depth + + " parent: distributionManagement.site.url child = " + projectDistMgmnt + " and parent = " + + parentDistMgmnt ); + } + assembler.assembleModelInheritance( name, decoration, parentDecoration, projectDistMgmnt, + parentDistMgmnt == null ? projectDistMgmnt : parentDistMgmnt ); + } + + return new AbstractMap.SimpleEntry( decoration, parentProject ); + } + + /** + * @param siteDescriptorContent not null + * @return the decoration model object + * @throws SiteToolException if any + */ + private DecorationModel readDecorationModel( String siteDescriptorContent ) + throws SiteToolException + { + try + { + return new DecorationXpp3Reader().read( new StringReader( siteDescriptorContent ) ); + } + catch ( XmlPullParserException e ) + { + throw new SiteToolException( "Error parsing site descriptor", e ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading site descriptor", e ); + } + } + + private String decorationModelToString( DecorationModel decoration ) + throws SiteToolException + { + StringWriter writer = new StringWriter(); + + try + { + new DecorationXpp3Writer().write( writer, decoration ); + return writer.toString(); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading site descriptor", e ); + } + finally + { + IOUtil.close( writer ); + } + } + + private static String buildRelativePath( final String toPath, final String fromPath, final char separatorChar ) + { + // use tokenizer to traverse paths and for lazy checking + StringTokenizer toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); + StringTokenizer fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); + + int count = 0; + + // walk along the to path looking for divergence from the from path + while ( toTokeniser.hasMoreTokens() && fromTokeniser.hasMoreTokens() ) + { + if ( separatorChar == '\\' ) + { + if ( !fromTokeniser.nextToken().equalsIgnoreCase( toTokeniser.nextToken() ) ) + { + break; + } + } + else + { + if ( !fromTokeniser.nextToken().equals( toTokeniser.nextToken() ) ) + { + break; + } + } + + count++; + } + + // reinitialize the tokenizers to count positions to retrieve the + // gobbled token + + toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); + fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); + + while ( count-- > 0 ) + { + fromTokeniser.nextToken(); + toTokeniser.nextToken(); + } + + StringBuilder relativePath = new StringBuilder(); + + // add back refs for the rest of from location. + while ( fromTokeniser.hasMoreTokens() ) + { + fromTokeniser.nextToken(); + + relativePath.append( ".." ); + + if ( fromTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + } + + if ( relativePath.length() != 0 && toTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + + // add fwd fills for whatever's left of to. + while ( toTokeniser.hasMoreTokens() ) + { + relativePath.append( toTokeniser.nextToken() ); + + if ( toTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + } + return relativePath.toString(); + } + + /** + * @param project not null + * @param menu not null + * @param name not null + * @param href could be null + * @param defaultHref not null + */ + private void appendMenuItem( MavenProject project, Menu menu, String name, String href, String defaultHref ) + { + String selectedHref = href; + + if ( selectedHref == null ) + { + selectedHref = defaultHref; + } + + MenuItem item = new MenuItem(); + item.setName( name ); + + String baseUrl = getDistMgmntSiteUrl( project ); + if ( baseUrl != null ) + { + selectedHref = getRelativePath( selectedHref, baseUrl ); + } + + if ( selectedHref.endsWith( "/" ) ) + { + item.setHref( selectedHref + "index.html" ); + } + else + { + item.setHref( selectedHref + "/index.html" ); + } + menu.addItem( item ); + } + + /** + * @param name not null + * @param href not null + * @param categoryReports not null + * @param locale not null + * @return the menu item object + */ + private MenuItem createCategoryMenu( String name, String href, List categoryReports, Locale locale ) + { + MenuItem item = new MenuItem(); + item.setName( name ); + item.setCollapse( true ); + item.setHref( href ); + + // MSHARED-172, allow reports to define their order in some other way? + //Collections.sort( categoryReports, new ReportComparator( locale ) ); + + for ( MavenReport report : categoryReports ) + { + MenuItem subitem = new MenuItem(); + subitem.setName( report.getName( locale ) ); + subitem.setHref( report.getOutputName() + ".html" ); + item.getItems().add( subitem ); + } + + return item; + } + + // ---------------------------------------------------------------------- + // static methods + // ---------------------------------------------------------------------- + + /** + * Convenience method. + * + * @param list could be null + * @return true if the list is null or empty + */ + private static boolean isEmptyList( List list ) + { + return list == null || list.isEmpty(); + } + + /** + * Return distributionManagement.site.url if defined, null otherwise. + * + * @param project not null + * @return could be null + */ + private static String getDistMgmntSiteUrl( MavenProject project ) + { + return getDistMgmntSiteUrl( project.getDistributionManagement() ); + } + + private static String getDistMgmntSiteUrl( DistributionManagement distMgmnt ) + { + if ( distMgmnt != null && distMgmnt.getSite() != null && distMgmnt.getSite().getUrl() != null ) + { + return urlEncode( distMgmnt.getSite().getUrl() ); + } + + return null; + } + + private static String urlEncode( final String url ) + { + if ( url == null ) + { + return null; + } + + try + { + return new File( url ).toURI().toURL().toExternalForm(); + } + catch ( MalformedURLException ex ) + { + return url; // this will then throw somewhere else + } + } + + private void checkNotNull( String name, Object value ) + { + if ( value == null ) + { + throw new IllegalArgumentException( "The parameter '" + name + "' cannot be null." ); + } + } + + /** + * Check the current Maven version to see if it's Maven 3.0 or newer. + */ + private static boolean isMaven3OrMore() + { + return new DefaultArtifactVersion( getMavenVersion() ).getMajorVersion() >= 3; + } + + private static String getMavenVersion() + { + // This relies on the fact that MavenProject is the in core classloader + // and that the core classloader is for the maven-core artifact + // and that should have a pom.properties file + // if this ever changes, we will have to revisit this code. + final Properties properties = new Properties(); + final String corePomProperties = "META-INF/maven/org.apache.maven/maven-core/pom.properties"; + final InputStream in = MavenProject.class.getClassLoader().getResourceAsStream( corePomProperties ); + try + { + properties.load( in ); + } + catch ( IOException ioe ) + { + return ""; + } + finally + { + IOUtil.close( in ); + } + + return properties.getProperty( "version" ).trim(); + } +} diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_587/npe.json b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_587/npe.json new file mode 100644 index 000000000..07e55beb0 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_587/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/DefaultSiteTool.java", + "line": 587, + "npe_method": "getParentProject", + "deref_field": "origParent", + "npe_class": "DefaultSiteTool" +} \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_686/buggy.java b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_686/buggy.java new file mode 100644 index 000000000..fbcecbbd7 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_686/buggy.java @@ -0,0 +1,1520 @@ +package org.apache.maven.doxia.tools; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.StringTokenizer; + +import org.apache.commons.io.FilenameUtils; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.factory.ArtifactFactory; +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.artifact.resolver.ArtifactNotFoundException; +import org.apache.maven.artifact.resolver.ArtifactResolutionException; +import org.apache.maven.artifact.resolver.ArtifactResolver; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; +import org.apache.maven.artifact.versioning.VersionRange; +import org.apache.maven.doxia.site.decoration.Banner; +import org.apache.maven.doxia.site.decoration.DecorationModel; +import org.apache.maven.doxia.site.decoration.Menu; +import org.apache.maven.doxia.site.decoration.MenuItem; +import org.apache.maven.doxia.site.decoration.Skin; +import org.apache.maven.doxia.site.decoration.inheritance.DecorationModelInheritanceAssembler; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Reader; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Writer; +import org.apache.maven.model.DistributionManagement; +import org.apache.maven.model.Site; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.MavenProjectBuilder; +import org.apache.maven.project.ProjectBuildingException; +import org.apache.maven.reporting.MavenReport; +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.IOUtil; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.interpolation.EnvarBasedValueSource; +import org.codehaus.plexus.interpolation.InterpolationException; +import org.codehaus.plexus.interpolation.MapBasedValueSource; +import org.codehaus.plexus.interpolation.ObjectBasedValueSource; +import org.codehaus.plexus.interpolation.PrefixedObjectValueSource; +import org.codehaus.plexus.interpolation.PrefixedPropertiesValueSource; +import org.codehaus.plexus.interpolation.RegexBasedInterpolator; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +/** + * Default implementation of the site tool. + * + * @author Vincent Siveton + */ +@Component( role = SiteTool.class ) +public class DefaultSiteTool + extends AbstractLogEnabled + implements SiteTool +{ + // ---------------------------------------------------------------------- + // Components + // ---------------------------------------------------------------------- + + /** + * The component that is used to resolve additional artifacts required. + */ + @Requirement + private ArtifactResolver artifactResolver; + + /** + * The component used for creating artifact instances. + */ + @Requirement + private ArtifactFactory artifactFactory; + + /** + * Internationalization. + */ + @Requirement + protected I18N i18n; + + /** + * The component for assembling inheritance. + */ + @Requirement + protected DecorationModelInheritanceAssembler assembler; + + /** + * Project builder (deprecated in Maven 3: should use ProjectBuilder, which will avoid + * issues like DOXIASITETOOLS-166) + */ + @Requirement + protected MavenProjectBuilder mavenProjectBuilder; + + // ---------------------------------------------------------------------- + // Public methods + // ---------------------------------------------------------------------- + + public Artifact getSkinArtifactFromRepository( ArtifactRepository localRepository, + List remoteArtifactRepositories, + DecorationModel decoration ) + throws SiteToolException + { + checkNotNull( "localRepository", localRepository ); + checkNotNull( "remoteArtifactRepositories", remoteArtifactRepositories ); + checkNotNull( "decoration", decoration ); + + Skin skin = decoration.getSkin(); + + if ( skin == null ) + { + skin = Skin.getDefaultSkin(); + } + + String version = skin.getVersion(); + Artifact artifact; + try + { + if ( version == null ) + { + version = Artifact.RELEASE_VERSION; + } + VersionRange versionSpec = VersionRange.createFromVersionSpec( version ); + artifact = artifactFactory.createDependencyArtifact( skin.getGroupId(), skin.getArtifactId(), versionSpec, + "jar", null, null ); + + artifactResolver.resolve( artifact, remoteArtifactRepositories, localRepository ); + } + catch ( InvalidVersionSpecificationException e ) + { + throw new SiteToolException( "InvalidVersionSpecificationException: The skin version '" + version + + "' is not valid: " + e.getMessage(), e ); + } + catch ( ArtifactResolutionException e ) + { + throw new SiteToolException( "ArtifactResolutionException: Unable to find skin", e ); + } + catch ( ArtifactNotFoundException e ) + { + throw new SiteToolException( "ArtifactNotFoundException: The skin does not exist: " + e.getMessage(), e ); + } + + return artifact; + } + + public Artifact getDefaultSkinArtifact( ArtifactRepository localRepository, + List remoteArtifactRepositories ) + throws SiteToolException + { + return getSkinArtifactFromRepository( localRepository, remoteArtifactRepositories, new DecorationModel() ); + } + + /** + * This method is not implemented according to the URI specification and has many weird + * corner cases where it doesn't do the right thing. Please consider using a better + * implemented method from a different library such as org.apache.http.client.utils.URIUtils#resolve. + */ + @Deprecated + public String getRelativePath( String to, String from ) + { + checkNotNull( "to", to ); + checkNotNull( "from", from ); + + if ( to.contains( ":" ) && from.contains( ":" ) ) + { + String toScheme = to.substring( 0, to.lastIndexOf( ':' ) ); + String fromScheme = from.substring( 0, from.lastIndexOf( ':' ) ); + if ( !toScheme.equals( fromScheme ) ) + { + return to; + } + } + + URL toUrl = null; + URL fromUrl = null; + + String toPath = to; + String fromPath = from; + + try + { + toUrl = new URL( to ); + } + catch ( MalformedURLException e ) + { + try + { + toUrl = new File( getNormalizedPath( to ) ).toURI().toURL(); + } + catch ( MalformedURLException e1 ) + { + getLogger().warn( "Unable to load a URL for '" + to + "': " + e.getMessage() ); + return to; + } + } + + try + { + fromUrl = new URL( from ); + } + catch ( MalformedURLException e ) + { + try + { + fromUrl = new File( getNormalizedPath( from ) ).toURI().toURL(); + } + catch ( MalformedURLException e1 ) + { + getLogger().warn( "Unable to load a URL for '" + from + "': " + e.getMessage() ); + return to; + } + } + + if ( toUrl != null && fromUrl != null ) + { + // URLs, determine if they share protocol and domain info + + if ( ( toUrl.getProtocol().equalsIgnoreCase( fromUrl.getProtocol() ) ) + && ( toUrl.getHost().equalsIgnoreCase( fromUrl.getHost() ) ) + && ( toUrl.getPort() == fromUrl.getPort() ) ) + { + // shared URL domain details, use URI to determine relative path + + toPath = toUrl.getFile(); + fromPath = fromUrl.getFile(); + } + else + { + // don't share basic URL information, no relative available + + return to; + } + } + else if ( ( toUrl != null && fromUrl == null ) || ( toUrl == null && fromUrl != null ) ) + { + // one is a URL and the other isn't, no relative available. + + return to; + } + + // either the two locations are not URLs or if they are they + // share the common protocol and domain info and we are left + // with their URI information + + String relativePath = getRelativeFilePath( fromPath, toPath ); + + if ( relativePath == null ) + { + relativePath = to; + } + + if ( getLogger().isDebugEnabled() && !relativePath.toString().equals( to ) ) + { + getLogger().debug( "Mapped url: " + to + " to relative path: " + relativePath ); + } + + return relativePath; + } + + private static String getRelativeFilePath( final String oldPath, final String newPath ) + { + // normalize the path delimiters + + String fromPath = new File( oldPath ).getPath(); + String toPath = new File( newPath ).getPath(); + + // strip any leading slashes if its a windows path + if ( toPath.matches( "^\\[a-zA-Z]:" ) ) + { + toPath = toPath.substring( 1 ); + } + if ( fromPath.matches( "^\\[a-zA-Z]:" ) ) + { + fromPath = fromPath.substring( 1 ); + } + + // lowercase windows drive letters. + if ( fromPath.startsWith( ":", 1 ) ) + { + fromPath = Character.toLowerCase( fromPath.charAt( 0 ) ) + fromPath.substring( 1 ); + } + if ( toPath.startsWith( ":", 1 ) ) + { + toPath = Character.toLowerCase( toPath.charAt( 0 ) ) + toPath.substring( 1 ); + } + + // check for the presence of windows drives. No relative way of + // traversing from one to the other. + + if ( ( toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) + && ( !toPath.substring( 0, 1 ).equals( fromPath.substring( 0, 1 ) ) ) ) + { + // they both have drive path element but they don't match, no + // relative path + + return null; + } + + if ( ( toPath.startsWith( ":", 1 ) && !fromPath.startsWith( ":", 1 ) ) + || ( !toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) ) + { + + // one has a drive path element and the other doesn't, no relative + // path. + + return null; + + } + + final String relativePath = buildRelativePath( toPath, fromPath, File.separatorChar ); + + return relativePath.toString(); + } + + /** {@inheritDoc} */ + public File getSiteDescriptor( File siteDirectory, Locale locale ) + { + checkNotNull( "siteDirectory", siteDirectory ); + final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale; + + File siteDescriptor = new File( siteDirectory, "site_" + llocale.getLanguage() + ".xml" ); + + if ( !siteDescriptor.isFile() ) + { + siteDescriptor = new File( siteDirectory, "site.xml" ); + } + return siteDescriptor; + } + + /** + * Get a site descriptor from one of the repositories. + * + * @param project the Maven project, not null. + * @param localRepository the Maven local repository, not null. + * @param repositories the Maven remote repositories, not null. + * @param locale the locale wanted for the site descriptor. If not null, searching for + * site_localeLanguage.xml, otherwise searching for site.xml. + * @return the site descriptor into the local repository after download of it from repositories or null if not + * found in repositories. + * @throws SiteToolException if any + */ + File getSiteDescriptorFromRepository( MavenProject project, ArtifactRepository localRepository, + List repositories, Locale locale ) + throws SiteToolException + { + checkNotNull( "project", project ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "repositories", repositories ); + + final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale; + + try + { + return resolveSiteDescriptor( project, localRepository, repositories, llocale ); + } + catch ( ArtifactNotFoundException e ) + { + getLogger().debug( "ArtifactNotFoundException: Unable to locate site descriptor: " + e ); + return null; + } + catch ( ArtifactResolutionException e ) + { + throw new SiteToolException( "ArtifactResolutionException: Unable to locate site descriptor: " + + e.getMessage(), e ); + } + catch ( IOException e ) + { + throw new SiteToolException( "IOException: Unable to locate site descriptor: " + e.getMessage(), e ); + } + } + + /** + * Read site descriptor content from Reader, adding support for deprecated ${reports}, + * ${parentProject} and ${modules} tags. + * + * @param reader + * @return the input content interpolated with deprecated tags + * @throws IOException + */ + private String readSiteDescriptor( Reader reader, String projectId ) + throws IOException + { + String siteDescriptorContent = IOUtil.toString( reader ); + + // This is to support the deprecated ${reports}, ${parentProject} and ${modules} tags. + Properties props = new Properties(); + props.put( "reports", "
" ); + props.put( "modules", "" ); + props.put( "parentProject", "" ); + + // warn if interpolation required + for ( Object prop : props.keySet() ) + { + if ( siteDescriptorContent.contains( "$" + prop ) ) + { + getLogger().warn( "Site descriptor for " + projectId + " contains $" + prop + + ": should be replaced with " + props.getProperty( (String) prop ) ); + } + if ( siteDescriptorContent.contains( "${" + prop + "}" ) ) + { + getLogger().warn( "Site descriptor for " + projectId + " contains ${" + prop + + "}: should be replaced with " + props.getProperty( (String) prop ) ); + } + } + + return StringUtils.interpolate( siteDescriptorContent, props ); + } + + /** {@inheritDoc} */ + public DecorationModel getDecorationModel( File siteDirectory, Locale locale, MavenProject project, + List reactorProjects, ArtifactRepository localRepository, + List repositories ) + throws SiteToolException + { + checkNotNull( "project", project ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "repositories", repositories ); + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + getLogger().debug( "Computing decoration model of " + project.getId() + " for locale " + llocale ); + + Map.Entry result = + getDecorationModel( 0, siteDirectory, llocale, project, reactorProjects, localRepository, repositories ); + DecorationModel decorationModel = result.getKey(); + MavenProject parentProject = result.getValue(); + + if ( decorationModel == null ) + { + getLogger().debug( "Using default site descriptor" ); + + String siteDescriptorContent; + + Reader reader = null; + try + { + // Note the default is not a super class - it is used when nothing else is found + reader = ReaderFactory.newXmlReader( getClass().getResourceAsStream( "/default-site.xml" ) ); + siteDescriptorContent = readSiteDescriptor( reader, "default-site.xml" ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading default site descriptor: " + e.getMessage(), e ); + } + finally + { + IOUtil.close( reader ); + } + + decorationModel = readDecorationModel( siteDescriptorContent ); + } + + // DecorationModel back to String to interpolate, then go back to DecorationModel + String siteDescriptorContent = decorationModelToString( decorationModel ); + + // "classical" late interpolation, after full inheritance + siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, false ); + + decorationModel = readDecorationModel( siteDescriptorContent ); + + if ( parentProject != null ) + { + populateParentMenu( decorationModel, llocale, project, parentProject, true ); + } + + try + { + populateModulesMenu( decorationModel, llocale, project, reactorProjects, localRepository, true ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error while populating modules menu: " + e.getMessage(), e ); + } + + if ( decorationModel.getBannerLeft() == null ) + { + // extra default to set + Banner banner = new Banner(); + banner.setName( project.getName() ); + decorationModel.setBannerLeft( banner ); + } + + return decorationModel; + } + + /** {@inheritDoc} */ + public String getInterpolatedSiteDescriptorContent( Map props, MavenProject aProject, + String siteDescriptorContent ) + throws SiteToolException + { + checkNotNull( "props", props ); + + // "classical" late interpolation + return getInterpolatedSiteDescriptorContent( aProject, siteDescriptorContent, false ); + } + + private String getInterpolatedSiteDescriptorContent( MavenProject aProject, + String siteDescriptorContent, boolean isEarly ) + throws SiteToolException + { + checkNotNull( "aProject", aProject ); + checkNotNull( "siteDescriptorContent", siteDescriptorContent ); + + RegexBasedInterpolator interpolator = new RegexBasedInterpolator(); + + if ( isEarly ) + { + interpolator.addValueSource( new PrefixedObjectValueSource( "this.", aProject ) ); + interpolator.addValueSource( new PrefixedPropertiesValueSource( "this.", aProject.getProperties() ) ); + } + else + { + interpolator.addValueSource( new ObjectBasedValueSource( aProject ) ); + interpolator.addValueSource( new MapBasedValueSource( aProject.getProperties() ) ); + + try + { + interpolator.addValueSource( new EnvarBasedValueSource() ); + } + catch ( IOException e ) + { + // Prefer logging? + throw new SiteToolException( "IOException: cannot interpolate environment properties: " + + e.getMessage(), e ); + } + } + + try + { + // FIXME: this does not escape xml entities, see MSITE-226, PLXCOMP-118 + return interpolator.interpolate( siteDescriptorContent, isEarly ? null : "project" ); + } + catch ( InterpolationException e ) + { + throw new SiteToolException( "Cannot interpolate site descriptor: " + e.getMessage(), e ); + } + } + + /** {@inheritDoc} */ + public MavenProject getParentProject( MavenProject aProject, List reactorProjects, + ArtifactRepository localRepository ) + { + checkNotNull( "aProject", aProject ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + + if ( isMaven3OrMore() ) + { + // no need to make voodoo with Maven 3: job already done + return aProject.getParent(); + } + + MavenProject parentProject = null; + + MavenProject origParent = aProject.getParent(); + if ( origParent != null ) + { + for ( MavenProject reactorProject : reactorProjects ) + { + if ( reactorProject.getGroupId().equals( origParent.getGroupId() ) + && reactorProject.getArtifactId().equals( origParent.getArtifactId() ) + && reactorProject.getVersion().equals( origParent.getVersion() ) ) + { + parentProject = reactorProject; + + getLogger().debug( "Parent project " + origParent.getId() + " picked from reactor" ); + break; + } + } + + if ( parentProject == null && aProject.getBasedir() != null + && StringUtils.isNotEmpty( aProject.getModel().getParent().getRelativePath() ) ) + { + try + { + String relativePath = aProject.getModel().getParent().getRelativePath(); + + File pomFile = new File( aProject.getBasedir(), relativePath ); + + if ( pomFile.isDirectory() ) + { + pomFile = new File( pomFile, "pom.xml" ); + } + pomFile = new File( getNormalizedPath( pomFile.getPath() ) ); + + if ( pomFile.isFile() ) + { + MavenProject mavenProject = mavenProjectBuilder.build( pomFile, localRepository, null ); + + if ( mavenProject.getGroupId().equals( origParent.getGroupId() ) + && mavenProject.getArtifactId().equals( origParent.getArtifactId() ) + && mavenProject.getVersion().equals( origParent.getVersion() ) ) + { + parentProject = mavenProject; + + getLogger().debug( "Parent project " + origParent.getId() + " loaded from a relative path: " + + relativePath ); + } + } + } + catch ( ProjectBuildingException e ) + { + getLogger().info( "Unable to load parent project " + origParent.getId() + " from a relative path: " + + e.getMessage() ); + } + } + + if ( parentProject == null ) + { + try + { + parentProject = mavenProjectBuilder.buildFromRepository( aProject.getParentArtifact(), aProject + .getRemoteArtifactRepositories(), localRepository ); + + getLogger().debug( "Parent project " + origParent.getId() + " loaded from repository" ); + } + catch ( ProjectBuildingException e ) + { + getLogger().warn( "Unable to load parent project " + origParent.getId() + " from repository: " + + e.getMessage() ); + } + } + + if ( parentProject == null ) + { + // fallback to original parent, which may contain uninterpolated value (still need a unit test) + + parentProject = origParent; + + getLogger().debug( "Parent project " + origParent.getId() + " picked from original value" ); + } + } + return parentProject; + } + + /** + * Populate the pre-defined parent menu of the decoration model, + * if used through <menu ref="parent"/>. + * + * @param decorationModel the Doxia Sitetools DecorationModel, not null. + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project a Maven project, not null. + * @param parentProject a Maven parent project, not null. + * @param keepInheritedRefs used for inherited references. + */ +/** + * Populate the pre-defined parent menu of the decoration model, + * if used through <menu ref="parent"/>. + * + * @param decorationModel + * the Doxia Sitetools DecorationModel, not null. + * @param locale + * the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project + * a Maven project, not null. + * @param parentProject + * a Maven parent project, not null. + * @param keepInheritedRefs + * used for inherited references. + */ +private void populateParentMenu(org.apache.maven.doxia.site.decoration.DecorationModel decorationModel, java.util.Locale locale, org.apache.maven.project.MavenProject project, org.apache.maven.project.MavenProject parentProject, boolean keepInheritedRefs) { + checkNotNull("decorationModel", decorationModel); + checkNotNull("project", project); + checkNotNull("parentProject", parentProject); + org.apache.maven.doxia.site.decoration.Menu menu = decorationModel.getMenuRef("parent"); + { + if (keepInheritedRefs && /* NPEX_NULL_EXP */ + menu.isInheritAsRef()) { + return; + } + final java.util.Locale llocale = (locale == null) ? java.util.Locale.getDefault() : locale; + java.lang.String parentUrl = org.apache.maven.doxia.tools.DefaultSiteTool.getDistMgmntSiteUrl(parentProject); + if (parentUrl != null) { + if (parentUrl.endsWith("/")) { + parentUrl += "index.html"; + } else { + parentUrl += "/index.html"; + } + parentUrl = getRelativePath(parentUrl, org.apache.maven.doxia.tools.DefaultSiteTool.getDistMgmntSiteUrl(project)); + } else { + // parent has no url, assume relative path is given by site structure + java.io.File parentBasedir = parentProject.getBasedir(); + // First make sure that the parent is available on the file system + if (parentBasedir != null) { + // Try to find the relative path to the parent via the file system + java.lang.String parentPath = parentBasedir.getAbsolutePath(); + java.lang.String projectPath = project.getBasedir().getAbsolutePath(); + parentUrl = getRelativePath(parentPath, projectPath) + "/index.html"; + } + } + // Only add the parent menu if we were able to find a URL for it + if (parentUrl == null) { + getLogger().warn("Unable to find a URL to the parent project. The parent menu will NOT be added."); + } else { + if (menu.getName() == null) { + menu.setName(i18n.getString("site-tool", llocale, "decorationModel.menu.parentproject")); + } + org.apache.maven.doxia.site.decoration.MenuItem item = new org.apache.maven.doxia.site.decoration.MenuItem(); + item.setName(parentProject.getName()); + item.setHref(parentUrl); + menu.addItem(item); + } + } +} + + /** + * Populate the pre-defined modules menu of the decoration model, + * if used through <menu ref="modules"/>. + * + * @param decorationModel the Doxia Sitetools DecorationModel, not null. + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project a Maven project, not null. + * @param reactorProjects the Maven reactor projects, not null. + * @param localRepository the Maven local repository, not null. + * @param keepInheritedRefs used for inherited references. + * @throws SiteToolException if any + * @throws IOException + */ + private void populateModulesMenu( DecorationModel decorationModel, Locale locale, MavenProject project, + List reactorProjects, ArtifactRepository localRepository, + boolean keepInheritedRefs ) + throws SiteToolException, IOException + { + checkNotNull( "project", project ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "decorationModel", decorationModel ); + + Menu menu = decorationModel.getMenuRef( "modules" ); + + if ( menu == null ) + { + return; + } + + if ( keepInheritedRefs && menu.isInheritAsRef() ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale ; + + // we require child modules and reactors to process module menu + if ( project.getModules().size() > 0 ) + { + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectmodules" ) ); + } + + for ( String module : (List) project.getModules() ) + { + MavenProject moduleProject = getModuleFromReactor( project, reactorProjects, module ); + + if ( moduleProject == null ) + { + getLogger().warn( "Module " + module + + " not found in reactor: loading locally" ); + + File f = new File( project.getBasedir(), module + "/pom.xml" ); + if ( f.exists() ) + { + try + { + moduleProject = mavenProjectBuilder.build( f, localRepository, null ); + } + catch ( ProjectBuildingException e ) + { + throw new SiteToolException( "Unable to read local module-POM", e ); + } + } + else + { + getLogger().warn( "No filesystem module-POM available" ); + + moduleProject = new MavenProject(); + moduleProject.setName( module ); + moduleProject.setDistributionManagement( new DistributionManagement() ); + moduleProject.getDistributionManagement().setSite( new Site() ); + moduleProject.getDistributionManagement().getSite().setUrl( module ); + } + } + + String siteUrl = getDistMgmntSiteUrl( moduleProject ); + String itemName = + ( moduleProject.getName() == null ) ? moduleProject.getArtifactId() : moduleProject.getName(); + + appendMenuItem( project, menu, itemName, siteUrl, moduleProject.getArtifactId() ); + } + } + else if ( decorationModel.getMenuRef( "modules" ).getInherit() == null ) + { + // only remove if project has no modules AND menu is not inherited, see MSHARED-174 + decorationModel.removeMenuRef( "modules" ); + } + } + + private static MavenProject getModuleFromReactor( MavenProject project, List reactorProjects, + String module ) + throws IOException + { + File moduleBasedir = new File( project.getBasedir(), module ).getCanonicalFile(); + + for ( MavenProject reactorProject : reactorProjects ) + { + if ( moduleBasedir.equals( reactorProject.getBasedir() ) ) + { + return reactorProject; + } + } + + // module not found in reactor + return null; + } + + /** {@inheritDoc} */ + public void populateReportsMenu( DecorationModel decorationModel, Locale locale, + Map> categories ) + { + checkNotNull( "decorationModel", decorationModel ); + checkNotNull( "categories", categories ); + + Menu menu = decorationModel.getMenuRef( "reports" ); + + if ( menu == null ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectdocumentation" ) ); + } + + boolean found = false; + if ( menu.getItems().isEmpty() ) + { + List categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_INFORMATION ); + if ( !isEmptyList( categoryReports ) ) + { + MenuItem item = createCategoryMenu( + i18n.getString( "site-tool", llocale, + "decorationModel.menu.projectinformation" ), + "/project-info.html", categoryReports, llocale ); + menu.getItems().add( item ); + found = true; + } + + categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_REPORTS ); + if ( !isEmptyList( categoryReports ) ) + { + MenuItem item = + createCategoryMenu( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectreports" ), + "/project-reports.html", categoryReports, llocale ); + menu.getItems().add( item ); + found = true; + } + } + if ( !found ) + { + decorationModel.removeMenuRef( "reports" ); + } + } + + /** {@inheritDoc} */ + public List getSiteLocales( String locales ) + { + if ( locales == null ) + { + return Collections.singletonList( DEFAULT_LOCALE ); + } + + String[] localesArray = StringUtils.split( locales, "," ); + List localesList = new ArrayList( localesArray.length ); + + for ( String localeString : localesArray ) + { + Locale locale = codeToLocale( localeString ); + + if ( locale == null ) + { + continue; + } + + if ( !Arrays.asList( Locale.getAvailableLocales() ).contains( locale ) ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "The locale defined by '" + locale + + "' is not available in this Java Virtual Machine (" + + System.getProperty( "java.version" ) + + " from " + System.getProperty( "java.vendor" ) + ") - IGNORING" ); + } + continue; + } + + // Default bundles are in English + if ( ( !locale.getLanguage().equals( DEFAULT_LOCALE.getLanguage() ) ) + && ( !i18n.getBundle( "site-tool", locale ).getLocale().getLanguage() + .equals( locale.getLanguage() ) ) ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "The locale '" + locale + "' (" + locale.getDisplayName( Locale.ENGLISH ) + + ") is not currently supported by Maven Site - IGNORING." + + "\nContributions are welcome and greatly appreciated!" + + "\nIf you want to contribute a new translation, please visit " + + "http://maven.apache.org/plugins/localization.html for detailed instructions." ); + } + + continue; + } + + localesList.add( locale ); + } + + if ( localesList.isEmpty() ) + { + localesList = Collections.singletonList( DEFAULT_LOCALE ); + } + + return localesList; + } + + /** + * Converts a locale code like "en", "en_US" or "en_US_win" to a java.util.Locale + * object. + *

If localeCode = default, return the current value of the default locale for this instance + * of the Java Virtual Machine.

+ * + * @param localeCode the locale code string. + * @return a java.util.Locale object instanced or null if errors occurred + * @see java.util.Locale#getDefault() + */ + private Locale codeToLocale( String localeCode ) + { + if ( localeCode == null ) + { + return null; + } + + if ( "default".equalsIgnoreCase( localeCode ) ) + { + return Locale.getDefault(); + } + + String language = ""; + String country = ""; + String variant = ""; + + StringTokenizer tokenizer = new StringTokenizer( localeCode, "_" ); + final int maxTokens = 3; + if ( tokenizer.countTokens() > maxTokens ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "Invalid java.util.Locale format for '" + localeCode + "' entry - IGNORING" ); + } + return null; + } + + if ( tokenizer.hasMoreTokens() ) + { + language = tokenizer.nextToken(); + if ( tokenizer.hasMoreTokens() ) + { + country = tokenizer.nextToken(); + if ( tokenizer.hasMoreTokens() ) + { + variant = tokenizer.nextToken(); + } + } + } + + return new Locale( language, country, variant ); + } + + // ---------------------------------------------------------------------- + // Protected methods + // ---------------------------------------------------------------------- + + /** + * @param path could be null. + * @return the path normalized, i.e. by eliminating "/../" and "/./" in the path. + * @see FilenameUtils#normalize(String) + */ + protected static String getNormalizedPath( String path ) + { + String normalized = FilenameUtils.normalize( path ); + if ( normalized == null ) + { + normalized = path; + } + return ( normalized == null ) ? null : normalized.replace( '\\', '/' ); + } + + // ---------------------------------------------------------------------- + // Private methods + // ---------------------------------------------------------------------- + + /** + * @param project not null + * @param localRepository not null + * @param repositories not null + * @param locale not null + * @return the resolved site descriptor + * @throws IOException if any + * @throws ArtifactResolutionException if any + * @throws ArtifactNotFoundException if any + */ + private File resolveSiteDescriptor( MavenProject project, ArtifactRepository localRepository, + List repositories, Locale locale ) + throws IOException, ArtifactResolutionException, ArtifactNotFoundException + { + File result; + + // TODO: this is a bit crude - proper type, or proper handling as metadata rather than an artifact in 2.1? + Artifact artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), + project.getArtifactId(), + project.getVersion(), "xml", + "site_" + locale.getLanguage() ); + + boolean found = false; + try + { + artifactResolver.resolve( artifact, repositories, localRepository ); + + result = artifact.getFile(); + + // we use zero length files to avoid re-resolution (see below) + if ( result.length() > 0 ) + { + found = true; + } + else + { + getLogger().debug( "No site descriptor found for " + project.getId() + " for locale " + + locale.getLanguage() + ", trying without locale..." ); + } + } + catch ( ArtifactNotFoundException e ) + { + getLogger().debug( "Unable to locate site descriptor for locale " + locale.getLanguage() + ": " + e ); + + // we can afford to write an empty descriptor here as we don't expect it to turn up later in the remote + // repository, because the parent was already released (and snapshots are updated automatically if changed) + result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); + result.getParentFile().mkdirs(); + result.createNewFile(); + } + + if ( !found ) + { + artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), project.getArtifactId(), + project.getVersion(), "xml", "site" ); + try + { + artifactResolver.resolve( artifact, repositories, localRepository ); + } + catch ( ArtifactNotFoundException e ) + { + // see above regarding this zero length file + result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); + result.getParentFile().mkdirs(); + result.createNewFile(); + + throw e; + } + + result = artifact.getFile(); + + // we use zero length files to avoid re-resolution (see below) + if ( result.length() == 0 ) + { + getLogger().debug( "No site descriptor found for " + project.getId() + " without locale." ); + result = null; + } + } + + return result; + } + + /** + * @param depth depth of project + * @param siteDirectory, can be null if project.basedir is null, ie POM from repository + * @param locale not null + * @param project not null + * @param reactorProjects not null + * @param localRepository not null + * @param repositories not null + * @param origProps not null + * @return the decoration model depending the locale and the parent project + * @throws SiteToolException if any + */ + private Map.Entry getDecorationModel( int depth, File siteDirectory, Locale locale, + MavenProject project, + List reactorProjects, + ArtifactRepository localRepository, + List repositories ) + throws SiteToolException + { + // 1. get site descriptor File + File siteDescriptor; + if ( project.getBasedir() == null ) + { + // POM is in the repository: look into the repository for site descriptor + try + { + siteDescriptor = getSiteDescriptorFromRepository( project, localRepository, repositories, locale ); + } + catch ( SiteToolException e ) + { + throw new SiteToolException( "The site descriptor cannot be resolved from the repository: " + + e.getMessage(), e ); + } + } + else + { + // POM is in build directory: look for site descriptor as local file + siteDescriptor = getSiteDescriptor( siteDirectory, locale ); + } + + // 2. read DecorationModel from site descriptor File and do early interpolation (${this.*}) + DecorationModel decoration = null; + Reader siteDescriptorReader = null; + try + { + if ( siteDescriptor != null && siteDescriptor.exists() ) + { + getLogger().debug( "Reading" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + + " site descriptor from " + siteDescriptor ); + + siteDescriptorReader = ReaderFactory.newXmlReader( siteDescriptor ); + + String siteDescriptorContent = readSiteDescriptor( siteDescriptorReader, project.getId() ); + + // interpolate ${this.*} = early interpolation + siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, true ); + + decoration = readDecorationModel( siteDescriptorContent ); + decoration.setLastModified( siteDescriptor.lastModified() ); + } + else + { + getLogger().debug( "No" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + " site descriptor." ); + } + } + catch ( IOException e ) + { + throw new SiteToolException( "The site descriptor for " + project.getId() + " cannot be read from " + + siteDescriptor, e ); + } + finally + { + IOUtil.close( siteDescriptorReader ); + } + + // 3. look for parent project + MavenProject parentProject = getParentProject( project, reactorProjects, localRepository ); + + // 4. merge with parent project DecorationModel + if ( parentProject != null && ( decoration == null || decoration.isMergeParent() ) ) + { + depth++; + getLogger().debug( "Looking for site descriptor of level " + depth + " parent project: " + + parentProject.getId() ); + + File parentSiteDirectory = null; + if ( parentProject.getBasedir() != null ) + { + // extrapolate parent project site directory + String siteRelativePath = getRelativeFilePath( project.getBasedir().getAbsolutePath(), + siteDescriptor.getParentFile().getAbsolutePath() ); + + parentSiteDirectory = new File( parentProject.getBasedir(), siteRelativePath ); + // notice: using same siteRelativePath for parent as current project; may be wrong if site plugin + // has different configuration. But this is a rare case (this only has impact if parent is from reactor) + } + + DecorationModel parentDecoration = + getDecorationModel( depth, parentSiteDirectory, locale, parentProject, reactorProjects, localRepository, + repositories ).getKey(); + + // MSHARED-116 requires an empty decoration model (instead of a null one) + // MSHARED-145 requires us to do this only if there is a parent to merge it with + if ( decoration == null && parentDecoration != null ) + { + // we have no site descriptor: merge the parent into an empty one + decoration = new DecorationModel(); + } + + String name = project.getName(); + if ( decoration != null && StringUtils.isNotEmpty( decoration.getName() ) ) + { + name = decoration.getName(); + } + + // Merge the parent and child DecorationModels + String projectDistMgmnt = getDistMgmntSiteUrl( project ); + String parentDistMgmnt = getDistMgmntSiteUrl( parentProject ); + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Site decoration model inheritance: assembling child with level " + depth + + " parent: distributionManagement.site.url child = " + projectDistMgmnt + " and parent = " + + parentDistMgmnt ); + } + assembler.assembleModelInheritance( name, decoration, parentDecoration, projectDistMgmnt, + parentDistMgmnt == null ? projectDistMgmnt : parentDistMgmnt ); + } + + return new AbstractMap.SimpleEntry( decoration, parentProject ); + } + + /** + * @param siteDescriptorContent not null + * @return the decoration model object + * @throws SiteToolException if any + */ + private DecorationModel readDecorationModel( String siteDescriptorContent ) + throws SiteToolException + { + try + { + return new DecorationXpp3Reader().read( new StringReader( siteDescriptorContent ) ); + } + catch ( XmlPullParserException e ) + { + throw new SiteToolException( "Error parsing site descriptor", e ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading site descriptor", e ); + } + } + + private String decorationModelToString( DecorationModel decoration ) + throws SiteToolException + { + StringWriter writer = new StringWriter(); + + try + { + new DecorationXpp3Writer().write( writer, decoration ); + return writer.toString(); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading site descriptor", e ); + } + finally + { + IOUtil.close( writer ); + } + } + + private static String buildRelativePath( final String toPath, final String fromPath, final char separatorChar ) + { + // use tokenizer to traverse paths and for lazy checking + StringTokenizer toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); + StringTokenizer fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); + + int count = 0; + + // walk along the to path looking for divergence from the from path + while ( toTokeniser.hasMoreTokens() && fromTokeniser.hasMoreTokens() ) + { + if ( separatorChar == '\\' ) + { + if ( !fromTokeniser.nextToken().equalsIgnoreCase( toTokeniser.nextToken() ) ) + { + break; + } + } + else + { + if ( !fromTokeniser.nextToken().equals( toTokeniser.nextToken() ) ) + { + break; + } + } + + count++; + } + + // reinitialize the tokenizers to count positions to retrieve the + // gobbled token + + toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); + fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); + + while ( count-- > 0 ) + { + fromTokeniser.nextToken(); + toTokeniser.nextToken(); + } + + StringBuilder relativePath = new StringBuilder(); + + // add back refs for the rest of from location. + while ( fromTokeniser.hasMoreTokens() ) + { + fromTokeniser.nextToken(); + + relativePath.append( ".." ); + + if ( fromTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + } + + if ( relativePath.length() != 0 && toTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + + // add fwd fills for whatever's left of to. + while ( toTokeniser.hasMoreTokens() ) + { + relativePath.append( toTokeniser.nextToken() ); + + if ( toTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + } + return relativePath.toString(); + } + + /** + * @param project not null + * @param menu not null + * @param name not null + * @param href could be null + * @param defaultHref not null + */ + private void appendMenuItem( MavenProject project, Menu menu, String name, String href, String defaultHref ) + { + String selectedHref = href; + + if ( selectedHref == null ) + { + selectedHref = defaultHref; + } + + MenuItem item = new MenuItem(); + item.setName( name ); + + String baseUrl = getDistMgmntSiteUrl( project ); + if ( baseUrl != null ) + { + selectedHref = getRelativePath( selectedHref, baseUrl ); + } + + if ( selectedHref.endsWith( "/" ) ) + { + item.setHref( selectedHref + "index.html" ); + } + else + { + item.setHref( selectedHref + "/index.html" ); + } + menu.addItem( item ); + } + + /** + * @param name not null + * @param href not null + * @param categoryReports not null + * @param locale not null + * @return the menu item object + */ + private MenuItem createCategoryMenu( String name, String href, List categoryReports, Locale locale ) + { + MenuItem item = new MenuItem(); + item.setName( name ); + item.setCollapse( true ); + item.setHref( href ); + + // MSHARED-172, allow reports to define their order in some other way? + //Collections.sort( categoryReports, new ReportComparator( locale ) ); + + for ( MavenReport report : categoryReports ) + { + MenuItem subitem = new MenuItem(); + subitem.setName( report.getName( locale ) ); + subitem.setHref( report.getOutputName() + ".html" ); + item.getItems().add( subitem ); + } + + return item; + } + + // ---------------------------------------------------------------------- + // static methods + // ---------------------------------------------------------------------- + + /** + * Convenience method. + * + * @param list could be null + * @return true if the list is null or empty + */ + private static boolean isEmptyList( List list ) + { + return list == null || list.isEmpty(); + } + + /** + * Return distributionManagement.site.url if defined, null otherwise. + * + * @param project not null + * @return could be null + */ + private static String getDistMgmntSiteUrl( MavenProject project ) + { + return getDistMgmntSiteUrl( project.getDistributionManagement() ); + } + + private static String getDistMgmntSiteUrl( DistributionManagement distMgmnt ) + { + if ( distMgmnt != null && distMgmnt.getSite() != null && distMgmnt.getSite().getUrl() != null ) + { + return urlEncode( distMgmnt.getSite().getUrl() ); + } + + return null; + } + + private static String urlEncode( final String url ) + { + if ( url == null ) + { + return null; + } + + try + { + return new File( url ).toURI().toURL().toExternalForm(); + } + catch ( MalformedURLException ex ) + { + return url; // this will then throw somewhere else + } + } + + private void checkNotNull( String name, Object value ) + { + if ( value == null ) + { + throw new IllegalArgumentException( "The parameter '" + name + "' cannot be null." ); + } + } + + /** + * Check the current Maven version to see if it's Maven 3.0 or newer. + */ + private static boolean isMaven3OrMore() + { + return new DefaultArtifactVersion( getMavenVersion() ).getMajorVersion() >= 3; + } + + private static String getMavenVersion() + { + // This relies on the fact that MavenProject is the in core classloader + // and that the core classloader is for the maven-core artifact + // and that should have a pom.properties file + // if this ever changes, we will have to revisit this code. + final Properties properties = new Properties(); + final String corePomProperties = "META-INF/maven/org.apache.maven/maven-core/pom.properties"; + final InputStream in = MavenProject.class.getClassLoader().getResourceAsStream( corePomProperties ); + try + { + properties.load( in ); + } + catch ( IOException ioe ) + { + return ""; + } + finally + { + IOUtil.close( in ); + } + + return properties.getProperty( "version" ).trim(); + } +} diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_686/npe.json b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_686/npe.json new file mode 100644 index 000000000..62b60039b --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_686/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/DefaultSiteTool.java", + "line": 699, + "npe_method": "populateParentMenu", + "deref_field": "menu", + "npe_class": "DefaultSiteTool" +} \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_771/buggy.java b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_771/buggy.java new file mode 100644 index 000000000..f98f1f211 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_771/buggy.java @@ -0,0 +1,1519 @@ +package org.apache.maven.doxia.tools; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.StringTokenizer; + +import org.apache.commons.io.FilenameUtils; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.factory.ArtifactFactory; +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.artifact.resolver.ArtifactNotFoundException; +import org.apache.maven.artifact.resolver.ArtifactResolutionException; +import org.apache.maven.artifact.resolver.ArtifactResolver; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; +import org.apache.maven.artifact.versioning.VersionRange; +import org.apache.maven.doxia.site.decoration.Banner; +import org.apache.maven.doxia.site.decoration.DecorationModel; +import org.apache.maven.doxia.site.decoration.Menu; +import org.apache.maven.doxia.site.decoration.MenuItem; +import org.apache.maven.doxia.site.decoration.Skin; +import org.apache.maven.doxia.site.decoration.inheritance.DecorationModelInheritanceAssembler; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Reader; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Writer; +import org.apache.maven.model.DistributionManagement; +import org.apache.maven.model.Site; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.MavenProjectBuilder; +import org.apache.maven.project.ProjectBuildingException; +import org.apache.maven.reporting.MavenReport; +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.IOUtil; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.interpolation.EnvarBasedValueSource; +import org.codehaus.plexus.interpolation.InterpolationException; +import org.codehaus.plexus.interpolation.MapBasedValueSource; +import org.codehaus.plexus.interpolation.ObjectBasedValueSource; +import org.codehaus.plexus.interpolation.PrefixedObjectValueSource; +import org.codehaus.plexus.interpolation.PrefixedPropertiesValueSource; +import org.codehaus.plexus.interpolation.RegexBasedInterpolator; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +/** + * Default implementation of the site tool. + * + * @author Vincent Siveton + */ +@Component( role = SiteTool.class ) +public class DefaultSiteTool + extends AbstractLogEnabled + implements SiteTool +{ + // ---------------------------------------------------------------------- + // Components + // ---------------------------------------------------------------------- + + /** + * The component that is used to resolve additional artifacts required. + */ + @Requirement + private ArtifactResolver artifactResolver; + + /** + * The component used for creating artifact instances. + */ + @Requirement + private ArtifactFactory artifactFactory; + + /** + * Internationalization. + */ + @Requirement + protected I18N i18n; + + /** + * The component for assembling inheritance. + */ + @Requirement + protected DecorationModelInheritanceAssembler assembler; + + /** + * Project builder (deprecated in Maven 3: should use ProjectBuilder, which will avoid + * issues like DOXIASITETOOLS-166) + */ + @Requirement + protected MavenProjectBuilder mavenProjectBuilder; + + // ---------------------------------------------------------------------- + // Public methods + // ---------------------------------------------------------------------- + + public Artifact getSkinArtifactFromRepository( ArtifactRepository localRepository, + List remoteArtifactRepositories, + DecorationModel decoration ) + throws SiteToolException + { + checkNotNull( "localRepository", localRepository ); + checkNotNull( "remoteArtifactRepositories", remoteArtifactRepositories ); + checkNotNull( "decoration", decoration ); + + Skin skin = decoration.getSkin(); + + if ( skin == null ) + { + skin = Skin.getDefaultSkin(); + } + + String version = skin.getVersion(); + Artifact artifact; + try + { + if ( version == null ) + { + version = Artifact.RELEASE_VERSION; + } + VersionRange versionSpec = VersionRange.createFromVersionSpec( version ); + artifact = artifactFactory.createDependencyArtifact( skin.getGroupId(), skin.getArtifactId(), versionSpec, + "jar", null, null ); + + artifactResolver.resolve( artifact, remoteArtifactRepositories, localRepository ); + } + catch ( InvalidVersionSpecificationException e ) + { + throw new SiteToolException( "InvalidVersionSpecificationException: The skin version '" + version + + "' is not valid: " + e.getMessage(), e ); + } + catch ( ArtifactResolutionException e ) + { + throw new SiteToolException( "ArtifactResolutionException: Unable to find skin", e ); + } + catch ( ArtifactNotFoundException e ) + { + throw new SiteToolException( "ArtifactNotFoundException: The skin does not exist: " + e.getMessage(), e ); + } + + return artifact; + } + + public Artifact getDefaultSkinArtifact( ArtifactRepository localRepository, + List remoteArtifactRepositories ) + throws SiteToolException + { + return getSkinArtifactFromRepository( localRepository, remoteArtifactRepositories, new DecorationModel() ); + } + + /** + * This method is not implemented according to the URI specification and has many weird + * corner cases where it doesn't do the right thing. Please consider using a better + * implemented method from a different library such as org.apache.http.client.utils.URIUtils#resolve. + */ + @Deprecated + public String getRelativePath( String to, String from ) + { + checkNotNull( "to", to ); + checkNotNull( "from", from ); + + if ( to.contains( ":" ) && from.contains( ":" ) ) + { + String toScheme = to.substring( 0, to.lastIndexOf( ':' ) ); + String fromScheme = from.substring( 0, from.lastIndexOf( ':' ) ); + if ( !toScheme.equals( fromScheme ) ) + { + return to; + } + } + + URL toUrl = null; + URL fromUrl = null; + + String toPath = to; + String fromPath = from; + + try + { + toUrl = new URL( to ); + } + catch ( MalformedURLException e ) + { + try + { + toUrl = new File( getNormalizedPath( to ) ).toURI().toURL(); + } + catch ( MalformedURLException e1 ) + { + getLogger().warn( "Unable to load a URL for '" + to + "': " + e.getMessage() ); + return to; + } + } + + try + { + fromUrl = new URL( from ); + } + catch ( MalformedURLException e ) + { + try + { + fromUrl = new File( getNormalizedPath( from ) ).toURI().toURL(); + } + catch ( MalformedURLException e1 ) + { + getLogger().warn( "Unable to load a URL for '" + from + "': " + e.getMessage() ); + return to; + } + } + + if ( toUrl != null && fromUrl != null ) + { + // URLs, determine if they share protocol and domain info + + if ( ( toUrl.getProtocol().equalsIgnoreCase( fromUrl.getProtocol() ) ) + && ( toUrl.getHost().equalsIgnoreCase( fromUrl.getHost() ) ) + && ( toUrl.getPort() == fromUrl.getPort() ) ) + { + // shared URL domain details, use URI to determine relative path + + toPath = toUrl.getFile(); + fromPath = fromUrl.getFile(); + } + else + { + // don't share basic URL information, no relative available + + return to; + } + } + else if ( ( toUrl != null && fromUrl == null ) || ( toUrl == null && fromUrl != null ) ) + { + // one is a URL and the other isn't, no relative available. + + return to; + } + + // either the two locations are not URLs or if they are they + // share the common protocol and domain info and we are left + // with their URI information + + String relativePath = getRelativeFilePath( fromPath, toPath ); + + if ( relativePath == null ) + { + relativePath = to; + } + + if ( getLogger().isDebugEnabled() && !relativePath.toString().equals( to ) ) + { + getLogger().debug( "Mapped url: " + to + " to relative path: " + relativePath ); + } + + return relativePath; + } + + private static String getRelativeFilePath( final String oldPath, final String newPath ) + { + // normalize the path delimiters + + String fromPath = new File( oldPath ).getPath(); + String toPath = new File( newPath ).getPath(); + + // strip any leading slashes if its a windows path + if ( toPath.matches( "^\\[a-zA-Z]:" ) ) + { + toPath = toPath.substring( 1 ); + } + if ( fromPath.matches( "^\\[a-zA-Z]:" ) ) + { + fromPath = fromPath.substring( 1 ); + } + + // lowercase windows drive letters. + if ( fromPath.startsWith( ":", 1 ) ) + { + fromPath = Character.toLowerCase( fromPath.charAt( 0 ) ) + fromPath.substring( 1 ); + } + if ( toPath.startsWith( ":", 1 ) ) + { + toPath = Character.toLowerCase( toPath.charAt( 0 ) ) + toPath.substring( 1 ); + } + + // check for the presence of windows drives. No relative way of + // traversing from one to the other. + + if ( ( toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) + && ( !toPath.substring( 0, 1 ).equals( fromPath.substring( 0, 1 ) ) ) ) + { + // they both have drive path element but they don't match, no + // relative path + + return null; + } + + if ( ( toPath.startsWith( ":", 1 ) && !fromPath.startsWith( ":", 1 ) ) + || ( !toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) ) + { + + // one has a drive path element and the other doesn't, no relative + // path. + + return null; + + } + + final String relativePath = buildRelativePath( toPath, fromPath, File.separatorChar ); + + return relativePath.toString(); + } + + /** {@inheritDoc} */ + public File getSiteDescriptor( File siteDirectory, Locale locale ) + { + checkNotNull( "siteDirectory", siteDirectory ); + final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale; + + File siteDescriptor = new File( siteDirectory, "site_" + llocale.getLanguage() + ".xml" ); + + if ( !siteDescriptor.isFile() ) + { + siteDescriptor = new File( siteDirectory, "site.xml" ); + } + return siteDescriptor; + } + + /** + * Get a site descriptor from one of the repositories. + * + * @param project the Maven project, not null. + * @param localRepository the Maven local repository, not null. + * @param repositories the Maven remote repositories, not null. + * @param locale the locale wanted for the site descriptor. If not null, searching for + * site_localeLanguage.xml, otherwise searching for site.xml. + * @return the site descriptor into the local repository after download of it from repositories or null if not + * found in repositories. + * @throws SiteToolException if any + */ + File getSiteDescriptorFromRepository( MavenProject project, ArtifactRepository localRepository, + List repositories, Locale locale ) + throws SiteToolException + { + checkNotNull( "project", project ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "repositories", repositories ); + + final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale; + + try + { + return resolveSiteDescriptor( project, localRepository, repositories, llocale ); + } + catch ( ArtifactNotFoundException e ) + { + getLogger().debug( "ArtifactNotFoundException: Unable to locate site descriptor: " + e ); + return null; + } + catch ( ArtifactResolutionException e ) + { + throw new SiteToolException( "ArtifactResolutionException: Unable to locate site descriptor: " + + e.getMessage(), e ); + } + catch ( IOException e ) + { + throw new SiteToolException( "IOException: Unable to locate site descriptor: " + e.getMessage(), e ); + } + } + + /** + * Read site descriptor content from Reader, adding support for deprecated ${reports}, + * ${parentProject} and ${modules} tags. + * + * @param reader + * @return the input content interpolated with deprecated tags + * @throws IOException + */ + private String readSiteDescriptor( Reader reader, String projectId ) + throws IOException + { + String siteDescriptorContent = IOUtil.toString( reader ); + + // This is to support the deprecated ${reports}, ${parentProject} and ${modules} tags. + Properties props = new Properties(); + props.put( "reports", "
" ); + props.put( "modules", "" ); + props.put( "parentProject", "" ); + + // warn if interpolation required + for ( Object prop : props.keySet() ) + { + if ( siteDescriptorContent.contains( "$" + prop ) ) + { + getLogger().warn( "Site descriptor for " + projectId + " contains $" + prop + + ": should be replaced with " + props.getProperty( (String) prop ) ); + } + if ( siteDescriptorContent.contains( "${" + prop + "}" ) ) + { + getLogger().warn( "Site descriptor for " + projectId + " contains ${" + prop + + "}: should be replaced with " + props.getProperty( (String) prop ) ); + } + } + + return StringUtils.interpolate( siteDescriptorContent, props ); + } + + /** {@inheritDoc} */ + public DecorationModel getDecorationModel( File siteDirectory, Locale locale, MavenProject project, + List reactorProjects, ArtifactRepository localRepository, + List repositories ) + throws SiteToolException + { + checkNotNull( "project", project ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "repositories", repositories ); + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + getLogger().debug( "Computing decoration model of " + project.getId() + " for locale " + llocale ); + + Map.Entry result = + getDecorationModel( 0, siteDirectory, llocale, project, reactorProjects, localRepository, repositories ); + DecorationModel decorationModel = result.getKey(); + MavenProject parentProject = result.getValue(); + + if ( decorationModel == null ) + { + getLogger().debug( "Using default site descriptor" ); + + String siteDescriptorContent; + + Reader reader = null; + try + { + // Note the default is not a super class - it is used when nothing else is found + reader = ReaderFactory.newXmlReader( getClass().getResourceAsStream( "/default-site.xml" ) ); + siteDescriptorContent = readSiteDescriptor( reader, "default-site.xml" ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading default site descriptor: " + e.getMessage(), e ); + } + finally + { + IOUtil.close( reader ); + } + + decorationModel = readDecorationModel( siteDescriptorContent ); + } + + // DecorationModel back to String to interpolate, then go back to DecorationModel + String siteDescriptorContent = decorationModelToString( decorationModel ); + + // "classical" late interpolation, after full inheritance + siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, false ); + + decorationModel = readDecorationModel( siteDescriptorContent ); + + if ( parentProject != null ) + { + populateParentMenu( decorationModel, llocale, project, parentProject, true ); + } + + try + { + populateModulesMenu( decorationModel, llocale, project, reactorProjects, localRepository, true ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error while populating modules menu: " + e.getMessage(), e ); + } + + if ( decorationModel.getBannerLeft() == null ) + { + // extra default to set + Banner banner = new Banner(); + banner.setName( project.getName() ); + decorationModel.setBannerLeft( banner ); + } + + return decorationModel; + } + + /** {@inheritDoc} */ + public String getInterpolatedSiteDescriptorContent( Map props, MavenProject aProject, + String siteDescriptorContent ) + throws SiteToolException + { + checkNotNull( "props", props ); + + // "classical" late interpolation + return getInterpolatedSiteDescriptorContent( aProject, siteDescriptorContent, false ); + } + + private String getInterpolatedSiteDescriptorContent( MavenProject aProject, + String siteDescriptorContent, boolean isEarly ) + throws SiteToolException + { + checkNotNull( "aProject", aProject ); + checkNotNull( "siteDescriptorContent", siteDescriptorContent ); + + RegexBasedInterpolator interpolator = new RegexBasedInterpolator(); + + if ( isEarly ) + { + interpolator.addValueSource( new PrefixedObjectValueSource( "this.", aProject ) ); + interpolator.addValueSource( new PrefixedPropertiesValueSource( "this.", aProject.getProperties() ) ); + } + else + { + interpolator.addValueSource( new ObjectBasedValueSource( aProject ) ); + interpolator.addValueSource( new MapBasedValueSource( aProject.getProperties() ) ); + + try + { + interpolator.addValueSource( new EnvarBasedValueSource() ); + } + catch ( IOException e ) + { + // Prefer logging? + throw new SiteToolException( "IOException: cannot interpolate environment properties: " + + e.getMessage(), e ); + } + } + + try + { + // FIXME: this does not escape xml entities, see MSITE-226, PLXCOMP-118 + return interpolator.interpolate( siteDescriptorContent, isEarly ? null : "project" ); + } + catch ( InterpolationException e ) + { + throw new SiteToolException( "Cannot interpolate site descriptor: " + e.getMessage(), e ); + } + } + + /** {@inheritDoc} */ + public MavenProject getParentProject( MavenProject aProject, List reactorProjects, + ArtifactRepository localRepository ) + { + checkNotNull( "aProject", aProject ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + + if ( isMaven3OrMore() ) + { + // no need to make voodoo with Maven 3: job already done + return aProject.getParent(); + } + + MavenProject parentProject = null; + + MavenProject origParent = aProject.getParent(); + if ( origParent != null ) + { + for ( MavenProject reactorProject : reactorProjects ) + { + if ( reactorProject.getGroupId().equals( origParent.getGroupId() ) + && reactorProject.getArtifactId().equals( origParent.getArtifactId() ) + && reactorProject.getVersion().equals( origParent.getVersion() ) ) + { + parentProject = reactorProject; + + getLogger().debug( "Parent project " + origParent.getId() + " picked from reactor" ); + break; + } + } + + if ( parentProject == null && aProject.getBasedir() != null + && StringUtils.isNotEmpty( aProject.getModel().getParent().getRelativePath() ) ) + { + try + { + String relativePath = aProject.getModel().getParent().getRelativePath(); + + File pomFile = new File( aProject.getBasedir(), relativePath ); + + if ( pomFile.isDirectory() ) + { + pomFile = new File( pomFile, "pom.xml" ); + } + pomFile = new File( getNormalizedPath( pomFile.getPath() ) ); + + if ( pomFile.isFile() ) + { + MavenProject mavenProject = mavenProjectBuilder.build( pomFile, localRepository, null ); + + if ( mavenProject.getGroupId().equals( origParent.getGroupId() ) + && mavenProject.getArtifactId().equals( origParent.getArtifactId() ) + && mavenProject.getVersion().equals( origParent.getVersion() ) ) + { + parentProject = mavenProject; + + getLogger().debug( "Parent project " + origParent.getId() + " loaded from a relative path: " + + relativePath ); + } + } + } + catch ( ProjectBuildingException e ) + { + getLogger().info( "Unable to load parent project " + origParent.getId() + " from a relative path: " + + e.getMessage() ); + } + } + + if ( parentProject == null ) + { + try + { + parentProject = mavenProjectBuilder.buildFromRepository( aProject.getParentArtifact(), aProject + .getRemoteArtifactRepositories(), localRepository ); + + getLogger().debug( "Parent project " + origParent.getId() + " loaded from repository" ); + } + catch ( ProjectBuildingException e ) + { + getLogger().warn( "Unable to load parent project " + origParent.getId() + " from repository: " + + e.getMessage() ); + } + } + + if ( parentProject == null ) + { + // fallback to original parent, which may contain uninterpolated value (still need a unit test) + + parentProject = origParent; + + getLogger().debug( "Parent project " + origParent.getId() + " picked from original value" ); + } + } + return parentProject; + } + + /** + * Populate the pre-defined parent menu of the decoration model, + * if used through <menu ref="parent"/>. + * + * @param decorationModel the Doxia Sitetools DecorationModel, not null. + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project a Maven project, not null. + * @param parentProject a Maven parent project, not null. + * @param keepInheritedRefs used for inherited references. + */ + private void populateParentMenu( DecorationModel decorationModel, Locale locale, MavenProject project, + MavenProject parentProject, boolean keepInheritedRefs ) + { + checkNotNull( "decorationModel", decorationModel ); + checkNotNull( "project", project ); + checkNotNull( "parentProject", parentProject ); + + Menu menu = decorationModel.getMenuRef( "parent" ); + + if ( menu == null ) + { + return; + } + + if ( keepInheritedRefs && menu.isInheritAsRef() ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + String parentUrl = getDistMgmntSiteUrl( parentProject ); + + if ( parentUrl != null ) + { + if ( parentUrl.endsWith( "/" ) ) + { + parentUrl += "index.html"; + } + else + { + parentUrl += "/index.html"; + } + + parentUrl = getRelativePath( parentUrl, getDistMgmntSiteUrl( project ) ); + } + else + { + // parent has no url, assume relative path is given by site structure + File parentBasedir = parentProject.getBasedir(); + // First make sure that the parent is available on the file system + if ( parentBasedir != null ) + { + // Try to find the relative path to the parent via the file system + String parentPath = parentBasedir.getAbsolutePath(); + String projectPath = project.getBasedir().getAbsolutePath(); + parentUrl = getRelativePath( parentPath, projectPath ) + "/index.html"; + } + } + + // Only add the parent menu if we were able to find a URL for it + if ( parentUrl == null ) + { + getLogger().warn( "Unable to find a URL to the parent project. The parent menu will NOT be added." ); + } + else + { + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.parentproject" ) ); + } + + MenuItem item = new MenuItem(); + item.setName( parentProject.getName() ); + item.setHref( parentUrl ); + menu.addItem( item ); + } + } + + /** + * Populate the pre-defined modules menu of the decoration model, + * if used through <menu ref="modules"/>. + * + * @param decorationModel the Doxia Sitetools DecorationModel, not null. + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project a Maven project, not null. + * @param reactorProjects the Maven reactor projects, not null. + * @param localRepository the Maven local repository, not null. + * @param keepInheritedRefs used for inherited references. + * @throws SiteToolException if any + * @throws IOException + */ +/** + * Populate the pre-defined modules menu of the decoration model, + * if used through <menu ref="modules"/>. + * + * @param decorationModel + * the Doxia Sitetools DecorationModel, not null. + * @param locale + * the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project + * a Maven project, not null. + * @param reactorProjects + * the Maven reactor projects, not null. + * @param localRepository + * the Maven local repository, not null. + * @param keepInheritedRefs + * used for inherited references. + * @throws SiteToolException + * if any + * @throws IOException + * + */ +private void populateModulesMenu(org.apache.maven.doxia.site.decoration.DecorationModel decorationModel, java.util.Locale locale, org.apache.maven.project.MavenProject project, java.util.List reactorProjects, org.apache.maven.artifact.repository.ArtifactRepository localRepository, boolean keepInheritedRefs) throws org.apache.maven.doxia.tools.SiteToolException, java.io.IOException { + checkNotNull("project", project); + checkNotNull("reactorProjects", reactorProjects); + checkNotNull("localRepository", localRepository); + checkNotNull("decorationModel", decorationModel); + org.apache.maven.doxia.site.decoration.Menu menu = decorationModel.getMenuRef("modules"); + { + if (keepInheritedRefs && /* NPEX_NULL_EXP */ + menu.isInheritAsRef()) { + return; + } + final java.util.Locale llocale = (locale == null) ? java.util.Locale.getDefault() : locale; + // we require child modules and reactors to process module menu + if (project.getModules().size() > 0) { + if (menu.getName() == null) { + menu.setName(i18n.getString("site-tool", llocale, "decorationModel.menu.projectmodules")); + } + for (java.lang.String module : ((java.util.List) (project.getModules()))) { + org.apache.maven.project.MavenProject moduleProject = org.apache.maven.doxia.tools.DefaultSiteTool.getModuleFromReactor(project, reactorProjects, module); + if (moduleProject == null) { + getLogger().warn(("Module " + module) + " not found in reactor: loading locally"); + java.io.File f = new java.io.File(project.getBasedir(), module + "/pom.xml"); + if (f.exists()) { + try { + moduleProject = mavenProjectBuilder.build(f, localRepository, null); + } catch (org.apache.maven.project.ProjectBuildingException e) { + throw new org.apache.maven.doxia.tools.SiteToolException("Unable to read local module-POM", e); + } + } else { + getLogger().warn("No filesystem module-POM available"); + moduleProject = new org.apache.maven.project.MavenProject(); + moduleProject.setName(module); + moduleProject.setDistributionManagement(new org.apache.maven.model.DistributionManagement()); + moduleProject.getDistributionManagement().setSite(new org.apache.maven.model.Site()); + moduleProject.getDistributionManagement().getSite().setUrl(module); + } + } + java.lang.String siteUrl = org.apache.maven.doxia.tools.DefaultSiteTool.getDistMgmntSiteUrl(moduleProject); + java.lang.String itemName = (moduleProject.getName() == null) ? moduleProject.getArtifactId() : moduleProject.getName(); + appendMenuItem(project, menu, itemName, siteUrl, moduleProject.getArtifactId()); + } + } else if (decorationModel.getMenuRef("modules").getInherit() == null) { + // only remove if project has no modules AND menu is not inherited, see MSHARED-174 + decorationModel.removeMenuRef("modules"); + } + } +} + + private static MavenProject getModuleFromReactor( MavenProject project, List reactorProjects, + String module ) + throws IOException + { + File moduleBasedir = new File( project.getBasedir(), module ).getCanonicalFile(); + + for ( MavenProject reactorProject : reactorProjects ) + { + if ( moduleBasedir.equals( reactorProject.getBasedir() ) ) + { + return reactorProject; + } + } + + // module not found in reactor + return null; + } + + /** {@inheritDoc} */ + public void populateReportsMenu( DecorationModel decorationModel, Locale locale, + Map> categories ) + { + checkNotNull( "decorationModel", decorationModel ); + checkNotNull( "categories", categories ); + + Menu menu = decorationModel.getMenuRef( "reports" ); + + if ( menu == null ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectdocumentation" ) ); + } + + boolean found = false; + if ( menu.getItems().isEmpty() ) + { + List categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_INFORMATION ); + if ( !isEmptyList( categoryReports ) ) + { + MenuItem item = createCategoryMenu( + i18n.getString( "site-tool", llocale, + "decorationModel.menu.projectinformation" ), + "/project-info.html", categoryReports, llocale ); + menu.getItems().add( item ); + found = true; + } + + categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_REPORTS ); + if ( !isEmptyList( categoryReports ) ) + { + MenuItem item = + createCategoryMenu( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectreports" ), + "/project-reports.html", categoryReports, llocale ); + menu.getItems().add( item ); + found = true; + } + } + if ( !found ) + { + decorationModel.removeMenuRef( "reports" ); + } + } + + /** {@inheritDoc} */ + public List getSiteLocales( String locales ) + { + if ( locales == null ) + { + return Collections.singletonList( DEFAULT_LOCALE ); + } + + String[] localesArray = StringUtils.split( locales, "," ); + List localesList = new ArrayList( localesArray.length ); + + for ( String localeString : localesArray ) + { + Locale locale = codeToLocale( localeString ); + + if ( locale == null ) + { + continue; + } + + if ( !Arrays.asList( Locale.getAvailableLocales() ).contains( locale ) ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "The locale defined by '" + locale + + "' is not available in this Java Virtual Machine (" + + System.getProperty( "java.version" ) + + " from " + System.getProperty( "java.vendor" ) + ") - IGNORING" ); + } + continue; + } + + // Default bundles are in English + if ( ( !locale.getLanguage().equals( DEFAULT_LOCALE.getLanguage() ) ) + && ( !i18n.getBundle( "site-tool", locale ).getLocale().getLanguage() + .equals( locale.getLanguage() ) ) ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "The locale '" + locale + "' (" + locale.getDisplayName( Locale.ENGLISH ) + + ") is not currently supported by Maven Site - IGNORING." + + "\nContributions are welcome and greatly appreciated!" + + "\nIf you want to contribute a new translation, please visit " + + "http://maven.apache.org/plugins/localization.html for detailed instructions." ); + } + + continue; + } + + localesList.add( locale ); + } + + if ( localesList.isEmpty() ) + { + localesList = Collections.singletonList( DEFAULT_LOCALE ); + } + + return localesList; + } + + /** + * Converts a locale code like "en", "en_US" or "en_US_win" to a java.util.Locale + * object. + *

If localeCode = default, return the current value of the default locale for this instance + * of the Java Virtual Machine.

+ * + * @param localeCode the locale code string. + * @return a java.util.Locale object instanced or null if errors occurred + * @see java.util.Locale#getDefault() + */ + private Locale codeToLocale( String localeCode ) + { + if ( localeCode == null ) + { + return null; + } + + if ( "default".equalsIgnoreCase( localeCode ) ) + { + return Locale.getDefault(); + } + + String language = ""; + String country = ""; + String variant = ""; + + StringTokenizer tokenizer = new StringTokenizer( localeCode, "_" ); + final int maxTokens = 3; + if ( tokenizer.countTokens() > maxTokens ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "Invalid java.util.Locale format for '" + localeCode + "' entry - IGNORING" ); + } + return null; + } + + if ( tokenizer.hasMoreTokens() ) + { + language = tokenizer.nextToken(); + if ( tokenizer.hasMoreTokens() ) + { + country = tokenizer.nextToken(); + if ( tokenizer.hasMoreTokens() ) + { + variant = tokenizer.nextToken(); + } + } + } + + return new Locale( language, country, variant ); + } + + // ---------------------------------------------------------------------- + // Protected methods + // ---------------------------------------------------------------------- + + /** + * @param path could be null. + * @return the path normalized, i.e. by eliminating "/../" and "/./" in the path. + * @see FilenameUtils#normalize(String) + */ + protected static String getNormalizedPath( String path ) + { + String normalized = FilenameUtils.normalize( path ); + if ( normalized == null ) + { + normalized = path; + } + return ( normalized == null ) ? null : normalized.replace( '\\', '/' ); + } + + // ---------------------------------------------------------------------- + // Private methods + // ---------------------------------------------------------------------- + + /** + * @param project not null + * @param localRepository not null + * @param repositories not null + * @param locale not null + * @return the resolved site descriptor + * @throws IOException if any + * @throws ArtifactResolutionException if any + * @throws ArtifactNotFoundException if any + */ + private File resolveSiteDescriptor( MavenProject project, ArtifactRepository localRepository, + List repositories, Locale locale ) + throws IOException, ArtifactResolutionException, ArtifactNotFoundException + { + File result; + + // TODO: this is a bit crude - proper type, or proper handling as metadata rather than an artifact in 2.1? + Artifact artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), + project.getArtifactId(), + project.getVersion(), "xml", + "site_" + locale.getLanguage() ); + + boolean found = false; + try + { + artifactResolver.resolve( artifact, repositories, localRepository ); + + result = artifact.getFile(); + + // we use zero length files to avoid re-resolution (see below) + if ( result.length() > 0 ) + { + found = true; + } + else + { + getLogger().debug( "No site descriptor found for " + project.getId() + " for locale " + + locale.getLanguage() + ", trying without locale..." ); + } + } + catch ( ArtifactNotFoundException e ) + { + getLogger().debug( "Unable to locate site descriptor for locale " + locale.getLanguage() + ": " + e ); + + // we can afford to write an empty descriptor here as we don't expect it to turn up later in the remote + // repository, because the parent was already released (and snapshots are updated automatically if changed) + result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); + result.getParentFile().mkdirs(); + result.createNewFile(); + } + + if ( !found ) + { + artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), project.getArtifactId(), + project.getVersion(), "xml", "site" ); + try + { + artifactResolver.resolve( artifact, repositories, localRepository ); + } + catch ( ArtifactNotFoundException e ) + { + // see above regarding this zero length file + result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); + result.getParentFile().mkdirs(); + result.createNewFile(); + + throw e; + } + + result = artifact.getFile(); + + // we use zero length files to avoid re-resolution (see below) + if ( result.length() == 0 ) + { + getLogger().debug( "No site descriptor found for " + project.getId() + " without locale." ); + result = null; + } + } + + return result; + } + + /** + * @param depth depth of project + * @param siteDirectory, can be null if project.basedir is null, ie POM from repository + * @param locale not null + * @param project not null + * @param reactorProjects not null + * @param localRepository not null + * @param repositories not null + * @param origProps not null + * @return the decoration model depending the locale and the parent project + * @throws SiteToolException if any + */ + private Map.Entry getDecorationModel( int depth, File siteDirectory, Locale locale, + MavenProject project, + List reactorProjects, + ArtifactRepository localRepository, + List repositories ) + throws SiteToolException + { + // 1. get site descriptor File + File siteDescriptor; + if ( project.getBasedir() == null ) + { + // POM is in the repository: look into the repository for site descriptor + try + { + siteDescriptor = getSiteDescriptorFromRepository( project, localRepository, repositories, locale ); + } + catch ( SiteToolException e ) + { + throw new SiteToolException( "The site descriptor cannot be resolved from the repository: " + + e.getMessage(), e ); + } + } + else + { + // POM is in build directory: look for site descriptor as local file + siteDescriptor = getSiteDescriptor( siteDirectory, locale ); + } + + // 2. read DecorationModel from site descriptor File and do early interpolation (${this.*}) + DecorationModel decoration = null; + Reader siteDescriptorReader = null; + try + { + if ( siteDescriptor != null && siteDescriptor.exists() ) + { + getLogger().debug( "Reading" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + + " site descriptor from " + siteDescriptor ); + + siteDescriptorReader = ReaderFactory.newXmlReader( siteDescriptor ); + + String siteDescriptorContent = readSiteDescriptor( siteDescriptorReader, project.getId() ); + + // interpolate ${this.*} = early interpolation + siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, true ); + + decoration = readDecorationModel( siteDescriptorContent ); + decoration.setLastModified( siteDescriptor.lastModified() ); + } + else + { + getLogger().debug( "No" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + " site descriptor." ); + } + } + catch ( IOException e ) + { + throw new SiteToolException( "The site descriptor for " + project.getId() + " cannot be read from " + + siteDescriptor, e ); + } + finally + { + IOUtil.close( siteDescriptorReader ); + } + + // 3. look for parent project + MavenProject parentProject = getParentProject( project, reactorProjects, localRepository ); + + // 4. merge with parent project DecorationModel + if ( parentProject != null && ( decoration == null || decoration.isMergeParent() ) ) + { + depth++; + getLogger().debug( "Looking for site descriptor of level " + depth + " parent project: " + + parentProject.getId() ); + + File parentSiteDirectory = null; + if ( parentProject.getBasedir() != null ) + { + // extrapolate parent project site directory + String siteRelativePath = getRelativeFilePath( project.getBasedir().getAbsolutePath(), + siteDescriptor.getParentFile().getAbsolutePath() ); + + parentSiteDirectory = new File( parentProject.getBasedir(), siteRelativePath ); + // notice: using same siteRelativePath for parent as current project; may be wrong if site plugin + // has different configuration. But this is a rare case (this only has impact if parent is from reactor) + } + + DecorationModel parentDecoration = + getDecorationModel( depth, parentSiteDirectory, locale, parentProject, reactorProjects, localRepository, + repositories ).getKey(); + + // MSHARED-116 requires an empty decoration model (instead of a null one) + // MSHARED-145 requires us to do this only if there is a parent to merge it with + if ( decoration == null && parentDecoration != null ) + { + // we have no site descriptor: merge the parent into an empty one + decoration = new DecorationModel(); + } + + String name = project.getName(); + if ( decoration != null && StringUtils.isNotEmpty( decoration.getName() ) ) + { + name = decoration.getName(); + } + + // Merge the parent and child DecorationModels + String projectDistMgmnt = getDistMgmntSiteUrl( project ); + String parentDistMgmnt = getDistMgmntSiteUrl( parentProject ); + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Site decoration model inheritance: assembling child with level " + depth + + " parent: distributionManagement.site.url child = " + projectDistMgmnt + " and parent = " + + parentDistMgmnt ); + } + assembler.assembleModelInheritance( name, decoration, parentDecoration, projectDistMgmnt, + parentDistMgmnt == null ? projectDistMgmnt : parentDistMgmnt ); + } + + return new AbstractMap.SimpleEntry( decoration, parentProject ); + } + + /** + * @param siteDescriptorContent not null + * @return the decoration model object + * @throws SiteToolException if any + */ + private DecorationModel readDecorationModel( String siteDescriptorContent ) + throws SiteToolException + { + try + { + return new DecorationXpp3Reader().read( new StringReader( siteDescriptorContent ) ); + } + catch ( XmlPullParserException e ) + { + throw new SiteToolException( "Error parsing site descriptor", e ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading site descriptor", e ); + } + } + + private String decorationModelToString( DecorationModel decoration ) + throws SiteToolException + { + StringWriter writer = new StringWriter(); + + try + { + new DecorationXpp3Writer().write( writer, decoration ); + return writer.toString(); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading site descriptor", e ); + } + finally + { + IOUtil.close( writer ); + } + } + + private static String buildRelativePath( final String toPath, final String fromPath, final char separatorChar ) + { + // use tokenizer to traverse paths and for lazy checking + StringTokenizer toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); + StringTokenizer fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); + + int count = 0; + + // walk along the to path looking for divergence from the from path + while ( toTokeniser.hasMoreTokens() && fromTokeniser.hasMoreTokens() ) + { + if ( separatorChar == '\\' ) + { + if ( !fromTokeniser.nextToken().equalsIgnoreCase( toTokeniser.nextToken() ) ) + { + break; + } + } + else + { + if ( !fromTokeniser.nextToken().equals( toTokeniser.nextToken() ) ) + { + break; + } + } + + count++; + } + + // reinitialize the tokenizers to count positions to retrieve the + // gobbled token + + toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); + fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); + + while ( count-- > 0 ) + { + fromTokeniser.nextToken(); + toTokeniser.nextToken(); + } + + StringBuilder relativePath = new StringBuilder(); + + // add back refs for the rest of from location. + while ( fromTokeniser.hasMoreTokens() ) + { + fromTokeniser.nextToken(); + + relativePath.append( ".." ); + + if ( fromTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + } + + if ( relativePath.length() != 0 && toTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + + // add fwd fills for whatever's left of to. + while ( toTokeniser.hasMoreTokens() ) + { + relativePath.append( toTokeniser.nextToken() ); + + if ( toTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + } + return relativePath.toString(); + } + + /** + * @param project not null + * @param menu not null + * @param name not null + * @param href could be null + * @param defaultHref not null + */ + private void appendMenuItem( MavenProject project, Menu menu, String name, String href, String defaultHref ) + { + String selectedHref = href; + + if ( selectedHref == null ) + { + selectedHref = defaultHref; + } + + MenuItem item = new MenuItem(); + item.setName( name ); + + String baseUrl = getDistMgmntSiteUrl( project ); + if ( baseUrl != null ) + { + selectedHref = getRelativePath( selectedHref, baseUrl ); + } + + if ( selectedHref.endsWith( "/" ) ) + { + item.setHref( selectedHref + "index.html" ); + } + else + { + item.setHref( selectedHref + "/index.html" ); + } + menu.addItem( item ); + } + + /** + * @param name not null + * @param href not null + * @param categoryReports not null + * @param locale not null + * @return the menu item object + */ + private MenuItem createCategoryMenu( String name, String href, List categoryReports, Locale locale ) + { + MenuItem item = new MenuItem(); + item.setName( name ); + item.setCollapse( true ); + item.setHref( href ); + + // MSHARED-172, allow reports to define their order in some other way? + //Collections.sort( categoryReports, new ReportComparator( locale ) ); + + for ( MavenReport report : categoryReports ) + { + MenuItem subitem = new MenuItem(); + subitem.setName( report.getName( locale ) ); + subitem.setHref( report.getOutputName() + ".html" ); + item.getItems().add( subitem ); + } + + return item; + } + + // ---------------------------------------------------------------------- + // static methods + // ---------------------------------------------------------------------- + + /** + * Convenience method. + * + * @param list could be null + * @return true if the list is null or empty + */ + private static boolean isEmptyList( List list ) + { + return list == null || list.isEmpty(); + } + + /** + * Return distributionManagement.site.url if defined, null otherwise. + * + * @param project not null + * @return could be null + */ + private static String getDistMgmntSiteUrl( MavenProject project ) + { + return getDistMgmntSiteUrl( project.getDistributionManagement() ); + } + + private static String getDistMgmntSiteUrl( DistributionManagement distMgmnt ) + { + if ( distMgmnt != null && distMgmnt.getSite() != null && distMgmnt.getSite().getUrl() != null ) + { + return urlEncode( distMgmnt.getSite().getUrl() ); + } + + return null; + } + + private static String urlEncode( final String url ) + { + if ( url == null ) + { + return null; + } + + try + { + return new File( url ).toURI().toURL().toExternalForm(); + } + catch ( MalformedURLException ex ) + { + return url; // this will then throw somewhere else + } + } + + private void checkNotNull( String name, Object value ) + { + if ( value == null ) + { + throw new IllegalArgumentException( "The parameter '" + name + "' cannot be null." ); + } + } + + /** + * Check the current Maven version to see if it's Maven 3.0 or newer. + */ + private static boolean isMaven3OrMore() + { + return new DefaultArtifactVersion( getMavenVersion() ).getMajorVersion() >= 3; + } + + private static String getMavenVersion() + { + // This relies on the fact that MavenProject is the in core classloader + // and that the core classloader is for the maven-core artifact + // and that should have a pom.properties file + // if this ever changes, we will have to revisit this code. + final Properties properties = new Properties(); + final String corePomProperties = "META-INF/maven/org.apache.maven/maven-core/pom.properties"; + final InputStream in = MavenProject.class.getClassLoader().getResourceAsStream( corePomProperties ); + try + { + properties.load( in ); + } + catch ( IOException ioe ) + { + return ""; + } + finally + { + IOUtil.close( in ); + } + + return properties.getProperty( "version" ).trim(); + } +} diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_771/npe.json b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_771/npe.json new file mode 100644 index 000000000..4a81867d6 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteTool_771/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/DefaultSiteTool.java", + "line": 788, + "npe_method": "populateModulesMenu", + "deref_field": "menu", + "npe_class": "DefaultSiteTool" +} \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/bugs/FoPdfRenderer_90/buggy.java b/Java-base/maven-doxia-sitetools/bugs/FoPdfRenderer_90/buggy.java new file mode 100644 index 000000000..3b4d030c8 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/FoPdfRenderer_90/buggy.java @@ -0,0 +1,325 @@ +package org.apache.maven.doxia.docrenderer.pdf.fo; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.IOException; +import java.io.Writer; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import javax.xml.transform.TransformerException; + +import org.apache.maven.doxia.docrenderer.DocumentRendererContext; +import org.apache.maven.doxia.docrenderer.DocumentRendererException; +import org.apache.maven.doxia.docrenderer.pdf.AbstractPdfRenderer; +import org.apache.maven.doxia.docrenderer.pdf.PdfRenderer; +import org.apache.maven.doxia.document.DocumentModel; +import org.apache.maven.doxia.document.DocumentTOC; +import org.apache.maven.doxia.document.DocumentTOCItem; +import org.apache.maven.doxia.module.fo.FoAggregateSink; +import org.apache.maven.doxia.module.fo.FoSink; +import org.apache.maven.doxia.module.fo.FoSinkFactory; +import org.apache.maven.doxia.module.fo.FoUtils; +import org.apache.maven.doxia.parser.module.ParserModule; + +import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.util.IOUtil; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.util.WriterFactory; + +import org.xml.sax.SAXParseException; + +/** + * PDF renderer that uses Doxia's FO module. + * + * @author ltheussl + * @since 1.1 + */ +@Component( role = PdfRenderer.class, hint = "fo" ) +public class FoPdfRenderer + extends AbstractPdfRenderer +{ + /** + * {@inheritDoc} + * @see org.apache.maven.doxia.module.fo.FoUtils#convertFO2PDF(File, File, String) + */ + public void generatePdf( File inputFile, File pdfFile ) + throws DocumentRendererException + { + // Should take care of the document model for the metadata... + generatePdf( inputFile, pdfFile, null ); + } + + /** {@inheritDoc} */ + @Override + public void render( Map filesToProcess, File outputDirectory, DocumentModel documentModel ) + throws DocumentRendererException, IOException + { + render( filesToProcess, outputDirectory, documentModel, null ); + } + + /** {@inheritDoc} */ + @Override +/** + * {@inheritDoc } + */ +public void render(java.util.Map filesToProcess, java.io.File outputDirectory, org.apache.maven.doxia.document.DocumentModel documentModel, org.apache.maven.doxia.docrenderer.DocumentRendererContext context) throws org.apache.maven.doxia.docrenderer.DocumentRendererException, java.io.IOException { + // copy resources, images, etc. + copyResources(outputDirectory); + { + java.lang.String outputName = getOutputName(/* NPEX_NULL_EXP */ + documentModel); + java.io.File outputFOFile = new java.io.File(outputDirectory, outputName + ".fo"); + if (!outputFOFile.getParentFile().exists()) { + outputFOFile.getParentFile().mkdirs(); + } + java.io.File pdfOutputFile = new java.io.File(outputDirectory, outputName + ".pdf"); + if (!pdfOutputFile.getParentFile().exists()) { + pdfOutputFile.getParentFile().mkdirs(); + } + java.io.Writer writer = null; + try { + writer = org.codehaus.plexus.util.WriterFactory.newXmlWriter(outputFOFile); + org.apache.maven.doxia.module.fo.FoAggregateSink sink = new org.apache.maven.doxia.module.fo.FoAggregateSink(writer); + java.io.File fOConfigFile = new java.io.File(outputDirectory, "pdf-config.xml"); + if (fOConfigFile.exists()) { + sink.load(fOConfigFile); + getLogger().debug("Loaded pdf config file: " + fOConfigFile.getAbsolutePath()); + } + java.lang.String generateTOC = ((context != null) && (context.get("generateTOC") != null)) ? context.get("generateTOC").toString().trim() : "start"; + int tocPosition = 0; + if ("start".equalsIgnoreCase(generateTOC)) { + tocPosition = org.apache.maven.doxia.module.fo.FoAggregateSink.TOC_START; + } else if ("end".equalsIgnoreCase(generateTOC)) { + tocPosition = org.apache.maven.doxia.module.fo.FoAggregateSink.TOC_END; + } else { + tocPosition = org.apache.maven.doxia.module.fo.FoAggregateSink.TOC_NONE; + } + sink.setDocumentModel(documentModel, tocPosition); + sink.beginDocument(); + sink.coverPage(); + if (tocPosition == org.apache.maven.doxia.module.fo.FoAggregateSink.TOC_START) { + sink.toc(); + } + if ((documentModel.getToc() == null) || (documentModel.getToc().getItems() == null)) { + getLogger().info("No TOC is defined in the document descriptor. Merging all documents."); + mergeAllSources(filesToProcess, sink, context); + } else { + getLogger().debug("Using TOC defined in the document descriptor."); + mergeSourcesFromTOC(documentModel.getToc(), sink, context); + } + if (tocPosition == org.apache.maven.doxia.module.fo.FoAggregateSink.TOC_END) { + sink.toc(); + } + sink.endDocument(); + } finally { + org.codehaus.plexus.util.IOUtil.close(writer); + } + generatePdf(outputFOFile, pdfOutputFile, documentModel); + } +} + + /** {@inheritDoc} */ + @Override + public void renderIndividual( Map filesToProcess, File outputDirectory ) + throws DocumentRendererException, IOException + { + renderIndividual( filesToProcess, outputDirectory, null ); + } + + /** {@inheritDoc} */ + @Override + public void renderIndividual( Map filesToProcess, File outputDirectory, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + for ( Map.Entry entry : filesToProcess.entrySet() ) + { + String key = entry.getKey(); + ParserModule module = entry.getValue(); + + File fullDoc = new File( getBaseDir(), module.getSourceDirectory() + File.separator + key ); + + String output = key; + for ( String extension : module.getExtensions() ) + { + String lowerCaseExtension = extension.toLowerCase( Locale.ENGLISH ); + if ( output.toLowerCase( Locale.ENGLISH ).indexOf( "." + lowerCaseExtension ) != -1 ) + { + output = + output.substring( 0, output.toLowerCase( Locale.ENGLISH ).indexOf( "." + lowerCaseExtension ) ); + } + } + + File outputFOFile = new File( outputDirectory, output + ".fo" ); + if ( !outputFOFile.getParentFile().exists() ) + { + outputFOFile.getParentFile().mkdirs(); + } + + File pdfOutputFile = new File( outputDirectory, output + ".pdf" ); + if ( !pdfOutputFile.getParentFile().exists() ) + { + pdfOutputFile.getParentFile().mkdirs(); + } + + FoSink sink = + (FoSink) new FoSinkFactory().createSink( outputFOFile.getParentFile(), outputFOFile.getName() ); + sink.beginDocument(); + parse( fullDoc.getAbsolutePath(), module.getParserId(), sink, context ); + sink.endDocument(); + + generatePdf( outputFOFile, pdfOutputFile, null ); + } + } + + private void mergeAllSources( Map filesToProcess, FoAggregateSink sink, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + for ( Map.Entry entry : filesToProcess.entrySet() ) + { + String key = entry.getKey(); + ParserModule module = entry.getValue(); + sink.setDocumentName( key ); + File fullDoc = new File( getBaseDir(), module.getSourceDirectory() + File.separator + key ); + + parse( fullDoc.getAbsolutePath(), module.getParserId(), sink, context ); + } + } + + private void mergeSourcesFromTOC( DocumentTOC toc, FoAggregateSink sink, DocumentRendererContext context ) + throws IOException, DocumentRendererException + { + parseTocItems( toc.getItems(), sink, context ); + } + + private void parseTocItems( List items, FoAggregateSink sink, DocumentRendererContext context ) + throws IOException, DocumentRendererException + { + for ( DocumentTOCItem tocItem : items ) + { + if ( tocItem.getRef() == null ) + { + if ( getLogger().isInfoEnabled() ) + { + getLogger().info( "No ref defined for tocItem " + tocItem.getName() ); + } + + continue; + } + + String href = StringUtils.replace( tocItem.getRef(), "\\", "/" ); + if ( href.lastIndexOf( '.' ) != -1 ) + { + href = href.substring( 0, href.lastIndexOf( '.' ) ); + } + + renderModules( href, sink, tocItem, context ); + + if ( tocItem.getItems() != null ) + { + parseTocItems( tocItem.getItems(), sink, context ); + } + } + } + + private void renderModules( String href, FoAggregateSink sink, DocumentTOCItem tocItem, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + Collection modules = parserModuleManager.getParserModules(); + for ( ParserModule module : modules ) + { + File moduleBasedir = new File( getBaseDir(), module.getSourceDirectory() ); + + if ( moduleBasedir.exists() ) + { + for ( String extension : module.getExtensions() ) + { + String doc = href + "." + extension; + File source = new File( moduleBasedir, doc ); + + // Velocity file? + if ( !source.exists() ) + { + if ( href.indexOf( "." + extension ) != -1 ) + { + doc = href + ".vm"; + } + else + { + doc = href + "." + extension + ".vm"; + } + source = new File( moduleBasedir, doc ); + } + + if ( source.exists() ) + { + sink.setDocumentName( doc ); + sink.setDocumentTitle( tocItem.getName() ); + + parse( source.getPath(), module.getParserId(), sink, context ); + } + } + } + } + } + + /** + * @param inputFile + * @param pdfFile + * @param documentModel could be null + * @throws DocumentRendererException if any + * @since 1.1.1 + */ + private void generatePdf( File inputFile, File pdfFile, DocumentModel documentModel ) + throws DocumentRendererException + { + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Generating: " + pdfFile ); + } + + try + { + FoUtils.convertFO2PDF( inputFile, pdfFile, null, documentModel ); + } + catch ( TransformerException e ) + { + if ( ( e.getCause() != null ) && ( e.getCause() instanceof SAXParseException ) ) + { + SAXParseException sax = (SAXParseException) e.getCause(); + + StringBuilder sb = new StringBuilder(); + sb.append( "Error creating PDF from " ).append( inputFile.getAbsolutePath() ).append( ":" ) + .append( sax.getLineNumber() ).append( ":" ).append( sax.getColumnNumber() ).append( "\n" ); + sb.append( e.getMessage() ); + + throw new DocumentRendererException( sb.toString() ); + } + + throw new DocumentRendererException( "Error creating PDF from " + inputFile + ": " + e.getMessage() ); + } + } +} diff --git a/Java-base/maven-doxia-sitetools/bugs/FoPdfRenderer_90/npe.json b/Java-base/maven-doxia-sitetools/bugs/FoPdfRenderer_90/npe.json new file mode 100644 index 000000000..45abac0f4 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/FoPdfRenderer_90/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/pdf/fo/FoPdfRenderer.java", + "line": 91, + "npe_method": "render", + "deref_field": "documentModel", + "npe_class": "FoPdfRenderer" +} \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/bugs/ITextPdfRenderer_152/buggy.java b/Java-base/maven-doxia-sitetools/bugs/ITextPdfRenderer_152/buggy.java new file mode 100644 index 000000000..af95bb63a --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/ITextPdfRenderer_152/buggy.java @@ -0,0 +1,669 @@ +package org.apache.maven.doxia.docrenderer.pdf.itext; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.Writer; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.text.SimpleDateFormat; +import java.util.Collection; +import java.util.Date; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.apache.maven.doxia.docrenderer.DocumentRendererContext; +import org.apache.maven.doxia.docrenderer.DocumentRendererException; +import org.apache.maven.doxia.docrenderer.pdf.AbstractPdfRenderer; +import org.apache.maven.doxia.docrenderer.pdf.PdfRenderer; +import org.apache.maven.doxia.document.DocumentCover; +import org.apache.maven.doxia.document.DocumentMeta; +import org.apache.maven.doxia.document.DocumentModel; +import org.apache.maven.doxia.document.DocumentTOCItem; +import org.apache.maven.doxia.module.itext.ITextSink; +import org.apache.maven.doxia.module.itext.ITextSinkFactory; +import org.apache.maven.doxia.module.itext.ITextUtil; +import org.apache.maven.doxia.parser.module.ParserModule; +import org.apache.xml.utils.DefaultErrorHandler; +import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.util.IOUtil; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.util.WriterFactory; +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.xml.sax.SAXException; + +import com.lowagie.text.ElementTags; + +/** + * Abstract document render with the iText framework + * + * @author Vincent Siveton + * @author ltheussl + * @since 1.1 + */ +@Component( role = PdfRenderer.class, hint = "itext" ) +public class ITextPdfRenderer + extends AbstractPdfRenderer +{ + /** The xslt style sheet used to transform a Document to an iText file. */ + private static final String XSLT_RESOURCE = "TOC.xslt"; + + /** The TransformerFactory. */ + private static final TransformerFactory TRANSFORMER_FACTORY = TransformerFactory.newInstance(); + + /** The DocumentBuilderFactory. */ + private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance(); + + /** The DocumentBuilder. */ + private static final DocumentBuilder DOCUMENT_BUILDER; + + static + { + TRANSFORMER_FACTORY.setErrorListener( new DefaultErrorHandler() ); + + try + { + DOCUMENT_BUILDER = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder(); + } + catch ( ParserConfigurationException e ) + { + throw new RuntimeException( "Error building document :" + e.getMessage() ); + } + } + + /** {@inheritDoc} */ + public void generatePdf( File inputFile, File pdfFile ) + throws DocumentRendererException + { + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Generating : " + pdfFile ); + } + + try + { + ITextUtil.writePdf( new FileInputStream( inputFile ), new FileOutputStream( pdfFile ) ); + } + catch ( IOException e ) + { + throw new DocumentRendererException( "Cannot create PDF from " + inputFile + ": " + e.getMessage(), e ); + } + catch ( RuntimeException e ) + { + throw new DocumentRendererException( "Error creating PDF from " + inputFile + ": " + e.getMessage(), e ); + } + } + + /** {@inheritDoc} */ + @Override + public void render( Map filesToProcess, File outputDirectory, DocumentModel documentModel ) + throws DocumentRendererException, IOException + { + render( filesToProcess, outputDirectory, documentModel, null ); + } + + /** {@inheritDoc} */ + @Override +/** + * {@inheritDoc } + */ +public void render(java.util.Map filesToProcess, java.io.File outputDirectory, org.apache.maven.doxia.document.DocumentModel documentModel, org.apache.maven.doxia.docrenderer.DocumentRendererContext context) throws org.apache.maven.doxia.docrenderer.DocumentRendererException, java.io.IOException { + // copy resources, images, etc. + copyResources(outputDirectory); + { + java.lang.String outputName = getOutputName(/* NPEX_NULL_EXP */ + documentModel); + java.io.File outputITextFile = new java.io.File(outputDirectory, outputName + ".xml"); + if (!outputITextFile.getParentFile().exists()) { + outputITextFile.getParentFile().mkdirs(); + } + java.io.File pdfOutputFile = new java.io.File(outputDirectory, outputName + ".pdf"); + if (!pdfOutputFile.getParentFile().exists()) { + pdfOutputFile.getParentFile().mkdirs(); + } + java.util.List iTextFiles; + if ((documentModel.getToc() == null) || (documentModel.getToc().getItems() == null)) { + getLogger().info("No TOC is defined in the document descriptor. Merging all documents."); + iTextFiles = parseAllFiles(filesToProcess, outputDirectory, context); + } else { + getLogger().debug("Using TOC defined in the document descriptor."); + iTextFiles = parseTOCFiles(outputDirectory, documentModel, context); + } + java.lang.String generateTOC = ((context != null) && (context.get("generateTOC") != null)) ? context.get("generateTOC").toString() : "start"; + java.io.File iTextFile = new java.io.File(outputDirectory, outputName + ".xml"); + java.io.File iTextOutput = new java.io.File(outputDirectory, (outputName + ".") + getOutputExtension()); + org.w3c.dom.Document document = generateDocument(iTextFiles); + transform(documentModel, document, iTextFile, generateTOC); + generatePdf(iTextFile, iTextOutput); + } +} + + /** {@inheritDoc} */ + @Override + public void renderIndividual( Map filesToProcess, File outputDirectory ) + throws DocumentRendererException, IOException + { + renderIndividual( filesToProcess, outputDirectory, null ); + } + + /** {@inheritDoc} */ + @Override + public void renderIndividual( Map filesToProcess, File outputDirectory, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + for ( Map.Entry entry : filesToProcess.entrySet() ) + { + String key = entry.getKey(); + ParserModule module = entry.getValue(); + File fullDoc = new File( getBaseDir(), module.getSourceDirectory() + File.separator + key ); + + String output = key; + for ( String extension : module.getExtensions() ) + { + String lowerCaseExtension = extension.toLowerCase( Locale.ENGLISH ); + if ( output.toLowerCase( Locale.ENGLISH ).indexOf( "." + lowerCaseExtension ) != -1 ) + { + output = + output.substring( 0, output.toLowerCase( Locale.ENGLISH ).indexOf( "." + lowerCaseExtension ) ); + } + } + + File outputITextFile = new File( outputDirectory, output + ".xml" ); + if ( !outputITextFile.getParentFile().exists() ) + { + outputITextFile.getParentFile().mkdirs(); + } + + File pdfOutputFile = new File( outputDirectory, output + ".pdf" ); + if ( !pdfOutputFile.getParentFile().exists() ) + { + pdfOutputFile.getParentFile().mkdirs(); + } + + parse( fullDoc, module, outputITextFile, context ); + + generatePdf( outputITextFile, pdfOutputFile ); + } + } + + //-------------------------------------------- + // + //-------------------------------------------- + + + /** + * Parse a source document and emit results into a sink. + * + * @param fullDocPath file to the source document. + * @param module the site module associated with the source document (determines the parser to use). + * @param iTextFile the resulting iText xml file. + * @throws DocumentRendererException in case of a parsing problem. + * @throws IOException if the source and/or target document cannot be opened. + */ + private void parse( File fullDoc, ParserModule module, File iTextFile, DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Parsing file " + fullDoc.getAbsolutePath() ); + } + + System.setProperty( "itext.basedir", iTextFile.getParentFile().getAbsolutePath() ); + + Writer writer = null; + ITextSink sink = null; + try + { + writer = WriterFactory.newXmlWriter( iTextFile ); + sink = (ITextSink) new ITextSinkFactory().createSink( writer ); + + sink.setClassLoader( new URLClassLoader( new URL[] { iTextFile.getParentFile().toURI().toURL() } ) ); + + parse( fullDoc.getAbsolutePath(), module.getParserId(), sink, context ); + } + finally + { + if ( sink != null ) + { + sink.flush(); + sink.close(); + } + IOUtil.close( writer ); + System.getProperties().remove( "itext.basedir" ); + } + } + + /** + * Merge all iTextFiles to a single one. + * + * @param iTextFiles list of iText xml files. + * @return Document. + * @throws DocumentRendererException if any. + * @throws IOException if any. + */ + private Document generateDocument( List iTextFiles ) + throws DocumentRendererException, IOException + { + Document document = DOCUMENT_BUILDER.newDocument(); + document.appendChild( document.createElement( ElementTags.ITEXT ) ); // Used only to set a root + + for ( File iTextFile : iTextFiles ) + { + Document iTextDocument; + + try + { + iTextDocument = DOCUMENT_BUILDER.parse( iTextFile ); + } + catch ( SAXException e ) + { + throw new DocumentRendererException( "SAX Error : " + e.getMessage() ); + } + + // Only one chapter per doc + Node chapter = iTextDocument.getElementsByTagName( ElementTags.CHAPTER ).item( 0 ); + + try + { + document.getDocumentElement().appendChild( document.importNode( chapter, true ) ); + } + catch ( DOMException e ) + { + throw new DocumentRendererException( "Error appending chapter for " + + iTextFile + " : " + e.getMessage() ); + } + } + + return document; + } + + /** + * Initialize the transformer object. + * + * @return an instance of a transformer object. + * @throws DocumentRendererException if any. + */ + private Transformer initTransformer() + throws DocumentRendererException + { + try + { + Transformer transformer = TRANSFORMER_FACTORY.newTransformer( new StreamSource( ITextPdfRenderer.class + .getResourceAsStream( XSLT_RESOURCE ) ) ); + + transformer.setErrorListener( TRANSFORMER_FACTORY.getErrorListener() ); + + transformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "false" ); + + transformer.setOutputProperty( OutputKeys.INDENT, "yes" ); + + transformer.setOutputProperty( OutputKeys.METHOD, "xml" ); + + transformer.setOutputProperty( OutputKeys.ENCODING, "UTF-8" ); + + // No doctype since itext doctype is not up to date! + + return transformer; + } + catch ( TransformerConfigurationException e ) + { + throw new DocumentRendererException( "Error configuring Transformer for " + XSLT_RESOURCE + ": " + + e.getMessage() ); + } + catch ( IllegalArgumentException e ) + { + throw new DocumentRendererException( "Error configuring Transformer for " + XSLT_RESOURCE + ": " + + e.getMessage() ); + } + } + + /** + * Add transformer parameters from a DocumentModel. + * + * @param transformer the Transformer to set the parameters. + * @param documentModel the DocumentModel to take the parameters from, could be null. + * @param iTextFile the iTextFile not null for the relative paths. + * @param generateTOC not null, possible values are: 'none', 'start' and 'end'. + */ + private void addTransformerParameters( Transformer transformer, DocumentModel documentModel, File iTextFile, + String generateTOC ) + { + if ( documentModel == null ) + { + return; + } + + // TOC + addTransformerParameter( transformer, "toc.position", generateTOC ); + + // Meta parameters + boolean hasNullMeta = false; + if ( documentModel.getMeta() == null ) + { + hasNullMeta = true; + documentModel.setMeta( new DocumentMeta() ); + } + addTransformerParameter( transformer, "meta.author", documentModel.getMeta().getAllAuthorNames(), + System.getProperty( "user.name", "null" ) ); + addTransformerParameter( transformer, "meta.creator", documentModel.getMeta().getCreator(), + System.getProperty( "user.name", "null" ) ); + // see com.lowagie.text.Document#addCreationDate() + SimpleDateFormat sdf = new SimpleDateFormat( "EEE MMM dd HH:mm:ss zzz yyyy" ); + addTransformerParameter( transformer, "meta.creationdate", documentModel.getMeta().getCreationdate(), + sdf.format( new Date() ) ); + addTransformerParameter( transformer, "meta.keywords", documentModel.getMeta().getAllKeyWords() ); + addTransformerParameter( transformer, "meta.pagesize", documentModel.getMeta().getPageSize(), + ITextUtil.getPageSize( ITextUtil.getDefaultPageSize() ) ); + addTransformerParameter( transformer, "meta.producer", documentModel.getMeta().getGenerator(), + "Apache Doxia iText" ); + addTransformerParameter( transformer, "meta.subject", documentModel.getMeta().getSubject(), + ( documentModel.getMeta().getTitle() != null ? documentModel.getMeta().getTitle() + : "" ) ); + addTransformerParameter( transformer, "meta.title", documentModel.getMeta().getTitle() ); + if ( hasNullMeta ) + { + documentModel.setMeta( null ); + } + + // cover parameter + boolean hasNullCover = false; + if ( documentModel.getCover() == null ) + { + hasNullCover = true; + documentModel.setCover( new DocumentCover() ); + } + addTransformerParameter( transformer, "cover.author", documentModel.getCover().getAllAuthorNames(), + System.getProperty( "user.name", "null" ) ); + String companyLogo = getLogoURL( documentModel.getCover().getCompanyLogo(), iTextFile.getParentFile() ); + addTransformerParameter( transformer, "cover.companyLogo", companyLogo ); + addTransformerParameter( transformer, "cover.companyName", documentModel.getCover().getCompanyName() ); + if ( documentModel.getCover().getCoverdate() == null ) + { + documentModel.getCover().setCoverDate( new Date() ); + addTransformerParameter( transformer, "cover.date", documentModel.getCover().getCoverdate() ); + documentModel.getCover().setCoverDate( null ); + } + else + { + addTransformerParameter( transformer, "cover.date", documentModel.getCover().getCoverdate() ); + } + addTransformerParameter( transformer, "cover.subtitle", documentModel.getCover().getCoverSubTitle() ); + addTransformerParameter( transformer, "cover.title", documentModel.getCover().getCoverTitle() ); + addTransformerParameter( transformer, "cover.type", documentModel.getCover().getCoverType() ); + addTransformerParameter( transformer, "cover.version", documentModel.getCover().getCoverVersion() ); + String projectLogo = getLogoURL( documentModel.getCover().getProjectLogo(), iTextFile.getParentFile() ); + addTransformerParameter( transformer, "cover.projectLogo", projectLogo ); + addTransformerParameter( transformer, "cover.projectName", documentModel.getCover().getProjectName() ); + if ( hasNullCover ) + { + documentModel.setCover( null ); + } + } + + /** + * @param transformer not null + * @param name not null + * @param value could be empty + * @param defaultValue could be empty + * @since 1.1.1 + */ + private void addTransformerParameter( Transformer transformer, String name, String value, String defaultValue ) + { + if ( StringUtils.isEmpty( value ) ) + { + addTransformerParameter( transformer, name, defaultValue ); + } + else + { + addTransformerParameter( transformer, name, value ); + } + } + + /** + * @param transformer not null + * @param name not null + * @param value could be empty + * @since 1.1.1 + */ + private void addTransformerParameter( Transformer transformer, String name, String value ) + { + if ( StringUtils.isEmpty( value ) ) + { + return; + } + + transformer.setParameter( name, value ); + } + + /** + * Transform a document to an iTextFile. + * + * @param documentModel the DocumentModel to take the parameters from, could be null. + * @param document the Document to transform. + * @param iTextFile the resulting iText xml file. + * @param generateTOC not null, possible values are: 'none', 'start' and 'end'. + * @throws DocumentRendererException in case of a transformation error. + */ + private void transform( DocumentModel documentModel, Document document, File iTextFile, String generateTOC ) + throws DocumentRendererException + { + Transformer transformer = initTransformer(); + + addTransformerParameters( transformer, documentModel, iTextFile, generateTOC ); + + // need a writer for StreamResult to prevent FileNotFoundException when iTextFile contains spaces + Writer writer = null; + try + { + writer = WriterFactory.newXmlWriter( iTextFile ); + transformer.transform( new DOMSource( document ), new StreamResult( writer ) ); + } + catch ( TransformerException e ) + { + throw new DocumentRendererException( + "Error transforming Document " + document + ": " + e.getMessage(), + e ); + } + catch ( IOException e ) + { + throw new DocumentRendererException( + "Error transforming Document " + document + ": " + e.getMessage(), + e ); + } + finally + { + IOUtil.close( writer ); + } + } + + /** + * @param filesToProcess not null + * @param outputDirectory not null + * @return a list of all parsed files. + * @throws DocumentRendererException if any + * @throws IOException if any + * @since 1.1.1 + */ + private List parseAllFiles( Map filesToProcess, File outputDirectory, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + List iTextFiles = new LinkedList(); + for ( Map.Entry entry : filesToProcess.entrySet() ) + { + String key = entry.getKey(); + ParserModule module = entry.getValue(); + File fullDoc = new File( getBaseDir(), module.getSourceDirectory() + File.separator + key ); + + String outputITextName = key.substring( 0, key.lastIndexOf( '.' ) + 1 ) + "xml"; + File outputITextFileTmp = new File( outputDirectory, outputITextName ); + outputITextFileTmp.deleteOnExit(); + if ( !outputITextFileTmp.getParentFile().exists() ) + { + outputITextFileTmp.getParentFile().mkdirs(); + } + + iTextFiles.add( outputITextFileTmp ); + parse( fullDoc, module, outputITextFileTmp, context ); + } + + return iTextFiles; + } + + /** + * @param filesToProcess not null + * @param outputDirectory not null + * @return a list of all parsed files. + * @throws DocumentRendererException if any + * @throws IOException if any + * @since 1.1.1 + */ + private List parseTOCFiles( File outputDirectory, DocumentModel documentModel, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + List iTextFiles = new LinkedList(); + for ( Iterator it = documentModel.getToc().getItems().iterator(); it.hasNext(); ) + { + DocumentTOCItem tocItem = it.next(); + + if ( tocItem.getRef() == null ) + { + getLogger().debug( + "No ref defined for the tocItem '" + tocItem.getName() + + "' in the document descriptor. IGNORING" ); + continue; + } + + String href = StringUtils.replace( tocItem.getRef(), "\\", "/" ); + if ( href.lastIndexOf( '.' ) != -1 ) + { + href = href.substring( 0, href.lastIndexOf( '.' ) ); + } + + Collection modules = parserModuleManager.getParserModules(); + for ( ParserModule module : modules ) + { + File moduleBasedir = new File( getBaseDir(), module.getSourceDirectory() ); + + if ( moduleBasedir.exists() ) + { + for ( String extension : module.getExtensions() ) + { + String doc = href + "." + extension; + File source = new File( moduleBasedir, doc ); + + // Velocity file? + if ( !source.exists() ) + { + if ( href.indexOf( "." + extension ) != -1 ) + { + doc = href + ".vm"; + } + else + { + doc = href + "." + extension + ".vm"; + } + source = new File( moduleBasedir, doc ); + } + + if ( source.exists() ) + { + String outputITextName = doc.substring( 0, doc.lastIndexOf( '.' ) + 1 ) + "xml"; + File outputITextFileTmp = new File( outputDirectory, outputITextName ); + outputITextFileTmp.deleteOnExit(); + if ( !outputITextFileTmp.getParentFile().exists() ) + { + outputITextFileTmp.getParentFile().mkdirs(); + } + + iTextFiles.add( outputITextFileTmp ); + parse( source, module, outputITextFileTmp, context ); + } + } + } + } + } + + return iTextFiles; + } + + /** + * @param logo + * @param parentFile + * @return the logo url or null if unable to create it. + * @since 1.1.1 + */ + private String getLogoURL( String logo, File parentFile ) + { + if ( logo == null ) + { + return null; + } + + try + { + return new URL( logo ).toString(); + } + catch ( MalformedURLException e ) + { + try + { + File f = new File( parentFile, logo ); + if ( !f.exists() ) + { + getLogger().warn( "The logo " + f.getAbsolutePath() + " doesnt exist. IGNORING" ); + } + else + { + return f.toURI().toURL().toString(); + } + } + catch ( MalformedURLException e1 ) + { + getLogger().debug( "Failed to convert to URL: " + logo, e1 ); + } + } + + return null; + } +} diff --git a/Java-base/maven-doxia-sitetools/bugs/ITextPdfRenderer_152/npe.json b/Java-base/maven-doxia-sitetools/bugs/ITextPdfRenderer_152/npe.json new file mode 100644 index 000000000..438f06b81 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/ITextPdfRenderer_152/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/pdf/itext/ITextPdfRenderer.java", + "line": 153, + "npe_method": "render", + "deref_field": "documentModel", + "npe_class": "ITextPdfRenderer" +} \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_118/buggy.java b/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_118/buggy.java new file mode 100644 index 000000000..81e127a37 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_118/buggy.java @@ -0,0 +1,248 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; + +import org.codehaus.plexus.util.StringUtils; + +/** + * This class holds an instance of a maven path. This consists of a relative path (e.g. images/maven-logo.png) and a + * base reference which can also be a relative path (e.g. '.' or '../doxia') or an URL that is used for an absolute + * anchor. + * + * @author Henning P. Schmiedehausen + * @deprecated use {@link URIPathDescriptor} instead. + */ + +public class PathDescriptor +{ + private final URL baseUrl; + + private final URL pathUrl; + + private final String relativePath; + + /** + * Construct a PathDescriptor from a path. + * + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final String path ) + throws MalformedURLException + { + this( (URL) null, path ); + } + + /** + * Construct a PathDescriptor from a path and a base. + * + * @param base a base reference. + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final String base, final String path ) + throws MalformedURLException + { + this( PathDescriptor.buildBaseUrl( base ), path ); + } + + /** + * Construct a PathDescriptor from a path and a base. + * + * @param baseUrl a base reference. + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final URL baseUrl, final String path ) + throws MalformedURLException + { + this.baseUrl = baseUrl; + + URL pathURL = null; + String relPath = null; + + try + { + pathURL = new URL( path ); + } + catch ( MalformedURLException e ) + { + try + { + pathURL = buildUrl( baseUrl, path ); + } + catch ( MalformedURLException e2 ) + { + // If we got an absolute path passed in and end here, then the path + // is converted to relative because we have no reference URL anyway + // to which it has been anchored. + if ( path != null && path.startsWith( "/" ) ) + { + relPath = path.substring( 1 ); + } + else + { + relPath = path; + } + } + } + + this.pathUrl = pathURL; + this.relativePath = relPath; + } + +private static java.net.URL buildBaseUrl(final java.lang.String base) throws java.net.MalformedURLException { + { + try { + return new java.net.URL(/* NPEX_NULL_EXP */ + base); + } catch (java.net.MalformedURLException e) { + return new java.io.File(base).toURI().toURL(); + } + } +} + + private static URL buildUrl( final URL baseUrl, final String path ) + throws MalformedURLException + { + if ( baseUrl == null ) + { + throw new MalformedURLException( "Base is null!" ); + } + + if ( path == null ) + { + return baseUrl; + } + + if ( baseUrl.getProtocol().equals( "file" ) ) + { + return new File( baseUrl.getFile(), path ).toURI().toURL(); + } + + if ( path.startsWith( "/" ) && baseUrl.getPath().endsWith( "/" ) ) + { + return new URL( baseUrl, path.substring( 1 ) ); + } + + return new URL( baseUrl, path ); + } + + /** + * Check if this PathDescriptor describes a file. + * + * @return true for file, false otherwise. + */ + public boolean isFile() + { + return isRelative() || pathUrl.getProtocol().equals( "file" ); + } + + /** + * Check if this PathDescriptor describes a relative path. + * + * @return true if {@link #getPathUrl()} returns null. + */ + public boolean isRelative() + { + return pathUrl == null; + } + + /** + * Get the base URL. + * + * @return the base URL. + */ + public URL getBaseUrl() + { + return baseUrl; + } + + /** + * Get the path as a URL. + * + * @return the path as a URL. + */ + public URL getPathUrl() + { + return pathUrl; + } + + /** + * Get the path. + * + * @return the path. + */ + public String getPath() + { + if ( getPathUrl() != null ) + { + if ( isFile() ) + { + return StringUtils.stripEnd( getPathUrl().getPath(), "/" ); + } + else + { + return getPathUrl().getPath(); + } + } + else + { + return relativePath; + } + } + + /** + * Get the location for files. + * + * @return the location. + */ + public String getLocation() + { + if ( isFile() ) + { + if ( getPathUrl() != null ) + { + return StringUtils.stripEnd( getPathUrl().getFile(), "/" ); + } + else + { + return relativePath; + } + } + else + { + return getPathUrl().toExternalForm(); + } + } + + /** {@inheritDoc} */ + public String toString() + { + StringBuilder res = + new StringBuilder( ( StringUtils.isNotEmpty( relativePath ) ) ? relativePath : String.valueOf( pathUrl ) ); + res.append( " (Base: " ).append( baseUrl ).append( ") Location: " ).append( getLocation() ); + return res.toString(); + } +} diff --git a/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_118/npe.json b/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_118/npe.json new file mode 100644 index 000000000..949de8328 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_118/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/PathDescriptor.java", + "line": 119, + "npe_method": "buildBaseUrl", + "deref_field": "base", + "npe_class": "PathDescriptor" +} \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_136/buggy.java b/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_136/buggy.java new file mode 100644 index 000000000..6b42bf732 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_136/buggy.java @@ -0,0 +1,245 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; + +import org.codehaus.plexus.util.StringUtils; + +/** + * This class holds an instance of a maven path. This consists of a relative path (e.g. images/maven-logo.png) and a + * base reference which can also be a relative path (e.g. '.' or '../doxia') or an URL that is used for an absolute + * anchor. + * + * @author Henning P. Schmiedehausen + * @deprecated use {@link URIPathDescriptor} instead. + */ + +public class PathDescriptor +{ + private final URL baseUrl; + + private final URL pathUrl; + + private final String relativePath; + + /** + * Construct a PathDescriptor from a path. + * + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final String path ) + throws MalformedURLException + { + this( (URL) null, path ); + } + + /** + * Construct a PathDescriptor from a path and a base. + * + * @param base a base reference. + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final String base, final String path ) + throws MalformedURLException + { + this( PathDescriptor.buildBaseUrl( base ), path ); + } + + /** + * Construct a PathDescriptor from a path and a base. + * + * @param baseUrl a base reference. + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final URL baseUrl, final String path ) + throws MalformedURLException + { + this.baseUrl = baseUrl; + + URL pathURL = null; + String relPath = null; + + try + { + pathURL = new URL( path ); + } + catch ( MalformedURLException e ) + { + try + { + pathURL = buildUrl( baseUrl, path ); + } + catch ( MalformedURLException e2 ) + { + // If we got an absolute path passed in and end here, then the path + // is converted to relative because we have no reference URL anyway + // to which it has been anchored. + if ( path != null && path.startsWith( "/" ) ) + { + relPath = path.substring( 1 ); + } + else + { + relPath = path; + } + } + } + + this.pathUrl = pathURL; + this.relativePath = relPath; + } + + private static URL buildBaseUrl( final String base ) + throws MalformedURLException + { + if ( base == null ) + { + return null; + } + + try + { + return new URL( base ); + } + catch ( MalformedURLException e ) + { + return new File( base ).toURI().toURL(); + } + } + +private static java.net.URL buildUrl(final java.net.URL baseUrl, final java.lang.String path) throws java.net.MalformedURLException { + { + if (path == null) { + return /* NPEX_NULL_EXP */ + baseUrl; + } + if (baseUrl.getProtocol().equals("file")) { + return new java.io.File(baseUrl.getFile(), path).toURI().toURL(); + } + if (path.startsWith("/") && baseUrl.getPath().endsWith("/")) { + return new java.net.URL(baseUrl, path.substring(1)); + } + return new java.net.URL(baseUrl, path); + } +} + + /** + * Check if this PathDescriptor describes a file. + * + * @return true for file, false otherwise. + */ + public boolean isFile() + { + return isRelative() || pathUrl.getProtocol().equals( "file" ); + } + + /** + * Check if this PathDescriptor describes a relative path. + * + * @return true if {@link #getPathUrl()} returns null. + */ + public boolean isRelative() + { + return pathUrl == null; + } + + /** + * Get the base URL. + * + * @return the base URL. + */ + public URL getBaseUrl() + { + return baseUrl; + } + + /** + * Get the path as a URL. + * + * @return the path as a URL. + */ + public URL getPathUrl() + { + return pathUrl; + } + + /** + * Get the path. + * + * @return the path. + */ + public String getPath() + { + if ( getPathUrl() != null ) + { + if ( isFile() ) + { + return StringUtils.stripEnd( getPathUrl().getPath(), "/" ); + } + else + { + return getPathUrl().getPath(); + } + } + else + { + return relativePath; + } + } + + /** + * Get the location for files. + * + * @return the location. + */ + public String getLocation() + { + if ( isFile() ) + { + if ( getPathUrl() != null ) + { + return StringUtils.stripEnd( getPathUrl().getFile(), "/" ); + } + else + { + return relativePath; + } + } + else + { + return getPathUrl().toExternalForm(); + } + } + + /** {@inheritDoc} */ + public String toString() + { + StringBuilder res = + new StringBuilder( ( StringUtils.isNotEmpty( relativePath ) ) ? relativePath : String.valueOf( pathUrl ) ); + res.append( " (Base: " ).append( baseUrl ).append( ") Location: " ).append( getLocation() ); + return res.toString(); + } +} diff --git a/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_136/npe.json b/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_136/npe.json new file mode 100644 index 000000000..03eb52e74 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_136/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/PathDescriptor.java", + "line": 137, + "npe_method": "buildUrl", + "deref_field": "baseUrl", + "npe_class": "PathDescriptor" +} \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_141/buggy.java b/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_141/buggy.java new file mode 100644 index 000000000..0d15abb59 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_141/buggy.java @@ -0,0 +1,245 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; + +import org.codehaus.plexus.util.StringUtils; + +/** + * This class holds an instance of a maven path. This consists of a relative path (e.g. images/maven-logo.png) and a + * base reference which can also be a relative path (e.g. '.' or '../doxia') or an URL that is used for an absolute + * anchor. + * + * @author Henning P. Schmiedehausen + * @deprecated use {@link URIPathDescriptor} instead. + */ + +public class PathDescriptor +{ + private final URL baseUrl; + + private final URL pathUrl; + + private final String relativePath; + + /** + * Construct a PathDescriptor from a path. + * + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final String path ) + throws MalformedURLException + { + this( (URL) null, path ); + } + + /** + * Construct a PathDescriptor from a path and a base. + * + * @param base a base reference. + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final String base, final String path ) + throws MalformedURLException + { + this( PathDescriptor.buildBaseUrl( base ), path ); + } + + /** + * Construct a PathDescriptor from a path and a base. + * + * @param baseUrl a base reference. + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final URL baseUrl, final String path ) + throws MalformedURLException + { + this.baseUrl = baseUrl; + + URL pathURL = null; + String relPath = null; + + try + { + pathURL = new URL( path ); + } + catch ( MalformedURLException e ) + { + try + { + pathURL = buildUrl( baseUrl, path ); + } + catch ( MalformedURLException e2 ) + { + // If we got an absolute path passed in and end here, then the path + // is converted to relative because we have no reference URL anyway + // to which it has been anchored. + if ( path != null && path.startsWith( "/" ) ) + { + relPath = path.substring( 1 ); + } + else + { + relPath = path; + } + } + } + + this.pathUrl = pathURL; + this.relativePath = relPath; + } + + private static URL buildBaseUrl( final String base ) + throws MalformedURLException + { + if ( base == null ) + { + return null; + } + + try + { + return new URL( base ); + } + catch ( MalformedURLException e ) + { + return new File( base ).toURI().toURL(); + } + } + +private static java.net.URL buildUrl(final java.net.URL baseUrl, final java.lang.String path) throws java.net.MalformedURLException { + if (baseUrl == null) { + throw new java.net.MalformedURLException("Base is null!"); + } + { + if (baseUrl.getProtocol().equals("file")) { + return new java.io.File(baseUrl.getFile(), /* NPEX_NULL_EXP */ + path).toURI().toURL(); + } + if (path.startsWith("/") && baseUrl.getPath().endsWith("/")) { + return new java.net.URL(baseUrl, path.substring(1)); + } + return new java.net.URL(baseUrl, path); + } +} + + /** + * Check if this PathDescriptor describes a file. + * + * @return true for file, false otherwise. + */ + public boolean isFile() + { + return isRelative() || pathUrl.getProtocol().equals( "file" ); + } + + /** + * Check if this PathDescriptor describes a relative path. + * + * @return true if {@link #getPathUrl()} returns null. + */ + public boolean isRelative() + { + return pathUrl == null; + } + + /** + * Get the base URL. + * + * @return the base URL. + */ + public URL getBaseUrl() + { + return baseUrl; + } + + /** + * Get the path as a URL. + * + * @return the path as a URL. + */ + public URL getPathUrl() + { + return pathUrl; + } + + /** + * Get the path. + * + * @return the path. + */ + public String getPath() + { + if ( getPathUrl() != null ) + { + if ( isFile() ) + { + return StringUtils.stripEnd( getPathUrl().getPath(), "/" ); + } + else + { + return getPathUrl().getPath(); + } + } + else + { + return relativePath; + } + } + + /** + * Get the location for files. + * + * @return the location. + */ + public String getLocation() + { + if ( isFile() ) + { + if ( getPathUrl() != null ) + { + return StringUtils.stripEnd( getPathUrl().getFile(), "/" ); + } + else + { + return relativePath; + } + } + else + { + return getPathUrl().toExternalForm(); + } + } + + /** {@inheritDoc} */ + public String toString() + { + StringBuilder res = + new StringBuilder( ( StringUtils.isNotEmpty( relativePath ) ) ? relativePath : String.valueOf( pathUrl ) ); + res.append( " (Base: " ).append( baseUrl ).append( ") Location: " ).append( getLocation() ); + return res.toString(); + } +} diff --git a/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_141/npe.json b/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_141/npe.json new file mode 100644 index 000000000..5026743d7 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_141/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/PathDescriptor.java", + "line": 140, + "npe_method": "buildUrl", + "deref_field": "path", + "npe_class": "PathDescriptor" +} \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_206/buggy.java b/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_206/buggy.java new file mode 100644 index 000000000..dc33280df --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_206/buggy.java @@ -0,0 +1,252 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; + +import org.codehaus.plexus.util.StringUtils; + +/** + * This class holds an instance of a maven path. This consists of a relative path (e.g. images/maven-logo.png) and a + * base reference which can also be a relative path (e.g. '.' or '../doxia') or an URL that is used for an absolute + * anchor. + * + * @author Henning P. Schmiedehausen + * @deprecated use {@link URIPathDescriptor} instead. + */ + +public class PathDescriptor +{ + private final URL baseUrl; + + private final URL pathUrl; + + private final String relativePath; + + /** + * Construct a PathDescriptor from a path. + * + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final String path ) + throws MalformedURLException + { + this( (URL) null, path ); + } + + /** + * Construct a PathDescriptor from a path and a base. + * + * @param base a base reference. + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final String base, final String path ) + throws MalformedURLException + { + this( PathDescriptor.buildBaseUrl( base ), path ); + } + + /** + * Construct a PathDescriptor from a path and a base. + * + * @param baseUrl a base reference. + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final URL baseUrl, final String path ) + throws MalformedURLException + { + this.baseUrl = baseUrl; + + URL pathURL = null; + String relPath = null; + + try + { + pathURL = new URL( path ); + } + catch ( MalformedURLException e ) + { + try + { + pathURL = buildUrl( baseUrl, path ); + } + catch ( MalformedURLException e2 ) + { + // If we got an absolute path passed in and end here, then the path + // is converted to relative because we have no reference URL anyway + // to which it has been anchored. + if ( path != null && path.startsWith( "/" ) ) + { + relPath = path.substring( 1 ); + } + else + { + relPath = path; + } + } + } + + this.pathUrl = pathURL; + this.relativePath = relPath; + } + + private static URL buildBaseUrl( final String base ) + throws MalformedURLException + { + if ( base == null ) + { + return null; + } + + try + { + return new URL( base ); + } + catch ( MalformedURLException e ) + { + return new File( base ).toURI().toURL(); + } + } + + private static URL buildUrl( final URL baseUrl, final String path ) + throws MalformedURLException + { + if ( baseUrl == null ) + { + throw new MalformedURLException( "Base is null!" ); + } + + if ( path == null ) + { + return baseUrl; + } + + if ( baseUrl.getProtocol().equals( "file" ) ) + { + return new File( baseUrl.getFile(), path ).toURI().toURL(); + } + + if ( path.startsWith( "/" ) && baseUrl.getPath().endsWith( "/" ) ) + { + return new URL( baseUrl, path.substring( 1 ) ); + } + + return new URL( baseUrl, path ); + } + + /** + * Check if this PathDescriptor describes a file. + * + * @return true for file, false otherwise. + */ + public boolean isFile() + { + return isRelative() || pathUrl.getProtocol().equals( "file" ); + } + + /** + * Check if this PathDescriptor describes a relative path. + * + * @return true if {@link #getPathUrl()} returns null. + */ + public boolean isRelative() + { + return pathUrl == null; + } + + /** + * Get the base URL. + * + * @return the base URL. + */ + public URL getBaseUrl() + { + return baseUrl; + } + + /** + * Get the path as a URL. + * + * @return the path as a URL. + */ + public URL getPathUrl() + { + return pathUrl; + } + + /** + * Get the path. + * + * @return the path. + */ +/** + * Get the path. + * + * @return the path. + */ +public java.lang.String getPath() { + { + if (isFile()) { + return org.codehaus.plexus.util.StringUtils.stripEnd(/* NPEX_NULL_EXP */ + getPathUrl().getPath(), "/"); + } else { + return getPathUrl().getPath(); + } + } +} + + /** + * Get the location for files. + * + * @return the location. + */ + public String getLocation() + { + if ( isFile() ) + { + if ( getPathUrl() != null ) + { + return StringUtils.stripEnd( getPathUrl().getFile(), "/" ); + } + else + { + return relativePath; + } + } + else + { + return getPathUrl().toExternalForm(); + } + } + + /** {@inheritDoc} */ + public String toString() + { + StringBuilder res = + new StringBuilder( ( StringUtils.isNotEmpty( relativePath ) ) ? relativePath : String.valueOf( pathUrl ) ); + res.append( " (Base: " ).append( baseUrl ).append( ") Location: " ).append( getLocation() ); + return res.toString(); + } +} diff --git a/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_206/npe.json b/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_206/npe.json new file mode 100644 index 000000000..44d7ca90e --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_206/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/PathDescriptor.java", + "line": 213, + "npe_method": "getPath", + "deref_field": "getPathUrl", + "npe_class": "PathDescriptor" +} \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_232/buggy.java b/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_232/buggy.java new file mode 100644 index 000000000..b941fdd6d --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_232/buggy.java @@ -0,0 +1,252 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; + +import org.codehaus.plexus.util.StringUtils; + +/** + * This class holds an instance of a maven path. This consists of a relative path (e.g. images/maven-logo.png) and a + * base reference which can also be a relative path (e.g. '.' or '../doxia') or an URL that is used for an absolute + * anchor. + * + * @author Henning P. Schmiedehausen + * @deprecated use {@link URIPathDescriptor} instead. + */ + +public class PathDescriptor +{ + private final URL baseUrl; + + private final URL pathUrl; + + private final String relativePath; + + /** + * Construct a PathDescriptor from a path. + * + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final String path ) + throws MalformedURLException + { + this( (URL) null, path ); + } + + /** + * Construct a PathDescriptor from a path and a base. + * + * @param base a base reference. + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final String base, final String path ) + throws MalformedURLException + { + this( PathDescriptor.buildBaseUrl( base ), path ); + } + + /** + * Construct a PathDescriptor from a path and a base. + * + * @param baseUrl a base reference. + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final URL baseUrl, final String path ) + throws MalformedURLException + { + this.baseUrl = baseUrl; + + URL pathURL = null; + String relPath = null; + + try + { + pathURL = new URL( path ); + } + catch ( MalformedURLException e ) + { + try + { + pathURL = buildUrl( baseUrl, path ); + } + catch ( MalformedURLException e2 ) + { + // If we got an absolute path passed in and end here, then the path + // is converted to relative because we have no reference URL anyway + // to which it has been anchored. + if ( path != null && path.startsWith( "/" ) ) + { + relPath = path.substring( 1 ); + } + else + { + relPath = path; + } + } + } + + this.pathUrl = pathURL; + this.relativePath = relPath; + } + + private static URL buildBaseUrl( final String base ) + throws MalformedURLException + { + if ( base == null ) + { + return null; + } + + try + { + return new URL( base ); + } + catch ( MalformedURLException e ) + { + return new File( base ).toURI().toURL(); + } + } + + private static URL buildUrl( final URL baseUrl, final String path ) + throws MalformedURLException + { + if ( baseUrl == null ) + { + throw new MalformedURLException( "Base is null!" ); + } + + if ( path == null ) + { + return baseUrl; + } + + if ( baseUrl.getProtocol().equals( "file" ) ) + { + return new File( baseUrl.getFile(), path ).toURI().toURL(); + } + + if ( path.startsWith( "/" ) && baseUrl.getPath().endsWith( "/" ) ) + { + return new URL( baseUrl, path.substring( 1 ) ); + } + + return new URL( baseUrl, path ); + } + + /** + * Check if this PathDescriptor describes a file. + * + * @return true for file, false otherwise. + */ + public boolean isFile() + { + return isRelative() || pathUrl.getProtocol().equals( "file" ); + } + + /** + * Check if this PathDescriptor describes a relative path. + * + * @return true if {@link #getPathUrl()} returns null. + */ + public boolean isRelative() + { + return pathUrl == null; + } + + /** + * Get the base URL. + * + * @return the base URL. + */ + public URL getBaseUrl() + { + return baseUrl; + } + + /** + * Get the path as a URL. + * + * @return the path as a URL. + */ + public URL getPathUrl() + { + return pathUrl; + } + + /** + * Get the path. + * + * @return the path. + */ + public String getPath() + { + if ( getPathUrl() != null ) + { + if ( isFile() ) + { + return StringUtils.stripEnd( getPathUrl().getPath(), "/" ); + } + else + { + return getPathUrl().getPath(); + } + } + else + { + return relativePath; + } + } + + /** + * Get the location for files. + * + * @return the location. + */ +/** + * Get the location for files. + * + * @return the location. + */ +public java.lang.String getLocation() { + if (isFile()) { + { + return org.codehaus.plexus.util.StringUtils.stripEnd(/* NPEX_NULL_EXP */ + getPathUrl().getFile(), "/"); + } + } else { + return getPathUrl().toExternalForm(); + } +} + + /** {@inheritDoc} */ + public String toString() + { + StringBuilder res = + new StringBuilder( ( StringUtils.isNotEmpty( relativePath ) ) ? relativePath : String.valueOf( pathUrl ) ); + res.append( " (Base: " ).append( baseUrl ).append( ") Location: " ).append( getLocation() ); + return res.toString(); + } +} diff --git a/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_232/npe.json b/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_232/npe.json new file mode 100644 index 000000000..79a9f5cc1 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/PathDescriptor_232/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/PathDescriptor.java", + "line": 237, + "npe_method": "getLocation", + "deref_field": "getPathUrl", + "npe_class": "PathDescriptor" +} \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/bugs/PathUtils_57/buggy.java b/Java-base/maven-doxia-sitetools/bugs/PathUtils_57/buggy.java new file mode 100644 index 000000000..718484ff7 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/PathUtils_57/buggy.java @@ -0,0 +1,151 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * 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.net.MalformedURLException; +import java.net.URL; + +import org.codehaus.plexus.util.PathTool; + +/** + * Utilities that allow conversion of old and new pathes and URLs relative to each other. + * + * @author Brett Porter + * @author Henning P. Schmiedehausen + * @deprecated this only operates on deprecated classes, it is not used anymore. + */ +public abstract class PathUtils +{ + /** + * Private constructor. + */ + private PathUtils() + { + // do not instantiate + } + + /** + *

convertPath.

+ * + * @param oldPath not null + * @param newPath not null + * @return a PathDescriptor converted by the new path + * @throws java.net.MalformedURLException if any + */ +/** + *

convertPath.

+ * + * @param oldPath + * not null + * @param newPath + * not null + * @return a PathDescriptor converted by the new path + * @throws java.net.MalformedURLException + * if any + */ +public static final org.apache.maven.doxia.site.decoration.inheritance.PathDescriptor convertPath(final org.apache.maven.doxia.site.decoration.inheritance.PathDescriptor oldPath, final org.apache.maven.doxia.site.decoration.inheritance.PathDescriptor newPath) throws java.net.MalformedURLException { + java.lang.String relative = org.apache.maven.doxia.site.decoration.inheritance.PathUtils.getRelativePath(oldPath, newPath); + { + return new org.apache.maven.doxia.site.decoration.inheritance.PathDescriptor(/* NPEX_NULL_EXP */ + relative); + } +} + + /** + *

getRelativePath.

+ * + * @param oldPathDescriptor not null + * @param newPathDescriptor not null + * @return a relative path depending if PathDescriptor is a file or a web url. + * @see PathTool#getRelativeFilePath(String, String) + * @see PathTool#getRelativeWebPath(String, String) + */ + public static final String getRelativePath( final PathDescriptor oldPathDescriptor, + final PathDescriptor newPathDescriptor ) + { + // Cannot convert from URL to file. + if ( oldPathDescriptor.isFile() ) + { + if ( !newPathDescriptor.isFile() ) + { + // We want to convert from a file to an URL. This is normally not possible... + if ( oldPathDescriptor.isRelative() ) + { + // unless the old path is a relative path. Then we might convert an existing + // site into a new URL using resolvePaths()... + return oldPathDescriptor.getPath(); + } + + // The old path is not relative. Bail out. + return null; + } + else + { + // both are files, if either of them is relative, bail out + // see DOXIASITETOOLS-29, MSITE-404, PLXUTILS-116 + if ( oldPathDescriptor.isRelative() || newPathDescriptor.isRelative() ) + { + return null; + } + } + } + + // Don't optimize to else. This might also be old.isFile && new.isFile ... + if ( !oldPathDescriptor.isFile() ) + { + // URLs, determine if they share protocol and domain info + URL oldUrl = oldPathDescriptor.getPathUrl(); + URL newUrl = newPathDescriptor.getPathUrl(); + + if ( oldUrl == null || newUrl == null ) + { + // One of the sites has a strange URL. no relative path possible, bail out. + return null; + } + + if ( ( newUrl.getProtocol().equalsIgnoreCase( oldUrl.getProtocol() ) ) + && ( newUrl.getHost().equalsIgnoreCase( oldUrl.getHost() ) ) + && ( newUrl.getPort() == oldUrl.getPort() ) ) + { + // Both paths point to the same site. So we can use relative paths. + + String oldPath = oldPathDescriptor.getPath(); + String newPath = newPathDescriptor.getPath(); + + return PathTool.getRelativeWebPath( newPath, oldPath ); + } + + // Different sites. No relative Path possible. + return null; + } + + // Both Descriptors point to an absolute path. We can build a relative path. + String oldPath = oldPathDescriptor.getPath(); + String newPath = newPathDescriptor.getPath(); + + if ( oldPath == null || newPath == null ) + { + // One of the sites has a strange URL. no relative path possible, bail out. + return null; + } + + return PathTool.getRelativeFilePath( oldPath, newPath ); + } +} diff --git a/Java-base/maven-doxia-sitetools/bugs/PathUtils_57/npe.json b/Java-base/maven-doxia-sitetools/bugs/PathUtils_57/npe.json new file mode 100644 index 000000000..20dc3fb9b --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/PathUtils_57/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/PathUtils.java", + "line": 67, + "npe_method": "convertPath", + "deref_field": "relative", + "npe_class": "PathUtils" +} \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/bugs/SiteRendererSink_269/buggy.java b/Java-base/maven-doxia-sitetools/bugs/SiteRendererSink_269/buggy.java new file mode 100644 index 000000000..e3fb4826b --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/SiteRendererSink_269/buggy.java @@ -0,0 +1,354 @@ +package org.apache.maven.doxia.siterenderer.sink; + +/* + * 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.StringWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.swing.text.html.HTML.Attribute; + +import org.apache.maven.doxia.markup.HtmlMarkup; +import org.apache.maven.doxia.module.xhtml5.Xhtml5Sink; +import org.apache.maven.doxia.sink.Sink; +import org.apache.maven.doxia.sink.SinkEventAttributes; +import org.apache.maven.doxia.siterenderer.DocumentContent; +import org.apache.maven.doxia.siterenderer.RenderingContext; +import org.apache.maven.doxia.util.HtmlTools; +import org.codehaus.plexus.util.StringUtils; + +/** + * Sink for site rendering of a document, to allow later merge document's output with a template. + * During raw Doxia rendering, content is stored in multiple fields for later use when incorporating + * into skin or template: title, date, authors, head, body + * + * @author Emmanuel Venisse + */ +@SuppressWarnings( "checkstyle:methodname" ) +public class SiteRendererSink + extends Xhtml5Sink + implements Sink, org.codehaus.doxia.sink.Sink, DocumentContent +{ + private String date = ""; + + private String title = ""; + + private List authors = new ArrayList(); + + private final StringWriter headWriter; + + private StringBuilder sectionTitleBuffer; + + private StringBuilder sectionTitleWriteBuffer; + + private boolean sectionHasID; + + private boolean isSectionTitle; + + private Set anchorsInSectionTitle; + + private final Writer writer; + + private RenderingContext renderingContext; + + /** + * Construct a new SiteRendererSink for a document. + * + * @param renderingContext the document's RenderingContext. + */ + public SiteRendererSink( RenderingContext renderingContext ) + { + this( new StringWriter(), renderingContext ); + } + + /** + * Construct a new SiteRendererSink for a document. + * + * @param writer the writer for the sink. + * @param renderingContext the document's RenderingContext. + */ + private SiteRendererSink( StringWriter writer, RenderingContext renderingContext ) + { + super( writer ); + + this.writer = writer; + this.headWriter = new StringWriter(); + this.renderingContext = renderingContext; + + /* the template is expected to have used the main tag, which can be used only once */ + super.contentStack.push( HtmlMarkup.MAIN ); + } + + /** {@inheritDoc} */ + @Override + public void title_() + { + if ( getTextBuffer().length() > 0 ) + { + title = getTextBuffer().toString(); + } + + resetTextBuffer(); + } + + /** + * {@inheritDoc} + * + * Reset text buffer, since text content before title mustn't be in title. + * @see org.apache.maven.doxia.module.xhtml5.Xhtml5Sink#title() + */ + @Override + public void title() + { + resetTextBuffer(); + } + + /** {@inheritDoc} */ + @Override + public void author() + { + resetTextBuffer(); + } + + /** {@inheritDoc} */ + @Override + public void author_() + { + if ( getTextBuffer().length() > 0 ) + { + String text = HtmlTools.escapeHTML( getTextBuffer().toString() ); + text = StringUtils.replace( text, "&#", "&#" ); + authors.add( text.trim() ); + } + + resetTextBuffer(); + } + + /** {@inheritDoc} */ + @Override + public void date() + { + resetTextBuffer(); + } + + /** {@inheritDoc} */ + @Override + public void date_() + { + if ( getTextBuffer().length() > 0 ) + { + date = getTextBuffer().toString().trim(); + } + + resetTextBuffer(); + } + + /** + * {@inheritDoc} + * + * Do nothing. + * @see org.apache.maven.doxia.module.xhtml5.Xhtml5Sink#body_() + */ + @Override + public void body_() + { + // nop + } + + /** + * {@inheritDoc} + * + * Do nothing. + * @see org.apache.maven.doxia.module.xhtml5.Xhtml5Sink#body() + */ + @Override + public void body() + { + // nop + } + + /** {@inheritDoc} */ + @Override + public void head_() + { + setHeadFlag( false ); + } + + /** {@inheritDoc} */ + @Override + public void head() + { + setHeadFlag( true ); + } + + /** {@inheritDoc} */ + @Override + public void anchor( String name, SinkEventAttributes attributes ) + { + super.anchor( name, attributes ); + if ( isSectionTitle ) + { + if ( anchorsInSectionTitle == null ) + { + anchorsInSectionTitle = new HashSet(); + } + anchorsInSectionTitle.add( name ); + } + } + + /** {@inheritDoc} */ + @Override + protected void onSectionTitle( int depth, SinkEventAttributes attributes ) + { + sectionHasID = ( attributes != null && attributes.isDefined ( Attribute.ID.toString() ) ); + isSectionTitle = true; + + super.onSectionTitle( depth, attributes ); + + this.sectionTitleBuffer = new StringBuilder(); + this.sectionTitleWriteBuffer = new StringBuilder(); + } + + /** {@inheritDoc} */ + @Override + protected void onSectionTitle_( int depth ) + { + String sectionTitle = sectionTitleBuffer.toString(); + this.sectionTitleBuffer = null; + String sectionWriteTitle = sectionTitleWriteBuffer.toString(); + this.sectionTitleWriteBuffer = null; + + if ( !StringUtils.isEmpty( sectionTitle ) ) + { + if ( sectionHasID ) + { + sectionHasID = false; + } + else + { + String id = HtmlTools.encodeId( sectionTitle ); + if ( ( anchorsInSectionTitle == null ) || ( !anchorsInSectionTitle.contains( id ) ) ) + { + anchor( id ); + anchor_(); + } + } + } + + super.write( sectionWriteTitle ); + + this.isSectionTitle = false; + anchorsInSectionTitle = null; + super.onSectionTitle_( depth ); + } + + /** {@inheritDoc} */ + @Override +/** + * {@inheritDoc } + */ +public void text(java.lang.String text) { + { + // this implies we're inside a section title, collect text events for anchor generation + /* NPEX_NULL_EXP */ + sectionTitleBuffer.append(text); + } + super.text(text); +} + + /** {@inheritDoc} */ + @Override + protected void write( String text ) + { + String txt = text; + + if ( isHeadFlag() ) + { + headWriter.write( unifyEOLs( txt ) ); + + return; + } + + if ( renderingContext != null ) + { + String relativePathToBasedir = renderingContext.getRelativePath(); + + if ( relativePathToBasedir == null ) + { + txt = StringUtils.replace( txt, "$relativePath", "." ); + } + else + { + txt = StringUtils.replace( txt, "$relativePath", relativePathToBasedir ); + } + } + + if ( sectionTitleWriteBuffer != null ) + { + // this implies we're inside a section title, collect text events for anchor generation + sectionTitleWriteBuffer.append( txt ); + } + else + { + super.write( txt ); + } + } + + // DocumentContent interface + + /** {@inheritDoc} */ + public String getTitle() + { + return title; + } + + /** {@inheritDoc} */ + public List getAuthors() + { + return authors; + } + + /** {@inheritDoc} */ + public String getDate() + { + return date; + } + + /** {@inheritDoc} */ + public String getBody() + { + return writer.toString(); + } + + /** {@inheritDoc} */ + public String getHead() + { + return headWriter.toString(); + } + + /** {@inheritDoc} */ + public RenderingContext getRenderingContext() + { + return renderingContext; + } +} diff --git a/Java-base/maven-doxia-sitetools/bugs/SiteRendererSink_269/npe.json b/Java-base/maven-doxia-sitetools/bugs/SiteRendererSink_269/npe.json new file mode 100644 index 000000000..a97d8b44b --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/SiteRendererSink_269/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/sink/SiteRendererSink.java", + "line": 274, + "npe_method": "text", + "deref_field": "sectionTitleBuffer", + "npe_class": "SiteRendererSink" +} \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/bugs/SiteRendererSink_305/buggy.java b/Java-base/maven-doxia-sitetools/bugs/SiteRendererSink_305/buggy.java new file mode 100644 index 000000000..0cdc44736 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/SiteRendererSink_305/buggy.java @@ -0,0 +1,341 @@ +package org.apache.maven.doxia.siterenderer.sink; + +/* + * 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.StringWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.swing.text.html.HTML.Attribute; + +import org.apache.maven.doxia.markup.HtmlMarkup; +import org.apache.maven.doxia.module.xhtml5.Xhtml5Sink; +import org.apache.maven.doxia.sink.Sink; +import org.apache.maven.doxia.sink.SinkEventAttributes; +import org.apache.maven.doxia.siterenderer.DocumentContent; +import org.apache.maven.doxia.siterenderer.RenderingContext; +import org.apache.maven.doxia.util.HtmlTools; +import org.codehaus.plexus.util.StringUtils; + +/** + * Sink for site rendering of a document, to allow later merge document's output with a template. + * During raw Doxia rendering, content is stored in multiple fields for later use when incorporating + * into skin or template: title, date, authors, head, body + * + * @author Emmanuel Venisse + */ +@SuppressWarnings( "checkstyle:methodname" ) +public class SiteRendererSink + extends Xhtml5Sink + implements Sink, org.codehaus.doxia.sink.Sink, DocumentContent +{ + private String date = ""; + + private String title = ""; + + private List authors = new ArrayList(); + + private final StringWriter headWriter; + + private StringBuilder sectionTitleBuffer; + + private StringBuilder sectionTitleWriteBuffer; + + private boolean sectionHasID; + + private boolean isSectionTitle; + + private Set anchorsInSectionTitle; + + private final Writer writer; + + private RenderingContext renderingContext; + + /** + * Construct a new SiteRendererSink for a document. + * + * @param renderingContext the document's RenderingContext. + */ + public SiteRendererSink( RenderingContext renderingContext ) + { + this( new StringWriter(), renderingContext ); + } + + /** + * Construct a new SiteRendererSink for a document. + * + * @param writer the writer for the sink. + * @param renderingContext the document's RenderingContext. + */ + private SiteRendererSink( StringWriter writer, RenderingContext renderingContext ) + { + super( writer ); + + this.writer = writer; + this.headWriter = new StringWriter(); + this.renderingContext = renderingContext; + + /* the template is expected to have used the main tag, which can be used only once */ + super.contentStack.push( HtmlMarkup.MAIN ); + } + + /** {@inheritDoc} */ + @Override + public void title_() + { + if ( getTextBuffer().length() > 0 ) + { + title = getTextBuffer().toString(); + } + + resetTextBuffer(); + } + + /** + * {@inheritDoc} + * + * Reset text buffer, since text content before title mustn't be in title. + * @see org.apache.maven.doxia.module.xhtml5.Xhtml5Sink#title() + */ + @Override + public void title() + { + resetTextBuffer(); + } + + /** {@inheritDoc} */ + @Override + public void author() + { + resetTextBuffer(); + } + + /** {@inheritDoc} */ + @Override + public void author_() + { + if ( getTextBuffer().length() > 0 ) + { + String text = HtmlTools.escapeHTML( getTextBuffer().toString() ); + text = StringUtils.replace( text, "&#", "&#" ); + authors.add( text.trim() ); + } + + resetTextBuffer(); + } + + /** {@inheritDoc} */ + @Override + public void date() + { + resetTextBuffer(); + } + + /** {@inheritDoc} */ + @Override + public void date_() + { + if ( getTextBuffer().length() > 0 ) + { + date = getTextBuffer().toString().trim(); + } + + resetTextBuffer(); + } + + /** + * {@inheritDoc} + * + * Do nothing. + * @see org.apache.maven.doxia.module.xhtml5.Xhtml5Sink#body_() + */ + @Override + public void body_() + { + // nop + } + + /** + * {@inheritDoc} + * + * Do nothing. + * @see org.apache.maven.doxia.module.xhtml5.Xhtml5Sink#body() + */ + @Override + public void body() + { + // nop + } + + /** {@inheritDoc} */ + @Override + public void head_() + { + setHeadFlag( false ); + } + + /** {@inheritDoc} */ + @Override + public void head() + { + setHeadFlag( true ); + } + + /** {@inheritDoc} */ + @Override + public void anchor( String name, SinkEventAttributes attributes ) + { + super.anchor( name, attributes ); + if ( isSectionTitle ) + { + if ( anchorsInSectionTitle == null ) + { + anchorsInSectionTitle = new HashSet(); + } + anchorsInSectionTitle.add( name ); + } + } + + /** {@inheritDoc} */ + @Override + protected void onSectionTitle( int depth, SinkEventAttributes attributes ) + { + sectionHasID = ( attributes != null && attributes.isDefined ( Attribute.ID.toString() ) ); + isSectionTitle = true; + + super.onSectionTitle( depth, attributes ); + + this.sectionTitleBuffer = new StringBuilder(); + this.sectionTitleWriteBuffer = new StringBuilder(); + } + + /** {@inheritDoc} */ + @Override + protected void onSectionTitle_( int depth ) + { + String sectionTitle = sectionTitleBuffer.toString(); + this.sectionTitleBuffer = null; + String sectionWriteTitle = sectionTitleWriteBuffer.toString(); + this.sectionTitleWriteBuffer = null; + + if ( !StringUtils.isEmpty( sectionTitle ) ) + { + if ( sectionHasID ) + { + sectionHasID = false; + } + else + { + String id = HtmlTools.encodeId( sectionTitle ); + if ( ( anchorsInSectionTitle == null ) || ( !anchorsInSectionTitle.contains( id ) ) ) + { + anchor( id ); + anchor_(); + } + } + } + + super.write( sectionWriteTitle ); + + this.isSectionTitle = false; + anchorsInSectionTitle = null; + super.onSectionTitle_( depth ); + } + + /** {@inheritDoc} */ + @Override + public void text( String text ) + { + if ( sectionTitleBuffer != null ) + { + // this implies we're inside a section title, collect text events for anchor generation + sectionTitleBuffer.append( text ); + } + + super.text( text ); + } + + /** {@inheritDoc} */ + @Override +/** + * {@inheritDoc } + */ +protected void write(java.lang.String text) { + java.lang.String txt = text; + if (isHeadFlag()) { + headWriter.write(org.apache.maven.doxia.sink.impl.AbstractSink.unifyEOLs(txt)); + return; + } + if (renderingContext != null) { + java.lang.String relativePathToBasedir = renderingContext.getRelativePath(); + if (relativePathToBasedir == null) { + txt = org.codehaus.plexus.util.StringUtils.replace(txt, "$relativePath", "."); + } else { + txt = org.codehaus.plexus.util.StringUtils.replace(txt, "$relativePath", relativePathToBasedir); + } + } + { + // this implies we're inside a section title, collect text events for anchor generation + /* NPEX_NULL_EXP */ + sectionTitleWriteBuffer.append(txt); + } +} + + // DocumentContent interface + + /** {@inheritDoc} */ + public String getTitle() + { + return title; + } + + /** {@inheritDoc} */ + public List getAuthors() + { + return authors; + } + + /** {@inheritDoc} */ + public String getDate() + { + return date; + } + + /** {@inheritDoc} */ + public String getBody() + { + return writer.toString(); + } + + /** {@inheritDoc} */ + public String getHead() + { + return headWriter.toString(); + } + + /** {@inheritDoc} */ + public RenderingContext getRenderingContext() + { + return renderingContext; + } +} diff --git a/Java-base/maven-doxia-sitetools/bugs/SiteRendererSink_305/npe.json b/Java-base/maven-doxia-sitetools/bugs/SiteRendererSink_305/npe.json new file mode 100644 index 000000000..67b09b048 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/SiteRendererSink_305/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/sink/SiteRendererSink.java", + "line": 300, + "npe_method": "write", + "deref_field": "sectionTitleWriteBuffer", + "npe_class": "SiteRendererSink" +} \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/bugs/URIPathDescriptor_166/buggy.java b/Java-base/maven-doxia-sitetools/bugs/URIPathDescriptor_166/buggy.java new file mode 100644 index 000000000..bfbad3cf4 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/URIPathDescriptor_166/buggy.java @@ -0,0 +1,267 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * 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.net.URI; +import java.net.URISyntaxException; + +import org.codehaus.plexus.util.PathTool; + +/** + * Describes a link that may be absolute or relative, and that is anchored to an absolute URI. + * + * @author ltheussl + * + * @since 1.2 + */ +public class URIPathDescriptor +{ + private final URI baseURI; + private final URI link; + + /** + * A URIPathDescriptor consists of a base URI and a link. + * Both arguments to this constructor have to be parsable to URIs. + * The baseURI parameter has to be absolute in the sense of {@link URI#isAbsolute()}. + * + * Before being parsed to {@link URI}s, the arguments are modified to catch + * some common bad practices: first all Windows-style backslashes '\' are replaced by + * forward slashes '/'. + * If the baseURI does not end with '/', a slash is appended. + * If the link starts with a '/', the first character is stripped. + * + * @param baseURI The base URI. Has to be a valid absolute URI. + * In addition, the path of the URI should not have any file part, + * ie http://maven.apache.org/ is valid, + * http://maven.apache.org/index.html is not. + * Even though the latter form is accepted without warning, + * the methods in this class will not return what is probably expected, + * because a slash is appended during construction, as noted above. + * @param link the link. This may be a relative link or an absolute link. + * Note that URIs that start with a "/", ie don't specify a scheme, are considered relative. + * + * @throws IllegalArgumentException if either argument is not parsable as a URI, + * or if baseURI is not absolute. + */ + public URIPathDescriptor( final String baseURI, final String link ) + { + final String llink = sanitizeLink( link ); + final String bbase = sanitizeBase( baseURI ); + + this.baseURI = URI.create( bbase ).normalize(); + this.link = URI.create( llink ).normalize(); + + if ( !this.baseURI.isAbsolute() ) + { + throw new IllegalArgumentException( "Base URI is not absolute: " + baseURI ); + } + } + + /** + * Return the base of this URIPathDescriptor as a URI. + * This is always {@link URI#normalize() normalized}. + * + * @return the normalized base URI. + */ + public URI getBaseURI() + { + return baseURI; + } + + /** + * Return the link of this URIPathDescriptor as a URI. + * This is always {@link URI#normalize() normalized}. + * + * @return the normalized link URI. + */ + public URI getLink() + { + return link; + } + + /** + * Resolve the link to the base. + * This always returns an absolute URI. If link is absolute, link is returned. + * + * @return the resolved link. This is equivalent to calling + * {@link #getBaseURI()}.{@link URI#resolve(java.net.URI) resolve}( {@link #getLink()} ). + */ + public URI resolveLink() + { + return baseURI.resolve( link ); + } + + /** + * Calculate the relative link with respect to the base. + * The original link is returned if either + * link is relative; + * or link and base do not share the {@link #sameSite(java.net.URI) same site}. + * + * @return the link as a relative URI. + */ + public URI relativizeLink() + { + return relativizeLink( baseURI.toString(), link ); + } + + // NOTE: URI.relativize does not work as expected, see + // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6226081 + private static URI relativizeLink( final String base, final URI link ) + { + if ( !link.isAbsolute() ) + { + return link; + } + + final URI newBaseURI = URI.create( base ); + + if ( !sameSite( link, newBaseURI ) ) + { + return link; + } + + final String relativePath = PathTool.getRelativeWebPath( newBaseURI.toString(), link.toString() ); + + return URI.create( correctRelativePath( relativePath ) ); + } + + /** + * Calculate the link as viewed from a different base. + * This returns the original link if link is absolute. + * This returns {@link #resolveLink()} if either + * newBase == null, + * or newBase is not parsable as a URI, + * or newBase and this {@link #getBaseURI()} do not share the + * {@link #sameSite(java.net.URI) same site}. + * + * @param newBase the new base URI. Has to be parsable as a URI. + *. + * @return a new relative link or the original link {@link #resolveLink() resolved}, + * i.e. as an absolute link, if the link cannot be re-based. + */ +/** + * Calculate the link as viewed from a different base. + * This returns the original link if link is absolute. + * This returns {@link #resolveLink()} if either + * newBase == null, + * or newBase is not parsable as a URI, + * or newBase and this {@link #getBaseURI()} do not share the + * {@link #sameSite(java.net.URI) same site}. + * + * @param newBase + * the new base URI. Has to be parsable as a URI. + * . + * @return a new relative link or the original link {@link #resolveLink() resolved}, +i.e. as an absolute link, if the link cannot be re-based. + */ +public java.net.URI rebaseLink(final java.lang.String newBase) { + if (link.isAbsolute()) { + return link; + } + { + final java.net.URI newBaseURI; + try { + newBaseURI = new java.net.URI(/* NPEX_NULL_EXP */ + newBase); + } catch (java.net.URISyntaxException ex) { + return resolveLink(); + } + if (!sameSite(newBaseURI)) { + return resolveLink(); + } + final java.lang.String relativeBasePath = org.codehaus.plexus.util.PathTool.getRelativeWebPath(newBaseURI.getPath(), baseURI.getPath()); + return java.net.URI.create(org.apache.maven.doxia.site.decoration.inheritance.URIPathDescriptor.correctRelativePath(relativeBasePath)).resolve(link); + } +} + + private static String correctRelativePath( final String relativePath ) + { + if ( "".equals( relativePath ) || "/".equals( relativePath ) ) + { + return "./"; + } + else + { + return relativePath; + } + } + + /** + * Check if this URIPathDescriptor lives on the same site as the given URI. + * + * @param uri a URI to compare with. + * May be null, in which case false is returned. + * + * @return true if {@link #getBaseURI()} shares the same scheme, host and port with the given URI + * where null values are allowed. + */ + public boolean sameSite( final URI uri ) + { + return ( uri != null ) && sameSite( this.baseURI, uri ); + } + + private static boolean sameSite( final URI baseURI, final URI newBaseURI ) + { + final boolean sameScheme = + ( newBaseURI.getScheme() == null ? false : baseURI.getScheme().equalsIgnoreCase( newBaseURI.getScheme() ) ); + final boolean sameHost = + ( baseURI.getHost() == null ? newBaseURI.getHost() == null + : baseURI.getHost().equalsIgnoreCase( newBaseURI.getHost() ) ); + final boolean samePort = ( baseURI.getPort() == newBaseURI.getPort() ); + + return ( sameScheme && samePort && sameHost ); + } + + /** + * Construct a string representation of this URIPathDescriptor. + * This is equivalent to calling {@link #resolveLink()}.toString(). + * + * @return this URIPathDescriptor as a String. + */ + @Override + public String toString() + { + return resolveLink().toString(); + } + + private static String sanitizeBase( final String base ) + { + String sane = base.replace( '\\', '/' ); + + if ( !sane.endsWith( "/" ) ) + { + sane += "/"; + } + + return sane; + } + + private static String sanitizeLink( final String link ) + { + String sane = link.replace( '\\', '/' ); + + if ( sane.startsWith( "/" ) ) + { + sane = sane.substring( 1 ); + } + + return sane; + } +} diff --git a/Java-base/maven-doxia-sitetools/bugs/URIPathDescriptor_166/npe.json b/Java-base/maven-doxia-sitetools/bugs/URIPathDescriptor_166/npe.json new file mode 100644 index 000000000..ef09d5e34 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/URIPathDescriptor_166/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/URIPathDescriptor.java", + "line": 182, + "npe_method": "rebaseLink", + "deref_field": "newBase", + "npe_class": "URIPathDescriptor" +} \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/bugs/URIPathDescriptor_222/buggy.java b/Java-base/maven-doxia-sitetools/bugs/URIPathDescriptor_222/buggy.java new file mode 100644 index 000000000..63e69fb2c --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/URIPathDescriptor_222/buggy.java @@ -0,0 +1,261 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * 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.net.URI; +import java.net.URISyntaxException; + +import org.codehaus.plexus.util.PathTool; + +/** + * Describes a link that may be absolute or relative, and that is anchored to an absolute URI. + * + * @author ltheussl + * + * @since 1.2 + */ +public class URIPathDescriptor +{ + private final URI baseURI; + private final URI link; + + /** + * A URIPathDescriptor consists of a base URI and a link. + * Both arguments to this constructor have to be parsable to URIs. + * The baseURI parameter has to be absolute in the sense of {@link URI#isAbsolute()}. + * + * Before being parsed to {@link URI}s, the arguments are modified to catch + * some common bad practices: first all Windows-style backslashes '\' are replaced by + * forward slashes '/'. + * If the baseURI does not end with '/', a slash is appended. + * If the link starts with a '/', the first character is stripped. + * + * @param baseURI The base URI. Has to be a valid absolute URI. + * In addition, the path of the URI should not have any file part, + * ie http://maven.apache.org/ is valid, + * http://maven.apache.org/index.html is not. + * Even though the latter form is accepted without warning, + * the methods in this class will not return what is probably expected, + * because a slash is appended during construction, as noted above. + * @param link the link. This may be a relative link or an absolute link. + * Note that URIs that start with a "/", ie don't specify a scheme, are considered relative. + * + * @throws IllegalArgumentException if either argument is not parsable as a URI, + * or if baseURI is not absolute. + */ + public URIPathDescriptor( final String baseURI, final String link ) + { + final String llink = sanitizeLink( link ); + final String bbase = sanitizeBase( baseURI ); + + this.baseURI = URI.create( bbase ).normalize(); + this.link = URI.create( llink ).normalize(); + + if ( !this.baseURI.isAbsolute() ) + { + throw new IllegalArgumentException( "Base URI is not absolute: " + baseURI ); + } + } + + /** + * Return the base of this URIPathDescriptor as a URI. + * This is always {@link URI#normalize() normalized}. + * + * @return the normalized base URI. + */ + public URI getBaseURI() + { + return baseURI; + } + + /** + * Return the link of this URIPathDescriptor as a URI. + * This is always {@link URI#normalize() normalized}. + * + * @return the normalized link URI. + */ + public URI getLink() + { + return link; + } + + /** + * Resolve the link to the base. + * This always returns an absolute URI. If link is absolute, link is returned. + * + * @return the resolved link. This is equivalent to calling + * {@link #getBaseURI()}.{@link URI#resolve(java.net.URI) resolve}( {@link #getLink()} ). + */ + public URI resolveLink() + { + return baseURI.resolve( link ); + } + + /** + * Calculate the relative link with respect to the base. + * The original link is returned if either + * link is relative; + * or link and base do not share the {@link #sameSite(java.net.URI) same site}. + * + * @return the link as a relative URI. + */ + public URI relativizeLink() + { + return relativizeLink( baseURI.toString(), link ); + } + + // NOTE: URI.relativize does not work as expected, see + // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6226081 + private static URI relativizeLink( final String base, final URI link ) + { + if ( !link.isAbsolute() ) + { + return link; + } + + final URI newBaseURI = URI.create( base ); + + if ( !sameSite( link, newBaseURI ) ) + { + return link; + } + + final String relativePath = PathTool.getRelativeWebPath( newBaseURI.toString(), link.toString() ); + + return URI.create( correctRelativePath( relativePath ) ); + } + + /** + * Calculate the link as viewed from a different base. + * This returns the original link if link is absolute. + * This returns {@link #resolveLink()} if either + * newBase == null, + * or newBase is not parsable as a URI, + * or newBase and this {@link #getBaseURI()} do not share the + * {@link #sameSite(java.net.URI) same site}. + * + * @param newBase the new base URI. Has to be parsable as a URI. + *. + * @return a new relative link or the original link {@link #resolveLink() resolved}, + * i.e. as an absolute link, if the link cannot be re-based. + */ + public URI rebaseLink( final String newBase ) + { + if ( link.isAbsolute() ) + { + return link; + } + + if ( newBase == null ) + { + return resolveLink(); + } + + final URI newBaseURI; + + try + { + newBaseURI = new URI( newBase ); + } + catch ( URISyntaxException ex ) + { + return resolveLink(); + } + + if ( !sameSite( newBaseURI ) ) + { + return resolveLink(); + } + + final String relativeBasePath = PathTool.getRelativeWebPath( newBaseURI.getPath(), baseURI.getPath() ); + + return URI.create( correctRelativePath( relativeBasePath ) ).resolve( link ); + } + + private static String correctRelativePath( final String relativePath ) + { + if ( "".equals( relativePath ) || "/".equals( relativePath ) ) + { + return "./"; + } + else + { + return relativePath; + } + } + + /** + * Check if this URIPathDescriptor lives on the same site as the given URI. + * + * @param uri a URI to compare with. + * May be null, in which case false is returned. + * + * @return true if {@link #getBaseURI()} shares the same scheme, host and port with the given URI + * where null values are allowed. + */ + public boolean sameSite( final URI uri ) + { + return ( uri != null ) && sameSite( this.baseURI, uri ); + } + +private static boolean sameSite(final java.net.URI baseURI, final java.net.URI newBaseURI) { + final boolean sameScheme = (newBaseURI.getScheme() == null) ? false : baseURI.getScheme().equalsIgnoreCase(newBaseURI.getScheme()); + final boolean sameHost = /* NPEX_NULL_EXP */ + baseURI.getHost().equalsIgnoreCase(newBaseURI.getHost()); + final boolean samePort = baseURI.getPort() == newBaseURI.getPort(); + return (sameScheme && samePort) && sameHost; +} + + /** + * Construct a string representation of this URIPathDescriptor. + * This is equivalent to calling {@link #resolveLink()}.toString(). + * + * @return this URIPathDescriptor as a String. + */ + @Override + public String toString() + { + return resolveLink().toString(); + } + + private static String sanitizeBase( final String base ) + { + String sane = base.replace( '\\', '/' ); + + if ( !sane.endsWith( "/" ) ) + { + sane += "/"; + } + + return sane; + } + + private static String sanitizeLink( final String link ) + { + String sane = link.replace( '\\', '/' ); + + if ( sane.startsWith( "/" ) ) + { + sane = sane.substring( 1 ); + } + + return sane; + } +} diff --git a/Java-base/maven-doxia-sitetools/bugs/URIPathDescriptor_222/npe.json b/Java-base/maven-doxia-sitetools/bugs/URIPathDescriptor_222/npe.json new file mode 100644 index 000000000..8db35eb63 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/bugs/URIPathDescriptor_222/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/URIPathDescriptor.java", + "line": 221, + "npe_method": "sameSite", + "deref_field": "getHost", + "npe_class": "URIPathDescriptor" +} \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/Jenkinsfile b/Java-base/maven-doxia-sitetools/src/Jenkinsfile new file mode 100644 index 000000000..09ac70f12 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/Jenkinsfile @@ -0,0 +1,20 @@ +/** + * 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. + */ + +asfMavenTlpStdBuild() diff --git a/Java-base/maven-doxia-sitetools/src/README.md b/Java-base/maven-doxia-sitetools/src/README.md new file mode 100644 index 000000000..bc0034dca --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/README.md @@ -0,0 +1,99 @@ + +Contributing to [Apache Maven Doxia Sitetools](https://maven.apache.org/doxia/doxia-sitetools/) +====================== + +[![ASF Jira](https://img.shields.io/endpoint?url=https%3A%2F%2Fmaven.apache.org%2Fbadges%2Fasf_jira-DOXIASITETOOLS.json)][jira] +[![Apache License, Version 2.0, January 2004](https://img.shields.io/github/license/apache/maven.svg?label=License)][license] +[![Maven Central](https://img.shields.io/maven-central/v/org.apache.maven.doxia/doxia-sitetools.svg?label=Maven%20Central)](https://search.maven.org/artifact/org.apache.maven.doxia/doxia-sitetools) +[![Jenkins Status](https://img.shields.io/jenkins/s/https/builds.apache.org/job/maven-box/job/maven-doxia-sitetools/job/master.svg)][build] +[![Jenkins tests](https://img.shields.io/jenkins/t/https/builds.apache.org/job/maven-box/job/maven-doxia-sitetools/job/master.svg)][test-results] + + +You have found a bug or you have an idea for a cool new feature? Contributing +code is a great way to give something back to the open source community. Before +you dig right into the code, there are a few guidelines that we need +contributors to follow so that we can have a chance of keeping on top of +things. + +Getting Started +--------------- + ++ Make sure you have a [JIRA account](https://issues.apache.org/jira/). ++ Make sure you have a [GitHub account](https://github.com/signup/free). ++ If you're planning to implement a new feature, it makes sense to discuss your changes + on the [dev list][ml-list] first. + This way you can make sure you're not wasting your time on something that isn't + considered to be in Apache Maven's scope. ++ Submit a ticket for your issue, assuming one does not already exist. + + Clearly describe the issue, including steps to reproduce when it is a bug. + + Make sure you fill in the earliest version that you know has the issue. ++ Fork the repository on GitHub. + +Making and Submitting Changes +-------------- + +We accept Pull Requests via GitHub. The [developer mailing list][ml-list] is the +main channel of communication for contributors. +There are some guidelines which will make applying PRs easier for us: ++ Create a topic branch from where you want to base your work (this is usually the master branch). + Push your changes to a topic branch in your fork of the repository. ++ Make commits of logical units. ++ Respect the original code style: by using the same [codestyle][code-style], + patches should only highlight the actual difference, not being disturbed by any formatting issues: + + Only use spaces for indentation. + + Create minimal diffs - disable on save actions like reformat source code or organize imports. + If you feel the source code should be reformatted, create a separate PR for this change. + + Check for unnecessary whitespace with `git diff --check` before committing. ++ Make sure your commit messages are in the proper format. Your commit message should contain the key of the JIRA issue. +``` +[DOXIASITETOOLS-XXX] - Subject of the JIRA Ticket + Optional supplemental description. +``` ++ Make sure you have added the necessary tests (JUnit/IT) for your changes. ++ Run all the tests with `mvn -Prun-its verify` to assure nothing else was accidentally broken. ++ Submit a pull request to the repository in the Apache organization. ++ Update your JIRA ticket and include a link to the pull request in the ticket. + +If you plan to contribute on a regular basis, please consider filing a [contributor license agreement][cla]. + +Making Trivial Changes +---------------------- + +For changes of a trivial nature to comments and documentation, it is not always +necessary to create a new ticket in JIRA. In this case, it is appropriate to +start the first line of a commit with '(doc)' instead of a ticket number. + +Additional Resources +-------------------- + ++ [Contributing patches](https://maven.apache.org/guides/development/guide-maven-development.html#Creating_and_submitting_a_patch) ++ [Apache Maven Doxia Sitetools JIRA project page][jira] ++ [Contributor License Agreement][cla] ++ [General GitHub documentation](https://help.github.com/) ++ [GitHub pull request documentation](https://help.github.com/send-pull-requests/) ++ [Apache Maven Twitter Account](https://twitter.com/ASFMavenProject) ++ #Maven IRC channel on freenode.org + +[jira]: https://issues.apache.org/jira/projects/DOXIASITETOOLS/ +[license]: https://www.apache.org/licenses/LICENSE-2.0 +[ml-list]: https://maven.apache.org/mailing-lists.html +[code-style]: https://maven.apache.org/developers/conventions/code.html +[cla]: https://www.apache.org/licenses/#clas +[maven-wiki]: https://cwiki.apache.org/confluence/display/MAVEN/Index +[test-results]: https://builds.apache.org/job/maven-box/job/maven-doxia-sitetools/job/master/lastCompletedBuild/testReport/ +[build]: https://builds.apache.org/job/maven-box/job/maven-doxia-sitetools/job/master/ diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/pom.xml b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/pom.xml new file mode 100644 index 000000000..600b1a700 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/pom.xml @@ -0,0 +1,98 @@ + + + + + + 4.0.0 + + + org.apache.maven.doxia + doxia-sitetools + 1.9.3-SNAPSHOT + ../pom.xml + + + doxia-decoration-model + + Doxia Sitetools :: Decoration Model + The Decoration Model handles the decoration descriptor for sites, also known as site.xml. + + + + org.codehaus.plexus + plexus-component-annotations + + + org.codehaus.plexus + plexus-utils + + + junit + junit + test + + + + + + + org.codehaus.modello + modello-maven-plugin + + + src/main/mdo/decoration.mdo + + + 1.8.0 + 1.0.0 + + + + descriptor + generate-sources + + xpp3-writer + java + xpp3-reader + xsd + + + + descriptor-xdoc + pre-site + + xdoc + + + + descriptor-xsd + pre-site + + xsd + + + ${project.build.directory}/generated-site/resources/xsd + + + + + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/DecorationUtils.java b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/DecorationUtils.java new file mode 100644 index 000000000..20dd732e7 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/DecorationUtils.java @@ -0,0 +1,115 @@ +package org.apache.maven.doxia.site.decoration; + +/* + * 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 org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.util.xml.Xpp3Dom; + +/** + * Decoration model utilities. + * + * @since 1.7 + */ +public class DecorationUtils +{ + public static boolean isLink( String href ) + { + return StringUtils.isNotBlank( href ) + && ( startsWithAnyIgnoreCase( href, "http:/", "https:/", "ftp:/", "mailto:", "file:/" ) + || href.contains( "://" ) ); + } + + private static boolean startsWithIgnoreCase( String str, String prefix ) + { + if ( str == null || prefix == null ) + { + return ( str == null && prefix == null ); + } + if ( prefix.length() > str.length() ) + { + return false; + } + return str.regionMatches( true, 0, prefix, 0, prefix.length() ); + } + + public static boolean startsWithAnyIgnoreCase( String string, String... searchStrings ) + { + for ( int i = 0; i < searchStrings.length; i++ ) + { + String searchString = searchStrings[i]; + if ( startsWithIgnoreCase( string, searchString ) ) + { + return true; + } + } + return false; + } + + /** + * Helper to get decoration custom DOM element by simply specifying a dotted path. + * + * @param custom the custom DOM element + * @param path the dotted path to the child + * @return null if any element in the path does not exist + * @since 1.8 + */ + public static Xpp3Dom getCustomChild( Xpp3Dom custom, String path ) + { + String[] elements = path.split( "\\." ); + for ( String element : elements ) + { + if ( custom == null ) + { + return null; + } + custom = custom.getChild( element ); + } + return custom; + } + + /** + * Helper to get decoration custom DOM element value by simply specifying a dotted path. + * + * @param custom the custom DOM element + * @param path the dotted path to the child + * @return the element value or null if any element in the path does not exist + * @since 1.8 + */ + public static String getCustomValue( Xpp3Dom custom, String path ) + { + custom = getCustomChild( custom, path ); + return ( custom == null ) ? null : custom.getValue(); + } + + /** + * Helper to get decoration custom DOM element value by simply specifying a dotted path. + * + * @param custom the custom DOM element + * @param path the dotted path to the child + * @param defaultValue default value + * @return the element value or the default value if any element in the path does not exist + * @since 1.8 + */ + public static String getCustomValue( Xpp3Dom custom, String path, String defaultValue ) + { + custom = getCustomChild( custom, path ); + return ( custom == null ) ? defaultValue : custom.getValue(); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/DecorationModelInheritanceAssembler.java b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/DecorationModelInheritanceAssembler.java new file mode 100644 index 000000000..52ebdce0c --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/DecorationModelInheritanceAssembler.java @@ -0,0 +1,72 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * 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 org.apache.maven.doxia.site.decoration.DecorationModel; + +/** + * Manage inheritance of the decoration model. + * + * @author Brett Porter + */ +public interface DecorationModelInheritanceAssembler +{ + /** Plexus lookup role. */ + String ROLE = DecorationModelInheritanceAssembler.class.getName(); + + /** + * Manage inheritance of the decoration model between a parent and child. + * + * Any relative links in the parent model will be re-based to work from the merged child + * model, otherwise no content from either the parent or child model should be modified. + * + * @param name a name, used for breadcrumb. + * If the parent model contains breadcrumbs and the child doesn't, + * a child breadcrumb will be added to the merged model with this name. Not null. + * @param child the child DecorationModel to be merged with parent. + * Not null. If parent == null, the child is unchanged, otherwise + * child will contain the merged model upon exit. + * @param parent the parent DecorationModel. Unchanged upon exit. + * May be null in which case the child is not changed. + * @param childBaseUrl the child base URL. + * May be null, in which case relative links inherited from the parent + * will not be resolved in the merged child. + * @param parentBaseUrl the parent base URL. + * May be null, in which case relative links inherited from the parent + * will not be resolved in the merged child. + */ + void assembleModelInheritance( String name, DecorationModel child, DecorationModel parent, + String childBaseUrl, String parentBaseUrl ); + + /** + * Resolve relative paths for a DecorationModel given a base URL. + * + * Note that 'resolve' here means 'relativize' in the sense of + * {@link java.net.URI#relativize(java.net.URI)}, ie if any link in the decoration model + * has a base URL that is equal to the given baseUrl, it is replaced by a relative link + * with respect to that base. + * + * @param decoration the DecorationModel. + * Not null. + * @param baseUrl the base URL. + * May be null in which case the decoration model is unchanged. + */ + void resolvePaths( DecorationModel decoration, String baseUrl ); +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/DefaultDecorationModelInheritanceAssembler.java b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/DefaultDecorationModelInheritanceAssembler.java new file mode 100644 index 000000000..fb4195b83 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/DefaultDecorationModelInheritanceAssembler.java @@ -0,0 +1,469 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.util.ArrayList; +import java.util.List; + +import org.apache.maven.doxia.site.decoration.Banner; +import org.apache.maven.doxia.site.decoration.Body; +import org.apache.maven.doxia.site.decoration.DecorationModel; +import org.apache.maven.doxia.site.decoration.LinkItem; +import org.apache.maven.doxia.site.decoration.Logo; +import org.apache.maven.doxia.site.decoration.Menu; +import org.apache.maven.doxia.site.decoration.MenuItem; + +import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.util.xml.Xpp3Dom; + +/** + * Manage inheritance of the decoration model. + * + * @author Brett Porter + * @author Henning P. Schmiedehausen + */ +@Component( role = DecorationModelInheritanceAssembler.class ) +public class DefaultDecorationModelInheritanceAssembler + implements DecorationModelInheritanceAssembler +{ + /** {@inheritDoc} */ + public void assembleModelInheritance( String name, DecorationModel child, DecorationModel parent, + String childBaseUrl, String parentBaseUrl ) + { + if ( parent == null || !child.isMergeParent() ) + { + return; + } + + child.setCombineSelf( parent.getCombineSelf() ); + + URLRebaser urlContainer = new URLRebaser( parentBaseUrl, childBaseUrl ); + + if ( child.getBannerLeft() == null && parent.getBannerLeft() != null ) + { + child.setBannerLeft( parent.getBannerLeft().clone() ); + rebaseBannerPaths( child.getBannerLeft(), urlContainer ); + } + + if ( child.getBannerRight() == null && parent.getBannerRight() != null ) + { + child.setBannerRight( parent.getBannerRight().clone() ); + rebaseBannerPaths( child.getBannerRight(), urlContainer ); + } + + if ( child.isDefaultPublishDate() && parent.getPublishDate() != null ) + { + child.setPublishDate( parent.getPublishDate().clone() ); + } + + if ( child.isDefaultVersion() && parent.getVersion() != null ) + { + child.setVersion( parent.getVersion().clone() ); + } + + if ( child.getEdit() == null && parent.getEdit() != null ) + { + child.setEdit( parent.getEdit() ); + } + + if ( child.getSkin() == null && parent.getSkin() != null ) + { + child.setSkin( parent.getSkin().clone() ); + } + + child.setPoweredBy( mergePoweredByLists( child.getPoweredBy(), parent.getPoweredBy(), urlContainer ) ); + + if ( parent.getLastModified() > child.getLastModified() ) + { + child.setLastModified( parent.getLastModified() ); + } + + if ( child.getGoogleAdSenseClient() == null && parent.getGoogleAdSenseClient() != null ) + { + child.setGoogleAdSenseClient( parent.getGoogleAdSenseClient() ); + } + + if ( child.getGoogleAdSenseSlot() == null && parent.getGoogleAdSenseSlot() != null ) + { + child.setGoogleAdSenseSlot( parent.getGoogleAdSenseSlot() ); + } + + if ( child.getGoogleAnalyticsAccountId() == null && parent.getGoogleAnalyticsAccountId() != null ) + { + child.setGoogleAnalyticsAccountId( parent.getGoogleAnalyticsAccountId() ); + } + + assembleBodyInheritance( name, child, parent, urlContainer ); + + assembleCustomInheritance( child, parent ); + } + + /** {@inheritDoc} */ + public void resolvePaths( final DecorationModel decoration, final String baseUrl ) + { + if ( baseUrl == null ) + { + return; + } + + if ( decoration.getBannerLeft() != null ) + { + relativizeBannerPaths( decoration.getBannerLeft(), baseUrl ); + } + + if ( decoration.getBannerRight() != null ) + { + relativizeBannerPaths( decoration.getBannerRight(), baseUrl ); + } + + for ( Logo logo : decoration.getPoweredBy() ) + { + relativizeLogoPaths( logo, baseUrl ); + } + + if ( decoration.getBody() != null ) + { + for ( LinkItem linkItem : decoration.getBody().getLinks() ) + { + relativizeLinkItemPaths( linkItem, baseUrl ); + } + + for ( LinkItem linkItem : decoration.getBody().getBreadcrumbs() ) + { + relativizeLinkItemPaths( linkItem, baseUrl ); + } + + for ( Menu menu : decoration.getBody().getMenus() ) + { + relativizeMenuPaths( menu.getItems(), baseUrl ); + } + } + } + + /** + * Resolves all relative paths between the elements in a banner. The banner element might contain relative paths + * to the oldBaseUrl, these are changed to the newBannerUrl. + * + * @param banner + * @param baseUrl + */ + private void relativizeBannerPaths( final Banner banner, final String baseUrl ) + { + // banner has been checked to be not null, both href and src may be empty or null + banner.setHref( relativizeLink( banner.getHref(), baseUrl ) ); + banner.setSrc( relativizeLink( banner.getSrc(), baseUrl ) ); + } + + private void rebaseBannerPaths( final Banner banner, final URLRebaser urlContainer ) + { + if ( banner.getHref() != null ) // it may be empty + { + banner.setHref( urlContainer.rebaseLink( banner.getHref() ) ); + } + + if ( banner.getSrc() != null ) + { + banner.setSrc( urlContainer.rebaseLink( banner.getSrc() ) ); + } + } + + private void assembleCustomInheritance( final DecorationModel child, final DecorationModel parent ) + { + if ( child.getCustom() == null ) + { + child.setCustom( parent.getCustom() ); + } + else + { + child.setCustom( Xpp3Dom.mergeXpp3Dom( (Xpp3Dom) child.getCustom(), (Xpp3Dom) parent.getCustom() ) ); + } + } + + private void assembleBodyInheritance( final String name, final DecorationModel child, final DecorationModel parent, + final URLRebaser urlContainer ) + { + Body cBody = child.getBody(); + Body pBody = parent.getBody(); + + if ( cBody != null || pBody != null ) + { + if ( cBody == null ) + { + cBody = new Body(); + child.setBody( cBody ); + } + + if ( pBody == null ) + { + pBody = new Body(); + } + + if ( cBody.getHead() == null && pBody.getHead() != null ) + { + cBody.setHead( pBody.getHead() ); + } + + cBody.setLinks( mergeLinkItemLists( cBody.getLinks(), pBody.getLinks(), urlContainer, false ) ); + + if ( cBody.getBreadcrumbs().isEmpty() && !pBody.getBreadcrumbs().isEmpty() ) + { + LinkItem breadcrumb = new LinkItem(); + breadcrumb.setName( name ); + breadcrumb.setHref( "index.html" ); + cBody.getBreadcrumbs().add( breadcrumb ); + } + cBody.setBreadcrumbs( mergeLinkItemLists( cBody.getBreadcrumbs(), pBody.getBreadcrumbs(), urlContainer, + true ) ); + + cBody.setMenus( mergeMenus( cBody.getMenus(), pBody.getMenus(), urlContainer ) ); + + if ( cBody.getFooter() == null && pBody.getFooter() != null ) + { + cBody.setFooter( pBody.getFooter() ); + } + } + } + + private List
mergeMenus( final List childMenus, final List parentMenus, + final URLRebaser urlContainer ) + { + List menus = new ArrayList( childMenus.size() + parentMenus.size() ); + + for ( Menu menu : childMenus ) + { + menus.add( menu ); + } + + int topCounter = 0; + for ( Menu menu : parentMenus ) + { + if ( "top".equals( menu.getInherit() ) ) + { + final Menu clone = menu.clone(); + + rebaseMenuPaths( clone.getItems(), urlContainer ); + + menus.add( topCounter, clone ); + topCounter++; + } + else if ( "bottom".equals( menu.getInherit() ) ) + { + final Menu clone = menu.clone(); + + rebaseMenuPaths( clone.getItems(), urlContainer ); + + menus.add( clone ); + } + } + + return menus; + } + + private void relativizeMenuPaths( final List items, final String baseUrl ) + { + for ( MenuItem item : items ) + { + relativizeLinkItemPaths( item, baseUrl ); + relativizeMenuPaths( item.getItems(), baseUrl ); + } + } + + private void rebaseMenuPaths( final List items, final URLRebaser urlContainer ) + { + for ( MenuItem item : items ) + { + rebaseLinkItemPaths( item, urlContainer ); + rebaseMenuPaths( item.getItems(), urlContainer ); + } + } + + private void relativizeLinkItemPaths( final LinkItem item, final String baseUrl ) + { + item.setHref( relativizeLink( item.getHref(), baseUrl ) ); + } + + private void rebaseLinkItemPaths( final LinkItem item, final URLRebaser urlContainer ) + { + item.setHref( urlContainer.rebaseLink( item.getHref() ) ); + } + + private void relativizeLogoPaths( final Logo logo, final String baseUrl ) + { + logo.setImg( relativizeLink( logo.getImg(), baseUrl ) ); + relativizeLinkItemPaths( logo, baseUrl ); + } + + private void rebaseLogoPaths( final Logo logo, final URLRebaser urlContainer ) + { + logo.setImg( urlContainer.rebaseLink( logo.getImg() ) ); + rebaseLinkItemPaths( logo, urlContainer ); + } + + private List mergeLinkItemLists( final List childList, final List parentList, + final URLRebaser urlContainer, boolean cutParentAfterDuplicate ) + { + List items = new ArrayList( childList.size() + parentList.size() ); + + for ( LinkItem item : parentList ) + { + if ( !items.contains( item ) && !childList.contains( item ) ) + { + final LinkItem clone = item.clone(); + + rebaseLinkItemPaths( clone, urlContainer ); + + items.add( clone ); + } + else if ( cutParentAfterDuplicate ) + { + // if a parent item is found in child, ignore next items (case for breadcrumbs) + // merge ( "B > E", "A > B > C > D" ) -> "A > B > E" (notice missing "C > D") + // see https://issues.apache.org/jira/browse/DOXIASITETOOLS-62 + break; + } + } + + for ( LinkItem item : childList ) + { + if ( !items.contains( item ) ) + { + items.add( item ); + } + } + + return items; + } + + private List mergePoweredByLists( final List childList, final List parentList, + final URLRebaser urlContainer ) + { + List logos = new ArrayList( 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/src/doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/PathDescriptor.java b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/PathDescriptor.java new file mode 100644 index 000000000..6c5f55076 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/PathDescriptor.java @@ -0,0 +1,255 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; + +import org.codehaus.plexus.util.StringUtils; + +/** + * This class holds an instance of a maven path. This consists of a relative path (e.g. images/maven-logo.png) and a + * base reference which can also be a relative path (e.g. '.' or '../doxia') or an URL that is used for an absolute + * anchor. + * + * @author Henning P. Schmiedehausen + * @deprecated use {@link URIPathDescriptor} instead. + */ + +public class PathDescriptor +{ + private final URL baseUrl; + + private final URL pathUrl; + + private final String relativePath; + + /** + * Construct a PathDescriptor from a path. + * + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final String path ) + throws MalformedURLException + { + this( (URL) null, path ); + } + + /** + * Construct a PathDescriptor from a path and a base. + * + * @param base a base reference. + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final String base, final String path ) + throws MalformedURLException + { + this( PathDescriptor.buildBaseUrl( base ), path ); + } + + /** + * Construct a PathDescriptor from a path and a base. + * + * @param baseUrl a base reference. + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final URL baseUrl, final String path ) + throws MalformedURLException + { + this.baseUrl = baseUrl; + + URL pathURL = null; + String relPath = null; + + try + { + pathURL = new URL( path ); + } + catch ( MalformedURLException e ) + { + try + { + pathURL = buildUrl( baseUrl, path ); + } + catch ( MalformedURLException e2 ) + { + // If we got an absolute path passed in and end here, then the path + // is converted to relative because we have no reference URL anyway + // to which it has been anchored. + if ( path != null && path.startsWith( "/" ) ) + { + relPath = path.substring( 1 ); + } + else + { + relPath = path; + } + } + } + + this.pathUrl = pathURL; + this.relativePath = relPath; + } + + private static URL buildBaseUrl( final String base ) + throws MalformedURLException + { + if ( base == null ) + { + return null; + } + + try + { + return new URL( base ); + } + catch ( MalformedURLException e ) + { + return new File( base ).toURI().toURL(); + } + } + + private static URL buildUrl( final URL baseUrl, final String path ) + throws MalformedURLException + { + if ( baseUrl == null ) + { + throw new MalformedURLException( "Base is null!" ); + } + + if ( path == null ) + { + return baseUrl; + } + + if ( baseUrl.getProtocol().equals( "file" ) ) + { + return new File( baseUrl.getFile(), path ).toURI().toURL(); + } + + if ( path.startsWith( "/" ) && baseUrl.getPath().endsWith( "/" ) ) + { + return new URL( baseUrl, path.substring( 1 ) ); + } + + return new URL( baseUrl, path ); + } + + /** + * Check if this PathDescriptor describes a file. + * + * @return true for file, false otherwise. + */ + public boolean isFile() + { + return isRelative() || pathUrl.getProtocol().equals( "file" ); + } + + /** + * Check if this PathDescriptor describes a relative path. + * + * @return true if {@link #getPathUrl()} returns null. + */ + public boolean isRelative() + { + return pathUrl == null; + } + + /** + * Get the base URL. + * + * @return the base URL. + */ + public URL getBaseUrl() + { + return baseUrl; + } + + /** + * Get the path as a URL. + * + * @return the path as a URL. + */ + public URL getPathUrl() + { + return pathUrl; + } + + /** + * Get the path. + * + * @return the path. + */ + public String getPath() + { + if ( getPathUrl() != null ) + { + if ( isFile() ) + { + return StringUtils.stripEnd( getPathUrl().getPath(), "/" ); + } + else + { + return getPathUrl().getPath(); + } + } + else + { + return relativePath; + } + } + + /** + * Get the location for files. + * + * @return the location. + */ + public String getLocation() + { + if ( isFile() ) + { + if ( getPathUrl() != null ) + { + return StringUtils.stripEnd( getPathUrl().getFile(), "/" ); + } + else + { + return relativePath; + } + } + else + { + return getPathUrl().toExternalForm(); + } + } + + /** {@inheritDoc} */ + public String toString() + { + StringBuilder res = + new StringBuilder( ( StringUtils.isNotEmpty( relativePath ) ) ? relativePath : String.valueOf( pathUrl ) ); + res.append( " (Base: " ).append( baseUrl ).append( ") Location: " ).append( getLocation() ); + return res.toString(); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/PathUtils.java b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/PathUtils.java new file mode 100644 index 000000000..fad4afbab --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/PathUtils.java @@ -0,0 +1,145 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * 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.net.MalformedURLException; +import java.net.URL; + +import org.codehaus.plexus.util.PathTool; + +/** + * Utilities that allow conversion of old and new pathes and URLs relative to each other. + * + * @author Brett Porter + * @author Henning P. Schmiedehausen + * @deprecated this only operates on deprecated classes, it is not used anymore. + */ +public abstract class PathUtils +{ + /** + * Private constructor. + */ + private PathUtils() + { + // do not instantiate + } + + /** + *

convertPath.

+ * + * @param oldPath not null + * @param newPath not null + * @return a PathDescriptor converted by the new path + * @throws java.net.MalformedURLException if any + */ + public static final PathDescriptor convertPath( final PathDescriptor oldPath, final PathDescriptor newPath ) + throws MalformedURLException + { + String relative = getRelativePath( oldPath, newPath ); + + if ( relative == null ) + { + return oldPath; + } + + return new PathDescriptor( relative ); + } + + /** + *

getRelativePath.

+ * + * @param oldPathDescriptor not null + * @param newPathDescriptor not null + * @return a relative path depending if PathDescriptor is a file or a web url. + * @see PathTool#getRelativeFilePath(String, String) + * @see PathTool#getRelativeWebPath(String, String) + */ + public static final String getRelativePath( final PathDescriptor oldPathDescriptor, + final PathDescriptor newPathDescriptor ) + { + // Cannot convert from URL to file. + if ( oldPathDescriptor.isFile() ) + { + if ( !newPathDescriptor.isFile() ) + { + // We want to convert from a file to an URL. This is normally not possible... + if ( oldPathDescriptor.isRelative() ) + { + // unless the old path is a relative path. Then we might convert an existing + // site into a new URL using resolvePaths()... + return oldPathDescriptor.getPath(); + } + + // The old path is not relative. Bail out. + return null; + } + else + { + // both are files, if either of them is relative, bail out + // see DOXIASITETOOLS-29, MSITE-404, PLXUTILS-116 + if ( oldPathDescriptor.isRelative() || newPathDescriptor.isRelative() ) + { + return null; + } + } + } + + // Don't optimize to else. This might also be old.isFile && new.isFile ... + if ( !oldPathDescriptor.isFile() ) + { + // URLs, determine if they share protocol and domain info + URL oldUrl = oldPathDescriptor.getPathUrl(); + URL newUrl = newPathDescriptor.getPathUrl(); + + if ( oldUrl == null || newUrl == null ) + { + // One of the sites has a strange URL. no relative path possible, bail out. + return null; + } + + if ( ( newUrl.getProtocol().equalsIgnoreCase( oldUrl.getProtocol() ) ) + && ( newUrl.getHost().equalsIgnoreCase( oldUrl.getHost() ) ) + && ( newUrl.getPort() == oldUrl.getPort() ) ) + { + // Both paths point to the same site. So we can use relative paths. + + String oldPath = oldPathDescriptor.getPath(); + String newPath = newPathDescriptor.getPath(); + + return PathTool.getRelativeWebPath( newPath, oldPath ); + } + + // Different sites. No relative Path possible. + return null; + } + + // Both Descriptors point to an absolute path. We can build a relative path. + String oldPath = oldPathDescriptor.getPath(); + String newPath = newPathDescriptor.getPath(); + + if ( oldPath == null || newPath == null ) + { + // One of the sites has a strange URL. no relative path possible, bail out. + return null; + } + + return PathTool.getRelativeFilePath( oldPath, newPath ); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/URIPathDescriptor.java b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/URIPathDescriptor.java new file mode 100644 index 000000000..1bf75a52c --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/URIPathDescriptor.java @@ -0,0 +1,265 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * 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.net.URI; +import java.net.URISyntaxException; + +import org.codehaus.plexus.util.PathTool; + +/** + * Describes a link that may be absolute or relative, and that is anchored to an absolute URI. + * + * @author ltheussl + * + * @since 1.2 + */ +public class URIPathDescriptor +{ + private final URI baseURI; + private final URI link; + + /** + * A URIPathDescriptor consists of a base URI and a link. + * Both arguments to this constructor have to be parsable to URIs. + * The baseURI parameter has to be absolute in the sense of {@link URI#isAbsolute()}. + * + * Before being parsed to {@link URI}s, the arguments are modified to catch + * some common bad practices: first all Windows-style backslashes '\' are replaced by + * forward slashes '/'. + * If the baseURI does not end with '/', a slash is appended. + * If the link starts with a '/', the first character is stripped. + * + * @param baseURI The base URI. Has to be a valid absolute URI. + * In addition, the path of the URI should not have any file part, + * ie http://maven.apache.org/ is valid, + * http://maven.apache.org/index.html is not. + * Even though the latter form is accepted without warning, + * the methods in this class will not return what is probably expected, + * because a slash is appended during construction, as noted above. + * @param link the link. This may be a relative link or an absolute link. + * Note that URIs that start with a "/", ie don't specify a scheme, are considered relative. + * + * @throws IllegalArgumentException if either argument is not parsable as a URI, + * or if baseURI is not absolute. + */ + public URIPathDescriptor( final String baseURI, final String link ) + { + final String llink = sanitizeLink( link ); + final String bbase = sanitizeBase( baseURI ); + + this.baseURI = URI.create( bbase ).normalize(); + this.link = URI.create( llink ).normalize(); + + if ( !this.baseURI.isAbsolute() ) + { + throw new IllegalArgumentException( "Base URI is not absolute: " + baseURI ); + } + } + + /** + * Return the base of this URIPathDescriptor as a URI. + * This is always {@link URI#normalize() normalized}. + * + * @return the normalized base URI. + */ + public URI getBaseURI() + { + return baseURI; + } + + /** + * Return the link of this URIPathDescriptor as a URI. + * This is always {@link URI#normalize() normalized}. + * + * @return the normalized link URI. + */ + public URI getLink() + { + return link; + } + + /** + * Resolve the link to the base. + * This always returns an absolute URI. If link is absolute, link is returned. + * + * @return the resolved link. This is equivalent to calling + * {@link #getBaseURI()}.{@link URI#resolve(java.net.URI) resolve}( {@link #getLink()} ). + */ + public URI resolveLink() + { + return baseURI.resolve( link ); + } + + /** + * Calculate the relative link with respect to the base. + * The original link is returned if either + * link is relative; + * or link and base do not share the {@link #sameSite(java.net.URI) same site}. + * + * @return the link as a relative URI. + */ + public URI relativizeLink() + { + return relativizeLink( baseURI.toString(), link ); + } + + // NOTE: URI.relativize does not work as expected, see + // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6226081 + private static URI relativizeLink( final String base, final URI link ) + { + if ( !link.isAbsolute() ) + { + return link; + } + + final URI newBaseURI = URI.create( base ); + + if ( !sameSite( link, newBaseURI ) ) + { + return link; + } + + final String relativePath = PathTool.getRelativeWebPath( newBaseURI.toString(), link.toString() ); + + return URI.create( correctRelativePath( relativePath ) ); + } + + /** + * Calculate the link as viewed from a different base. + * This returns the original link if link is absolute. + * This returns {@link #resolveLink()} if either + * newBase == null, + * or newBase is not parsable as a URI, + * or newBase and this {@link #getBaseURI()} do not share the + * {@link #sameSite(java.net.URI) same site}. + * + * @param newBase the new base URI. Has to be parsable as a URI. + *. + * @return a new relative link or the original link {@link #resolveLink() resolved}, + * i.e. as an absolute link, if the link cannot be re-based. + */ + public URI rebaseLink( final String newBase ) + { + if ( link.isAbsolute() ) + { + return link; + } + + if ( newBase == null ) + { + return resolveLink(); + } + + final URI newBaseURI; + + try + { + newBaseURI = new URI( newBase ); + } + catch ( URISyntaxException ex ) + { + return resolveLink(); + } + + if ( !sameSite( newBaseURI ) ) + { + return resolveLink(); + } + + final String relativeBasePath = PathTool.getRelativeWebPath( newBaseURI.getPath(), baseURI.getPath() ); + + return URI.create( correctRelativePath( relativeBasePath ) ).resolve( link ); + } + + private static String correctRelativePath( final String relativePath ) + { + if ( "".equals( relativePath ) || "/".equals( relativePath ) ) + { + return "./"; + } + else + { + return relativePath; + } + } + + /** + * Check if this URIPathDescriptor lives on the same site as the given URI. + * + * @param uri a URI to compare with. + * May be null, in which case false is returned. + * + * @return true if {@link #getBaseURI()} shares the same scheme, host and port with the given URI + * where null values are allowed. + */ + public boolean sameSite( final URI uri ) + { + return ( uri != null ) && sameSite( this.baseURI, uri ); + } + + private static boolean sameSite( final URI baseURI, final URI newBaseURI ) + { + final boolean sameScheme = + ( newBaseURI.getScheme() == null ? false : baseURI.getScheme().equalsIgnoreCase( newBaseURI.getScheme() ) ); + final boolean sameHost = + ( baseURI.getHost() == null ? newBaseURI.getHost() == null + : baseURI.getHost().equalsIgnoreCase( newBaseURI.getHost() ) ); + final boolean samePort = ( baseURI.getPort() == newBaseURI.getPort() ); + + return ( sameScheme && samePort && sameHost ); + } + + /** + * Construct a string representation of this URIPathDescriptor. + * This is equivalent to calling {@link #resolveLink()}.toString(). + * + * @return this URIPathDescriptor as a String. + */ + @Override + public String toString() + { + return resolveLink().toString(); + } + + private static String sanitizeBase( final String base ) + { + String sane = base.replace( '\\', '/' ); + + if ( !sane.endsWith( "/" ) ) + { + sane += "/"; + } + + return sane; + } + + private static String sanitizeLink( final String link ) + { + String sane = link.replace( '\\', '/' ); + + if ( sane.startsWith( "/" ) ) + { + sane = sane.substring( 1 ); + } + + return sane; + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/main/mdo/decoration.mdo b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/main/mdo/decoration.mdo new file mode 100644 index 000000000..50539334f --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/main/mdo/decoration.mdo @@ -0,0 +1,784 @@ + + + + + + decoration + Decoration + This is a reference for the site decoration descriptor used in Doxia Sitetools, also known as site.xml: + it is used to configure a site template (aka skin).

+

An XSD is available at:

+ + ]]>
+ + + + package + org.apache.maven.doxia.site.decoration + + + + + + DecorationModel + <project> element is the root of the site decoration descriptor. + ]]> + 1.0.0+ + + + name + The full name of the project. + 1.0.0+ + String + true + + + merge) + or not (override). + ]]> + combineSelf + 1.6.0+ + String + merge + + + bannerLeft + Banner logo on the masthead of the site to the left. + 1.0.0+ + + Banner + + true + + + bannerRight + Banner logo on the masthead of the site to the right. + 1.0.0+ + + Banner + + true + + + googleAdSenseClient + Your Google AdSense client id. + 1.3.0+ + String + true + + + googleAdSenseSlot + Your Google AdSense slot id. + 1.3.0+ + String + true + + + googleAnalyticsAccountId + The id for your Google Analytics account. + 1.1.0+ + String + true + + + publishDate + Modify the date published display properties. + 1.0.0+ + + PublishDate + + true + + + version + Modify the version published display properties. + 1.0.0+ + + Version + + true + + + edit + ${project.scm.url} value should do the job. + ]]> + 1.8.0+ + String + true + + + poweredBy + Powered by logos list. + 1.0.0+ + + Logo + * + + true + + + skin + The artifact containing the skin for the site. + 1.0.0+ + + Skin + + true + + + body + The main site content decoration. + 1.0.0+ + + Body + + true + + + custom + $decoration.custom variable as DOM content. + Example: $decoration.custom.getChild( 'customElement' ).getValue() + ]]> + 1.0.0+ + DOM + true + + + lastModified + Timestamp of the last modification of this decoration model. + 1.0.1+ + long + + + + + 1.0.0+ + + menusByRef; + + /** + * @param key not null + * @return the menu ref defined by the given key. + */ + public Menu getMenuRef( String key ) + { + if ( menusByRef == null ) + { + menusByRef = new java.util.HashMap(); + + if ( body != null ) + { + for ( Menu menu : body.getMenus() ) + { + if ( menu.getRef() != null ) + { + menusByRef.put( menu.getRef(), menu ); + } + } + } + } + return menusByRef.get( key ); + } + + /** + * @param key not null + */ + public void removeMenuRef( String key ) + { + if ( body != null ) + { + for ( java.util.Iterator
i = body.getMenus().iterator(); i.hasNext(); ) + { + Menu menu = i.next(); + if ( key.equals( menu.getRef() ) ) + { + i.remove(); + } + } + } + } + + /** + * @return the menus list or EMPTY_LIST. + */ + public java.util.List getMenus() + { + java.util.List menus; + if ( body != null && body.getMenus() != null ) + { + menus = body.getMenus(); + } + else + { + menus = java.util.Collections.emptyList(); + } + return menus; + } + ]]> + + + + 1.7.0+ + + /** + * @since 1.7 + * @see DecorationUtils#isLink + */ + public boolean isLink( String href ) + { + return DecorationUtils.isLink( href ); + } + + + + 1.8.0+ + + /** + * @since 1.8 + * @see DecorationUtils#getCustomChild + */ + public Object getCustomChild( String path ) + { + return DecorationUtils.getCustomChild( (org.codehaus.plexus.util.xml.Xpp3Dom) custom, path ); + } + + /** + * @since 1.8 + * @see DecorationUtils#getCustomValue + */ + public String getCustomValue( String path ) + { + return DecorationUtils.getCustomValue( (org.codehaus.plexus.util.xml.Xpp3Dom) custom, path ); + } + + /** + * @since 1.8 + * @see DecorationUtils#getCustomValue + */ + public String getCustomValue( String path, String defaultValue ) + { + return DecorationUtils.getCustomValue( (org.codehaus.plexus.util.xml.Xpp3Dom) custom, path, defaultValue ); + } + + + + + + + Banner + Banner logo on the masthead of the site. + 1.0.0+ + + + + name + 1.0.0+ + The name of the banner. + String + true + + + src + 1.0.0+ + The source location of an image for the banner. + String + true + + + alt + 1.0.0+ + The alt description for the banner image. + String + true + + + href + 1.0.0+ + The href of a link to be used for the banner image. + String + true + + + border + The border to use for the banner image. + 1.0.1+ + String + true + + + width + The width to use for the banner image. + 1.0.1+ + String + true + + + height + The height to use for the banner image. + 1.0.1+ + String + true + + + title + 1.3.0+ + The title for the banner image. + String + true + + + + + + PublishDate + Modify display properties for date published. + 1.0.0+ + + + position + Where to place the date published (left, right, navigation-top, navigation-bottom, bottom). + 1.0.0+ + String + true + left + + + format + Date format to use. + 1.0.0+ + String + true + yyyy-MM-dd + + + + + + Version + Modify display properties for version published. + 1.0.0+ + + + position + Where to place the version published (left, right, navigation-top, navigation-bottom, bottom). + 1.0.0+ + String + true + left + + + + + + Logo + Power by logo on the navigation. + 1.0.0+ + LinkItem + + + + Body + The main content decoration. + 1.0.0+ + + + head + Additional content (like JavaScript) to include in the HEAD block of the generated pages. + 1.0.0/1.6.0 + DOM + true + + + head + Additional content (like JavaScript) to include in the HEAD block of the generated pages. + 1.7.0+ + String + true + + + links + A list of links to display in the navigation. + 1.0.0+ + + LinkItem + * + + true + + + breadcrumbs + A list of breadcrumbs to display in the navigation. + 1.0.0+ + + LinkItem + * + + true + + + menus + A list of menus to include in the navigation. + 1.0.0+ + + Menu + * + + true + + + footer + If present, the contained text will be used instead of the generated copyright text. + 1.7.0+ + String + true + + + footer + If present, the contained text will be used instead of the generated copyright text. + 1.1.0/1.6.0 + DOM + true + + + + + + LinkItem + A link in the navigation. + 1.0.0+ + + + name + The name to display for the link. + 1.0.0+ + String + true + + + href + The href to use for the link. + 1.0.0+ + String + true + + + img + The source location of an image. + 1.0.0+ + String + true + + + position + Where to place the image regarding the displayed name (left or right). + 1.0.1+ + String + true + left + + + alt + The alt to use for the image. + 1.0.1+ + String + true + + + border + The border to use for the image. + 1.0.1+ + String + true + + + width + The width to use for the image. + 1.0.1+ + String + true + + + height + The height to use for the image. + 1.0.1+ + String + true + + + target + Where the new document will be displayed when the user follows a link, i.e. _blank opens the new document in a new window. + 1.0.1+ + String + true + + + title + The title to use for the image. + 1.3.0+ + String + true + + + + + + Menu + A menu in the navigation. + 1.0.0+ + + + name + The name to display for the menu. + 1.0.0+ + String + true + + + inherit + top, bottom. + ]]> + 1.0.0+ + String + true + + + inheritAsRef + true means that it will be populated + in the project, whereas if it is false, it is populated in the parent and then inherited. + ]]> + 1.0.0+ + boolean + + + ref + reports, modules + or parent. It will be populated at runtime with corresponding pre-defined content. + ]]> + 1.0.0+ + String + true + + + img + The source location of an menu image. + 1.0.0+ + String + true + + + alt + 1.0.1+ + The alt description for the image. + String + true + + + position + Where to place the image regarding the displayed name (left or right). + 1.0.1+ + String + true + left + + + border + The border to use for the menu image. + 1.0.1+ + String + true + + + width + The width to use for the menu image. + 1.0.1+ + String + true + + + height + The height to use for the menu image. + 1.0.1+ + String + true + + + title + 1.3.0+ + The title for the image. + String + true + + + items + A list of menu item. + 1.0.0+ + + MenuItem + * + + true + + + + + + MenuItem + A menu item. + 1.0.0+ + LinkItem + + + description + A description of the menu item. This is used on any summary pages for a menu. + 1.0.0+ + String + true + + + collapse + Whether to collapse children elements of an item menu (by default). + 1.0.0+ + boolean + true + + + ref + A reference to a pre-defined menu item, such as a report (specified by the report goal + name). Any elements explicitly given override those from the pre-defined reference. + 1.0.0+ + String + true + + + items + A list of menu item. + 1.0.0+ + + MenuItem + * + + true + + + + + + Skin + An skin artifact declaration. + 1.0.0+ + + + groupId + The skin group ID. + 1.0.0+ + String + true + true + + + artifactId + The skin artifact ID. + 1.0.0+ + String + true + true + + + version + The skin version. + 1.0.0+ + String + true + + + + + 1.0.0+ + + org.apache.maven.skins:maven-default-skin:1.3. + */ + public static Skin getDefaultSkin() + { + Skin skin = new Skin(); + skin.setGroupId( "org.apache.maven.skins" ); + skin.setArtifactId( "maven-default-skin" ); + skin.setVersion( "1.3" ); + return skin; + } + ]]> + + + + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/site/apt/index.apt b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/site/apt/index.apt new file mode 100644 index 000000000..9cec49b65 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/site/apt/index.apt @@ -0,0 +1,48 @@ + ----- + Doxia Sitetool Decoration Model + ----- + Hervé Boutemy + ----- + 2011-08-18 + ----- + + ~~ 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. + + ~~ NOTE: For help with the syntax of this file, see: + ~~ http://maven.apache.org/doxia/references/apt-format.html + +Doxia Sitetool Decoration Model + + This is strictly the model for Doxia Sitetool Decoration Model, used in maven-site-plugin as <<>> to inject + parameters into decoration template: see {{{../doxia-site-renderer/}Doxia Site Renderer}} for more details on site rendering. + + The following are generated from this model: + + * {{{./apidocs/index.html}Java sources}} with Reader and Writers for the Xpp3 XML parser + + * A {{{./decoration.html}Descriptor Reference}} + + * An XSD referenced in the {{{./decoration.html}Descriptor Reference}}. + +* Inheritance + + Decoration model can be merged from a parent <<>> into a child <<>> using + <<>> ({{{./apidocs/org/apache/maven/doxia/site/decoration/inheritance/DecorationModelInheritanceAssembler.html}javadoc}}) + with its <<>> implementation + ({{{./xref/org/apache/maven/doxia/site/decoration/inheritance/DefaultDecorationModelInheritanceAssembler.html}source}}). + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/site/site.xml b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/site/site.xml new file mode 100644 index 000000000..83e81a94e --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/site/site.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/java/org/apache/maven/doxia/site/decoration/DecorationUtilsTest.java b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/java/org/apache/maven/doxia/site/decoration/DecorationUtilsTest.java new file mode 100644 index 000000000..4d1e1c76d --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/java/org/apache/maven/doxia/site/decoration/DecorationUtilsTest.java @@ -0,0 +1,67 @@ +package org.apache.maven.doxia.site.decoration; + +import org.codehaus.plexus.util.xml.Xpp3Dom; + +/* + * 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 org.junit.Test; + +import static org.junit.Assert.*; + +public class DecorationUtilsTest +{ + @Test + public void testIsLink() + { + assertFalse( DecorationUtils.isLink( null ) ); + assertFalse( DecorationUtils.isLink( "" ) ); + assertFalse( DecorationUtils.isLink( " " ) ); + assertTrue( DecorationUtils.isLink( "http://maven.apache.org/" ) ); + assertTrue( DecorationUtils.isLink( "https://maven.apache.org/" ) ); + assertTrue( DecorationUtils.isLink( "ftp://maven.apache.org/pub/" ) ); + assertTrue( DecorationUtils.isLink( "file:///home" ) ); + assertTrue( DecorationUtils.isLink( "mailto:toto@maven.org" ) ); + assertTrue( DecorationUtils.isLink( "any-protocol://" ) ); + } + + @Test + public void testGetCustomChild() + { + Xpp3Dom dom = new Xpp3Dom( "root" ); + Xpp3Dom level1 = new Xpp3Dom( "level1" ); + dom.addChild( level1 ); + Xpp3Dom level2 = new Xpp3Dom( "level2" ); + level2.setValue( "value" ); + level1.addChild( level2 ); + + assertEquals( level1, DecorationUtils.getCustomChild( dom, "level1" ) ); + assertEquals( level2, DecorationUtils.getCustomChild( dom, "level1.level2" ) ); + assertNull( DecorationUtils.getCustomChild( dom, "no.level2" ) ); + assertNull( DecorationUtils.getCustomChild( dom, "level1.no" ) ); + + assertEquals( "value", DecorationUtils.getCustomValue( dom, "level1.level2" ) ); + assertNull( DecorationUtils.getCustomValue( dom, "no.level2" ) ); + assertNull( DecorationUtils.getCustomValue( dom, "level1.no" ) ); + + assertEquals( "value", DecorationUtils.getCustomValue( dom, "level1.level2", "default" ) ); + assertEquals( "default", DecorationUtils.getCustomValue( dom, "no.level2", "default" ) ); + assertEquals( "default", DecorationUtils.getCustomValue( dom, "level1.no", "default" ) ); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/java/org/apache/maven/doxia/site/decoration/inheritance/DecorationModelInheritanceAssemblerTest.java b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/java/org/apache/maven/doxia/site/decoration/inheritance/DecorationModelInheritanceAssemblerTest.java new file mode 100644 index 000000000..38699224c --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/java/org/apache/maven/doxia/site/decoration/inheritance/DecorationModelInheritanceAssemblerTest.java @@ -0,0 +1,957 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * 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.IOException; +import java.io.Reader; + +import java.util.List; + +import org.apache.maven.doxia.site.decoration.Banner; +import org.apache.maven.doxia.site.decoration.Body; +import org.apache.maven.doxia.site.decoration.DecorationModel; +import org.apache.maven.doxia.site.decoration.LinkItem; +import org.apache.maven.doxia.site.decoration.Logo; +import org.apache.maven.doxia.site.decoration.Menu; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Reader; +import org.codehaus.plexus.util.IOUtil; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Test the inheritance assembler. + * + * @author Brett Porter + */ +public class DecorationModelInheritanceAssemblerTest +{ + private DecorationModelInheritanceAssembler assembler = new DefaultDecorationModelInheritanceAssembler(); + + private static final String NAME = "Name"; + + /** + * + * @throws IOException + * @throws XmlPullParserException + */ + @Test + public void testInheritance() + throws IOException, XmlPullParserException + { + DecorationModel childModel = readModel( "inheritance-child.xml" ); + DecorationModel parentModel = readModel( "inheritance-parent.xml" ); + + assembler.assembleModelInheritance( NAME, childModel, parentModel, "http://maven.apache.org/doxia", + "http://maven.apache.org" ); + DecorationModel expectedModel = readModel( "inheritance-expected.xml" ); + + assertEquals( "Check result", expectedModel, childModel ); + + // same with scp url, DOXIASITETOOLS-47 + childModel = readModel( "inheritance-child.xml" ); + assembler.assembleModelInheritance( NAME, childModel, parentModel, "scp://people.apache.org/doxia", + "scp://people.apache.org" ); + assertEquals( "Check scp result", expectedModel, childModel ); + + assertEquals( "Modified parent!", readModel( "inheritance-parent.xml" ), parentModel ); + + // late inheritance in links can't be rebased: check friendly message + parentModel.getBannerLeft().setHref( "${project.url}" ); + childModel = readModel( "inheritance-child.xml" ); + try + { + assembler.assembleModelInheritance( NAME, childModel, parentModel, "scp://people.apache.org/doxia", + "scp://people.apache.org" ); + fail( "late interpolation in link should cause IllegalArgumentException" ); + } + catch ( IllegalArgumentException iae ) + { + assertTrue( iae.getMessage().startsWith( "site.xml late interpolation" ) ); + } + } + + /** + * + * @throws IOException + * @throws XmlPullParserException + */ + @Test + public void testSuppressedInheritance() + throws IOException, XmlPullParserException + { + DecorationModel unassembledChildModel = readModel( "inheritance-child-no-inheritance.xml" ); + DecorationModel childModel = readModel( "inheritance-child-no-inheritance.xml" ); + DecorationModel parentModel = readModel( "inheritance-parent.xml" ); + assembler.assembleModelInheritance( NAME, childModel, parentModel, "http://maven.apache.org/doxia", + "http://maven.apache.org" ); + assertEquals( "Check result", unassembledChildModel, childModel ); + + // 2 levels of inheritance + DecorationModel childOfchildModel = new DecorationModel(); + assembler.assembleModelInheritance( "Child of Child", childOfchildModel, childModel, + "http://maven.apache.org/doxia/child", "http://maven.apache.org/doxia" ); + assembler.assembleModelInheritance( NAME, childOfchildModel, parentModel, "http://maven.apache.org/doxia", + "http://maven.apache.org" ); + // check that the 3 breadcrumb items from parent.xml are not inherited + assertEquals( "child of child no inheritance: breadcrumbs count", 0, + childOfchildModel.getBody().getBreadcrumbs().size() ); + } + + /** + * + * @throws IOException + * @throws XmlPullParserException + */ + @Test + public void testPathsResolvedWhenEmpty() + throws IOException, XmlPullParserException + { + // Test an empty model avoids NPEs + DecorationModel childModel = readModel( "empty.xml" ); + DecorationModel parentModel = readModel( "empty.xml" ); + + assembler.assembleModelInheritance( NAME, childModel, parentModel, "http://maven.apache.org/doxia", + "http://maven.apache.org" ); + DecorationModel mergedModel = readModel( "empty.xml" ); + + assertEquals( "Check result", mergedModel, childModel ); + + // same with scp url, DOXIASITETOOLS-47 + childModel = readModel( "empty.xml" ); + assembler.assembleModelInheritance( NAME, childModel, parentModel, "scp://people.apache.org/doxia", + "scp://people.apache.org" ); + assertEquals( "Check scp result", mergedModel, childModel ); + + assertEquals( "Modified parent!", readModel( "empty.xml" ), parentModel ); + } + + /** + * + * @throws IOException + * @throws XmlPullParserException + */ + @Test + public void testPathsNotResolvedForExternalUrls() + throws IOException, XmlPullParserException + { + DecorationModel parentModel = readModel( "external-urls.xml" ); + DecorationModel childModel = readModel( "empty.xml" ); + + assembler.assembleModelInheritance( NAME, childModel, parentModel, "http://maven.apache.org/doxia", + "http://maven.apache.org" ); + assertPathsNotResolvedForExternalUrls( childModel ); + + // same with scp url, DOXIASITETOOLS-47 + childModel = readModel( "empty.xml" ); + assembler.assembleModelInheritance( NAME, childModel, parentModel, "scp://people.apache.org/doxia", + "scp://people.apache.org" ); + assertPathsNotResolvedForExternalUrls( childModel ); + + assertEquals( "Modified parent!", readModel( "external-urls.xml" ), parentModel ); + } + + private static void assertPathsNotResolvedForExternalUrls( final DecorationModel childModel ) + { + assertEquals( "check left banner href", "http://jakarta.apache.org/", + childModel.getBannerLeft().getHref() ); + assertEquals( "check left banner image", "http://jakarta.apache.org/images/jakarta-logo.gif", + childModel.getBannerLeft().getSrc() ); + + assertEquals( "check right banner href", "http://jakarta.apache.org/commons/sandbox", + childModel.getBannerRight().getHref() ); + assertEquals( "check right banner image", "http://jakarta.apache.org/commons/images/logo.png", + childModel.getBannerRight().getSrc() ); + + Logo poweredBy = childModel.getPoweredBy().get( 0 ); + assertEquals( "check powered by logo href", "http://tomcat.apache.org/", poweredBy.getHref() ); + assertEquals( "check powered by logo image", "http://tomcat.apache.org/logo.gif", poweredBy.getImg() ); + + LinkItem breadcrumb = childModel.getBody().getBreadcrumbs().get( 0 ); + assertEquals( "check breadcrumb href", "http://www.apache.org/", breadcrumb.getHref() ); + + LinkItem link = childModel.getBody().getLinks().get( 0 ); + assertEquals( "check link href", "http://www.bouncycastle.org", link.getHref() ); + + Menu menu = childModel.getBody().getMenus().get( 0 ); + LinkItem menuItem = menu.getItems().get( 0 ); + assertEquals( "check menu item href", "http://www.apache.org/special/", menuItem.getHref() ); + } + + /** + * + * @throws IOException + * @throws XmlPullParserException + */ + @Test + public void testPathsResolvedForRelativeUrls() + throws IOException, XmlPullParserException + { + DecorationModel parentModel = readModel( "relative-urls.xml" ); + DecorationModel childModel = readModel( "empty.xml" ); + + assembler.assembleModelInheritance( NAME, childModel, parentModel, "http://maven.apache.org/doxia/", + "http://maven.apache.org" ); + assertPathsResolvedForRelativeUrls( childModel ); + + // same with scp url, DOXIASITETOOLS-47 + childModel = readModel( "empty.xml" ); + assembler.assembleModelInheritance( NAME, childModel, parentModel, "scp://people.apache.org/doxia", + "scp://people.apache.org" ); + assertPathsResolvedForRelativeUrls( childModel ); + + assertEquals( "Modified parent!", readModel( "relative-urls.xml" ), parentModel ); + } + + private static void assertPathsResolvedForRelativeUrls( final DecorationModel childModel ) + { + assertEquals( "check left banner href", "../banner/left", childModel.getBannerLeft().getHref() ); + assertEquals( "check left banner image", "../images/jakarta-logo.gif", + childModel.getBannerLeft().getSrc() ); + + assertEquals( "check right banner href", "../banner/right/", childModel.getBannerRight().getHref() ); + assertEquals( "check right banner image", "../commons/images/logo.png", + childModel.getBannerRight().getSrc() ); + + Logo poweredBy = childModel.getPoweredBy().get( 0 ); + assertEquals( "check powered by logo href", "../tomcat", poweredBy.getHref() ); + assertEquals( "check powered by logo image", "../tomcat/logo.gif", poweredBy.getImg() ); + + LinkItem breadcrumb = childModel.getBody().getBreadcrumbs().get( 0 ); + assertEquals( "check breadcrumb href", "../apache", breadcrumb.getHref() ); + + LinkItem link = childModel.getBody().getLinks().get( 0 ); + assertEquals( "check link href", "../bouncycastle/", link.getHref() ); + + Menu menu = childModel.getBody().getMenus().get( 0 ); + LinkItem menuItem = menu.getItems().get( 0 ); + assertEquals( "check menu item href", "../special/", menuItem.getHref() ); + } + + /** + * + * @throws IOException + * @throws XmlPullParserException + */ + @Test + public void testPathsResolvedForSubsiteUrls() + throws IOException, XmlPullParserException + { + DecorationModel parentModel = readModel( "subsite-urls.xml" ); + DecorationModel childModel = readModel( "empty.xml" ); + + assembler.assembleModelInheritance( NAME, childModel, parentModel, "http://maven.apache.org/doxia/", + "http://maven.apache.org" ); + assembler.resolvePaths( childModel, "http://maven.apache.org/doxia" ); + + assertPathsResolvedForSubsiteUrls( childModel ); + + // same with scp url, DOXIASITETOOLS-47 + childModel = readModel( "empty.xml" ); + assembler.assembleModelInheritance( NAME, childModel, parentModel, "scp://people.apache.org/doxia", + "scp://people.apache.org" ); + assembler.resolvePaths( childModel, "http://maven.apache.org/doxia" ); + assertPathsResolvedForSubsiteUrls( childModel ); + + assertEquals( "Modified parent!", readModel( "subsite-urls.xml" ), parentModel ); + } + + private static void assertPathsResolvedForSubsiteUrls( final DecorationModel childModel ) + { + assertEquals( "check left banner href", "../banner/left", childModel.getBannerLeft().getHref() ); + assertEquals( "check left banner image", "../images/jakarta-logo.gif", + childModel.getBannerLeft().getSrc() ); + + assertEquals( "check right banner href", "../banner/right/", childModel.getBannerRight().getHref() ); + assertEquals( "check right banner image", "../commons/images/logo.png", + childModel.getBannerRight().getSrc() ); + + Logo poweredBy = childModel.getPoweredBy().get( 0 ); + assertEquals( "check powered by logo href", "../tomcat", poweredBy.getHref() ); + assertEquals( "check powered by logo image", "../tomcat/logo.gif", poweredBy.getImg() ); + + LinkItem breadcrumb = childModel.getBody().getBreadcrumbs().get( 0 ); + assertEquals( "check breadcrumb href", "../apache", breadcrumb.getHref() ); + + LinkItem link = childModel.getBody().getLinks().get( 0 ); + assertEquals( "check link href", "../bouncycastle/", link.getHref() ); + + Menu menu = childModel.getBody().getMenus().get( 0 ); + LinkItem menuItem = menu.getItems().get( 0 ); + assertEquals( "check menu item href", "../special/", menuItem.getHref() ); + } + + /** + * + * @throws IOException + * @throws XmlPullParserException + */ + @Test + public void testPathsResolvedForRelativeUrlsDepthOfTwo() + throws IOException, XmlPullParserException + { + DecorationModel parentModel = readModel( "relative-urls.xml" ); + DecorationModel childModel = readModel( "empty.xml" ); + + assembler.assembleModelInheritance( NAME, childModel, parentModel, "http://maven.apache.org/doxia/core", + "http://maven.apache.org" ); + assertPathsResolvedForRelativeUrlsDepthOfTwo( childModel ); + + // same with scp url, DOXIASITETOOLS-47 + childModel = readModel( "empty.xml" ); + assembler.assembleModelInheritance( NAME, childModel, parentModel, "scp://people.apache.org/doxia/core", + "scp://people.apache.org" ); + assertPathsResolvedForRelativeUrlsDepthOfTwo( childModel ); + + assertEquals( "Modified parent!", readModel( "relative-urls.xml" ), parentModel ); + } + + private static void assertPathsResolvedForRelativeUrlsDepthOfTwo( final DecorationModel childModel ) + { + assertEquals( "check left banner href", "../../banner/left", childModel.getBannerLeft().getHref() ); + assertEquals( "check left banner image", "../../images/jakarta-logo.gif", + childModel.getBannerLeft().getSrc() ); + + assertEquals( "check right banner href", "../../banner/right/", childModel.getBannerRight().getHref() ); + assertEquals( "check right banner image", "../../commons/images/logo.png", + childModel.getBannerRight().getSrc() ); + + Logo poweredBy = childModel.getPoweredBy().get( 0 ); + assertEquals( "check powered by logo href", "../../tomcat", poweredBy.getHref() ); + assertEquals( "check powered by logo image", "../../tomcat/logo.gif", poweredBy.getImg() ); + + LinkItem breadcrumb = childModel.getBody().getBreadcrumbs().get( 0 ); + assertEquals( "check breadcrumb href", "../../apache", breadcrumb.getHref() ); + + LinkItem link = childModel.getBody().getLinks().get( 0 ); + assertEquals( "check link href", "../../bouncycastle/", link.getHref() ); + + Menu menu = childModel.getBody().getMenus().get( 0 ); + LinkItem menuItem = menu.getItems().get( 0 ); + assertEquals( "check menu item href", "../../special/", menuItem.getHref() ); + } + + /** + * + * @throws IOException + * @throws XmlPullParserException + */ + @Test + public void testPathsResolvedForReverseRelativeUrls() + throws IOException, XmlPullParserException + { + DecorationModel parentModel = readModel( "relative-urls.xml" ); + DecorationModel childModel = readModel( "empty.xml" ); + + assembler.assembleModelInheritance( NAME, childModel, parentModel, "http://maven.apache.org/", + "http://maven.apache.org/doxia/" ); + assertPathsResolvedForReverseRelativeUrls( childModel ); + + // same with scp url, DOXIASITETOOLS-47 + childModel = readModel( "empty.xml" ); + assembler.assembleModelInheritance( NAME, childModel, parentModel, "scp://people.apache.org/", + "scp://people.apache.org/doxia/" ); + assertPathsResolvedForReverseRelativeUrls( childModel ); + + assertEquals( "Modified parent!", readModel( "relative-urls.xml" ), parentModel ); + } + + private static void assertPathsResolvedForReverseRelativeUrls( final DecorationModel childModel ) + { + assertEquals( "check left banner href", "doxia/banner/left", childModel.getBannerLeft().getHref() ); + assertEquals( "check left banner image", "doxia/images/jakarta-logo.gif", + childModel.getBannerLeft().getSrc() ); + + assertEquals( "check right banner href", "doxia/banner/right/", childModel.getBannerRight().getHref() ); + assertEquals( "check right banner image", "doxia/commons/images/logo.png", + childModel.getBannerRight().getSrc() ); + + Logo poweredBy = childModel.getPoweredBy().get( 0 ); + assertEquals( "check powered by logo href", "doxia/tomcat", poweredBy.getHref() ); + assertEquals( "check powered by logo image", "doxia/tomcat/logo.gif", poweredBy.getImg() ); + + LinkItem breadcrumb = childModel.getBody().getBreadcrumbs().get( 0 ); + assertEquals( "check breadcrumb href", "doxia/apache", breadcrumb.getHref() ); + + LinkItem link = childModel.getBody().getLinks().get( 0 ); + assertEquals( "check link href", "doxia/bouncycastle/", link.getHref() ); + + Menu menu = childModel.getBody().getMenus().get( 0 ); + LinkItem menuItem = menu.getItems().get( 0 ); + assertEquals( "check menu item href", "doxia/special/", menuItem.getHref() ); + } + + /** + * + * @throws IOException + * @throws XmlPullParserException + */ + @Test + public void testPathsResolvedForReverseRelativeUrlsDepthOfTwo() + throws IOException, XmlPullParserException + { + DecorationModel parentModel = readModel( "relative-urls.xml" ); + DecorationModel childModel = readModel( "empty.xml" ); + + assembler.assembleModelInheritance( NAME, childModel, parentModel, "http://maven.apache.org/", + "http://maven.apache.org/doxia/core/" ); + assertPathsResolvedForReverseRelativeUrlsDepthOfTwo( childModel ); + + // same with scp url, DOXIASITETOOLS-47 + childModel = readModel( "empty.xml" ); + assembler.assembleModelInheritance( NAME, childModel, parentModel, "scp://people.apache.org/", + "scp://people.apache.org/doxia/core/" ); + assertPathsResolvedForReverseRelativeUrlsDepthOfTwo( childModel ); + + assertEquals( "Modified parent!", readModel( "relative-urls.xml" ), parentModel ); + } + + private static void assertPathsResolvedForReverseRelativeUrlsDepthOfTwo( final DecorationModel childModel ) + { + assertEquals( "check left banner href", "doxia/core/banner/left", childModel.getBannerLeft().getHref() ); + assertEquals( "check left banner image", "doxia/core/images/jakarta-logo.gif", + childModel.getBannerLeft().getSrc() ); + + assertEquals( "check right banner href", "doxia/core/banner/right/", + childModel.getBannerRight().getHref() ); + assertEquals( "check right banner image", "doxia/core/commons/images/logo.png", + childModel.getBannerRight().getSrc() ); + + Logo poweredBy = childModel.getPoweredBy().get( 0 ); + assertEquals( "check powered by logo href", "doxia/core/tomcat", poweredBy.getHref() ); + assertEquals( "check powered by logo image", "doxia/core/tomcat/logo.gif", poweredBy.getImg() ); + + LinkItem breadcrumb = childModel.getBody().getBreadcrumbs().get( 0 ); + assertEquals( "check breadcrumb href", "doxia/core/apache", breadcrumb.getHref() ); + + LinkItem link = childModel.getBody().getLinks().get( 0 ); + assertEquals( "check link href", "doxia/core/bouncycastle/", link.getHref() ); + + Menu menu = childModel.getBody().getMenus().get( 0 ); + LinkItem menuItem = menu.getItems().get( 0 ); + assertEquals( "check menu item href", "doxia/core/special/", menuItem.getHref() ); + } + + /** + * + * @throws IOException + * @throws XmlPullParserException + */ + @Test + public void testPathsResolvedForUnrelatedRelativeUrls() + throws IOException, XmlPullParserException + { + DecorationModel parentModel = readModel( "relative-urls.xml" ); + DecorationModel childModel = readModel( "empty.xml" ); + + assembler.assembleModelInheritance( NAME, childModel, parentModel, "http://maven.apache.org", + "http://jakarta.apache.org" ); + assertPathsResolvedForUnrelatedRelativeUrls( childModel ); + + // same with scp url, DOXIASITETOOLS-47 + childModel = readModel( "empty.xml" ); + assembler.assembleModelInheritance( NAME, childModel, parentModel, "scp://people.apache.org/", + "http://jakarta.apache.org" ); + assertPathsResolvedForUnrelatedRelativeUrls( childModel ); + + assertEquals( "Modified parent!", readModel( "relative-urls.xml" ), parentModel ); + } + + private static void assertPathsResolvedForUnrelatedRelativeUrls( final DecorationModel childModel ) + { + assertEquals( "check left banner href", "http://jakarta.apache.org/banner/left", + childModel.getBannerLeft().getHref() ); + assertEquals( "check left banner image", "http://jakarta.apache.org/images/jakarta-logo.gif", + childModel.getBannerLeft().getSrc() ); + + assertEquals( "check right banner href", "http://jakarta.apache.org/banner/right/", + childModel.getBannerRight().getHref() ); + assertEquals( "check right banner image", "http://jakarta.apache.org/commons/images/logo.png", + childModel.getBannerRight().getSrc() ); + + Logo poweredBy = childModel.getPoweredBy().get( 0 ); + assertEquals( "check powered by logo href", "http://jakarta.apache.org/tomcat", poweredBy.getHref() ); + assertEquals( "check powered by logo image", "http://jakarta.apache.org/tomcat/logo.gif", + poweredBy.getImg() ); + + LinkItem breadcrumb = childModel.getBody().getBreadcrumbs().get( 0 ); + assertEquals( "check breadcrumb href", "http://jakarta.apache.org/apache", breadcrumb.getHref() ); + + LinkItem link = childModel.getBody().getLinks().get( 0 ); + assertEquals( "check link href", "http://jakarta.apache.org/bouncycastle/", link.getHref() ); + + Menu menu = childModel.getBody().getMenus().get( 0 ); + LinkItem menuItem = menu.getItems().get( 0 ); + assertEquals( "check menu item href", "http://jakarta.apache.org/special/", menuItem.getHref() ); + } + + /** + * + * @throws IOException + * @throws XmlPullParserException + */ + @Test + public void testNullParent() + throws IOException, XmlPullParserException + { + DecorationModel childModel = readModel( "empty.xml" ); + + assembler.assembleModelInheritance( NAME, childModel, null, "http://maven.apache.org/doxia", + "http://maven.apache.org" ); + DecorationModel mergedModel = readModel( "empty.xml" ); + + assertEquals( "Check result", mergedModel, childModel ); + + // same with scp url, DOXIASITETOOLS-47 + childModel = readModel( "empty.xml" ); + assembler.assembleModelInheritance( NAME, childModel, null, "scp://people.apache.org/doxia", + "scp://people.apache.org" ); + assertEquals( "Check scp result", mergedModel, childModel ); + } + + /** + * + * @throws IOException + * @throws XmlPullParserException + */ + @Test + public void testFullyPopulatedChild() + throws IOException, XmlPullParserException + { + DecorationModel childModel = readModel( "fully-populated-child.xml" ); + DecorationModel parentModel = readModel( "fully-populated-child.xml" ); + + assembler.assembleModelInheritance( NAME, childModel, parentModel, "http://foo.apache.org/doxia", + "http://foo.apache.org" ); + DecorationModel mergedModel = readModel( "fully-populated-child.xml" ); + + assertEquals( "Check result", mergedModel, childModel ); + + // same with scp url, DOXIASITETOOLS-47 + childModel = readModel( "fully-populated-child.xml" ); + assembler.assembleModelInheritance( NAME, childModel, parentModel, "scp://foo.apache.org/doxia", + "scp://foo.apache.org" ); + assertEquals( "Check scp result", mergedModel, childModel ); + + assertEquals( "Modified parent!", readModel( "fully-populated-child.xml" ), parentModel ); + } + + /** + * + * @throws IOException + * @throws XmlPullParserException + */ + @Test + public void testFullyPopulatedParentAndEmptyChild() + throws IOException, XmlPullParserException + { + DecorationModel childModel = readModel( "empty.xml" ); + DecorationModel parentModel = readModel( "fully-populated-child.xml" ); + + assembler.assembleModelInheritance( NAME, childModel, parentModel, "http://maven.apache.org/doxia", + "http://maven.apache.org" ); + + DecorationModel unresolvedModel = readModel( "fully-populated-unresolved.xml" ); + assertEquals( "Check result", unresolvedModel, childModel ); + + assembler.resolvePaths( childModel, "http://maven.apache.org/doxia" ); + DecorationModel mergedModel = readModel( "fully-populated-merged.xml" ); + + assertEquals( "Check result", mergedModel, childModel ); + + // same with scp url, DOXIASITETOOLS-47 + childModel = readModel( "empty.xml" ); + assembler.assembleModelInheritance( NAME, childModel, parentModel, "scp://maven.apache.org/doxia", + "scp://maven.apache.org" ); + assembler.resolvePaths( childModel, "http://maven.apache.org/doxia" ); + assertEquals( "Check scp result", mergedModel, childModel ); + + assertEquals( "Modified parent!", readModel( "fully-populated-child.xml" ), parentModel ); + } + + /** + * + * @throws IOException + * @throws XmlPullParserException + */ + @Test + public void testResolvingAllExternalUrls() + throws IOException, XmlPullParserException + { + DecorationModel model = readModel( "external-urls.xml" ); + + assembler.resolvePaths( model, "http://foo.com/" ); + DecorationModel mergedModel = readModel( "external-urls.xml" ); + + assertEquals( "Check result", mergedModel, model ); + } + + /** + * + * @throws IOException + * @throws XmlPullParserException + */ + @Test + public void testResolvingAllRelativeUrls() + throws IOException, XmlPullParserException + { + DecorationModel model = readModel( "relative-urls.xml" ); + + assembler.resolvePaths( model, "http://foo.com/" ); + + DecorationModel resolvedModel = readModel( "relative-urls-resolved.xml" ); + + assertEquals( "Check result", resolvedModel, model ); + } + + /** + * + * @throws IOException + * @throws XmlPullParserException + */ + @Test + public void testResolvingAllSiteUrls() + throws IOException, XmlPullParserException + { + DecorationModel model = readModel( "subsite-urls.xml" ); + + assembler.resolvePaths( model, "http://maven.apache.org/" ); + + DecorationModel resolvedModel = readModel( "relative-urls-resolved.xml" ); + assertEquals( "Check result", resolvedModel, model ); + } + +/* [MSITE-62] This is to test the ../ relative paths, which I am inclined not to use + public void testResolvingAllSiteChildUrls() + throws IOException, XmlPullParserException + { + DecorationModel model = readModel( "subsite-urls.xml" ); + + assembler.resolvePaths( model, "http://maven.apache.org/foo" ); + + DecorationModel resolvedModel = readModel( "subsite-relative-urls-resolved.xml" ); + assertEquals( "Check result", resolvedModel, model ); + } + + public void testResolvingAllSiteChildUrlsMultipleLevels() + throws IOException, XmlPullParserException + { + DecorationModel model = readModel( "subsite-urls.xml" ); + + assembler.resolvePaths( model, "http://maven.apache.org/banner/right" ); + + DecorationModel resolvedModel = readModel( "subsite-relative-urls-multiple-resolved.xml" ); + assertEquals( "Check result", resolvedModel, model ); + } + + public void testResolvingAllSiteChildFilesystemUrls() + throws IOException, XmlPullParserException + { + DecorationModel model = readModel( "subsite-urls-file.xml" ); + + assembler.resolvePaths( model, "file://localhost/www/maven.apache.org/foo" ); + + DecorationModel resolvedModel = readModel( "subsite-relative-urls-resolved.xml" ); + assertEquals( "Check result", resolvedModel, model ); + } + +*/ + + /** + * + * @throws IOException + * @throws XmlPullParserException + */ + @Test + public void testResolvingEmptyDescriptor() + throws IOException, XmlPullParserException + { + DecorationModel model = readModel( "empty.xml" ); + assembler.resolvePaths( model, "http://maven.apache.org" ); + DecorationModel mergedModel = readModel( "empty.xml" ); + + assertEquals( "Check result", mergedModel, model ); + } + + /** + * + */ + @Test + public void testDuplicateParentElements() + { + DecorationModel model = new DecorationModel(); + model.setBody( new Body() ); + model.getBody().addLink( createLinkItem( "Foo", "http://foo.apache.org" ) ); + model.getBody().addLink( createLinkItem( "Foo", "http://foo.apache.org" ) ); + + model.addPoweredBy( createLogo( "Foo", "http://foo.apache.org", "http://foo.apache.org/foo.jpg" ) ); + model.addPoweredBy( createLogo( "Foo", "http://foo.apache.org", "http://foo.apache.org/foo.jpg" ) ); + + DecorationModel child = new DecorationModel(); + assembler.assembleModelInheritance( NAME, child, model, "http://maven.apache.org/doxia", + "http://maven.apache.org" ); + + assertEquals( "Check size", 1, child.getBody().getLinks().size() ); + assertEquals( "Check item", createLinkItem( "Foo", "http://foo.apache.org" ), + child.getBody().getLinks().get( 0 ) ); + + assertEquals( "Check size", 1, child.getPoweredBy().size() ); + assertEquals( "Check item", + createLogo( "Foo", "http://foo.apache.org", "http://foo.apache.org/foo.jpg" ), + child.getPoweredBy().get( 0 ) ); + } + + /** + * + */ + @Test + public void testDuplicateChildElements() + { + DecorationModel model = new DecorationModel(); + model.setBody( new Body() ); + model.getBody().addLink( createLinkItem( "Foo", "http://foo.apache.org" ) ); + model.getBody().addLink( createLinkItem( "Foo", "http://foo.apache.org" ) ); + + model.addPoweredBy( createLogo( "Foo", "http://foo.apache.org", "http://foo.apache.org/foo.jpg" ) ); + model.addPoweredBy( createLogo( "Foo", "http://foo.apache.org", "http://foo.apache.org/foo.jpg" ) ); + + DecorationModel parent = new DecorationModel(); + assembler.assembleModelInheritance( NAME, model, parent, "http://maven.apache.org/doxia", + "http://maven.apache.org" ); + + assertEquals( "Check size", 1, model.getBody().getLinks().size() ); + assertEquals( "Check item", createLinkItem( "Foo", "http://foo.apache.org" ), + model.getBody().getLinks().get( 0 ) ); + + assertEquals( "Check size", 1, model.getPoweredBy().size() ); + assertEquals( "Check item", + createLogo( "Foo", "http://foo.apache.org", "http://foo.apache.org/foo.jpg" ), + model.getPoweredBy().get( 0 ) ); + + assertEquals( "Modified parent!", new DecorationModel(), parent ); + } + + /** + * + */ + @Test + public void testBadHref() + { + final DecorationModel model = new DecorationModel(); + model.setBody( new Body() ); + model.getBody().addBreadcrumb( createLinkItem( "Foo", "http://foo.apache.org/${property}" ) ); + assembler.resolvePaths( model, "http://foo.apache.org" ); + assertEquals( "Check size", 1, model.getBody().getBreadcrumbs().size() ); + assertEquals( "Check item", createLinkItem( "Foo", "http://foo.apache.org/${property}" ), + model.getBody().getBreadcrumbs().get( 0 ) ); + } + + /** + * + */ + @Test + public void testBreadcrumbWithoutHref() + { + DecorationModel model = new DecorationModel(); + model.setBody( new Body() ); + model.getBody().addBreadcrumb( createLinkItem( "Foo", null ) ); + assembler.resolvePaths( model, "http://foo.apache.org" ); + assertEquals( "Check size", 1, model.getBody().getBreadcrumbs().size() ); + assertEquals( "Check item", createLinkItem( "Foo", null ), model.getBody().getBreadcrumbs().get( 0 ) ); + } + + /** + * + */ + @Test + public void testBreadcrumbs() + { + String parentHref = "http://parent.com/index.html"; + + final DecorationModel parent = new DecorationModel(); + parent.setBody( new Body() ); + parent.getBody().addBreadcrumb( createLinkItem( "Parent", parentHref ) ); + + DecorationModel child = new DecorationModel(); + assembler.assembleModelInheritance( "childName", child, parent, + "http://parent.com/child", "http://parent.com" ); + assertBreadcrumbsCorrect( child.getBody().getBreadcrumbs(), "childName", parentHref ); + + + // same with trailing slash + child = new DecorationModel(); + assembler.assembleModelInheritance( "childName", child, parent, + "http://parent.com/child/", "http://parent.com/" ); + assertBreadcrumbsCorrect( child.getBody().getBreadcrumbs(), "childName", parentHref ); + + // now mixed + child = new DecorationModel(); + assembler.assembleModelInheritance( "childName", child, parent, + "http://parent.com/child/", "http://parent.com" ); + assertBreadcrumbsCorrect( child.getBody().getBreadcrumbs(), "childName", parentHref ); + + // and other way round + child = new DecorationModel(); + assembler.assembleModelInheritance( "childName", child, parent, + "http://parent.com/child", "http://parent.com/" ); + assertBreadcrumbsCorrect( child.getBody().getBreadcrumbs(), "childName", parentHref ); + + + // now with child breadcrumb + child = new DecorationModel(); + child.setBody( new Body() ); + child.getBody().addBreadcrumb( createLinkItem( "Child", "index.html" ) ); + assembler.assembleModelInheritance( "childName", child, parent, + "http://parent.com/child/", "http://parent.com/" ); + assertBreadcrumbsCorrect( child.getBody().getBreadcrumbs(), "Child", parentHref ); + + + // now with file url + parentHref = "file://parent.com/index.html"; + ( parent.getBody().getBreadcrumbs().get( 0 ) ).setHref( parentHref ); + child = new DecorationModel(); + assembler.assembleModelInheritance( "childName", child, parent, + "file://parent.com/child/", "file://parent.com/" ); + assertBreadcrumbsCorrect( child.getBody().getBreadcrumbs(), "childName", parentHref ); + + + // now with scp url + parentHref = "scp://parent.com/index.html"; + ( parent.getBody().getBreadcrumbs().get( 0 ) ).setHref( parentHref ); + child = new DecorationModel(); + assembler.assembleModelInheritance( "childName", child, parent, + "scp://parent.com/child/", "scp://parent.com/" ); + assertBreadcrumbsCorrect( child.getBody().getBreadcrumbs(), "childName", parentHref ); + } + + private static void assertBreadcrumbsCorrect( final List breadcrumbs, final String childName, + final String parentHref ) + { + assertEquals( "Check size", 2, breadcrumbs.size() ); + assertEquals( "Check parent item", createLinkItem( "Parent", parentHref ), breadcrumbs.get( 0 ) ); + assertEquals( "Check child item", createLinkItem( childName, "index.html" ), breadcrumbs.get( 1 ) ); + } + + /** + * https://issues.apache.org/jira/browse/DOXIASITETOOLS-62 + */ + @Test + public void testBreadcrumbCutParentAfterDuplicate() + { + DecorationModel child = new DecorationModel(); // B > E + child.setBody( new Body() ); + child.getBody().addBreadcrumb( createLinkItem( "B", null ) ); + child.getBody().addBreadcrumb( createLinkItem( "E", null ) ); + + DecorationModel parent = new DecorationModel(); // A > B > C > D + parent.setBody( new Body() ); + parent.getBody().addBreadcrumb( createLinkItem( "A", null ) ); + parent.getBody().addBreadcrumb( createLinkItem( "B", null ) ); + parent.getBody().addBreadcrumb( createLinkItem( "C", null ) ); + parent.getBody().addBreadcrumb( createLinkItem( "D", null ) ); + + assembler.assembleModelInheritance( NAME, child, parent, "http://maven.apache.org/doxia", + "http://maven.apache.org" ); + + final List breadcrumbs = child.getBody().getBreadcrumbs(); // expected: A > B > E + assertEquals( "Check size", 3, breadcrumbs.size() ); + assertEquals( "Check item", createLinkItem( "A", null ), breadcrumbs.get( 0 ) ); + assertEquals( "Check item", createLinkItem( "B", null ), breadcrumbs.get( 1 ) ); + assertEquals( "Check item", createLinkItem( "E", null ), breadcrumbs.get( 2 ) ); + } + + /** + * + */ + @Test + public void testBannerWithoutHref() + { + DecorationModel model = new DecorationModel(); + model.setBody( new Body() ); + + Banner banner = createBanner( "Left", null, "/images/src.gif", "alt" ); + + model.setBannerLeft( banner ); + + assembler.resolvePaths( model, "http://foo.apache.org" ); + + assertEquals( "Check banner", createBanner( "Left", null, "images/src.gif", "alt" ), + model.getBannerLeft() ); + } + + /** + * + */ + @Test + public void testLogoWithoutImage() + { + // This should actually be validated in the model, it doesn't really make sense + DecorationModel model = new DecorationModel(); + model.setBody( new Body() ); + model.addPoweredBy( createLogo( "Foo", "http://foo.apache.org", null ) ); + assembler.resolvePaths( model, "http://foo.apache.org" ); + assertEquals( "Check size", 1, model.getPoweredBy().size() ); + assertEquals( "Check item", createLogo( "Foo", "./", null ), model.getPoweredBy().get( 0 ) ); + } + + private static Banner createBanner( String name, String href, String src, String alt ) + { + Banner banner = new Banner(); + banner.setName( name ); + banner.setHref( href ); + banner.setSrc( src ); + banner.setAlt( alt ); + return banner; + } + + private Logo createLogo( String name, String href, String img ) + { + Logo logo = new Logo(); + logo.setHref( href ); + logo.setImg( img ); + logo.setName( name ); + return logo; + } + + private static LinkItem createLinkItem( String name, String href ) + { + LinkItem item = new LinkItem(); + item.setName( name ); + item.setHref( href ); + return item; + } + + private DecorationModel readModel( String name ) + throws IOException, XmlPullParserException + { + Reader reader = null; + try + { + reader = ReaderFactory.newXmlReader( getClass().getResourceAsStream( "/" + name ) ); + return new DecorationXpp3Reader().read( reader ); + } + finally + { + IOUtil.close( reader ); + } + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/java/org/apache/maven/doxia/site/decoration/inheritance/Doxia91Test.java b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/java/org/apache/maven/doxia/site/decoration/inheritance/Doxia91Test.java new file mode 100644 index 000000000..bcb8585f6 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/java/org/apache/maven/doxia/site/decoration/inheritance/Doxia91Test.java @@ -0,0 +1,72 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * 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 org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Tests for DOXIA-91 problems. All tests make sure that a passed in null will not generate any path conversion but + * just returns the old path. + * + * @author Henning P. Schmiedehausen + */ +public class Doxia91Test +{ + /** @throws Exception */ + @Test + public void testOldPathNull() + throws Exception + { + PathDescriptor oldPath = new PathDescriptor( null ); + PathDescriptor newPath = new PathDescriptor( "http://www.apache.org/" ); + + PathDescriptor diff = PathUtils.convertPath( oldPath, newPath ); + + assertEquals( diff, oldPath ); + } + + /** @throws Exception */ + @Test + public void testNewPathNull() + throws Exception + { + PathDescriptor oldPath = new PathDescriptor( "http://www.apache.org/", "file:///home/henning/foo" ); + PathDescriptor newPath = new PathDescriptor( null ); + + PathDescriptor diff = PathUtils.convertPath( oldPath, newPath ); + + assertEquals( diff, oldPath ); + } + + /** @throws Exception */ + @Test + public void testBothPathNull() + throws Exception + { + PathDescriptor oldPath = new PathDescriptor( null ); + PathDescriptor newPath = new PathDescriptor( null ); + + PathDescriptor diff = PathUtils.convertPath( oldPath, newPath ); + + assertEquals( diff, oldPath ); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/java/org/apache/maven/doxia/site/decoration/inheritance/PathDescriptorTest.java b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/java/org/apache/maven/doxia/site/decoration/inheritance/PathDescriptorTest.java new file mode 100644 index 000000000..3662b726f --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/java/org/apache/maven/doxia/site/decoration/inheritance/PathDescriptorTest.java @@ -0,0 +1,655 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; + +import org.codehaus.plexus.util.Os; +import org.codehaus.plexus.util.StringUtils; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Test the PathDescriptor creation under various circumstances. + * + * @author Henning P. Schmiedehausen + */ +public class PathDescriptorTest +{ + /** @throws Exception */ + @Test + public void testAbsPath() + throws Exception + { + String path = "absolutePath"; + + PathDescriptor desc = new PathDescriptor( "/" + path ); + + assertTrue( desc.isFile() ); + assertTrue( desc.isRelative() ); + assertNull( desc.getBaseUrl() ); + assertNull( desc.getPathUrl() ); + assertNotNull( desc.getPath() ); + assertNotNull( desc.getLocation() ); + assertEquals( "wrong path", path, desc.getPath() ); + assertEquals( "wrong location", path, desc.getLocation() ); + } + + /** @throws Exception */ + @Test + public void testRelPath() + throws Exception + { + String path = "relativePath"; + + PathDescriptor desc = new PathDescriptor( path ); + + assertTrue( desc.isFile() ); + assertTrue( desc.isRelative() ); + assertNull( desc.getBaseUrl() ); + assertNull( desc.getPathUrl() ); + assertNotNull( desc.getPath() ); + assertNotNull( desc.getLocation() ); + assertEquals( "wrong path", path, desc.getPath() ); + assertEquals( "wrong location", path, desc.getLocation() ); + } + + /** @throws Exception */ + @Test + public void testEmptyAbsPath() + throws Exception + { + String path = ""; + + PathDescriptor desc = new PathDescriptor( "/" + path ); + + assertTrue( desc.isFile() ); + assertTrue( desc.isRelative() ); + assertNull( desc.getBaseUrl() ); + assertNull( desc.getPathUrl() ); + assertNotNull( desc.getPath() ); + assertNotNull( desc.getLocation() ); + assertEquals( "wrong path", path, desc.getPath() ); + assertEquals( "wrong location", path, desc.getLocation() ); + } + + /** @throws Exception */ + @Test + public void testEmptyRelPath() + throws Exception + { + String path = ""; + + PathDescriptor desc = new PathDescriptor( path ); + + assertTrue( desc.isFile() ); + assertTrue( desc.isRelative() ); + assertNull( desc.getBaseUrl() ); + assertNull( desc.getPathUrl() ); + assertNotNull( desc.getPath() ); + assertNotNull( desc.getLocation() ); + assertEquals( "wrong path", path, desc.getPath() ); + assertEquals( "wrong location", path, desc.getLocation() ); + } + + /** @throws Exception */ + @Test + public void testNullPath() + throws Exception + { + String path = null; + + PathDescriptor desc = new PathDescriptor( path ); + + assertTrue( desc.isFile() ); + assertTrue( desc.isRelative() ); + assertNull( desc.getBaseUrl() ); + assertNull( desc.getPathUrl() ); + assertNull( desc.getPath() ); + assertNull( desc.getLocation() ); + assertEquals( "wrong path", path, desc.getPath() ); + assertEquals( "wrong location", path, desc.getLocation() ); + } + + /** @throws Exception */ + @Test + public void testNullBaseAbsPath() + throws Exception + { + String base = null; + String path = "absolutePath"; + + PathDescriptor desc = new PathDescriptor( base, "/" + path ); + + assertTrue( desc.isFile() ); + assertTrue( desc.isRelative() ); + assertNull( desc.getBaseUrl() ); + assertNull( desc.getPathUrl() ); + assertNotNull( desc.getPath() ); + assertNotNull( desc.getLocation() ); + assertEquals( "wrong path", path, desc.getPath() ); + assertEquals( "wrong location", path, desc.getLocation() ); + } + + /** @throws Exception */ + @Test + public void testNullBaseRelPath() + throws Exception + { + String base = null; + String path = "relativePath"; + + PathDescriptor desc = new PathDescriptor( base, path ); + + assertTrue( desc.isFile() ); + assertTrue( desc.isRelative() ); + assertNull( desc.getBaseUrl() ); + assertNull( desc.getPathUrl() ); + assertNotNull( desc.getPath() ); + assertNotNull( desc.getLocation() ); + assertEquals( "wrong path", path, desc.getPath() ); + assertEquals( "wrong location", path, desc.getLocation() ); + } + + /** @throws Exception */ + @Test + public void testNullBaseEmptyAbsPath() + throws Exception + { + String base = null; + String path = ""; + + PathDescriptor desc = new PathDescriptor( base, "/" + path ); + + assertTrue( desc.isFile() ); + assertTrue( desc.isRelative() ); + assertNull( desc.getBaseUrl() ); + assertNull( desc.getPathUrl() ); + assertNotNull( desc.getPath() ); + assertNotNull( desc.getLocation() ); + assertEquals( "wrong path", path, desc.getPath() ); + assertEquals( "wrong location", path, desc.getLocation() ); + } + + /** @throws Exception */ + @Test + public void testNullBaseEmptyRelPath() + throws Exception + { + String base = null; + String path = ""; + + PathDescriptor desc = new PathDescriptor( base, path ); + + assertTrue( desc.isFile() ); + assertTrue( desc.isRelative() ); + assertNull( desc.getBaseUrl() ); + assertNull( desc.getPathUrl() ); + assertNotNull( desc.getPath() ); + assertNotNull( desc.getLocation() ); + assertEquals( "wrong path", path, desc.getPath() ); + assertEquals( "wrong location", path, desc.getLocation() ); + } + + /** @throws Exception */ + @Test + public void testNullBaseNullPath() + throws Exception + { + String base = null; + String path = null; + + PathDescriptor desc = new PathDescriptor( base, path ); + + assertTrue( desc.isFile() ); + assertTrue( desc.isRelative() ); + assertNull( desc.getBaseUrl() ); + assertNull( desc.getPathUrl() ); + assertNull( desc.getPath() ); + assertNull( desc.getLocation() ); + assertEquals( "wrong path", path, desc.getPath() ); + assertEquals( "wrong location", path, desc.getLocation() ); + } + + /** @throws Exception */ + @Test + public void testUrlBaseAbsPath() + throws Exception + { + String base = "http://maven.apache.org/"; + String path = "absolutePath"; + + PathDescriptor desc = new PathDescriptor( base, "/" + path ); + + assertFalse( desc.isFile() ); + assertFalse( desc.isRelative() ); + assertNotNull( desc.getBaseUrl() ); + assertNotNull( desc.getPathUrl() ); + assertNotNull( desc.getPath() ); + assertNotNull( desc.getLocation() ); + assertEquals( "wrong path", "/" + path, desc.getPath() ); + assertEquals( "wrong location", base + path, desc.getLocation() ); + } + + /** @throws Exception */ + @Test + public void testUrlBaseRelPath() + throws Exception + { + String base = "http://maven.apache.org/"; + String path = "relativePath"; + + PathDescriptor desc = new PathDescriptor( base, path ); + + assertFalse( desc.isFile() ); + assertFalse( desc.isRelative() ); + assertNotNull( desc.getBaseUrl() ); + assertNotNull( desc.getPathUrl() ); + assertNotNull( desc.getPath() ); + assertNotNull( desc.getLocation() ); + assertEquals( "wrong path", "/" + path, desc.getPath() ); + assertEquals( "wrong location", base + path, desc.getLocation() ); + } + + /** @throws Exception */ + @Test + public void testUrlBaseEmptyAbsPath() + throws Exception + { + String base = "http://maven.apache.org/"; + String path = ""; + + PathDescriptor desc = new PathDescriptor( base, "/" + path ); + + assertFalse( desc.isFile() ); + assertFalse( desc.isRelative() ); + assertNotNull( desc.getBaseUrl() ); + assertNotNull( desc.getPathUrl() ); + assertNotNull( desc.getPath() ); + assertNotNull( desc.getLocation() ); + assertEquals( "wrong path", "/" + path, desc.getPath() ); + assertEquals( "wrong location", base + path, desc.getLocation() ); + } + + /** @throws Exception */ + @Test + public void testUrlBaseEmptyRelPath() + throws Exception + { + String base = "http://maven.apache.org/"; + String path = ""; + + PathDescriptor desc = new PathDescriptor( base, path ); + + assertFalse( desc.isFile() ); + assertFalse( desc.isRelative() ); + assertNotNull( desc.getBaseUrl() ); + assertNotNull( desc.getPathUrl() ); + assertNotNull( desc.getPath() ); + assertNotNull( desc.getLocation() ); + assertEquals( "wrong path", "/" + path, desc.getPath() ); + assertEquals( "wrong location", base + path, desc.getLocation() ); + } + + /** @throws Exception */ + @Test + public void testUrlBaseNullPath() + throws Exception + { + String base = "http://maven.apache.org/"; + String path = null; + + PathDescriptor desc = new PathDescriptor( base, path ); + + assertFalse( desc.isFile() ); + assertFalse( desc.isRelative() ); + assertNotNull( desc.getBaseUrl() ); + assertNotNull( desc.getPathUrl() ); + assertNotNull( desc.getPath() ); + assertNotNull( desc.getLocation() ); + assertEquals( "wrong path", "/", desc.getPath() ); + assertEquals( "wrong location", base, desc.getLocation() ); + } + + /** @throws Exception */ + @Test + public void testFileBaseAbsPath() + throws Exception + { + String base = "/tmp/foo"; + String path = "absolutePath"; + + PathDescriptor desc = new PathDescriptor( "file://" + base, "/" + path ); + + assertTrue( desc.isFile() ); + assertFalse( desc.isRelative() ); + assertNotNull( desc.getBaseUrl() ); + assertNotNull( desc.getPathUrl() ); + assertNotNull( desc.getPath() ); + assertNotNull( desc.getLocation() ); + if ( Os.isFamily( Os.FAMILY_WINDOWS ) ) + { + String s = StringUtils.replace( new File( base + "/" + path ).toURI().toURL().toString(), "file:", "" ); + assertEquals( "wrong path", s, desc.getPath() ); + assertEquals( "wrong location", s, desc.getLocation() ); + } + else + { + assertEquals( "wrong path", base + "/" + path, desc.getPath() ); + assertEquals( "wrong location", base + "/" + path, desc.getLocation() ); + } + } + + /** @throws Exception */ + @Test + public void testFileBaseRelPath() + throws Exception + { + String base = "/tmp/foo"; + String path = "relativePath"; + + PathDescriptor desc = new PathDescriptor( "file://" + base, path ); + + assertTrue( desc.isFile() ); + assertFalse( desc.isRelative() ); + assertNotNull( desc.getBaseUrl() ); + assertNotNull( desc.getPathUrl() ); + assertNotNull( desc.getPath() ); + assertNotNull( desc.getLocation() ); + if ( Os.isFamily( Os.FAMILY_WINDOWS ) ) + { + String s = StringUtils.replace( new File( base + "/" + path ).toURI().toURL().toString(), "file:", "" ); + assertEquals( "wrong path", s, desc.getPath() ); + assertEquals( "wrong location", s, desc.getLocation() ); + } + else + { + assertEquals( "wrong path", base + "/" + path, desc.getPath() ); + assertEquals( "wrong location", base + "/" + path, desc.getLocation() ); + } + } + + /** @throws Exception */ + @Test + public void testFileBaseEmptyAbsPath() + throws Exception + { + String base = "/tmp/foo"; + String path = ""; + + PathDescriptor desc = new PathDescriptor( "file://" + base, "/" + path ); + + assertTrue( desc.isFile() ); + assertFalse( desc.isRelative() ); + assertNotNull( desc.getBaseUrl() ); + assertNotNull( desc.getPathUrl() ); + assertNotNull( desc.getPath() ); + assertNotNull( desc.getLocation() ); + if ( Os.isFamily( Os.FAMILY_WINDOWS ) ) + { + String s = StringUtils.replace( new File( base ).toURI().toURL().toString(), "file:", "" ); + assertEquals( "wrong path", s, desc.getPath() ); + assertEquals( "wrong location", s, desc.getLocation() ); + } + else + { + assertEquals( "wrong path", base, desc.getPath() ); + assertEquals( "wrong location", base, desc.getLocation() ); + } + } + + /** @throws Exception */ + @Test + public void testFileBaseEmptyRelPath() + throws Exception + { + String base = "/tmp/foo"; + String path = ""; + + PathDescriptor desc = new PathDescriptor( "file://" + base, path ); + + assertTrue( desc.isFile() ); + assertFalse( desc.isRelative() ); + assertNotNull( desc.getBaseUrl() ); + assertNotNull( desc.getPathUrl() ); + assertNotNull( desc.getPath() ); + assertNotNull( desc.getLocation() ); + if ( Os.isFamily( Os.FAMILY_WINDOWS ) ) + { + String s = StringUtils.replace( new File( base ).toURI().toURL().toString(), "file:", "" ); + assertEquals( "wrong path", s, desc.getPath() ); + assertEquals( "wrong location", s, desc.getLocation() ); + } + else + { + assertEquals( "wrong path", base, desc.getPath() ); + assertEquals( "wrong location", base, desc.getLocation() ); + } + } + + /** @throws Exception */ + @Test + public void testFileBaseNullPath() + throws Exception + { + String base = "/tmp/foo"; + String path = null; + + PathDescriptor desc = new PathDescriptor( "file://" + base, path ); + + assertTrue( desc.isFile() ); + assertFalse( desc.isRelative() ); + assertNotNull( desc.getBaseUrl() ); + assertNotNull( desc.getPathUrl() ); + assertNotNull( desc.getPath() ); + assertNotNull( desc.getLocation() ); + assertEquals( "wrong path", base, desc.getPath() ); + assertEquals( "wrong location", base, desc.getLocation() ); + } + +/* + // same as testUrlBaseAbsPath with scp, this fails!? DOXIASITETOOLS-47 + public void testUriBaseAbsPath() + throws Exception + { + String base = "scp://people.apache.org/"; + String path = "absolutePath"; + + PathDescriptor desc = new PathDescriptor( base, "/" + path ); + + assertFalse( desc.isFile() ); + assertFalse( desc.isRelative() ); + assertNotNull( desc.getBaseUrl() ); + assertNotNull( desc.getPathUrl() ); + assertNotNull( desc.getPath() ); + assertNotNull( desc.getLocation() ); + assertEquals( "wrong path", "/" + path, desc.getPath() ); + assertEquals( "wrong location", base + path, desc.getLocation() ); + } +*/ + + /** @throws Exception */ + @Test + public void testPathBaseAbsPath() + throws Exception + { + String base = "/tmp/foo"; + String path = "absolutePath"; + + PathDescriptor desc = new PathDescriptor( base, "/" + path ); + + assertTrue( desc.isFile() ); + assertFalse( desc.isRelative() ); + assertNotNull( desc.getBaseUrl() ); + assertNotNull( desc.getPathUrl() ); + assertNotNull( desc.getPath() ); + assertNotNull( desc.getLocation() ); + if ( Os.isFamily( Os.FAMILY_WINDOWS ) ) + { + String s = StringUtils.replace( new File( base + "/" + path ).toURI().toURL().toString(), "file:", "" ); + assertEquals( "wrong path", s, desc.getPath() ); + assertEquals( "wrong location", s, desc.getLocation() ); + } + else + { + assertEquals( "wrong path", base + "/" + path, desc.getPath() ); + assertEquals( "wrong location", base + "/" + path, desc.getLocation() ); + } + } + + /** @throws Exception */ + @Test + public void testPathBaseRelPath() + throws Exception + { + String base = "/tmp/foo"; + String path = "relativePath"; + + PathDescriptor desc = new PathDescriptor( base, path ); + + assertTrue( desc.isFile() ); + assertFalse( desc.isRelative() ); + assertNotNull( desc.getBaseUrl() ); + assertNotNull( desc.getPathUrl() ); + assertNotNull( desc.getPath() ); + assertNotNull( desc.getLocation() ); + if ( Os.isFamily( Os.FAMILY_WINDOWS ) ) + { + String s = StringUtils.replace( new File( base + "/" + path ).toURI().toURL().toString(), "file:", "" ); + assertEquals( "wrong path", s, desc.getPath() ); + assertEquals( "wrong location", s, desc.getLocation() ); + } + else + { + assertEquals( "wrong path", base + "/" + path, desc.getPath() ); + assertEquals( "wrong location", base + "/" + path, desc.getLocation() ); + } + } + + /** @throws Exception */ + @Test + public void testPathBaseEmptyAbsPath() + throws Exception + { + String base = "/tmp/foo"; + String path = ""; + + PathDescriptor desc = new PathDescriptor( base, "/" + path ); + + assertTrue( desc.isFile() ); + assertFalse( desc.isRelative() ); + assertNotNull( desc.getBaseUrl() ); + assertNotNull( desc.getPathUrl() ); + assertNotNull( desc.getPath() ); + assertNotNull( desc.getLocation() ); + if ( Os.isFamily( Os.FAMILY_WINDOWS ) ) + { + String s = StringUtils.replace( new File( base ).toURI().toURL().toString(), "file:", "" ); + assertEquals( "wrong path", s, desc.getPath() ); + assertEquals( "wrong location", s, desc.getLocation() ); + } + else + { + assertEquals( "wrong path", base, desc.getPath() ); + assertEquals( "wrong location", base, desc.getLocation() ); + } + } + + /** @throws Exception */ + @Test + public void testPathBaseEmptyRelPath() + throws Exception + { + String base = "/tmp/foo"; + String path = ""; + + PathDescriptor desc = new PathDescriptor( base, path ); + + assertTrue( desc.isFile() ); + assertFalse( desc.isRelative() ); + assertNotNull( desc.getBaseUrl() ); + assertNotNull( desc.getPathUrl() ); + assertNotNull( desc.getPath() ); + assertNotNull( desc.getLocation() ); + if ( Os.isFamily( Os.FAMILY_WINDOWS ) ) + { + String s = StringUtils.replace( new File( base ).toURI().toURL().toString(), "file:", "" ); + assertEquals( "wrong path", s, desc.getPath() ); + assertEquals( "wrong location", s, desc.getLocation() ); + } + else + { + assertEquals( "wrong path", base, desc.getPath() ); + assertEquals( "wrong location", base, desc.getLocation() ); + } + } + + /** @throws Exception */ + @Test + public void testPathBaseNullPath() + throws Exception + { + String base = "/tmp/foo"; + String path = null; + + PathDescriptor desc = new PathDescriptor( base, path ); + + assertTrue( desc.isFile() ); + assertFalse( desc.isRelative() ); + assertNotNull( desc.getBaseUrl() ); + assertNotNull( desc.getPathUrl() ); + assertNotNull( desc.getPath() ); + assertNotNull( desc.getLocation() ); + if ( Os.isFamily( Os.FAMILY_WINDOWS ) ) + { + String s = StringUtils.replace( new File( base ).toURI().toURL().toString(), "file:", "" ); + assertEquals( "wrong path", s, desc.getPath() ); + assertEquals( "wrong location", s, desc.getLocation() ); + } + else + { + assertEquals( "wrong path", base, desc.getPath() ); + assertEquals( "wrong location", base, desc.getLocation() ); + } + } + + /** @throws Exception */ + @Test + public void testPathRelBase() + throws Exception + { + String base = "../msite-404"; + String path = "index.html"; + + PathDescriptor desc = new PathDescriptor( base, path ); + + assertTrue( desc.isFile() ); + assertFalse( desc.isRelative() ); + assertNotNull( desc.getBaseUrl() ); + assertNotNull( desc.getPathUrl() ); + assertNotNull( desc.getPath() ); + assertNotNull( desc.getLocation() ); + assertEquals( desc.getPath(), desc.getLocation() ); + // Hudson doesn't like this? + //assertEquals( desc.getPathUrl().toString(), desc.getBaseUrl().toString() + "/" + path ); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/java/org/apache/maven/doxia/site/decoration/inheritance/PathUtilsTest.java b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/java/org/apache/maven/doxia/site/decoration/inheritance/PathUtilsTest.java new file mode 100644 index 000000000..06130c539 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/java/org/apache/maven/doxia/site/decoration/inheritance/PathUtilsTest.java @@ -0,0 +1,80 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +/** + * + * @author ltheussl + */ +public class PathUtilsTest +{ + private static final String SLASH = File.separator; + + /** @throws Exception */ + @Test + public void testConvertPath() + throws Exception + { + PathDescriptor oldPath = new PathDescriptor( (String) null, "base" ); + PathDescriptor newPath = new PathDescriptor( "/tmp", "target" ); + assertEquals( oldPath, PathUtils.convertPath( oldPath, newPath ) ); + assertEquals( newPath, PathUtils.convertPath( newPath, oldPath ) ); + } + + /** @throws Exception */ + @Test + public void testGetRelativePath() + throws Exception + { + PathDescriptor oldPath = new PathDescriptor( "/tmp/foo", "base" ); + PathDescriptor newPath = new PathDescriptor( "/tmp", "target" ); + assertEquals( ".." + SLASH + ".." + SLASH + "target", PathUtils.getRelativePath( oldPath, newPath ) ); + + oldPath = new PathDescriptor( (String) null, "base" ); + assertNull( PathUtils.getRelativePath( oldPath, newPath ) ); + assertNull( PathUtils.getRelativePath( newPath, oldPath ) ); + + oldPath = new PathDescriptor( "/tmp/foo", null ); + assertEquals( ".." + SLASH + "target", PathUtils.getRelativePath( oldPath, newPath ) ); + assertEquals( ".." + SLASH + "foo", PathUtils.getRelativePath( newPath, oldPath ) ); + } + + /** @throws Exception */ + @Test + public void testRelativePathScpBase() + throws Exception + { + PathDescriptor oldPath = new PathDescriptor( "http://maven.apache.org/", "source" ); + PathDescriptor newPath = new PathDescriptor( "http://maven.apache.org/", "target" ); + assertEquals( "../source", PathUtils.getRelativePath( oldPath, newPath ) ); + + oldPath = new PathDescriptor( "scp://people.apache.org/", "source" ); + newPath = new PathDescriptor( "scp://people.apache.org/", "target" ); + // same with scp URLs fails?! DOXIASITETOOLS-47 + //assertEquals( "../source", PathUtils.getRelativePath( oldPath, newPath ) ); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/java/org/apache/maven/doxia/site/decoration/inheritance/URIPathDescriptorTest.java b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/java/org/apache/maven/doxia/site/decoration/inheritance/URIPathDescriptorTest.java new file mode 100644 index 000000000..d76789244 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/java/org/apache/maven/doxia/site/decoration/inheritance/URIPathDescriptorTest.java @@ -0,0 +1,294 @@ + +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * 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.net.URI; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * + * @author ltheussl + * + * @since 1.2 + */ +public class URIPathDescriptorTest +{ + private static final String BASE_URL = "http://maven.apache.org/"; + + /** + * Test of constructor, of class URIPathDescriptor. + * + * @throws Exception + */ + @Test + public void testConstructor() + throws Exception + { + final String expected = BASE_URL + "doxia"; + + final URIPathDescriptor path = new URIPathDescriptor( BASE_URL, "doxia" ); + assertEquals( expected, path.toString() ); + assertEquals( BASE_URL, path.getBaseURI().toString() ); + assertEquals( "doxia", path.getLink().toString() ); + + URIPathDescriptor compare = new URIPathDescriptor( "http://maven.apache.org", "/doxia" ); + assertEquals( expected, compare.toString() ); + + compare = new URIPathDescriptor( "http://maven.apache.org/./doxia/../", "/sub/./sub/../../doxia" ); + assertEquals( expected, compare.toString() ); + + compare = new URIPathDescriptor( "http://maven.apache.org/doxia", "" ); + assertEquals( expected + "/", compare.toString() ); + + compare = new URIPathDescriptor( "file:///C:\\Foo\\bar1", "" ); + assertEquals( "file:///C:/Foo/bar1/", compare.getBaseURI().toString() ); + // toString() calls resolve() which removes two slashes because authority is empty + assertEquals( "file:/C:/Foo/bar1/", compare.toString() ); + + compare = new URIPathDescriptor( "file:///C:/Documents%20and%20Settings/foo/", "bar" ); + assertEquals( "file:/C:/Documents%20and%20Settings/foo/bar", compare.toString() ); + + compare = new URIPathDescriptor( "file:////Users/", "user" ); + assertEquals( "file:/Users/user", compare.toString() ); + + compare = new URIPathDescriptor( "file:/C:/Documents%20and%20Settings/foo/", "bar" ); + assertEquals( "file:/C:/Documents%20and%20Settings/foo/bar", compare.toString() ); + + compare = new URIPathDescriptor( "file://C:/Documents%20and%20Settings/foo/", "bar" ); + // toString() calls resolve() which removes the colon if port is empty, C is the host here! + assertEquals( "file://C/Documents%20and%20Settings/foo/bar", compare.toString() ); + + compare = new URIPathDescriptor( "file://C:8080/Documents%20and%20Settings/foo/", "bar" ); + assertEquals( "file://C:8080/Documents%20and%20Settings/foo/bar", compare.toString() ); + + compare = new URIPathDescriptor( "C:\\Foo\\bar", "bar" ); + assertEquals( "C:/Foo/bar/bar", compare.toString() ); // NOTE: C: is the scheme here! + + assertFailure( "/doxia", BASE_URL ); + assertFailure( "file:///C:/Documents and Settings/foo/", "bar" ); + } + + /** + * Test of resolveLink method, of class URIPathDescriptor. + * + * @throws Exception + */ + @Test + public void testResolveLink() + throws Exception + { + final String expected = BASE_URL + "source"; + + URIPathDescriptor oldPath = new URIPathDescriptor( BASE_URL, "source" ); + assertEquals( expected, oldPath.resolveLink().toString() ); + + oldPath = new URIPathDescriptor( BASE_URL, "source/" ); + assertEquals( expected + "/", oldPath.resolveLink().toString() ); + + oldPath = new URIPathDescriptor( BASE_URL, "/source" ); + assertEquals( expected, oldPath.resolveLink().toString() ); + + oldPath = new URIPathDescriptor( "http://maven.apache.org", "source" ); + assertEquals( expected, oldPath.resolveLink().toString() ); + + oldPath = new URIPathDescriptor( BASE_URL, "source/index.html" ); + assertEquals( expected + "/index.html", oldPath.resolveLink().toString() ); + + oldPath = new URIPathDescriptor( BASE_URL, "source/index.html?var=foo&var2=bar" ); + assertEquals( expected + "/index.html?var=foo&var2=bar", oldPath.resolveLink().toString() ); + + oldPath = new URIPathDescriptor( "file:////Users/", "user" ); + assertEquals( "file:/Users/user", oldPath.resolveLink().toString() ); + + oldPath = new URIPathDescriptor( "file:///C:/Documents%20and%20Settings/", "source" ); + // resolve() removes two slashes because authority is empty + assertEquals( "file:/C:/Documents%20and%20Settings/source", oldPath.resolveLink().toString() ); + + oldPath = new URIPathDescriptor( "file://C:/Documents%20and%20Settings/", "source" ); + // resolve() removes the colon if port is empty + assertEquals( "file://C/Documents%20and%20Settings/source", oldPath.resolveLink().toString() ); + + oldPath = new URIPathDescriptor( "file:/C:/Documents%20and%20Settings/", "source" ); + assertEquals( "file:/C:/Documents%20and%20Settings/source", oldPath.resolveLink().toString() ); + } + + /** + * Test of rebaseLink method, of class URIPathDescriptor. + * + * @throws Exception + */ + @Test + public void testRebaseLink() + throws Exception + { + URIPathDescriptor oldPath = new URIPathDescriptor( BASE_URL, "source" ); + assertEquals( "../source", oldPath.rebaseLink( "http://maven.apache.org/doxia/" ).toString() ); + assertEquals( "http://maven.apache.org/source", oldPath.rebaseLink( null ).toString() ); + assertEquals( "http://maven.apache.org/source", + oldPath.rebaseLink( "C:/Documents and Settings/" ).toString() ); + + oldPath = new URIPathDescriptor( BASE_URL, "./" ); + assertEquals( "", oldPath.rebaseLink( "http://maven.apache.org/" ).toString() ); + + oldPath = new URIPathDescriptor( BASE_URL, "" ); + assertEquals( "", oldPath.rebaseLink( "http://maven.apache.org/" ).toString() ); + + oldPath = new URIPathDescriptor( BASE_URL, "source/index.html" ); + assertEquals( "../source/index.html", + oldPath.rebaseLink( "http://maven.apache.org/doxia/" ).toString() ); + + oldPath = new URIPathDescriptor( BASE_URL, "source/index.html?var=foo&var2=bar" ); + assertEquals( "../source/index.html?var=foo&var2=bar", + oldPath.rebaseLink( "http://maven.apache.org/doxia/" ).toString() ); + + oldPath = new URIPathDescriptor( "scp://people.apache.org/", "source" ); + assertEquals( "../source", oldPath.rebaseLink( "scp://people.apache.org/doxia" ).toString() ); + + oldPath = new URIPathDescriptor( BASE_URL, "banner/left" ); + assertEquals( "../banner/left", oldPath.rebaseLink( "http://maven.apache.org/doxia/" ).toString() ); + + oldPath = new URIPathDescriptor( BASE_URL, "index.html?var=foo&var2=bar" ); + assertEquals( "../index.html?var=foo&var2=bar", + oldPath.rebaseLink( "http://maven.apache.org/doxia/" ).toString() ); + + oldPath = new URIPathDescriptor( "http://jakarta.apache.org/", "banner/left" ); + assertEquals( "http://jakarta.apache.org/banner/left", oldPath.rebaseLink( BASE_URL ).toString() ); + + oldPath = new URIPathDescriptor( "file:////Users/", "user" ); + assertEquals( "../user", oldPath.rebaseLink( "file:////Users/target" ).toString() ); + assertEquals( "../user", oldPath.rebaseLink( "file:/Users/target" ).toString() ); + + oldPath = new URIPathDescriptor( "file:///C:/Documents%20and%20Settings/", "source" ); + assertEquals( "../source", + oldPath.rebaseLink( "file:///C:/Documents%20and%20Settings/target" ).toString() ); + + oldPath = new URIPathDescriptor( "file://C:/Documents%20and%20Settings/", "source" ); + assertEquals( "../source", + oldPath.rebaseLink( "file://C:/Documents%20and%20Settings/target" ).toString() ); + + oldPath = new URIPathDescriptor( "file:/C:/Documents%20and%20Settings/", "source" ); + assertEquals( "../source", + oldPath.rebaseLink( "file:/C:/Documents%20and%20Settings/target" ).toString() ); + } + + /** + * Test of relativizeLink method, of class URIPathDescriptor. + * + * @throws Exception + */ + @Test + public void testRelativizeLink() + throws Exception + { + URIPathDescriptor path = new URIPathDescriptor( BASE_URL, "source" ); + assertEquals( "source", path.relativizeLink().toString() ); + + path = new URIPathDescriptor( BASE_URL, "http://maven.apache.org/source" ); + assertEquals( "source", path.relativizeLink().toString() ); + + path = new URIPathDescriptor( BASE_URL, "http://maven.apache.org/" ); + assertEquals( "./", path.relativizeLink().toString() ); + + path = new URIPathDescriptor( BASE_URL, "http://maven.apache.org" ); + assertEquals( "./", path.relativizeLink().toString() ); + + path = new URIPathDescriptor( "http://maven.apache.org", BASE_URL ); + assertEquals( "./", path.relativizeLink().toString() ); + + path = new URIPathDescriptor( "http://maven.apache.org", "http://maven.apache.org" ); + assertEquals( "./", path.relativizeLink().toString() ); + + path = new URIPathDescriptor( "http://maven.apache.org/doxia/", "http://maven.apache.org/source/" ); + assertEquals( "../source/", path.relativizeLink().toString() ); + + path = new URIPathDescriptor( "http://maven.apache.org/doxia", "http://maven.apache.org/source" ); + assertEquals( "../source", path.relativizeLink().toString() ); + + path = new URIPathDescriptor( BASE_URL, "http://maven.apache.org/index.html" ); + assertEquals( "index.html", path.relativizeLink().toString() ); + + path = new URIPathDescriptor( BASE_URL, "http://maven.apache.org/index.html?var=foo&var2=bar" ); + assertEquals( "index.html?var=foo&var2=bar", path.relativizeLink().toString() ); + + path = new URIPathDescriptor( "file:////Users/", "index.html" ); + assertEquals( "index.html", path.relativizeLink().toString() ); + + path = new URIPathDescriptor( "file:///C:/Documents%20and%20Settings/", "index.html" ); + assertEquals( "index.html", path.relativizeLink().toString() ); + + path = new URIPathDescriptor( "file://C:/Documents%20and%20Settings/", "index.html" ); + assertEquals( "index.html", path.relativizeLink().toString() ); + + path = new URIPathDescriptor( "file:/C:/Documents%20and%20Settings/", "index.html" ); + assertEquals( "index.html", path.relativizeLink().toString() ); + } + + /** + * Test of sameSite method, of class URIPathDescriptor. + * + * @throws Exception + */ + @Test + public void testSameSite() + throws Exception + { + final URIPathDescriptor path = new URIPathDescriptor( BASE_URL, "doxia" ); + + assertTrue( path.sameSite( new URI( "http://maven.apache.org/" ) ) ); + assertTrue( path.sameSite( new URI( "http://maven.apache.org" ) ) ); + assertTrue( path.sameSite( new URI( "HTTP://maven.apache.org/" ) ) ); + assertTrue( path.sameSite( new URI( "http://MAVEN.apache.org/" ) ) ); + assertTrue( path.sameSite( new URI( "http://maven.apache.org/wagon/index.html" ) ) ); + + assertFalse( path.sameSite( null ) ); + assertFalse( path.sameSite( new URI( "https://maven.apache.org/" ) ) ); + assertFalse( path.sameSite( new URI( "http://ant.apache.org/" ) ) ); + assertFalse( path.sameSite( new URI( "http://maven.apache.org:80" ) ) ); + assertFalse( path.sameSite( new URI( "/usr/share/bin/" ) ) ); + assertFalse( path.sameSite( new URI( "http:///maven.apache.org/" ) ) ); + + final URIPathDescriptor nullHost = new URIPathDescriptor( "http:///maven.apache.org/", "doxia" ); + assertTrue( nullHost.sameSite( new URI( "http:///maven.apache.org/" ) ) ); + assertFalse( nullHost.sameSite( new URI( "http://maven.apache.org/" ) ) ); + + URIPathDescriptor newPath = new URIPathDescriptor( "file:///C:/Documents%20and%20Settings/", "source" ); + assertTrue( newPath.sameSite( new URI( "file:///C:/Documents%20and%20Settings/" ) ) ); + assertFalse( newPath.sameSite( new URI( "file://C:/Documents%20and%20Settings/" ) ) ); + // authority is empty + assertTrue( newPath.sameSite( new URI( "file:/C:/Documents%20and%20Settings/" ) ) ); + } + + private static void assertFailure( final String base, final String link ) + { + try + { + final URIPathDescriptor test = new URIPathDescriptor( base, link ); + fail( "Should fail: " + test.toString() ); + } + catch ( IllegalArgumentException ex ) + { + assertNotNull( ex ); + } + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/empty.xml b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/empty.xml new file mode 100644 index 000000000..182b9dd2e --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/empty.xml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/external-urls.xml b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/external-urls.xml new file mode 100644 index 000000000..0791a9672 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/external-urls.xml @@ -0,0 +1,54 @@ + + + + + + + The Jakarta Project + http://jakarta.apache.org/images/jakarta-logo.gif + http://jakarta.apache.org/ + + + Jakarta Commons Sandbox + http://jakarta.apache.org/commons/images/logo.png + http://jakarta.apache.org/commons/sandbox + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/fully-populated-child.xml b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/fully-populated-child.xml new file mode 100644 index 000000000..09208b71c --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/fully-populated-child.xml @@ -0,0 +1,69 @@ + + + + + + + name + src + href + + + name + src + href + + + + + + + + + + + org.apache.maven.skins + maven-default-skin + + + + + + ]]> + + + + + + + + + + + + + + junk + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/fully-populated-merged.xml b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/fully-populated-merged.xml new file mode 100644 index 000000000..47e68581b --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/fully-populated-merged.xml @@ -0,0 +1,64 @@ + + + + + + + name + ../src + ../href + + + name + ../src + ../href + + + + + + + + + + + org.apache.maven.skins + maven-default-skin + + + + + + ]]> + + + + + + + + + junk + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/fully-populated-unresolved.xml b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/fully-populated-unresolved.xml new file mode 100644 index 000000000..0f9c66daa --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/fully-populated-unresolved.xml @@ -0,0 +1,64 @@ + + + + + + + name + ../src + ../href + + + name + ../src + ../href + + + + + + + + + + + org.apache.maven.skins + maven-default-skin + + + + + + ]]> + + + + + + + + + junk + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/inheritance-child-no-inheritance.xml b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/inheritance-child-no-inheritance.xml new file mode 100644 index 000000000..64c545c29 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/inheritance-child-no-inheritance.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/inheritance-child.xml b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/inheritance-child.xml new file mode 100644 index 000000000..30482a8ca --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/inheritance-child.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/inheritance-expected.xml b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/inheritance-expected.xml new file mode 100644 index 000000000..355e775bb --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/inheritance-expected.xml @@ -0,0 +1,69 @@ + + + + + + + The Jakarta Project + http://jakarta.apache.org/ + + + Jakarta Commons Sandbox + http://jakarta.apache.org/commons/images/logo.png + http://jakarta.apache.org/commons/sandbox + + + + + + org.apache.maven.skins + maven-default-skin + + ${project.scm.url} + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/inheritance-parent.xml b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/inheritance-parent.xml new file mode 100644 index 000000000..8965e2b59 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/inheritance-parent.xml @@ -0,0 +1,64 @@ + + + + + + + The Jakarta Project + http://jakarta.apache.org/ + + + Jakarta Commons Sandbox + http://jakarta.apache.org/commons/images/logo.png + http://jakarta.apache.org/commons/sandbox + + + + + + org.apache.maven.skins + maven-default-skin + + ${project.scm.url} + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/relative-urls-resolved.xml b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/relative-urls-resolved.xml new file mode 100644 index 000000000..5493373a3 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/relative-urls-resolved.xml @@ -0,0 +1,53 @@ + + + + + + + The Jakarta Project + images/jakarta-logo.gif + banner/left + + + Jakarta Commons Sandbox + commons/images/logo.png + banner/right/ + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/relative-urls.xml b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/relative-urls.xml new file mode 100644 index 000000000..8353486a7 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/relative-urls.xml @@ -0,0 +1,53 @@ + + + + + + + The Jakarta Project + /images/jakarta-logo.gif + banner/left + + + Jakarta Commons Sandbox + commons/images/logo.png + /banner/right/ + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/subsite-relative-urls-multiple-resolved.xml b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/subsite-relative-urls-multiple-resolved.xml new file mode 100644 index 000000000..71b50e408 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/subsite-relative-urls-multiple-resolved.xml @@ -0,0 +1,53 @@ + + + + + + + The Jakarta Project + ../../images/jakarta-logo.gif + ../left + + + Jakarta Commons Sandbox + ../../commons/images/logo.png + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/subsite-relative-urls-resolved.xml b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/subsite-relative-urls-resolved.xml new file mode 100644 index 000000000..68d894d07 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/subsite-relative-urls-resolved.xml @@ -0,0 +1,53 @@ + + + + + + + The Jakarta Project + ../images/jakarta-logo.gif + ../banner/left + + + Jakarta Commons Sandbox + ../commons/images/logo.png + ../banner/right/ + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/subsite-urls-file.xml b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/subsite-urls-file.xml new file mode 100644 index 000000000..de3c78a7a --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/subsite-urls-file.xml @@ -0,0 +1,53 @@ + + + + + + + The Jakarta Project + file:///www/maven.apache.org/images/jakarta-logo.gif + file:///www/maven.apache.org/banner/left + + + Jakarta Commons Sandbox + file:///www/maven.apache.org/commons/images/logo.png + file:///www/maven.apache.org/banner/right/ + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/subsite-urls.xml b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/subsite-urls.xml new file mode 100644 index 000000000..5452755ce --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-decoration-model/src/test/resources/subsite-urls.xml @@ -0,0 +1,53 @@ + + + + + + + The Jakarta Project + http://maven.apache.org/images/jakarta-logo.gif + http://maven.apache.org/banner/left + + + Jakarta Commons Sandbox + http://maven.apache.org/commons/images/logo.png + http://maven.apache.org/banner/right/ + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/pom.xml b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/pom.xml new file mode 100644 index 000000000..ae553874b --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/pom.xml @@ -0,0 +1,169 @@ + + + + + + 4.0.0 + + + doxia-sitetools + org.apache.maven.doxia + 1.9.3-SNAPSHOT + ../pom.xml + + + doxia-doc-renderer + + Doxia Sitetools :: Document Renderer + The Document Renderer handles the rendering of documents, in formats like PDF and RTF. + + + + + org.apache.maven.doxia + doxia-core + + + org.apache.maven.doxia + doxia-logging-api + + + org.apache.maven.doxia + doxia-sink-api + + + org.apache.maven.doxia + doxia-module-itext + ${doxiaVersion} + + + org.apache.maven.doxia + doxia-module-fo + ${doxiaVersion} + + + + org.apache.maven.doxia + doxia-module-apt + runtime + + + org.apache.maven.doxia + doxia-module-fml + runtime + + + org.apache.maven.doxia + doxia-module-xdoc + runtime + + + org.apache.maven.doxia + doxia-module-xhtml + runtime + + + org.apache.maven.doxia + doxia-module-markdown + runtime + + + + + org.codehaus.plexus + plexus-component-annotations + + + org.codehaus.plexus + plexus-container-default + + + org.codehaus.plexus + plexus-utils + + + org.codehaus.plexus + plexus-velocity + 1.2 + + + org.codehaus.plexus + plexus-component-api + + + + + + + org.apache.velocity + velocity + + + + + xalan + xalan + 2.7.2 + + + xml-apis + xml-apis + 2.0.2 + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + + java.awt.headless + true + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + enforce-bytecode-version + + + + + org.apache.maven.doxia:doxia-module-markdown + org.nibor.autolink:autolink + + + + + + + + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/AbstractDocumentRenderer.java b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/AbstractDocumentRenderer.java new file mode 100644 index 000000000..bc9f7e549 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/AbstractDocumentRenderer.java @@ -0,0 +1,727 @@ +package org.apache.maven.doxia.docrenderer; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.apache.maven.doxia.Doxia; +import org.apache.maven.doxia.document.DocumentModel; +import org.apache.maven.doxia.document.io.xpp3.DocumentXpp3Reader; +import org.apache.maven.doxia.sink.Sink; +import org.apache.maven.doxia.parser.ParseException; +import org.apache.maven.doxia.parser.Parser; +import org.apache.maven.doxia.parser.manager.ParserNotFoundException; +import org.apache.maven.doxia.logging.PlexusLoggerWrapper; +import org.apache.maven.doxia.parser.module.ParserModule; +import org.apache.maven.doxia.parser.module.ParserModuleManager; +import org.apache.maven.doxia.util.XmlValidator; + +import org.apache.velocity.VelocityContext; +import org.apache.velocity.context.Context; + +import org.codehaus.plexus.component.annotations.Requirement; +import org.codehaus.plexus.logging.AbstractLogEnabled; + +import org.codehaus.plexus.util.DirectoryScanner; +import org.codehaus.plexus.util.FileUtils; +import org.codehaus.plexus.util.IOUtil; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.xml.XmlStreamReader; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; +import org.codehaus.plexus.velocity.SiteResourceLoader; +import org.codehaus.plexus.velocity.VelocityComponent; + +/** + * Abstract document renderer. + * + * @author Vincent Siveton + * @author ltheussl + * @since 1.1 + */ +public abstract class AbstractDocumentRenderer + extends AbstractLogEnabled + implements DocumentRenderer +{ + @Requirement + protected ParserModuleManager parserModuleManager; + + @Requirement + protected Doxia doxia; + + @Requirement + private VelocityComponent velocity; + + /** + * The common base directory of source files. + */ + private String baseDir; + + //-------------------------------------------- + // + //-------------------------------------------- + + /** + * Render an aggregate document from the files found in a Map. + * + * @param filesToProcess the Map of Files to process. The Map should contain as keys the paths of the + * source files (relative to {@link #getBaseDir() baseDir}), and the corresponding ParserModule as values. + * @param outputDirectory the output directory where the aggregate document should be generated. + * @param documentModel the document model, containing all the metadata, etc. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + * @throws java.io.IOException if any + * @deprecated since 1.1.2, use {@link #render(Map, File, DocumentModel, DocumentRendererContext)} + */ + public abstract void render( Map filesToProcess, File outputDirectory, + DocumentModel documentModel ) + throws DocumentRendererException, IOException; + + //-------------------------------------------- + // + //-------------------------------------------- + + /** {@inheritDoc} */ + public void render( Collection files, File outputDirectory, DocumentModel documentModel ) + throws DocumentRendererException, IOException + { + render( getFilesToProcess( files ), outputDirectory, documentModel, null ); + } + + /** {@inheritDoc} */ + public void render( File baseDirectory, File outputDirectory, DocumentModel documentModel ) + throws DocumentRendererException, IOException + { + render( baseDirectory, outputDirectory, documentModel, null ); + } + + /** + * Render an aggregate document from the files found in a Map. + * + * @param filesToProcess the Map of Files to process. The Map should contain as keys the paths of the + * source files (relative to {@link #getBaseDir() baseDir}), and the corresponding ParserModule as values. + * @param outputDirectory the output directory where the aggregate document should be generated. + * @param documentModel the document model, containing all the metadata, etc. + * @param context the rendering context when processing files. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + * @throws java.io.IOException if any + */ + public void render( Map filesToProcess, File outputDirectory, DocumentModel documentModel, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + // nop + } + + /** + * Render a document from the files found in a source directory, depending on a rendering context. + * + * @param baseDirectory the directory containing the source files. + * This should follow the standard Maven convention, ie containing all the site modules. + * @param outputDirectory the output directory where the document should be generated. + * @param documentModel the document model, containing all the metadata, etc. + * If the model contains a TOC, only the files found in this TOC are rendered, + * otherwise all files found under baseDirectory will be processed. + * If the model is null, render all files from baseDirectory individually. + * @param context the rendering context when processing files. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + * @throws java.io.IOException if any + * @since 1.1.2 + */ + public void render( File baseDirectory, File outputDirectory, DocumentModel documentModel, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + render( getFilesToProcess( baseDirectory ), outputDirectory, documentModel, context ); + } + + /** + * Render a document from the files found in baseDirectory. This just forwards to + * {@link #render(File,File,DocumentModel)} with a new DocumentModel. + * + * @param baseDirectory the directory containing the source files. + * This should follow the standard Maven convention, ie containing all the site modules. + * @param outputDirectory the output directory where the document should be generated. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + * @throws java.io.IOException if any + * @see #render(File, File, DocumentModel) + */ + public void render( File baseDirectory, File outputDirectory ) + throws DocumentRendererException, IOException + { + render( baseDirectory, outputDirectory, (DocumentModel) null ); + } + + /** + * Render a document from the files found in baseDirectory. + * + * @param baseDirectory the directory containing the source files. + * This should follow the standard Maven convention, ie containing all the site modules. + * @param outputDirectory the output directory where the document should be generated. + * @param documentDescriptor a file containing the document model. + * If this file does not exist or is null, some default settings will be used. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + * @throws java.io.IOException if any + * @see #render(File, File) if documentDescriptor does not exist or is null + * @see #render(Map, File, DocumentModel) otherwise + */ + public void render( File baseDirectory, File outputDirectory, File documentDescriptor ) + throws DocumentRendererException, IOException + { + if ( ( documentDescriptor == null ) || ( !documentDescriptor.exists() ) ) + { + getLogger().warn( "No documentDescriptor found: using default settings!" ); + + render( baseDirectory, outputDirectory ); + } + else + { + render( getFilesToProcess( baseDirectory ), outputDirectory, readDocumentModel( documentDescriptor ), + null ); + } + } + + /** + * Render documents separately for each file found in a Map. + * + * @param filesToProcess the Map of Files to process. The Map should contain as keys the paths of the + * source files (relative to {@link #getBaseDir() baseDir}), and the corresponding ParserModule as values. + * @param outputDirectory the output directory where the documents should be generated. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + * @throws java.io.IOException if any + * @since 1.1.1 + * @deprecated since 1.1.2, use {@link #renderIndividual(Map, File, DocumentRendererContext)} + */ + public void renderIndividual( Map filesToProcess, File outputDirectory ) + throws DocumentRendererException, IOException + { + // nop + } + + /** + * Render documents separately for each file found in a Map. + * + * @param filesToProcess the Map of Files to process. The Map should contain as keys the paths of the + * source files (relative to {@link #getBaseDir() baseDir}), and the corresponding ParserModule as values. + * @param outputDirectory the output directory where the documents should be generated. + * @param context the rendering context. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + * @throws java.io.IOException if any + * @since 1.1.2 + */ + public void renderIndividual( Map filesToProcess, File outputDirectory, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + // nop + } + + /** + * Returns a Map of files to process. The Map contains as keys the paths of the source files + * (relative to {@link #getBaseDir() baseDir}), and the corresponding ParserModule as values. + * + * @param baseDirectory the directory containing the source files. + * This should follow the standard Maven convention, ie containing all the site modules. + * @return a Map of files to process. + * @throws java.io.IOException in case of a problem reading the files under baseDirectory. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + */ + public Map getFilesToProcess( File baseDirectory ) + throws IOException, DocumentRendererException + { + if ( !baseDirectory.isDirectory() ) + { + getLogger().warn( "No files found to process!" ); + + return new HashMap(); + } + + setBaseDir( baseDirectory.getAbsolutePath() ); + + Map filesToProcess = new LinkedHashMap(); + Map duplicatesFiles = new LinkedHashMap(); + + Collection modules = parserModuleManager.getParserModules(); + for ( ParserModule module : modules ) + { + File moduleBasedir = new File( baseDirectory, module.getSourceDirectory() ); + + if ( moduleBasedir.exists() ) + { + // TODO: handle in/excludes + List allFiles = FileUtils.getFileNames( moduleBasedir, "**/*.*", null, false ); + + String[] extensions = getExtensions( module ); + List docs = new LinkedList( allFiles ); + // Take care of extension case + for ( Iterator it = docs.iterator(); it.hasNext(); ) + { + String name = it.next().trim(); + + if ( !endsWithIgnoreCase( name, extensions ) ) + { + it.remove(); + } + } + + String[] vmExtensions = new String[extensions.length]; + for ( int i = 0; i < extensions.length; i++ ) + { + vmExtensions[i] = extensions[i] + ".vm"; + } + List velocityFiles = new LinkedList( allFiles ); + // *.xml.vm + for ( Iterator it = velocityFiles.iterator(); it.hasNext(); ) + { + String name = it.next().trim(); + + if ( !endsWithIgnoreCase( name, vmExtensions ) ) + { + it.remove(); + } + } + docs.addAll( velocityFiles ); + + for ( String filePath : docs ) + { + filePath = filePath.trim(); + + if ( filePath.lastIndexOf( '.' ) > 0 ) + { + String key = filePath.substring( 0, filePath.lastIndexOf( '.' ) ); + + if ( duplicatesFiles.containsKey( key ) ) + { + throw new DocumentRendererException( "Files '" + module.getSourceDirectory() + + File.separator + filePath + "' clashes with existing '" + + duplicatesFiles.get( key ) + "'." ); + } + + duplicatesFiles.put( key, module.getSourceDirectory() + File.separator + filePath ); + } + + filesToProcess.put( filePath, module ); + } + } + } + + return filesToProcess; + } + + protected static String[] getExtensions( ParserModule module ) + { + String[] extensions = new String[module.getExtensions().length]; + for ( int i = module.getExtensions().length - 1; i >= 0; i-- ) + { + extensions[i] = '.' + module.getExtensions()[i]; + } + return extensions; + } + + // TODO replace with StringUtils.endsWithIgnoreCase() from maven-shared-utils 0.7 + protected 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() ); + } + + protected static boolean endsWithIgnoreCase( String str, String[] searchStrs ) + { + for ( String searchStr : searchStrs ) + { + if ( endsWithIgnoreCase( str, searchStr ) ) + { + return true; + } + } + return false; + } + + /** + * Returns a Map of files to process. The Map contains as keys the paths of the source files + * (relative to {@link #getBaseDir() baseDir}), and the corresponding ParserModule as values. + * + * @param files The Collection of source files. + * @return a Map of files to process. + */ + public Map getFilesToProcess( Collection files ) + { + // ---------------------------------------------------------------------- + // Map all the file names to parser ids + // ---------------------------------------------------------------------- + + Map filesToProcess = new HashMap(); + + Collection modules = parserModuleManager.getParserModules(); + for ( ParserModule module : modules ) + { + String[] extensions = getExtensions( module ); + + String sourceDirectory = File.separator + module.getSourceDirectory() + File.separator; + + for ( String file : files ) + { + // first check if the file path contains one of the recognized source dir identifiers + // (there's trouble if a pathname contains 2 identifiers), then match file extensions (not unique). + + if ( file.indexOf( sourceDirectory ) != -1 ) + { + filesToProcess.put( file, module ); + } + else + { + // don't overwrite if it's there already + if ( endsWithIgnoreCase( file, extensions ) && !filesToProcess.containsKey( file ) ) + { + filesToProcess.put( file, module ); + } + } + } + } + + return filesToProcess; + } + + /** {@inheritDoc} */ + public DocumentModel readDocumentModel( File documentDescriptor ) + throws DocumentRendererException, IOException + { + DocumentModel documentModel; + + Reader reader = null; + try + { + reader = ReaderFactory.newXmlReader( documentDescriptor ); + documentModel = new DocumentXpp3Reader().read( reader ); + } + catch ( XmlPullParserException e ) + { + throw new DocumentRendererException( "Error parsing document descriptor", e ); + } + finally + { + IOUtil.close( reader ); + } + + return documentModel; + } + + /** + * Sets the current base directory. + * + * @param newDir the absolute path to the base directory to set. + */ + public void setBaseDir( String newDir ) + { + this.baseDir = newDir; + } + + /** + * Return the current base directory. + * + * @return the current base directory. + */ + public String getBaseDir() + { + return this.baseDir; + } + + //-------------------------------------------- + // + //-------------------------------------------- + + /** + * Parse a source document into a sink. + * + * @param fullDocPath absolute path to the source document. + * @param parserId determines the parser to use. + * @param sink the sink to receive the events. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException in case of a parsing error. + * @throws java.io.IOException if the source document cannot be opened. + * @deprecated since 1.1.2, use {@link #parse(String, String, Sink, DocumentRendererContext)} + */ + protected void parse( String fullDocPath, String parserId, Sink sink ) + throws DocumentRendererException, IOException + { + parse( fullDocPath, parserId, sink, null ); + } + + /** + * Parse a source document into a sink. + * + * @param fullDocPath absolute path to the source document. + * @param parserId determines the parser to use. + * @param sink the sink to receive the events. + * @param context the rendering context. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException in case of a parsing error. + * @throws java.io.IOException if the source document cannot be opened. + */ + protected void parse( String fullDocPath, String parserId, Sink sink, DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Parsing file " + fullDocPath ); + } + + Reader reader = null; + try + { + File f = new File( fullDocPath ); + + Parser parser = doxia.getParser( parserId ); + switch ( parser.getType() ) + { + case Parser.XML_TYPE: + reader = ReaderFactory.newXmlReader( f ); + + if ( isVelocityFile( f ) ) + { + reader = getVelocityReader( f, ( (XmlStreamReader) reader ).getEncoding(), context ); + } + if ( context != null && Boolean.TRUE.equals( (Boolean) context.get( "validate" ) ) ) + { + reader = validate( reader, fullDocPath ); + } + break; + + case Parser.TXT_TYPE: + case Parser.UNKNOWN_TYPE: + default: + if ( isVelocityFile( f ) ) + { + reader = + getVelocityReader( f, ( context == null ? ReaderFactory.FILE_ENCODING + : context.getInputEncoding() ), context ); + } + else + { + if ( context == null ) + { + reader = ReaderFactory.newPlatformReader( f ); + } + else + { + reader = ReaderFactory.newReader( f, context.getInputEncoding() ); + } + } + } + + sink.enableLogging( new PlexusLoggerWrapper( getLogger() ) ); + + doxia.parse( reader, parserId, sink ); + } + catch ( ParserNotFoundException e ) + { + throw new DocumentRendererException( "No parser '" + parserId + + "' found for " + fullDocPath + ": " + e.getMessage(), e ); + } + catch ( ParseException e ) + { + throw new DocumentRendererException( "Error parsing " + fullDocPath + ": " + e.getMessage(), e ); + } + finally + { + IOUtil.close( reader ); + + sink.flush(); + } + } + + /** + * Copies the contents of the resource directory to an output folder. + * + * @param outputDirectory the destination folder. + * @throws java.io.IOException if any. + */ + protected void copyResources( File outputDirectory ) + throws IOException + { + File resourcesDirectory = new File( getBaseDir(), "resources" ); + + if ( !resourcesDirectory.isDirectory() ) + { + return; + } + + if ( !outputDirectory.exists() ) + { + outputDirectory.mkdirs(); + } + + copyDirectory( resourcesDirectory, outputDirectory ); + } + + /** + * Copy content of a directory, excluding scm-specific files. + * + * @param source directory that contains the files and sub-directories to be copied. + * @param destination destination folder. + * @throws java.io.IOException if any. + */ + protected void copyDirectory( File source, File destination ) + throws IOException + { + if ( source.isDirectory() && destination.isDirectory() ) + { + DirectoryScanner scanner = new DirectoryScanner(); + + String[] includedResources = {"**/**"}; + + scanner.setIncludes( includedResources ); + + scanner.addDefaultExcludes(); + + scanner.setBasedir( source ); + + scanner.scan(); + + List includedFiles = Arrays.asList( scanner.getIncludedFiles() ); + + for ( String name : includedFiles ) + { + File sourceFile = new File( source, name ); + + File destinationFile = new File( destination, name ); + + FileUtils.copyFile( sourceFile, destinationFile ); + } + } + } + + /** + * @param documentModel not null + * @return the output name defined in the documentModel without the output extension. If the output name is not + * defined, return target by default. + * @since 1.1.1 + * @see org.apache.maven.doxia.document.DocumentModel#getOutputName() + * @see #getOutputExtension() + */ + protected String getOutputName( DocumentModel documentModel ) + { + String outputName = documentModel.getOutputName(); + if ( outputName == null ) + { + getLogger().info( "No outputName is defined in the document descriptor. Using 'target'" ); + + documentModel.setOutputName( "target" ); + } + + outputName = outputName.trim(); + if ( outputName.toLowerCase( Locale.ENGLISH ).endsWith( "." + getOutputExtension() ) ) + { + outputName = + outputName.substring( 0, outputName.toLowerCase( Locale.ENGLISH ) + .lastIndexOf( "." + getOutputExtension() ) ); + } + documentModel.setOutputName( outputName ); + + return documentModel.getOutputName(); + } + + /** + * TODO: DOXIA-111: we need a general filter here that knows how to alter the context + * + * @param f the file to process, not null + * @param encoding the wanted encoding, not null + * @param context the current render document context not null + * @return a reader with + * @throws DocumentRendererException + */ + private Reader getVelocityReader( File f, String encoding, DocumentRendererContext context ) + throws DocumentRendererException + { + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Velocity render for " + f.getAbsolutePath() ); + } + + SiteResourceLoader.setResource( f.getAbsolutePath() ); + + Context velocityContext = new VelocityContext(); + + if ( context.getKeys() != null ) + { + for ( int i = 0; i < context.getKeys().length; i++ ) + { + String key = (String) context.getKeys()[i]; + + velocityContext.put( key, context.get( key ) ); + } + } + + StringWriter sw = new StringWriter(); + try + { + velocity.getEngine().mergeTemplate( f.getAbsolutePath(), encoding, velocityContext, sw ); + } + catch ( Exception e ) + { + throw new DocumentRendererException( "Error whenn parsing Velocity file " + f.getAbsolutePath() + ": " + + e.getMessage(), e ); + } + + return new StringReader( sw.toString() ); + } + + /** + * @param f not null + * @return true if file has a vm extension, false otherwise. + */ + private static boolean isVelocityFile( File f ) + { + return FileUtils.getExtension( f.getAbsolutePath() ).toLowerCase( Locale.ENGLISH ).endsWith( "vm" ); + } + + 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 ); + } + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/DocRenderer.java b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/DocRenderer.java new file mode 100644 index 000000000..6bc737a85 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/DocRenderer.java @@ -0,0 +1,65 @@ +package org.apache.maven.doxia.docrenderer; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.IOException; + +/** + * Base renderer interface for the document + * + * @author Vincent Siveton + * @deprecated Since 1.1, use {@link DocumentRenderer} instead. + */ +public interface DocRenderer +{ + /** Plexus lookup. */ + String ROLE = DocRenderer.class.getName(); + + /** + * Render all files from a site directory to an output directory + * + * @param siteDirectory the input directory contains files to be generated + * @param outputDirectory the output directory where files are generated + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + * @throws java.io.IOException if any + */ + void render( File siteDirectory, File outputDirectory ) + throws DocumentRendererException, IOException; + + /** + * Render a document depending a context and a document descriptor + * + * @param siteDirectory the input directory contains files to be generated + * @param outputDirectory the output directory where file are generated + * @param documentDescriptor the document descriptor + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + * @throws java.io.IOException if any + */ + void render( File siteDirectory, File outputDirectory, File documentDescriptor ) + throws DocumentRendererException, IOException; + + /** + * Get the output extension supported + * + * @return the ouput extension supported + */ + String getOutputExtension(); +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/DocumentRenderer.java b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/DocumentRenderer.java new file mode 100644 index 000000000..6f151c52f --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/DocumentRenderer.java @@ -0,0 +1,109 @@ +package org.apache.maven.doxia.docrenderer; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.IOException; +import java.util.Collection; + +import org.apache.maven.doxia.document.DocumentModel; + +/** + * Base interface for rendering documents from a set of input files. + * + * @author Vincent Siveton + * @author ltheussl + * @since 1.1 + */ +public interface DocumentRenderer +{ + /** Plexus lookup role. */ + String ROLE = DocumentRenderer.class.getName(); + + /** + * Render a document from a set of files, depending on a rendering context. + * + * @param files the path name Strings (relative to a common base directory) + * of files to include in the document generation. + * @param outputDirectory the output directory where the document should be generated. + * @param documentModel the document model, containing all the metadata, etc. + * If the model contains a TOC, only the files found in this TOC are rendered, + * otherwise all files from the Collection of files will be processed. + * If the model is null, render all files individually. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any. + * @throws java.io.IOException if any. + */ + void render( Collection files, File outputDirectory, DocumentModel documentModel ) + throws DocumentRendererException, IOException; + + /** + * Render a document from the files found in a source directory, depending on a rendering context. + * + * @param baseDirectory the directory containing the source files. + * This should follow the standard Maven convention, ie containing all the site modules. + * @param outputDirectory the output directory where the document should be generated. + * @param documentModel the document model, containing all the metadata, etc. + * If the model contains a TOC, only the files found in this TOC are rendered, + * otherwise all files found under baseDirectory will be processed. + * If the model is null, render all files from baseDirectory individually. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + * @throws java.io.IOException if any +// * @deprecated since 1.1.2, use {@link #render(File, File, DocumentModel, DocumentRendererContext)} + */ + void render( File baseDirectory, File outputDirectory, DocumentModel documentModel ) + throws DocumentRendererException, IOException; + +// /** +// * Render a document from the files found in a source directory, depending on a rendering context. +// * +// * @param baseDirectory the directory containing the source files. +// * This should follow the standard Maven convention, ie containing all the site modules. +// * @param outputDirectory the output directory where the document should be generated. +// * @param documentModel the document model, containing all the metadata, etc. +// * If the model contains a TOC, only the files found in this TOC are rendered, +// * otherwise all files found under baseDirectory will be processed. +// * If the model is null, render all files from baseDirectory individually. +// * @param context the rendering context when processing files. +// * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any +// * @throws java.io.IOException if any +// * @since 1.1.2 +// */ +// void render( File baseDirectory, File outputDirectory, DocumentModel documentModel, +// DocumentRendererContext context ) +// throws DocumentRendererException, IOException; + + /** + * Read a document model from a file. + * + * @param documentDescriptor a document descriptor file that contains the document model. + * @return the document model, containing all the metadata, etc. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + * @throws java.io.IOException if any + */ + DocumentModel readDocumentModel( File documentDescriptor ) + throws DocumentRendererException, IOException; + + /** + * Get the output extension associated with this DocumentRenderer. + * + * @return the ouput extension. + */ + String getOutputExtension(); +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/DocumentRendererContext.java b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/DocumentRendererContext.java new file mode 100644 index 000000000..4610637c8 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/DocumentRendererContext.java @@ -0,0 +1,140 @@ +package org.apache.maven.doxia.docrenderer; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.util.HashMap; +import java.util.Map; + +import org.codehaus.plexus.util.ReaderFactory; + +/** + * Context when processing Velocity files using a {@link java.util.HashMap} for data storage. + * + * @author Vincent Siveton + * @since 1.1.2 + */ +public class DocumentRendererContext +{ + private String inputEncoding = ReaderFactory.UTF_8; + + /** + * Storage for key/value pairs. + */ + private final Map context; + + /** + * Default constructor. + */ + public DocumentRendererContext() + { + context = new HashMap(); + } + + /** + * @return The input encoding when processing files. + */ + public String getInputEncoding() + { + return inputEncoding; + } + + /** + * @param inputEncoding new input encoding value when processing files. + */ + public void setInputEncoding( String inputEncoding ) + { + this.inputEncoding = inputEncoding; + } + + /** + * Adds a name/value pair to the context. + * + * @param key The name to key the provided value with. + * @param value The corresponding value. + * @return Object that was replaced in the the Context if applicable or null if not. + */ + public Object put( String key, Object value ) + { + if ( key == null ) + { + return null; + } + + return context.put( key, value ); + } + + /** + * Gets the value corresponding to the provided key from the context. + * + * @param key The name of the desired value. + * @return The value corresponding to the provided key or null if the key param is null. + */ + public Object get( String key ) + { + if ( key == null ) + { + return null; + } + + return context.get( key ); + } + + /** + * Indicates whether the specified key is in the context. + * + * @param key The key to look for. + * @return true if the key is in the context, false if not. + */ + public boolean containsKey( Object key ) + { + if ( !( key instanceof String ) ) // this includes null check + { + return false; + } + + return context.containsKey( key.toString() ); + } + + /** + * Get all the keys for the values in the context + * + * @return Object[] of keys in the Context. + */ + public Object[] getKeys() + { + return context.keySet().toArray(); + } + + /** + * Removes the value associated with the specified key from the context. + * + * @param key The name of the value to remove. + * @return The value that the key was mapped to, or null if unmapped. + */ + public Object remove( Object key ) + { + if ( !( key instanceof String ) ) // this includes null check + { + return null; + } + + return context.remove( key.toString() ); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/DocumentRendererException.java b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/DocumentRendererException.java new file mode 100644 index 000000000..b4a91607e --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/DocumentRendererException.java @@ -0,0 +1,54 @@ +package org.apache.maven.doxia.docrenderer; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * A document renderer exception + * + * @author Vincent Siveton + * @since 1.1 + */ +public class DocumentRendererException + extends Exception +{ + /** serialVersionUID */ + static final long serialVersionUID = 295967936746221567L; + + /** + * Default constructor. + * + * @param message An error message. + */ + public DocumentRendererException( String message ) + { + super( message ); + } + + /** + * Other constructor. + * + * @param message An error message. + * @param t The cause. + */ + public DocumentRendererException( String message, Throwable t ) + { + super( message, t ); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/itext/AbstractITextRender.java b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/itext/AbstractITextRender.java new file mode 100644 index 000000000..e672b80bb --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/itext/AbstractITextRender.java @@ -0,0 +1,519 @@ +package org.apache.maven.doxia.docrenderer.itext; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Collection; +import java.util.Date; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.apache.maven.doxia.Doxia; +import org.apache.maven.doxia.docrenderer.DocRenderer; +import org.apache.maven.doxia.docrenderer.DocumentRendererException; +import org.apache.maven.doxia.document.DocumentModel; +import org.apache.maven.doxia.document.DocumentTOCItem; +import org.apache.maven.doxia.document.io.xpp3.DocumentXpp3Reader; +import org.apache.maven.doxia.module.itext.ITextSink; +import org.apache.maven.doxia.module.itext.ITextSinkFactory; +import org.apache.maven.doxia.module.itext.ITextUtil; +import org.apache.maven.doxia.parser.module.ParserModule; +import org.apache.maven.doxia.parser.module.ParserModuleManager; +import org.apache.maven.doxia.parser.ParseException; +import org.apache.maven.doxia.parser.manager.ParserNotFoundException; +import org.apache.xml.utils.DefaultErrorHandler; +import org.codehaus.plexus.logging.AbstractLogEnabled; +import org.codehaus.plexus.util.FileUtils; +import org.codehaus.plexus.util.IOUtil; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.util.WriterFactory; +import org.codehaus.plexus.util.xml.XmlUtil; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.xml.sax.SAXException; + +import com.lowagie.text.ElementTags; + +/** + * Abstract document render with the iText framework + * + * @author Vincent Siveton + * @deprecated since 1.1, use an implementation of {@link org.apache.maven.doxia.docrenderer.DocumentRenderer}. + */ +public abstract class AbstractITextRender + extends AbstractLogEnabled + implements DocRenderer +{ + private static final String XSLT_RESOURCE = "org/apache/maven/doxia/docrenderer/pdf/itext/TOC.xslt"; + + private static final TransformerFactory TRANSFORMER_FACTORY = TransformerFactory.newInstance(); + + private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance(); + + /** + * @plexus.requirement + */ + protected ParserModuleManager parserModuleManager; + + /** + * @plexus.requirement + */ + protected Doxia doxia; + + static + { + TRANSFORMER_FACTORY.setErrorListener( new DefaultErrorHandler() ); + } + + private List getModuleFileNames( ParserModule module, File moduleBasedir ) + throws IOException + { + StringBuilder includes = new StringBuilder(); + + for ( String extension: module.getExtensions() ) + { + if ( includes.length() > 0 ) + { + includes.append( ',' ); + } + includes.append( "**/*." ); + includes.append( extension ); + } + + return FileUtils.getFileNames( moduleBasedir, includes.toString(), null, false ); + } + + /** {@inheritDoc} */ + public void render( File siteDirectory, File outputDirectory ) + throws DocumentRendererException, IOException + { + Collection modules = parserModuleManager.getParserModules(); + for ( ParserModule module : modules ) + { + File moduleBasedir = new File( siteDirectory, module.getSourceDirectory() ); + + if ( moduleBasedir.exists() ) + { + List docs = getModuleFileNames( module, moduleBasedir ); + + for ( String doc : docs ) + { + String fullPathDoc = new File( moduleBasedir, doc ).getPath(); + + String outputITextName = doc.substring( 0, doc.indexOf( '.' ) + 1 ) + "xml"; + File outputITextFile = new File( outputDirectory, outputITextName ); + if ( !outputITextFile.getParentFile().exists() ) + { + outputITextFile.getParentFile().mkdirs(); + } + String iTextOutputName = doc.substring( 0, doc.indexOf( '.' ) + 1 ) + getOutputExtension(); + File iTextOutputFile = new File( outputDirectory, iTextOutputName ); + if ( !iTextOutputFile.getParentFile().exists() ) + { + iTextOutputFile.getParentFile().mkdirs(); + } + + parse( fullPathDoc, module, outputITextFile ); + + generateOutput( outputITextFile, iTextOutputFile ); + } + } + } + } + + /** {@inheritDoc} */ + public void render( File siteDirectory, File outputDirectory, File documentDescriptor ) + throws DocumentRendererException, IOException + { + if ( ( documentDescriptor == null ) || ( !documentDescriptor.exists() ) ) + { + if ( getLogger().isInfoEnabled() ) + { + getLogger().info( "No documentDescriptor is found. Generate all documents." ); + } + render( siteDirectory, outputDirectory ); + return; + } + + DocumentModel documentModel; + Reader reader = null; + try + { + reader = ReaderFactory.newXmlReader( documentDescriptor ); + documentModel = new DocumentXpp3Reader().read( reader ); + } + catch ( XmlPullParserException e ) + { + throw new DocumentRendererException( "Error parsing document descriptor", e ); + } + catch ( IOException e ) + { + throw new DocumentRendererException( "Error reading document descriptor", e ); + } + finally + { + IOUtil.close( reader ); + } + + if ( documentModel.getOutputName() == null ) + { + if ( getLogger().isInfoEnabled() ) + { + getLogger().info( "No outputName is defined in the document descriptor. Using 'generated_itext'" ); + } + documentModel.setOutputName( "generated_itext" ); + } + + if ( ( documentModel.getToc() == null ) || ( documentModel.getToc().getItems() == null ) ) + { + if ( getLogger().isInfoEnabled() ) + { + getLogger().info( "No TOC is defined in the document descriptor. Merging all documents." ); + } + } + + List iTextFiles = new LinkedList(); + Collection modules = parserModuleManager.getParserModules(); + for ( ParserModule module : modules ) + { + File moduleBasedir = new File( siteDirectory, module.getSourceDirectory() ); + + if ( moduleBasedir.exists() ) + { + @SuppressWarnings ( "unchecked" ) + List docs = getModuleFileNames( module, moduleBasedir ); + + for ( String doc : docs ) + { + String fullPathDoc = new File( moduleBasedir, doc ).getPath(); + + String outputITextName = doc.substring( 0, doc.lastIndexOf( '.' ) + 1 ) + "xml"; + File outputITextFile = new File( outputDirectory, outputITextName ); + + if ( ( documentModel.getToc() == null ) || ( documentModel.getToc().getItems() == null ) ) + { + iTextFiles.add( outputITextFile ); + + if ( !outputITextFile.getParentFile().exists() ) + { + outputITextFile.getParentFile().mkdirs(); + } + + parse( fullPathDoc, module, outputITextFile ); + } + else + { + for ( Iterator k = documentModel.getToc().getItems().iterator(); k.hasNext(); ) + { + DocumentTOCItem tocItem = k.next(); + + if ( tocItem.getRef() == null ) + { + if ( getLogger().isInfoEnabled() ) + { + getLogger().info( "No ref defined for an tocItem in the document descriptor." ); + } + continue; + } + + String outTmp = StringUtils.replace( outputITextFile.getAbsolutePath(), "\\", "/" ); + outTmp = outTmp.substring( 0, outTmp.lastIndexOf( '.' ) ); + + String outRef = StringUtils.replace( tocItem.getRef(), "\\", "/" ); + if ( outRef.lastIndexOf( '.' ) != -1 ) + { + outRef = outRef.substring( 0, outRef.lastIndexOf( '.' ) ); + } + else + { + outRef = outRef.substring( 0, outRef.length() ); + } + + if ( outTmp.indexOf( outRef ) != -1 ) + { + iTextFiles.add( outputITextFile ); + + if ( !outputITextFile.getParentFile().exists() ) + { + outputITextFile.getParentFile().mkdirs(); + } + + parse( fullPathDoc, module, outputITextFile ); + } + } + } + } + } + } + + File iTextFile = new File( outputDirectory, documentModel.getOutputName() + ".xml" ); + File iTextOutput = new File( outputDirectory, documentModel.getOutputName() + "." + getOutputExtension() ); + Document document = generateDocument( iTextFiles ); + transform( documentModel, document, iTextFile ); + generateOutput( iTextFile, iTextOutput ); + } + + /** + * Generate an ouput file with the iText framework + * + * @param iTextFile + * @param iTextOutput + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + * @throws java.io.IOException if any + */ + public abstract void generateOutput( File iTextFile, File iTextOutput ) + throws DocumentRendererException, IOException; + + /** + * Parse a sink + * + * @param fullPathDoc + * @param module + * @param outputITextFile + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException + * @throws java.io.IOException + */ + private void parse( String fullPathDoc, ParserModule module, File outputITextFile ) + throws DocumentRendererException, IOException + { + Writer writer = WriterFactory.newXmlWriter( outputITextFile ); + ITextSink sink = (ITextSink) new ITextSinkFactory().createSink( writer ); + + sink.setClassLoader( new URLClassLoader( new URL[] { outputITextFile.getParentFile().toURI().toURL() } ) ); + + Reader reader = null; + try + { + File f = new File( fullPathDoc ); + if ( XmlUtil.isXml( f ) ) + { + reader = ReaderFactory.newXmlReader( f ); + } + else + { + // TODO Platform dependent? + reader = ReaderFactory.newPlatformReader( f ); + } + + System.setProperty( "itext.basedir", outputITextFile.getParentFile().getAbsolutePath() ); + + doxia.parse( reader, module.getParserId(), sink ); + } + catch ( ParserNotFoundException e ) + { + throw new DocumentRendererException( "Error getting a parser for '" + + fullPathDoc + "': " + e.getMessage() ); + } + catch ( ParseException e ) + { + throw new DocumentRendererException( "Error parsing '" + + fullPathDoc + "': line [" + e.getLineNumber() + "] " + e.getMessage(), e ); + } + finally + { + IOUtil.close( reader ); + + sink.flush(); + + sink.close(); + + IOUtil.close( writer ); + + System.getProperties().remove( "itext.basedir" ); + } + } + + /** + * Merge all iTextFiles to a single one + * + * @param iTextFiles + * @return a document + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + * @throws java.io.IOException if any + */ + private Document generateDocument( List iTextFiles ) + throws DocumentRendererException, IOException + { + Document document; + try + { + document = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder().newDocument(); + } + catch ( ParserConfigurationException e ) + { + throw new DocumentRendererException( "Error building document :" + e.getMessage() ); + } + document.appendChild( document.createElement( ElementTags.ITEXT ) ); // Used only to set a root + + for ( File iTextFile : iTextFiles ) + { + Document iTextDocument; + try + { + iTextDocument = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder().parse( iTextFile ); + } + catch ( SAXException e ) + { + throw new DocumentRendererException( "SAX Error : " + e.getMessage() ); + } + catch ( ParserConfigurationException e ) + { + throw new DocumentRendererException( "Error parsing configuration : " + e.getMessage() ); + } + + // Only one chapter per doc + Node chapter = iTextDocument.getElementsByTagName( ElementTags.CHAPTER ).item( 0 ); + try + { + document.getDocumentElement().appendChild( document.importNode( chapter, true ) ); + } + catch ( DOMException e ) + { + throw new DocumentRendererException( "Error appending chapter for " + + iTextFile + " : " + e.getMessage() ); + } + } + + return document; + } + + /** + * Init the transformer object + * + * @return an instanced transformer object + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + */ + private Transformer initTransformer() + throws DocumentRendererException + { + try + { + Transformer transformer = TRANSFORMER_FACTORY.newTransformer( new StreamSource( DefaultPdfRenderer.class + .getResourceAsStream( "/" + XSLT_RESOURCE ) ) ); + transformer.setErrorListener( TRANSFORMER_FACTORY.getErrorListener() ); + + transformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "false" ); + transformer.setOutputProperty( OutputKeys.INDENT, "yes" ); + transformer.setOutputProperty( OutputKeys.METHOD, "xml" ); + transformer.setOutputProperty( OutputKeys.ENCODING, "UTF-8" ); + + return transformer; + } + catch ( TransformerConfigurationException e ) + { + throw new DocumentRendererException( "Error configuring Transformer for " + XSLT_RESOURCE + ": " + + e.getMessage() ); + } + catch ( IllegalArgumentException e ) + { + throw new DocumentRendererException( "Error configuring Transformer for " + XSLT_RESOURCE + ": " + + e.getMessage() ); + } + } + + /** + * Add transformer parameters + * + * @param transformer + * @param documentModel + */ + private void addTransformerParameters( Transformer transformer, DocumentModel documentModel ) + { + if ( documentModel.getMeta().getTitle() != null ) + { + transformer.setParameter( "title", documentModel.getMeta().getTitle() ); + } + if ( documentModel.getMeta().getAuthor() != null ) + { + transformer.setParameter( "author", documentModel.getMeta().getAuthor() ); + } + transformer.setParameter( "creationdate", new Date().toString() ); + if ( documentModel.getMeta().getSubject() != null ) + { + transformer.setParameter( "subject", documentModel.getMeta().getSubject() ); + } + if ( documentModel.getMeta().getKeywords() != null ) + { + transformer.setParameter( "keywords", documentModel.getMeta().getKeywords() ); + } + transformer.setParameter( "producer", "Generated with Doxia by " + System.getProperty( "user.name" ) ); + if ( ITextUtil.isPageSizeSupported( documentModel.getMeta().getTitle() ) ) + { + transformer.setParameter( "pagesize", documentModel.getMeta().getPageSize() ); + } + else + { + transformer.setParameter( "pagesize", "A4" ); + } + + transformer.setParameter( "frontPageHeader", "" ); + if ( documentModel.getMeta().getTitle() != null ) + { + transformer.setParameter( "frontPageTitle", documentModel.getMeta().getTitle() ); + } + transformer.setParameter( "frontPageFooter", "Generated date " + new Date().toString() ); + } + + /** + * Transform a document to an iTextFile + * + * @param documentModel + * @param document + * @param iTextFile + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any. + */ + private void transform( DocumentModel documentModel, Document document, File iTextFile ) + throws DocumentRendererException + { + Transformer transformer = initTransformer(); + + addTransformerParameters( transformer, documentModel ); + + try + { + transformer.transform( new DOMSource( document ), new StreamResult( iTextFile ) ); + } + catch ( TransformerException e ) + { + throw new DocumentRendererException( "Error transformer Document from " + + document + ": " + e.getMessage() ); + } + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/itext/DefaultPdfRenderer.java b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/itext/DefaultPdfRenderer.java new file mode 100644 index 000000000..35a1efb43 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/itext/DefaultPdfRenderer.java @@ -0,0 +1,65 @@ +package org.apache.maven.doxia.docrenderer.itext; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +import org.apache.maven.doxia.docrenderer.DocumentRendererException; +import org.apache.maven.doxia.module.itext.ITextUtil; +import org.codehaus.plexus.component.annotations.Component; + +/** + * PDF render with the iText framework + * + * @author Vincent Siveton + * @deprecated since 1.1, use {@link org.apache.maven.doxia.docrenderer.pdf.itext.ITextPdfRenderer}. + */ +@Component( role = PdfRenderer.class, hint = "itext.pdf" ) +public class DefaultPdfRenderer + extends AbstractITextRender + implements PdfRenderer +{ + /** {@inheritDoc} */ + public String getOutputExtension() + { + return "pdf"; + } + + /** {@inheritDoc} */ + public void generateOutput( File iTextFile, File iTextOutput ) + throws DocumentRendererException, IOException + { + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Writing : " + iTextOutput ); + } + try + { + ITextUtil.writePdf( new FileInputStream( iTextFile ), new FileOutputStream( iTextOutput ) ); + } + catch ( RuntimeException e ) + { + throw new DocumentRendererException( "Error writing PDF from " + iTextOutput + ": " + e.getMessage() ); + } + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/itext/DefaultRtfRenderer.java b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/itext/DefaultRtfRenderer.java new file mode 100644 index 000000000..5f788b21f --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/itext/DefaultRtfRenderer.java @@ -0,0 +1,65 @@ +package org.apache.maven.doxia.docrenderer.itext; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +import org.apache.maven.doxia.docrenderer.DocumentRendererException; +import org.apache.maven.doxia.module.itext.ITextUtil; +import org.codehaus.plexus.component.annotations.Component; + +/** + * RTF render with the iText framework + * + * @author Vincent Siveton + * @deprecated since 1.1, use {@link org.apache.maven.doxia.docrenderer.pdf.itext.ITextPdfRenderer}. + */ +@Component( role = RtfRenderer.class, hint = "itext.rtf" ) +public class DefaultRtfRenderer + extends AbstractITextRender + implements RtfRenderer +{ + /** {@inheritDoc} */ + public String getOutputExtension() + { + return "rtf"; + } + + /** {@inheritDoc} */ + public void generateOutput( File iTextFile, File iTextOutput ) + throws DocumentRendererException, IOException + { + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Writing : " + iTextOutput ); + } + try + { + ITextUtil.writeRtf( new FileInputStream( iTextFile ), new FileOutputStream( iTextOutput ) ); + } + catch ( RuntimeException e ) + { + throw new DocumentRendererException( "Error writing RTF from " + iTextOutput + ": " + e.getMessage() ); + } + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/itext/PdfRenderer.java b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/itext/PdfRenderer.java new file mode 100644 index 000000000..e3773eeca --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/itext/PdfRenderer.java @@ -0,0 +1,35 @@ +package org.apache.maven.doxia.docrenderer.itext; + +/* + * 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 org.apache.maven.doxia.docrenderer.DocRenderer; + +/** + * PDF renderer interface for the iText framework + * + * @author Vincent Siveton + * @deprecated since 1.1, use an implementation of {@link org.apache.maven.doxia.docrenderer.DocumentRenderer}. + */ +@SuppressWarnings( "checkstyle:interfaceistype" ) +public interface PdfRenderer + extends DocRenderer +{ + String ROLE = PdfRenderer.class.getName(); +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/itext/RtfRenderer.java b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/itext/RtfRenderer.java new file mode 100644 index 000000000..2a28a8fd2 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/itext/RtfRenderer.java @@ -0,0 +1,35 @@ +package org.apache.maven.doxia.docrenderer.itext; + +/* + * 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 org.apache.maven.doxia.docrenderer.DocRenderer; + +/** + * RTF renderer interface for the iText framework + * + * @author Vincent Siveton + * @deprecated since 1.1, use an implementation of {@link org.apache.maven.doxia.docrenderer.DocumentRenderer}. + */ +@SuppressWarnings( "checkstyle:interfaceistype" ) +public interface RtfRenderer + extends DocRenderer +{ + String ROLE = RtfRenderer.class.getName(); +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/pdf/AbstractPdfRenderer.java b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/pdf/AbstractPdfRenderer.java new file mode 100644 index 000000000..ade91b06b --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/pdf/AbstractPdfRenderer.java @@ -0,0 +1,53 @@ +package org.apache.maven.doxia.docrenderer.pdf; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.IOException; +import java.util.Map; + +import org.apache.maven.doxia.docrenderer.AbstractDocumentRenderer; +import org.apache.maven.doxia.docrenderer.DocumentRendererException; +import org.apache.maven.doxia.document.DocumentModel; +import org.apache.maven.doxia.parser.module.ParserModule; + +/** + * Abstract pdf renderer, this doesn't depend on the framework. + * + * @author ltheussl + * @since 1.1 + */ +public abstract class AbstractPdfRenderer + extends AbstractDocumentRenderer + implements PdfRenderer +{ + /** {@inheritDoc} */ + public String getOutputExtension() + { + return "pdf"; + } + + /** {@inheritDoc} */ + public void render( Map filesToProcess, File outputDirectory, DocumentModel documentModel ) + throws DocumentRendererException, IOException + { + render( filesToProcess, outputDirectory, documentModel, null ); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/pdf/PdfRenderer.java b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/pdf/PdfRenderer.java new file mode 100644 index 000000000..6e961e2b9 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/pdf/PdfRenderer.java @@ -0,0 +1,48 @@ +package org.apache.maven.doxia.docrenderer.pdf; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; + +import org.apache.maven.doxia.docrenderer.DocumentRenderer; +import org.apache.maven.doxia.docrenderer.DocumentRendererException; + +/** + * PDF renderer interface. + * + * @author ltheussl + * @since 1.1 + */ +public interface PdfRenderer + extends DocumentRenderer +{ + /** Plexus lookup role. */ + String ROLE = PdfRenderer.class.getName(); + + /** + * Generate a final pdf ouput file from an intermediate format file. + * + * @param inputFile eg a fo or an itext file. + * @param pdfFile the pdf file to generate. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any. + */ + void generatePdf( File inputFile, File pdfFile ) + throws DocumentRendererException; +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/pdf/fo/FoPdfRenderer.java b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/pdf/fo/FoPdfRenderer.java new file mode 100644 index 000000000..e264f8375 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/pdf/fo/FoPdfRenderer.java @@ -0,0 +1,366 @@ +package org.apache.maven.doxia.docrenderer.pdf.fo; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.IOException; +import java.io.Writer; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import javax.xml.transform.TransformerException; + +import org.apache.maven.doxia.docrenderer.DocumentRendererContext; +import org.apache.maven.doxia.docrenderer.DocumentRendererException; +import org.apache.maven.doxia.docrenderer.pdf.AbstractPdfRenderer; +import org.apache.maven.doxia.docrenderer.pdf.PdfRenderer; +import org.apache.maven.doxia.document.DocumentModel; +import org.apache.maven.doxia.document.DocumentTOC; +import org.apache.maven.doxia.document.DocumentTOCItem; +import org.apache.maven.doxia.module.fo.FoAggregateSink; +import org.apache.maven.doxia.module.fo.FoSink; +import org.apache.maven.doxia.module.fo.FoSinkFactory; +import org.apache.maven.doxia.module.fo.FoUtils; +import org.apache.maven.doxia.parser.module.ParserModule; + +import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.util.IOUtil; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.util.WriterFactory; + +import org.xml.sax.SAXParseException; + +/** + * PDF renderer that uses Doxia's FO module. + * + * @author ltheussl + * @since 1.1 + */ +@Component( role = PdfRenderer.class, hint = "fo" ) +public class FoPdfRenderer + extends AbstractPdfRenderer +{ + /** + * {@inheritDoc} + * @see org.apache.maven.doxia.module.fo.FoUtils#convertFO2PDF(File, File, String) + */ + public void generatePdf( File inputFile, File pdfFile ) + throws DocumentRendererException + { + // Should take care of the document model for the metadata... + generatePdf( inputFile, pdfFile, null ); + } + + /** {@inheritDoc} */ + @Override + public void render( Map filesToProcess, File outputDirectory, DocumentModel documentModel ) + throws DocumentRendererException, IOException + { + render( filesToProcess, outputDirectory, documentModel, null ); + } + + /** {@inheritDoc} */ + @Override + public void render( Map filesToProcess, File outputDirectory, DocumentModel documentModel, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + // copy resources, images, etc. + copyResources( outputDirectory ); + + if ( documentModel == null ) + { + getLogger().debug( "No document model, generating all documents individually." ); + + renderIndividual( filesToProcess, outputDirectory, context ); + return; + } + + String outputName = getOutputName( documentModel ); + + File outputFOFile = new File( outputDirectory, outputName + ".fo" ); + if ( !outputFOFile.getParentFile().exists() ) + { + outputFOFile.getParentFile().mkdirs(); + } + + File pdfOutputFile = new File( outputDirectory, outputName + ".pdf" ); + if ( !pdfOutputFile.getParentFile().exists() ) + { + pdfOutputFile.getParentFile().mkdirs(); + } + + Writer writer = null; + try + { + writer = WriterFactory.newXmlWriter( outputFOFile ); + + FoAggregateSink sink = new FoAggregateSink( writer ); + + File fOConfigFile = new File( outputDirectory, "pdf-config.xml" ); + + if ( fOConfigFile.exists() ) + { + sink.load( fOConfigFile ); + getLogger().debug( "Loaded pdf config file: " + fOConfigFile.getAbsolutePath() ); + } + + String generateTOC = + ( context != null && context.get( "generateTOC" ) != null ) + ? context.get( "generateTOC" ).toString().trim() + : "start"; + int tocPosition = 0; + if ( "start".equalsIgnoreCase( generateTOC ) ) + { + tocPosition = FoAggregateSink.TOC_START; + } + else if ( "end".equalsIgnoreCase( generateTOC ) ) + { + tocPosition = FoAggregateSink.TOC_END; + } + else + { + tocPosition = FoAggregateSink.TOC_NONE; + } + sink.setDocumentModel( documentModel, tocPosition ); + + sink.beginDocument(); + + sink.coverPage(); + + if ( tocPosition == FoAggregateSink.TOC_START ) + { + sink.toc(); + } + + if ( ( documentModel.getToc() == null ) || ( documentModel.getToc().getItems() == null ) ) + { + getLogger().info( "No TOC is defined in the document descriptor. Merging all documents." ); + + mergeAllSources( filesToProcess, sink, context ); + } + else + { + getLogger().debug( "Using TOC defined in the document descriptor." ); + + mergeSourcesFromTOC( documentModel.getToc(), sink, context ); + } + + if ( tocPosition == FoAggregateSink.TOC_END ) + { + sink.toc(); + } + + sink.endDocument(); + } + finally + { + IOUtil.close( writer ); + } + + generatePdf( outputFOFile, pdfOutputFile, documentModel ); + } + + /** {@inheritDoc} */ + @Override + public void renderIndividual( Map filesToProcess, File outputDirectory ) + throws DocumentRendererException, IOException + { + renderIndividual( filesToProcess, outputDirectory, null ); + } + + /** {@inheritDoc} */ + @Override + public void renderIndividual( Map filesToProcess, File outputDirectory, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + for ( Map.Entry entry : filesToProcess.entrySet() ) + { + String key = entry.getKey(); + ParserModule module = entry.getValue(); + + File fullDoc = new File( getBaseDir(), module.getSourceDirectory() + File.separator + key ); + + String output = key; + for ( String extension : module.getExtensions() ) + { + String lowerCaseExtension = extension.toLowerCase( Locale.ENGLISH ); + if ( output.toLowerCase( Locale.ENGLISH ).indexOf( "." + lowerCaseExtension ) != -1 ) + { + output = + output.substring( 0, output.toLowerCase( Locale.ENGLISH ).indexOf( "." + lowerCaseExtension ) ); + } + } + + File outputFOFile = new File( outputDirectory, output + ".fo" ); + if ( !outputFOFile.getParentFile().exists() ) + { + outputFOFile.getParentFile().mkdirs(); + } + + File pdfOutputFile = new File( outputDirectory, output + ".pdf" ); + if ( !pdfOutputFile.getParentFile().exists() ) + { + pdfOutputFile.getParentFile().mkdirs(); + } + + FoSink sink = + (FoSink) new FoSinkFactory().createSink( outputFOFile.getParentFile(), outputFOFile.getName() ); + sink.beginDocument(); + parse( fullDoc.getAbsolutePath(), module.getParserId(), sink, context ); + sink.endDocument(); + + generatePdf( outputFOFile, pdfOutputFile, null ); + } + } + + private void mergeAllSources( Map filesToProcess, FoAggregateSink sink, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + for ( Map.Entry entry : filesToProcess.entrySet() ) + { + String key = entry.getKey(); + ParserModule module = entry.getValue(); + sink.setDocumentName( key ); + File fullDoc = new File( getBaseDir(), module.getSourceDirectory() + File.separator + key ); + + parse( fullDoc.getAbsolutePath(), module.getParserId(), sink, context ); + } + } + + private void mergeSourcesFromTOC( DocumentTOC toc, FoAggregateSink sink, DocumentRendererContext context ) + throws IOException, DocumentRendererException + { + parseTocItems( toc.getItems(), sink, context ); + } + + private void parseTocItems( List items, FoAggregateSink sink, DocumentRendererContext context ) + throws IOException, DocumentRendererException + { + for ( DocumentTOCItem tocItem : items ) + { + if ( tocItem.getRef() == null ) + { + if ( getLogger().isInfoEnabled() ) + { + getLogger().info( "No ref defined for tocItem " + tocItem.getName() ); + } + + continue; + } + + String href = StringUtils.replace( tocItem.getRef(), "\\", "/" ); + if ( href.lastIndexOf( '.' ) != -1 ) + { + href = href.substring( 0, href.lastIndexOf( '.' ) ); + } + + renderModules( href, sink, tocItem, context ); + + if ( tocItem.getItems() != null ) + { + parseTocItems( tocItem.getItems(), sink, context ); + } + } + } + + private void renderModules( String href, FoAggregateSink sink, DocumentTOCItem tocItem, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + Collection modules = parserModuleManager.getParserModules(); + for ( ParserModule module : modules ) + { + File moduleBasedir = new File( getBaseDir(), module.getSourceDirectory() ); + + if ( moduleBasedir.exists() ) + { + for ( String extension : module.getExtensions() ) + { + String doc = href + "." + extension; + File source = new File( moduleBasedir, doc ); + + // Velocity file? + if ( !source.exists() ) + { + if ( href.indexOf( "." + extension ) != -1 ) + { + doc = href + ".vm"; + } + else + { + doc = href + "." + extension + ".vm"; + } + source = new File( moduleBasedir, doc ); + } + + if ( source.exists() ) + { + sink.setDocumentName( doc ); + sink.setDocumentTitle( tocItem.getName() ); + + parse( source.getPath(), module.getParserId(), sink, context ); + } + } + } + } + } + + /** + * @param inputFile + * @param pdfFile + * @param documentModel could be null + * @throws DocumentRendererException if any + * @since 1.1.1 + */ + private void generatePdf( File inputFile, File pdfFile, DocumentModel documentModel ) + throws DocumentRendererException + { + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Generating: " + pdfFile ); + } + + try + { + FoUtils.convertFO2PDF( inputFile, pdfFile, null, documentModel ); + } + catch ( TransformerException e ) + { + if ( ( e.getCause() != null ) && ( e.getCause() instanceof SAXParseException ) ) + { + SAXParseException sax = (SAXParseException) e.getCause(); + + StringBuilder sb = new StringBuilder(); + sb.append( "Error creating PDF from " ).append( inputFile.getAbsolutePath() ).append( ":" ) + .append( sax.getLineNumber() ).append( ":" ).append( sax.getColumnNumber() ).append( "\n" ); + sb.append( e.getMessage() ); + + throw new DocumentRendererException( sb.toString() ); + } + + throw new DocumentRendererException( "Error creating PDF from " + inputFile + ": " + e.getMessage() ); + } + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/pdf/itext/ITextPdfRenderer.java b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/pdf/itext/ITextPdfRenderer.java new file mode 100644 index 000000000..dd477430d --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/pdf/itext/ITextPdfRenderer.java @@ -0,0 +1,689 @@ +package org.apache.maven.doxia.docrenderer.pdf.itext; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.Writer; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.text.SimpleDateFormat; +import java.util.Collection; +import java.util.Date; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.apache.maven.doxia.docrenderer.DocumentRendererContext; +import org.apache.maven.doxia.docrenderer.DocumentRendererException; +import org.apache.maven.doxia.docrenderer.pdf.AbstractPdfRenderer; +import org.apache.maven.doxia.docrenderer.pdf.PdfRenderer; +import org.apache.maven.doxia.document.DocumentCover; +import org.apache.maven.doxia.document.DocumentMeta; +import org.apache.maven.doxia.document.DocumentModel; +import org.apache.maven.doxia.document.DocumentTOCItem; +import org.apache.maven.doxia.module.itext.ITextSink; +import org.apache.maven.doxia.module.itext.ITextSinkFactory; +import org.apache.maven.doxia.module.itext.ITextUtil; +import org.apache.maven.doxia.parser.module.ParserModule; +import org.apache.xml.utils.DefaultErrorHandler; +import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.util.IOUtil; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.util.WriterFactory; +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.xml.sax.SAXException; + +import com.lowagie.text.ElementTags; + +/** + * Abstract document render with the iText framework + * + * @author Vincent Siveton + * @author ltheussl + * @since 1.1 + */ +@Component( role = PdfRenderer.class, hint = "itext" ) +public class ITextPdfRenderer + extends AbstractPdfRenderer +{ + /** The xslt style sheet used to transform a Document to an iText file. */ + private static final String XSLT_RESOURCE = "TOC.xslt"; + + /** The TransformerFactory. */ + private static final TransformerFactory TRANSFORMER_FACTORY = TransformerFactory.newInstance(); + + /** The DocumentBuilderFactory. */ + private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance(); + + /** The DocumentBuilder. */ + private static final DocumentBuilder DOCUMENT_BUILDER; + + static + { + TRANSFORMER_FACTORY.setErrorListener( new DefaultErrorHandler() ); + + try + { + DOCUMENT_BUILDER = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder(); + } + catch ( ParserConfigurationException e ) + { + throw new RuntimeException( "Error building document :" + e.getMessage() ); + } + } + + /** {@inheritDoc} */ + public void generatePdf( File inputFile, File pdfFile ) + throws DocumentRendererException + { + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Generating : " + pdfFile ); + } + + try + { + ITextUtil.writePdf( new FileInputStream( inputFile ), new FileOutputStream( pdfFile ) ); + } + catch ( IOException e ) + { + throw new DocumentRendererException( "Cannot create PDF from " + inputFile + ": " + e.getMessage(), e ); + } + catch ( RuntimeException e ) + { + throw new DocumentRendererException( "Error creating PDF from " + inputFile + ": " + e.getMessage(), e ); + } + } + + /** {@inheritDoc} */ + @Override + public void render( Map filesToProcess, File outputDirectory, DocumentModel documentModel ) + throws DocumentRendererException, IOException + { + render( filesToProcess, outputDirectory, documentModel, null ); + } + + /** {@inheritDoc} */ + @Override + public void render( Map filesToProcess, File outputDirectory, DocumentModel documentModel, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + // copy resources, images, etc. + copyResources( outputDirectory ); + + if ( documentModel == null ) + { + getLogger().debug( "No document model, generating all documents individually." ); + + renderIndividual( filesToProcess, outputDirectory, context ); + return; + } + + String outputName = getOutputName( documentModel ); + + File outputITextFile = new File( outputDirectory, outputName + ".xml" ); + if ( !outputITextFile.getParentFile().exists() ) + { + outputITextFile.getParentFile().mkdirs(); + } + + File pdfOutputFile = new File( outputDirectory, outputName + ".pdf" ); + if ( !pdfOutputFile.getParentFile().exists() ) + { + pdfOutputFile.getParentFile().mkdirs(); + } + + List iTextFiles; + if ( ( documentModel.getToc() == null ) || ( documentModel.getToc().getItems() == null ) ) + { + getLogger().info( "No TOC is defined in the document descriptor. Merging all documents." ); + + iTextFiles = parseAllFiles( filesToProcess, outputDirectory, context ); + } + else + { + getLogger().debug( "Using TOC defined in the document descriptor." ); + + iTextFiles = parseTOCFiles( outputDirectory, documentModel, context ); + } + + String generateTOC = + ( context != null && context.get( "generateTOC" ) != null ? context.get( "generateTOC" ).toString() + : "start" ); + + File iTextFile = new File( outputDirectory, outputName + ".xml" ); + File iTextOutput = new File( outputDirectory, outputName + "." + getOutputExtension() ); + Document document = generateDocument( iTextFiles ); + transform( documentModel, document, iTextFile, generateTOC ); + generatePdf( iTextFile, iTextOutput ); + } + + /** {@inheritDoc} */ + @Override + public void renderIndividual( Map filesToProcess, File outputDirectory ) + throws DocumentRendererException, IOException + { + renderIndividual( filesToProcess, outputDirectory, null ); + } + + /** {@inheritDoc} */ + @Override + public void renderIndividual( Map filesToProcess, File outputDirectory, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + for ( Map.Entry entry : filesToProcess.entrySet() ) + { + String key = entry.getKey(); + ParserModule module = entry.getValue(); + File fullDoc = new File( getBaseDir(), module.getSourceDirectory() + File.separator + key ); + + String output = key; + for ( String extension : module.getExtensions() ) + { + String lowerCaseExtension = extension.toLowerCase( Locale.ENGLISH ); + if ( output.toLowerCase( Locale.ENGLISH ).indexOf( "." + lowerCaseExtension ) != -1 ) + { + output = + output.substring( 0, output.toLowerCase( Locale.ENGLISH ).indexOf( "." + lowerCaseExtension ) ); + } + } + + File outputITextFile = new File( outputDirectory, output + ".xml" ); + if ( !outputITextFile.getParentFile().exists() ) + { + outputITextFile.getParentFile().mkdirs(); + } + + File pdfOutputFile = new File( outputDirectory, output + ".pdf" ); + if ( !pdfOutputFile.getParentFile().exists() ) + { + pdfOutputFile.getParentFile().mkdirs(); + } + + parse( fullDoc, module, outputITextFile, context ); + + generatePdf( outputITextFile, pdfOutputFile ); + } + } + + //-------------------------------------------- + // + //-------------------------------------------- + + + /** + * Parse a source document and emit results into a sink. + * + * @param fullDocPath file to the source document. + * @param module the site module associated with the source document (determines the parser to use). + * @param iTextFile the resulting iText xml file. + * @throws DocumentRendererException in case of a parsing problem. + * @throws IOException if the source and/or target document cannot be opened. + */ + private void parse( File fullDoc, ParserModule module, File iTextFile, DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Parsing file " + fullDoc.getAbsolutePath() ); + } + + System.setProperty( "itext.basedir", iTextFile.getParentFile().getAbsolutePath() ); + + Writer writer = null; + ITextSink sink = null; + try + { + writer = WriterFactory.newXmlWriter( iTextFile ); + sink = (ITextSink) new ITextSinkFactory().createSink( writer ); + + sink.setClassLoader( new URLClassLoader( new URL[] { iTextFile.getParentFile().toURI().toURL() } ) ); + + parse( fullDoc.getAbsolutePath(), module.getParserId(), sink, context ); + } + finally + { + if ( sink != null ) + { + sink.flush(); + sink.close(); + } + IOUtil.close( writer ); + System.getProperties().remove( "itext.basedir" ); + } + } + + /** + * Merge all iTextFiles to a single one. + * + * @param iTextFiles list of iText xml files. + * @return Document. + * @throws DocumentRendererException if any. + * @throws IOException if any. + */ + private Document generateDocument( List iTextFiles ) + throws DocumentRendererException, IOException + { + Document document = DOCUMENT_BUILDER.newDocument(); + document.appendChild( document.createElement( ElementTags.ITEXT ) ); // Used only to set a root + + for ( File iTextFile : iTextFiles ) + { + Document iTextDocument; + + try + { + iTextDocument = DOCUMENT_BUILDER.parse( iTextFile ); + } + catch ( SAXException e ) + { + throw new DocumentRendererException( "SAX Error : " + e.getMessage() ); + } + + // Only one chapter per doc + Node chapter = iTextDocument.getElementsByTagName( ElementTags.CHAPTER ).item( 0 ); + + try + { + document.getDocumentElement().appendChild( document.importNode( chapter, true ) ); + } + catch ( DOMException e ) + { + throw new DocumentRendererException( "Error appending chapter for " + + iTextFile + " : " + e.getMessage() ); + } + } + + return document; + } + + /** + * Initialize the transformer object. + * + * @return an instance of a transformer object. + * @throws DocumentRendererException if any. + */ + private Transformer initTransformer() + throws DocumentRendererException + { + try + { + Transformer transformer = TRANSFORMER_FACTORY.newTransformer( new StreamSource( ITextPdfRenderer.class + .getResourceAsStream( XSLT_RESOURCE ) ) ); + + transformer.setErrorListener( TRANSFORMER_FACTORY.getErrorListener() ); + + transformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "false" ); + + transformer.setOutputProperty( OutputKeys.INDENT, "yes" ); + + transformer.setOutputProperty( OutputKeys.METHOD, "xml" ); + + transformer.setOutputProperty( OutputKeys.ENCODING, "UTF-8" ); + + // No doctype since itext doctype is not up to date! + + return transformer; + } + catch ( TransformerConfigurationException e ) + { + throw new DocumentRendererException( "Error configuring Transformer for " + XSLT_RESOURCE + ": " + + e.getMessage() ); + } + catch ( IllegalArgumentException e ) + { + throw new DocumentRendererException( "Error configuring Transformer for " + XSLT_RESOURCE + ": " + + e.getMessage() ); + } + } + + /** + * Add transformer parameters from a DocumentModel. + * + * @param transformer the Transformer to set the parameters. + * @param documentModel the DocumentModel to take the parameters from, could be null. + * @param iTextFile the iTextFile not null for the relative paths. + * @param generateTOC not null, possible values are: 'none', 'start' and 'end'. + */ + private void addTransformerParameters( Transformer transformer, DocumentModel documentModel, File iTextFile, + String generateTOC ) + { + if ( documentModel == null ) + { + return; + } + + // TOC + addTransformerParameter( transformer, "toc.position", generateTOC ); + + // Meta parameters + boolean hasNullMeta = false; + if ( documentModel.getMeta() == null ) + { + hasNullMeta = true; + documentModel.setMeta( new DocumentMeta() ); + } + addTransformerParameter( transformer, "meta.author", documentModel.getMeta().getAllAuthorNames(), + System.getProperty( "user.name", "null" ) ); + addTransformerParameter( transformer, "meta.creator", documentModel.getMeta().getCreator(), + System.getProperty( "user.name", "null" ) ); + // see com.lowagie.text.Document#addCreationDate() + SimpleDateFormat sdf = new SimpleDateFormat( "EEE MMM dd HH:mm:ss zzz yyyy" ); + addTransformerParameter( transformer, "meta.creationdate", documentModel.getMeta().getCreationdate(), + sdf.format( new Date() ) ); + addTransformerParameter( transformer, "meta.keywords", documentModel.getMeta().getAllKeyWords() ); + addTransformerParameter( transformer, "meta.pagesize", documentModel.getMeta().getPageSize(), + ITextUtil.getPageSize( ITextUtil.getDefaultPageSize() ) ); + addTransformerParameter( transformer, "meta.producer", documentModel.getMeta().getGenerator(), + "Apache Doxia iText" ); + addTransformerParameter( transformer, "meta.subject", documentModel.getMeta().getSubject(), + ( documentModel.getMeta().getTitle() != null ? documentModel.getMeta().getTitle() + : "" ) ); + addTransformerParameter( transformer, "meta.title", documentModel.getMeta().getTitle() ); + if ( hasNullMeta ) + { + documentModel.setMeta( null ); + } + + // cover parameter + boolean hasNullCover = false; + if ( documentModel.getCover() == null ) + { + hasNullCover = true; + documentModel.setCover( new DocumentCover() ); + } + addTransformerParameter( transformer, "cover.author", documentModel.getCover().getAllAuthorNames(), + System.getProperty( "user.name", "null" ) ); + String companyLogo = getLogoURL( documentModel.getCover().getCompanyLogo(), iTextFile.getParentFile() ); + addTransformerParameter( transformer, "cover.companyLogo", companyLogo ); + addTransformerParameter( transformer, "cover.companyName", documentModel.getCover().getCompanyName() ); + if ( documentModel.getCover().getCoverdate() == null ) + { + documentModel.getCover().setCoverDate( new Date() ); + addTransformerParameter( transformer, "cover.date", documentModel.getCover().getCoverdate() ); + documentModel.getCover().setCoverDate( null ); + } + else + { + addTransformerParameter( transformer, "cover.date", documentModel.getCover().getCoverdate() ); + } + addTransformerParameter( transformer, "cover.subtitle", documentModel.getCover().getCoverSubTitle() ); + addTransformerParameter( transformer, "cover.title", documentModel.getCover().getCoverTitle() ); + addTransformerParameter( transformer, "cover.type", documentModel.getCover().getCoverType() ); + addTransformerParameter( transformer, "cover.version", documentModel.getCover().getCoverVersion() ); + String projectLogo = getLogoURL( documentModel.getCover().getProjectLogo(), iTextFile.getParentFile() ); + addTransformerParameter( transformer, "cover.projectLogo", projectLogo ); + addTransformerParameter( transformer, "cover.projectName", documentModel.getCover().getProjectName() ); + if ( hasNullCover ) + { + documentModel.setCover( null ); + } + } + + /** + * @param transformer not null + * @param name not null + * @param value could be empty + * @param defaultValue could be empty + * @since 1.1.1 + */ + private void addTransformerParameter( Transformer transformer, String name, String value, String defaultValue ) + { + if ( StringUtils.isEmpty( value ) ) + { + addTransformerParameter( transformer, name, defaultValue ); + } + else + { + addTransformerParameter( transformer, name, value ); + } + } + + /** + * @param transformer not null + * @param name not null + * @param value could be empty + * @since 1.1.1 + */ + private void addTransformerParameter( Transformer transformer, String name, String value ) + { + if ( StringUtils.isEmpty( value ) ) + { + return; + } + + transformer.setParameter( name, value ); + } + + /** + * Transform a document to an iTextFile. + * + * @param documentModel the DocumentModel to take the parameters from, could be null. + * @param document the Document to transform. + * @param iTextFile the resulting iText xml file. + * @param generateTOC not null, possible values are: 'none', 'start' and 'end'. + * @throws DocumentRendererException in case of a transformation error. + */ + private void transform( DocumentModel documentModel, Document document, File iTextFile, String generateTOC ) + throws DocumentRendererException + { + Transformer transformer = initTransformer(); + + addTransformerParameters( transformer, documentModel, iTextFile, generateTOC ); + + // need a writer for StreamResult to prevent FileNotFoundException when iTextFile contains spaces + Writer writer = null; + try + { + writer = WriterFactory.newXmlWriter( iTextFile ); + transformer.transform( new DOMSource( document ), new StreamResult( writer ) ); + } + catch ( TransformerException e ) + { + throw new DocumentRendererException( + "Error transforming Document " + document + ": " + e.getMessage(), + e ); + } + catch ( IOException e ) + { + throw new DocumentRendererException( + "Error transforming Document " + document + ": " + e.getMessage(), + e ); + } + finally + { + IOUtil.close( writer ); + } + } + + /** + * @param filesToProcess not null + * @param outputDirectory not null + * @return a list of all parsed files. + * @throws DocumentRendererException if any + * @throws IOException if any + * @since 1.1.1 + */ + private List parseAllFiles( Map filesToProcess, File outputDirectory, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + List iTextFiles = new LinkedList(); + for ( Map.Entry entry : filesToProcess.entrySet() ) + { + String key = entry.getKey(); + ParserModule module = entry.getValue(); + File fullDoc = new File( getBaseDir(), module.getSourceDirectory() + File.separator + key ); + + String outputITextName = key.substring( 0, key.lastIndexOf( '.' ) + 1 ) + "xml"; + File outputITextFileTmp = new File( outputDirectory, outputITextName ); + outputITextFileTmp.deleteOnExit(); + if ( !outputITextFileTmp.getParentFile().exists() ) + { + outputITextFileTmp.getParentFile().mkdirs(); + } + + iTextFiles.add( outputITextFileTmp ); + parse( fullDoc, module, outputITextFileTmp, context ); + } + + return iTextFiles; + } + + /** + * @param filesToProcess not null + * @param outputDirectory not null + * @return a list of all parsed files. + * @throws DocumentRendererException if any + * @throws IOException if any + * @since 1.1.1 + */ + private List parseTOCFiles( File outputDirectory, DocumentModel documentModel, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + List iTextFiles = new LinkedList(); + for ( Iterator it = documentModel.getToc().getItems().iterator(); it.hasNext(); ) + { + DocumentTOCItem tocItem = it.next(); + + if ( tocItem.getRef() == null ) + { + getLogger().debug( + "No ref defined for the tocItem '" + tocItem.getName() + + "' in the document descriptor. IGNORING" ); + continue; + } + + String href = StringUtils.replace( tocItem.getRef(), "\\", "/" ); + if ( href.lastIndexOf( '.' ) != -1 ) + { + href = href.substring( 0, href.lastIndexOf( '.' ) ); + } + + Collection modules = parserModuleManager.getParserModules(); + for ( ParserModule module : modules ) + { + File moduleBasedir = new File( getBaseDir(), module.getSourceDirectory() ); + + if ( moduleBasedir.exists() ) + { + for ( String extension : module.getExtensions() ) + { + String doc = href + "." + extension; + File source = new File( moduleBasedir, doc ); + + // Velocity file? + if ( !source.exists() ) + { + if ( href.indexOf( "." + extension ) != -1 ) + { + doc = href + ".vm"; + } + else + { + doc = href + "." + extension + ".vm"; + } + source = new File( moduleBasedir, doc ); + } + + if ( source.exists() ) + { + String outputITextName = doc.substring( 0, doc.lastIndexOf( '.' ) + 1 ) + "xml"; + File outputITextFileTmp = new File( outputDirectory, outputITextName ); + outputITextFileTmp.deleteOnExit(); + if ( !outputITextFileTmp.getParentFile().exists() ) + { + outputITextFileTmp.getParentFile().mkdirs(); + } + + iTextFiles.add( outputITextFileTmp ); + parse( source, module, outputITextFileTmp, context ); + } + } + } + } + } + + return iTextFiles; + } + + /** + * @param logo + * @param parentFile + * @return the logo url or null if unable to create it. + * @since 1.1.1 + */ + private String getLogoURL( String logo, File parentFile ) + { + if ( logo == null ) + { + return null; + } + + try + { + return new URL( logo ).toString(); + } + catch ( MalformedURLException e ) + { + try + { + File f = new File( parentFile, logo ); + if ( !f.exists() ) + { + getLogger().warn( "The logo " + f.getAbsolutePath() + " doesnt exist. IGNORING" ); + } + else + { + return f.toURI().toURL().toString(); + } + } + catch ( MalformedURLException e1 ) + { + getLogger().debug( "Failed to convert to URL: " + logo, e1 ); + } + } + + return null; + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/javadoc/org/apache/maven/doxia/docrenderer/itext/package.html b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/javadoc/org/apache/maven/doxia/docrenderer/itext/package.html new file mode 100644 index 000000000..7dc901f8f --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/javadoc/org/apache/maven/doxia/docrenderer/itext/package.html @@ -0,0 +1,27 @@ + + + + + +

DEPRECATED.

+

Since 1.1, the org.apache.maven.doxia.docrenderer.itext package is all deprecated and is unmaintained. + It will be removed in the future. You could use an implementation of + DocumentRenderer interface.

+ diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/resources/org/apache/maven/doxia/docrenderer/pdf/itext/TOC.xslt b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/resources/org/apache/maven/doxia/docrenderer/pdf/itext/TOC.xslt new file mode 100644 index 000000000..85753f764 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/main/resources/org/apache/maven/doxia/docrenderer/pdf/itext/TOC.xslt @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + Table Of Contents + + + + + + + + + + + + + + + + + + + + + Table Of Contents + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 1 + + + + + + + + + + + + + + + + +
diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/site/site.xml b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/site/site.xml new file mode 100644 index 000000000..9ea2c6e04 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/site/site.xml @@ -0,0 +1,41 @@ + + + + + + + + +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/java/org/apache/maven/doxia/docrenderer/DocumentRendererTest.java b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/java/org/apache/maven/doxia/docrenderer/DocumentRendererTest.java new file mode 100644 index 000000000..dcb1a5bcc --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/java/org/apache/maven/doxia/docrenderer/DocumentRendererTest.java @@ -0,0 +1,145 @@ +package org.apache.maven.doxia.docrenderer; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.util.List; + +import org.apache.maven.doxia.docrenderer.pdf.PdfRenderer; +import org.apache.maven.doxia.document.DocumentModel; +import org.codehaus.plexus.PlexusTestCase; +import org.codehaus.plexus.util.FileUtils; +import org.codehaus.plexus.util.StringUtils; + +/** + * @author Vincent Siveton + * @since 1.1.1 + */ +public class DocumentRendererTest + extends PlexusTestCase +{ + private PdfRenderer docRenderer; + + private File siteDirectoryFile; + + /** @throws java.lang.Exception */ + @Override + protected void setUp() + throws Exception + { + super.setUp(); + + siteDirectoryFile = getTestFile( "src/test/resources/site" ); + } + + /** @throws java.lang.Exception */ + @Override + protected void tearDown() + throws Exception + { + release( docRenderer ); + super.tearDown(); + } + + /** @throws java.lang.Exception */ + public void testFo() + throws Exception + { + renderImpl( "fo" ); + } + + /** @throws java.lang.Exception */ + public void testFoAggregate() + throws Exception + { + renderAggregatedImpl( "fo" ); + } + + /** @throws java.lang.Exception */ + public void testIText() + throws Exception + { + renderImpl( "itext" ); + } + + /** @throws java.lang.Exception */ + public void testITextAggregate() + throws Exception + { + renderAggregatedImpl( "itext" ); + } + + private void renderImpl( String implementation ) + throws Exception + { + File outputDirectory = getTestFile( "target/output/" + implementation ); + if ( outputDirectory.exists() ) + { + FileUtils.deleteDirectory( outputDirectory ); + } + outputDirectory.mkdirs(); + + docRenderer = (PdfRenderer) lookup( PdfRenderer.ROLE, implementation ); + assertNotNull( docRenderer ); + + docRenderer.render( siteDirectoryFile, outputDirectory, null ); + + List files = + FileUtils.getFileNames( new File( siteDirectoryFile, "apt" ), "**/*.apt", + FileUtils.getDefaultExcludesAsString(), false ); + files.addAll( FileUtils.getFileNames( new File( siteDirectoryFile, "fml" ), "**/*.fml", + FileUtils.getDefaultExcludesAsString(), false ) ); + files.addAll( FileUtils.getFileNames( new File( siteDirectoryFile, "xdoc" ), "**/*.xml", + FileUtils.getDefaultExcludesAsString(), false ) ); + + for ( String relativeFile : files ) + { + String relativePdf = StringUtils.replace( relativeFile, FileUtils.getExtension( relativeFile ), "pdf" ); + File pdf = new File( outputDirectory, relativePdf ); + + assertTrue( pdf.exists() ); + assertTrue( pdf.length() > 0 ); + } + } + + private void renderAggregatedImpl( String implementation ) + throws Exception + { + File outputDirectory = getTestFile( "target/output/" + implementation + "-aggregated" ); + if ( outputDirectory.exists() ) + { + FileUtils.deleteDirectory( outputDirectory ); + } + outputDirectory.mkdirs(); + + docRenderer = (PdfRenderer) lookup( PdfRenderer.ROLE, implementation ); + assertNotNull( docRenderer ); + + DocumentModel descriptor = docRenderer.readDocumentModel( new File( siteDirectoryFile, "pdf.xml" ) ); + assertNotNull( descriptor ); + + docRenderer.render( siteDirectoryFile, outputDirectory, descriptor ); + + File pdf = new File( outputDirectory, descriptor.getOutputName() + ".pdf" ); + + assertTrue( pdf.exists() ); + assertTrue( pdf.length() > 0 ); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/apt/index.apt b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/apt/index.apt new file mode 100644 index 000000000..5a9b4616e --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/apt/index.apt @@ -0,0 +1,66 @@ + ----- + Doxia + ----- + Jason van Zyl + Vincent Siveton + ------ + July 2007 + ------ + +~~ 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. + +~~ NOTE: For help with the syntax of this file, see: +~~ http://maven.apache.org/doxia/references/apt-format.html + +Maven Doxia + + Doxia is a content generation framework which aims to provide its users with powerful + techniques for generating static and dynamic content: Doxia can be used in web-based + publishing context to generate static sites, in addition to being incorporated into + dynamic content generation systems like blogs, wikis and content management systems. + + Doxia supports markup languages with simple syntaxes. Lightweight markup languages + are used by people who might be expected to read the document source as well as the rendered output. + + Doxia is used extensively by Maven and it powers the entire documentation system of Maven. + It gives Maven the ability to take any document that Doxia supports and output it any format. + +* Brief History + + Based on the {{{http://www.xmlmind.com/aptconvert.html}Aptconvert}} project developed by + {{{http://www.xmlmind.com/}Xmlmind}} company, Doxia was initially hosted by Codehaus, to become + a sub-project of Maven early in 2006. + +* Main Features + + * Developed in Java + + * Support of several markup formats: APT (Almost Plain Text), Confluence, DocBook, + FML (FAQ Markup Language), LaTeX, RTF, TWiki, XDoc (popular in Apache land), XHTML + +~~ iText should be replaced by FOP + + * Easy to learn the syntax of the supported markup formats + + * Macro support + + * No need to have a corporate infrastructure (like wiki) to host your documentation + + * Extensible framework + + [] diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/apt/overview.apt b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/apt/overview.apt new file mode 100644 index 000000000..5360991a4 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/apt/overview.apt @@ -0,0 +1,104 @@ + ----- + Overview Of The Doxia Framework + ----- + Vincent Siveton + ------ + July 2007 + ------ + +~~ 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. + +~~ NOTE: For help with the syntax of this file, see: +~~ http://maven.apache.org/doxia/references/apt-format.html + +Overview Of The Doxia Framework + + The following figure represents the main components of the Doxia Framework. + +[images/architecture.png] Doxia Framework + + <>: Just like Maven, Doxia uses {{{http://plexus.codehaus.org/}Plexus}} extensively. + +*Sink API + + The interface is a generic markup language interface. It contains several methods that + encapsulate common text syntax. A start tag is denoted by method + and a end of tag by method. + + For instance, you could do things like: + +----- + sink.paragraph(); + sink.text( "my text" ); + sink.paragraph_(); +----- + + similar to this HTML markup: + +----- +

my text

+----- + + To find out more about the Sink API, you could read the Javadoc + {{{http://maven.apache.org/doxia/doxia-sink-api/apidocs/org/apache/maven/doxia/sink/Sink.html}here}}. + +*Doxia Core + + The is the API to parse a source and populate it in a object. The interface + contains only one method: + +----- +void parse( Reader source, Sink sink ) + throws ParseException; +----- + + The class has the responsibility to catch all parsing exceptions. It provides an + helper method, , which helps to find where an error occurred. + + The class is an abstract implementation of the . It provides a macro mechanism + to give dynamic functionalities for the parsing. For more information on macros, read the + {{{./macros/index.html}Doxia Macro Guide}}. + + Finally, the interface is the last part of the puzzle. It provides main definitions of a + given Doxia module and it is used by the site tools. + +*Doxia Modules + + A Doxia module is an implementation of a given markup language like APT or Xdoc. Each module should + implement these interfaces: + + * interface, more specifically the class + + * interface + + [] + + Several modules provide also a implementation to handle a specific markup language. + + For more information on modules, read the {{{./modules/index.html}Doxia Module Guide}}. + +*Doxia Sitetools + + The are a collection of tools to renderer an output. The main tool used by Maven, + specifically the {{{http://maven.apache.org/plugins/maven-site-plugin/}Maven Site Plugin}}, is the + which renders in HTML any documents wrote with supported markup syntax. It used + {{{http://velocity.apache.org/}Velocity templates}} to customize the renderer and the + tool to decorate the renderer. This component describes the layout of the site + defined in the file. + + The tool is used to renderer any document in another document. diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/apt/resources.apt b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/apt/resources.apt new file mode 100644 index 000000000..9c606ec4c --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/apt/resources.apt @@ -0,0 +1,49 @@ + ----- + External Resources + ----- + Vincent Siveton + ------ + July 2007 + ------ + +~~ 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. + +~~ NOTE: For help with the syntax of this file, see: +~~ http://maven.apache.org/doxia/references/apt-format.html + +External Resources + +*Articles + +*-----------+--------------+--------------+ +|| Title || Publisher || Author +*-----------+--------------+--------------+ +| {{{http://software.newsforge.com/article.pl?sid=04/04/16/1428219}Quick and dirty typesetting with APT}} | newsforge.com | Scott Nesbitt +*-----------+--------------+--------------+ +| {{{http://en.wikipedia.org/wiki/Lightweight_markup_language}Lightweight markup language}} | wikipedia.org | ? +*-----------+--------------+--------------+ +| {{{http://project.knowledgeforge.net/kforge/trac/wiki/TextProcessing}Simple (Ascii-Based) Text Formats}} | project.knowledgeforge.net | ? +*-----------+--------------+--------------+ + +*Tools + +*-----------+--------------+ +|| Name || Author +*-----------+--------------+ +| {{{http://apteditor.sourceforge.net/}APT Editor (Eclipse plugin)}} | Mathieu Avoine +*-----------+--------------+ diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/fml/faq.fml b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/fml/faq.fml new file mode 100644 index 000000000..94ec21d99 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/fml/faq.fml @@ -0,0 +1,67 @@ + + + + + + + + How to handle style in the APT markup language? + +

+ APT doesn't currently support style. It is in the roadmap. +

+
+
+ + How to export in PDF? + +

+ An iText module exists using the iText XML document. + Unfortunately, the iText team discontinued the XML to PDF functionalities. +

+

+ A FOP module is currently in development in the + Doxia sandbox. You can get the source + here. +

+
+
+ + Is it possible to create a book? + +

+ Doxia also has a fairly simple tool for writing books. It comes complete with a Maven plugin + to produce PDFs, LaTeX documents and Xdoc for direct integration in your Maven site. +

+

+ The Doxia Book code is still in the + Doxia sandbox, + but it is fully functional allthough limited. +

+

+ See Writing Books in Doxia for more information. +

+
+
+
+
\ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/pdf.xml b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/pdf.xml new file mode 100644 index 000000000..4b9180e9f --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/pdf.xml @@ -0,0 +1,57 @@ + + + + + + + Apache Maven Doxia + The Apache Maven Project + + Content generation framework + Lightweight markup language + APT + FML + XDOC + + + + + + + + + + + + + + Apache Maven Doxia Test + v. 1.1.1-SNAPSHOT + User Guide + The Apache Software Foundation + ./images/asf_logo_wide.png + ./images/doxia-logo.png + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/resources/css/site.css b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/resources/css/site.css new file mode 100644 index 000000000..0310f3011 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/resources/css/site.css @@ -0,0 +1,57 @@ +/* + * 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. + */ + +a.externalLink, a.externalLink:link, a.externalLink:visited, a.externalLink:active, a.externalLink:hover { + background: none; + padding-right: 0; +} + +body ul { + list-style-type: square; +} + +#downloadbox { + float: right; + margin-left: 2em; + padding-left: 1em; + padding-right: 1em; + padding-bottom: 1em; + border: 1px solid #999; + background-color: #eee; + width: 17.5em; +} + +#downloadbox h5 { + color: #000; + margin: 0; + border-bottom: 1px solid #aaaaaa; + font-size: smaller; + padding: 0; + margin-top: 1em; +} + +#downloadbox p { + margin-top: 1em; + margin-bottom: 0; +} + +#downloadbox li { + text-indent: inherit; +} + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/resources/images/architecture.odg b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/resources/images/architecture.odg new file mode 100644 index 000000000..6a8ae4127 Binary files /dev/null and b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/resources/images/architecture.odg differ diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/resources/images/architecture.png b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/resources/images/architecture.png new file mode 100644 index 000000000..f37791d96 Binary files /dev/null and b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/resources/images/architecture.png differ diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/resources/images/asf_logo_wide.png b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/resources/images/asf_logo_wide.png new file mode 100644 index 000000000..c584ebad9 Binary files /dev/null and b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/resources/images/asf_logo_wide.png differ diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/resources/images/doxia-logo.png b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/resources/images/doxia-logo.png new file mode 100644 index 000000000..8c156558a Binary files /dev/null and b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/resources/images/doxia-logo.png differ diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/site.xml b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/site.xml new file mode 100644 index 000000000..a28856f8d --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/site.xml @@ -0,0 +1,84 @@ + + + + + + + Doxia + images/apache-maven-project-2.png + + + images/maven-logo-2.gif + + + + org.apache.maven.skins + maven-stylus-skin + 1.0.1 + + + + + + + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/xdoc/references/fml-format.xml b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/xdoc/references/fml-format.xml new file mode 100644 index 000000000..f08979468 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/xdoc/references/fml-format.xml @@ -0,0 +1,101 @@ + + + + + The FML (FAQ Markup Language) format + Lukas Theussl + + + + +
+ + + +

+ An 'fml' is an XML document conforming to a small and simple set of tags. + The format was first used in the Maven 1, + version of the FAQ plugin. +

+ +
+ + + +

+ The following is a sample FML document: +

+ + + + + + General + + + + What is Foo? + + +

some markup goes here

+ + some source code + +

some markup goes here

+
+
+ + + + What is Bar? + + +

some markup goes here

+
+
+
+ + + + Installation + + + + How do I install Foo? + + +

some markup goes here

+
+
+ +
+ +
+]]> + +
+ +
+ + + +
diff --git a/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/xdoc/references/xdoc-format.xml b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/xdoc/references/xdoc-format.xml new file mode 100644 index 000000000..ff935e607 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-doc-renderer/src/test/resources/site/xdoc/references/xdoc-format.xml @@ -0,0 +1,279 @@ + + + + + The Xdoc format + Lukas Theussl + + + + +
+ + + +

+ An 'xdoc' is an XML document conforming to a small and simple set of tags. + Xdoc was the primary documentation format in Maven 1, + Maven 2 largely replaced this by Apt, but xdoc is still supported. +

+ +

+ Historically, the xdoc format can be traced back to the + Anakia format, as once used by the + Apache Jakarta project. +

+ +

+ The Maven 1 Xdoc plugin introduced a few additions to the Anakia format, they are highlighted in the + plugin documentation. +

+ +
+ + + +

+ The following is a sample XDoc document: +

+ + + + Page Title + John Doe + + + + + + + + + + +
+ + +

Hi!

+ + + +

Subsection!

+
+ +
+ +
+ + + +code line 1 +code line 2 + + +
+ + + +]]> + +
+ + + +

+ Doxia will produce <h2> and + <h3> headings for <section> + and <subsection> elements, respectively. + It is therefore perfectly valid to put some sub-headings + (<h4>, <h5>, + <h6>) inside a subsection. For instance, +

+ + A subsubsection]]> + +

+ will produce: +

+ +
+ + + +

+ The core doxia modules do not construct anchors from + section/subsection names. If you want to reference a section, + you have to provide an explicit anchor: +

+ + +
+ + + + + +
]]> + +

+ Note that this differs from previous behavior, where anchors + were constructed from section/subsection names, replacing special + characters by underscores. This behavior presents two shortcomings: +

+ +
    + +
  • + If two sections or subsections have identical names + (within one source document), you will get an ambiguity when + referencing them. Also the resulting html document will not be + valid XHTML. For other output formats (eg pdf), it might even be impossible + to generate the target document. +
  • + +
  • + For long section titles, this leads to rather cumbersome anchor names. +
  • + +
+ +

+ If automatic anchor generation is desired for a particular output format, + it should be implemented / overridden by the corresponding low-level Sink. +

+ +
+ +
+ +
+ +

+ Doxia is currently not able to validate your xdoc files as no schema or DTD + exists yet (however this is planned before the 1.0 release). + It is therefore necessary to check manually whether your source files are valid xdocs, + this should ensure that the generated html files are valid + XHTML1-transitional. +

+ +

+ Here is a list of common mistakes to be aware of: +

+ + + +

Wrong:

+ + + Here's a list: +
    +
  • item 1
  • +
  • item 2
  • +
+ of things to do. +

]]> + +

Correct:

+ + + Here's a list: +

+
    +
  • item 1
  • +
  • item 2
  • +
+

+ of things to do. +

]]> + +

+ Typical block level elements are list elements, + <table>, <source>, + <div>, <p> and + <pre>. +

+ +
+ + + +

Wrong:

+ + + Downloads +
]]> + +

Correct:

+ + +

+ Downloads +

+]]> + +

+ Typical inline elements are + <a>, <strong>, + <code>, <font>, + <br> and <img>. +

+ + + + + +

+ The <title> element has to come before + <author>. +

+ +
+ + + +

Wrong:

+ + + The following command executes the program: + java -jar CoolApp.jar +

]]> + +

Correct:

+ + + The following command executes the program: +

+java -jar CoolApp.jar]]> + +

+ However, you may put <source> elements inside + list items or table rows. +

+ +
+ + + + + +
diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/pom.xml b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/pom.xml new file mode 100644 index 000000000..dabf9b3c6 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/pom.xml @@ -0,0 +1,191 @@ + + + + + + 4.0.0 + + + org.apache.maven.doxia + doxia-sitetools + 1.9.3-SNAPSHOT + ../pom.xml + + + doxia-integration-tools + + Doxia Sitetools :: Integration Tools + A collection of tools to help the integration of Doxia Sitetools in Maven plugins. + + + 2.2.1 + + + + + org.apache.maven.reporting + maven-reporting-api + 3.0 + + + + commons-io + commons-io + + + + + org.apache.maven + maven-artifact + ${mavenVersion} + + + org.apache.maven + maven-artifact-manager + ${mavenVersion} + provided + + + org.apache.maven + maven-model + ${mavenVersion} + + + org.apache.maven + maven-project + ${mavenVersion} + provided + + + org.apache.maven + maven-plugin-api + ${mavenVersion} + + + + + org.apache.maven.doxia + doxia-logging-api + + + + + org.apache.maven.doxia + doxia-decoration-model + + + + + org.codehaus.plexus + plexus-container-default + 1.0-alpha-9 + + + org.codehaus.plexus + plexus-i18n + + + org.codehaus.plexus + plexus-component-api + + + + + org.codehaus.plexus + plexus-utils + + + org.codehaus.plexus + plexus-component-annotations + + + org.codehaus.plexus + plexus-interpolation + 1.25 + + + + + junit + junit + test + + + org.apache.maven.shared + maven-plugin-testing-harness + 1.1 + test + + + + + + + org.codehaus.plexus + plexus-component-metadata + + + create-component-descriptor + + generate-metadata + + + + + + + + + + + org.codehaus.mojo + l10n-maven-plugin + 1.0-alpha-2 + + + ca + cs + da + de + es + fr + gl + hu + it + ja + ko + lt + nl + no + pl + pt + pt_BR + ru + sk + sv + tr + zh_CN + zh_TW + + + + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/DefaultSiteTool.java b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/DefaultSiteTool.java new file mode 100644 index 000000000..f7cea889d --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/DefaultSiteTool.java @@ -0,0 +1,1529 @@ +package org.apache.maven.doxia.tools; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.StringTokenizer; + +import org.apache.commons.io.FilenameUtils; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.factory.ArtifactFactory; +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.artifact.resolver.ArtifactNotFoundException; +import org.apache.maven.artifact.resolver.ArtifactResolutionException; +import org.apache.maven.artifact.resolver.ArtifactResolver; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; +import org.apache.maven.artifact.versioning.VersionRange; +import org.apache.maven.doxia.site.decoration.Banner; +import org.apache.maven.doxia.site.decoration.DecorationModel; +import org.apache.maven.doxia.site.decoration.Menu; +import org.apache.maven.doxia.site.decoration.MenuItem; +import org.apache.maven.doxia.site.decoration.Skin; +import org.apache.maven.doxia.site.decoration.inheritance.DecorationModelInheritanceAssembler; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Reader; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Writer; +import org.apache.maven.model.DistributionManagement; +import org.apache.maven.model.Site; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.MavenProjectBuilder; +import org.apache.maven.project.ProjectBuildingException; +import org.apache.maven.reporting.MavenReport; +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.IOUtil; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.interpolation.EnvarBasedValueSource; +import org.codehaus.plexus.interpolation.InterpolationException; +import org.codehaus.plexus.interpolation.MapBasedValueSource; +import org.codehaus.plexus.interpolation.ObjectBasedValueSource; +import org.codehaus.plexus.interpolation.PrefixedObjectValueSource; +import org.codehaus.plexus.interpolation.PrefixedPropertiesValueSource; +import org.codehaus.plexus.interpolation.RegexBasedInterpolator; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +/** + * Default implementation of the site tool. + * + * @author Vincent Siveton + */ +@Component( role = SiteTool.class ) +public class DefaultSiteTool + extends AbstractLogEnabled + implements SiteTool +{ + // ---------------------------------------------------------------------- + // Components + // ---------------------------------------------------------------------- + + /** + * The component that is used to resolve additional artifacts required. + */ + @Requirement + private ArtifactResolver artifactResolver; + + /** + * The component used for creating artifact instances. + */ + @Requirement + private ArtifactFactory artifactFactory; + + /** + * Internationalization. + */ + @Requirement + protected I18N i18n; + + /** + * The component for assembling inheritance. + */ + @Requirement + protected DecorationModelInheritanceAssembler assembler; + + /** + * Project builder (deprecated in Maven 3: should use ProjectBuilder, which will avoid + * issues like DOXIASITETOOLS-166) + */ + @Requirement + protected MavenProjectBuilder mavenProjectBuilder; + + // ---------------------------------------------------------------------- + // Public methods + // ---------------------------------------------------------------------- + + public Artifact getSkinArtifactFromRepository( ArtifactRepository localRepository, + List remoteArtifactRepositories, + DecorationModel decoration ) + throws SiteToolException + { + checkNotNull( "localRepository", localRepository ); + checkNotNull( "remoteArtifactRepositories", remoteArtifactRepositories ); + checkNotNull( "decoration", decoration ); + + Skin skin = decoration.getSkin(); + + if ( skin == null ) + { + skin = Skin.getDefaultSkin(); + } + + String version = skin.getVersion(); + Artifact artifact; + try + { + if ( version == null ) + { + version = Artifact.RELEASE_VERSION; + } + VersionRange versionSpec = VersionRange.createFromVersionSpec( version ); + artifact = artifactFactory.createDependencyArtifact( skin.getGroupId(), skin.getArtifactId(), versionSpec, + "jar", null, null ); + + artifactResolver.resolve( artifact, remoteArtifactRepositories, localRepository ); + } + catch ( InvalidVersionSpecificationException e ) + { + throw new SiteToolException( "InvalidVersionSpecificationException: The skin version '" + version + + "' is not valid: " + e.getMessage(), e ); + } + catch ( ArtifactResolutionException e ) + { + throw new SiteToolException( "ArtifactResolutionException: Unable to find skin", e ); + } + catch ( ArtifactNotFoundException e ) + { + throw new SiteToolException( "ArtifactNotFoundException: The skin does not exist: " + e.getMessage(), e ); + } + + return artifact; + } + + public Artifact getDefaultSkinArtifact( ArtifactRepository localRepository, + List remoteArtifactRepositories ) + throws SiteToolException + { + return getSkinArtifactFromRepository( localRepository, remoteArtifactRepositories, new DecorationModel() ); + } + + /** + * This method is not implemented according to the URI specification and has many weird + * corner cases where it doesn't do the right thing. Please consider using a better + * implemented method from a different library such as org.apache.http.client.utils.URIUtils#resolve. + */ + @Deprecated + public String getRelativePath( String to, String from ) + { + checkNotNull( "to", to ); + checkNotNull( "from", from ); + + if ( to.contains( ":" ) && from.contains( ":" ) ) + { + String toScheme = to.substring( 0, to.lastIndexOf( ':' ) ); + String fromScheme = from.substring( 0, from.lastIndexOf( ':' ) ); + if ( !toScheme.equals( fromScheme ) ) + { + return to; + } + } + + URL toUrl = null; + URL fromUrl = null; + + String toPath = to; + String fromPath = from; + + try + { + toUrl = new URL( to ); + } + catch ( MalformedURLException e ) + { + try + { + toUrl = new File( getNormalizedPath( to ) ).toURI().toURL(); + } + catch ( MalformedURLException e1 ) + { + getLogger().warn( "Unable to load a URL for '" + to + "': " + e.getMessage() ); + return to; + } + } + + try + { + fromUrl = new URL( from ); + } + catch ( MalformedURLException e ) + { + try + { + fromUrl = new File( getNormalizedPath( from ) ).toURI().toURL(); + } + catch ( MalformedURLException e1 ) + { + getLogger().warn( "Unable to load a URL for '" + from + "': " + e.getMessage() ); + return to; + } + } + + if ( toUrl != null && fromUrl != null ) + { + // URLs, determine if they share protocol and domain info + + if ( ( toUrl.getProtocol().equalsIgnoreCase( fromUrl.getProtocol() ) ) + && ( toUrl.getHost().equalsIgnoreCase( fromUrl.getHost() ) ) + && ( toUrl.getPort() == fromUrl.getPort() ) ) + { + // shared URL domain details, use URI to determine relative path + + toPath = toUrl.getFile(); + fromPath = fromUrl.getFile(); + } + else + { + // don't share basic URL information, no relative available + + return to; + } + } + else if ( ( toUrl != null && fromUrl == null ) || ( toUrl == null && fromUrl != null ) ) + { + // one is a URL and the other isn't, no relative available. + + return to; + } + + // either the two locations are not URLs or if they are they + // share the common protocol and domain info and we are left + // with their URI information + + String relativePath = getRelativeFilePath( fromPath, toPath ); + + if ( relativePath == null ) + { + relativePath = to; + } + + if ( getLogger().isDebugEnabled() && !relativePath.toString().equals( to ) ) + { + getLogger().debug( "Mapped url: " + to + " to relative path: " + relativePath ); + } + + return relativePath; + } + + private static String getRelativeFilePath( final String oldPath, final String newPath ) + { + // normalize the path delimiters + + String fromPath = new File( oldPath ).getPath(); + String toPath = new File( newPath ).getPath(); + + // strip any leading slashes if its a windows path + if ( toPath.matches( "^\\[a-zA-Z]:" ) ) + { + toPath = toPath.substring( 1 ); + } + if ( fromPath.matches( "^\\[a-zA-Z]:" ) ) + { + fromPath = fromPath.substring( 1 ); + } + + // lowercase windows drive letters. + if ( fromPath.startsWith( ":", 1 ) ) + { + fromPath = Character.toLowerCase( fromPath.charAt( 0 ) ) + fromPath.substring( 1 ); + } + if ( toPath.startsWith( ":", 1 ) ) + { + toPath = Character.toLowerCase( toPath.charAt( 0 ) ) + toPath.substring( 1 ); + } + + // check for the presence of windows drives. No relative way of + // traversing from one to the other. + + if ( ( toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) + && ( !toPath.substring( 0, 1 ).equals( fromPath.substring( 0, 1 ) ) ) ) + { + // they both have drive path element but they don't match, no + // relative path + + return null; + } + + if ( ( toPath.startsWith( ":", 1 ) && !fromPath.startsWith( ":", 1 ) ) + || ( !toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) ) + { + + // one has a drive path element and the other doesn't, no relative + // path. + + return null; + + } + + final String relativePath = buildRelativePath( toPath, fromPath, File.separatorChar ); + + return relativePath.toString(); + } + + /** {@inheritDoc} */ + public File getSiteDescriptor( File siteDirectory, Locale locale ) + { + checkNotNull( "siteDirectory", siteDirectory ); + final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale; + + File siteDescriptor = new File( siteDirectory, "site_" + llocale.getLanguage() + ".xml" ); + + if ( !siteDescriptor.isFile() ) + { + siteDescriptor = new File( siteDirectory, "site.xml" ); + } + return siteDescriptor; + } + + /** + * Get a site descriptor from one of the repositories. + * + * @param project the Maven project, not null. + * @param localRepository the Maven local repository, not null. + * @param repositories the Maven remote repositories, not null. + * @param locale the locale wanted for the site descriptor. If not null, searching for + * site_localeLanguage.xml, otherwise searching for site.xml. + * @return the site descriptor into the local repository after download of it from repositories or null if not + * found in repositories. + * @throws SiteToolException if any + */ + File getSiteDescriptorFromRepository( MavenProject project, ArtifactRepository localRepository, + List repositories, Locale locale ) + throws SiteToolException + { + checkNotNull( "project", project ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "repositories", repositories ); + + final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale; + + try + { + return resolveSiteDescriptor( project, localRepository, repositories, llocale ); + } + catch ( ArtifactNotFoundException e ) + { + getLogger().debug( "ArtifactNotFoundException: Unable to locate site descriptor: " + e ); + return null; + } + catch ( ArtifactResolutionException e ) + { + throw new SiteToolException( "ArtifactResolutionException: Unable to locate site descriptor: " + + e.getMessage(), e ); + } + catch ( IOException e ) + { + throw new SiteToolException( "IOException: Unable to locate site descriptor: " + e.getMessage(), e ); + } + } + + /** + * Read site descriptor content from Reader, adding support for deprecated ${reports}, + * ${parentProject} and ${modules} tags. + * + * @param reader + * @return the input content interpolated with deprecated tags + * @throws IOException + */ + private String readSiteDescriptor( Reader reader, String projectId ) + throws IOException + { + String siteDescriptorContent = IOUtil.toString( reader ); + + // This is to support the deprecated ${reports}, ${parentProject} and ${modules} tags. + Properties props = new Properties(); + props.put( "reports", "
" ); + props.put( "modules", "" ); + props.put( "parentProject", "" ); + + // warn if interpolation required + for ( Object prop : props.keySet() ) + { + if ( siteDescriptorContent.contains( "$" + prop ) ) + { + getLogger().warn( "Site descriptor for " + projectId + " contains $" + prop + + ": should be replaced with " + props.getProperty( (String) prop ) ); + } + if ( siteDescriptorContent.contains( "${" + prop + "}" ) ) + { + getLogger().warn( "Site descriptor for " + projectId + " contains ${" + prop + + "}: should be replaced with " + props.getProperty( (String) prop ) ); + } + } + + return StringUtils.interpolate( siteDescriptorContent, props ); + } + + /** {@inheritDoc} */ + public DecorationModel getDecorationModel( File siteDirectory, Locale locale, MavenProject project, + List reactorProjects, ArtifactRepository localRepository, + List repositories ) + throws SiteToolException + { + checkNotNull( "project", project ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "repositories", repositories ); + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + getLogger().debug( "Computing decoration model of " + project.getId() + " for locale " + llocale ); + + Map.Entry result = + getDecorationModel( 0, siteDirectory, llocale, project, reactorProjects, localRepository, repositories ); + DecorationModel decorationModel = result.getKey(); + MavenProject parentProject = result.getValue(); + + if ( decorationModel == null ) + { + getLogger().debug( "Using default site descriptor" ); + + String siteDescriptorContent; + + Reader reader = null; + try + { + // Note the default is not a super class - it is used when nothing else is found + reader = ReaderFactory.newXmlReader( getClass().getResourceAsStream( "/default-site.xml" ) ); + siteDescriptorContent = readSiteDescriptor( reader, "default-site.xml" ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading default site descriptor: " + e.getMessage(), e ); + } + finally + { + IOUtil.close( reader ); + } + + decorationModel = readDecorationModel( siteDescriptorContent ); + } + + // DecorationModel back to String to interpolate, then go back to DecorationModel + String siteDescriptorContent = decorationModelToString( decorationModel ); + + // "classical" late interpolation, after full inheritance + siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, false ); + + decorationModel = readDecorationModel( siteDescriptorContent ); + + if ( parentProject != null ) + { + populateParentMenu( decorationModel, llocale, project, parentProject, true ); + } + + try + { + populateModulesMenu( decorationModel, llocale, project, reactorProjects, localRepository, true ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error while populating modules menu: " + e.getMessage(), e ); + } + + if ( decorationModel.getBannerLeft() == null ) + { + // extra default to set + Banner banner = new Banner(); + banner.setName( project.getName() ); + decorationModel.setBannerLeft( banner ); + } + + return decorationModel; + } + + /** {@inheritDoc} */ + public String getInterpolatedSiteDescriptorContent( Map props, MavenProject aProject, + String siteDescriptorContent ) + throws SiteToolException + { + checkNotNull( "props", props ); + + // "classical" late interpolation + return getInterpolatedSiteDescriptorContent( aProject, siteDescriptorContent, false ); + } + + private String getInterpolatedSiteDescriptorContent( MavenProject aProject, + String siteDescriptorContent, boolean isEarly ) + throws SiteToolException + { + checkNotNull( "aProject", aProject ); + checkNotNull( "siteDescriptorContent", siteDescriptorContent ); + + RegexBasedInterpolator interpolator = new RegexBasedInterpolator(); + + if ( isEarly ) + { + interpolator.addValueSource( new PrefixedObjectValueSource( "this.", aProject ) ); + interpolator.addValueSource( new PrefixedPropertiesValueSource( "this.", aProject.getProperties() ) ); + } + else + { + interpolator.addValueSource( new ObjectBasedValueSource( aProject ) ); + interpolator.addValueSource( new MapBasedValueSource( aProject.getProperties() ) ); + + try + { + interpolator.addValueSource( new EnvarBasedValueSource() ); + } + catch ( IOException e ) + { + // Prefer logging? + throw new SiteToolException( "IOException: cannot interpolate environment properties: " + + e.getMessage(), e ); + } + } + + try + { + // FIXME: this does not escape xml entities, see MSITE-226, PLXCOMP-118 + return interpolator.interpolate( siteDescriptorContent, isEarly ? null : "project" ); + } + catch ( InterpolationException e ) + { + throw new SiteToolException( "Cannot interpolate site descriptor: " + e.getMessage(), e ); + } + } + + /** {@inheritDoc} */ + public MavenProject getParentProject( MavenProject aProject, List reactorProjects, + ArtifactRepository localRepository ) + { + checkNotNull( "aProject", aProject ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + + if ( isMaven3OrMore() ) + { + // no need to make voodoo with Maven 3: job already done + return aProject.getParent(); + } + + MavenProject parentProject = null; + + MavenProject origParent = aProject.getParent(); + if ( origParent != null ) + { + for ( MavenProject reactorProject : reactorProjects ) + { + if ( reactorProject.getGroupId().equals( origParent.getGroupId() ) + && reactorProject.getArtifactId().equals( origParent.getArtifactId() ) + && reactorProject.getVersion().equals( origParent.getVersion() ) ) + { + parentProject = reactorProject; + + getLogger().debug( "Parent project " + origParent.getId() + " picked from reactor" ); + break; + } + } + + if ( parentProject == null && aProject.getBasedir() != null + && StringUtils.isNotEmpty( aProject.getModel().getParent().getRelativePath() ) ) + { + try + { + String relativePath = aProject.getModel().getParent().getRelativePath(); + + File pomFile = new File( aProject.getBasedir(), relativePath ); + + if ( pomFile.isDirectory() ) + { + pomFile = new File( pomFile, "pom.xml" ); + } + pomFile = new File( getNormalizedPath( pomFile.getPath() ) ); + + if ( pomFile.isFile() ) + { + MavenProject mavenProject = mavenProjectBuilder.build( pomFile, localRepository, null ); + + if ( mavenProject.getGroupId().equals( origParent.getGroupId() ) + && mavenProject.getArtifactId().equals( origParent.getArtifactId() ) + && mavenProject.getVersion().equals( origParent.getVersion() ) ) + { + parentProject = mavenProject; + + getLogger().debug( "Parent project " + origParent.getId() + " loaded from a relative path: " + + relativePath ); + } + } + } + catch ( ProjectBuildingException e ) + { + getLogger().info( "Unable to load parent project " + origParent.getId() + " from a relative path: " + + e.getMessage() ); + } + } + + if ( parentProject == null ) + { + try + { + parentProject = mavenProjectBuilder.buildFromRepository( aProject.getParentArtifact(), aProject + .getRemoteArtifactRepositories(), localRepository ); + + getLogger().debug( "Parent project " + origParent.getId() + " loaded from repository" ); + } + catch ( ProjectBuildingException e ) + { + getLogger().warn( "Unable to load parent project " + origParent.getId() + " from repository: " + + e.getMessage() ); + } + } + + if ( parentProject == null ) + { + // fallback to original parent, which may contain uninterpolated value (still need a unit test) + + parentProject = origParent; + + getLogger().debug( "Parent project " + origParent.getId() + " picked from original value" ); + } + } + return parentProject; + } + + /** + * Populate the pre-defined parent menu of the decoration model, + * if used through <menu ref="parent"/>. + * + * @param decorationModel the Doxia Sitetools DecorationModel, not null. + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project a Maven project, not null. + * @param parentProject a Maven parent project, not null. + * @param keepInheritedRefs used for inherited references. + */ + private void populateParentMenu( DecorationModel decorationModel, Locale locale, MavenProject project, + MavenProject parentProject, boolean keepInheritedRefs ) + { + checkNotNull( "decorationModel", decorationModel ); + checkNotNull( "project", project ); + checkNotNull( "parentProject", parentProject ); + + Menu menu = decorationModel.getMenuRef( "parent" ); + + if ( menu == null ) + { + return; + } + + if ( keepInheritedRefs && menu.isInheritAsRef() ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + String parentUrl = getDistMgmntSiteUrl( parentProject ); + + if ( parentUrl != null ) + { + if ( parentUrl.endsWith( "/" ) ) + { + parentUrl += "index.html"; + } + else + { + parentUrl += "/index.html"; + } + + parentUrl = getRelativePath( parentUrl, getDistMgmntSiteUrl( project ) ); + } + else + { + // parent has no url, assume relative path is given by site structure + File parentBasedir = parentProject.getBasedir(); + // First make sure that the parent is available on the file system + if ( parentBasedir != null ) + { + // Try to find the relative path to the parent via the file system + String parentPath = parentBasedir.getAbsolutePath(); + String projectPath = project.getBasedir().getAbsolutePath(); + parentUrl = getRelativePath( parentPath, projectPath ) + "/index.html"; + } + } + + // Only add the parent menu if we were able to find a URL for it + if ( parentUrl == null ) + { + getLogger().warn( "Unable to find a URL to the parent project. The parent menu will NOT be added." ); + } + else + { + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.parentproject" ) ); + } + + MenuItem item = new MenuItem(); + item.setName( parentProject.getName() ); + item.setHref( parentUrl ); + menu.addItem( item ); + } + } + + /** + * Populate the pre-defined modules menu of the decoration model, + * if used through <menu ref="modules"/>. + * + * @param decorationModel the Doxia Sitetools DecorationModel, not null. + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project a Maven project, not null. + * @param reactorProjects the Maven reactor projects, not null. + * @param localRepository the Maven local repository, not null. + * @param keepInheritedRefs used for inherited references. + * @throws SiteToolException if any + * @throws IOException + */ + private void populateModulesMenu( DecorationModel decorationModel, Locale locale, MavenProject project, + List reactorProjects, ArtifactRepository localRepository, + boolean keepInheritedRefs ) + throws SiteToolException, IOException + { + checkNotNull( "project", project ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "decorationModel", decorationModel ); + + Menu menu = decorationModel.getMenuRef( "modules" ); + + if ( menu == null ) + { + return; + } + + if ( keepInheritedRefs && menu.isInheritAsRef() ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale ; + + // we require child modules and reactors to process module menu + if ( project.getModules().size() > 0 ) + { + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectmodules" ) ); + } + + for ( String module : (List) project.getModules() ) + { + MavenProject moduleProject = getModuleFromReactor( project, reactorProjects, module ); + + if ( moduleProject == null ) + { + getLogger().warn( "Module " + module + + " not found in reactor: loading locally" ); + + File f = new File( project.getBasedir(), module + "/pom.xml" ); + if ( f.exists() ) + { + try + { + moduleProject = mavenProjectBuilder.build( f, localRepository, null ); + } + catch ( ProjectBuildingException e ) + { + throw new SiteToolException( "Unable to read local module-POM", e ); + } + } + else + { + getLogger().warn( "No filesystem module-POM available" ); + + moduleProject = new MavenProject(); + moduleProject.setName( module ); + moduleProject.setDistributionManagement( new DistributionManagement() ); + moduleProject.getDistributionManagement().setSite( new Site() ); + moduleProject.getDistributionManagement().getSite().setUrl( module ); + } + } + + String siteUrl = getDistMgmntSiteUrl( moduleProject ); + String itemName = + ( moduleProject.getName() == null ) ? moduleProject.getArtifactId() : moduleProject.getName(); + + appendMenuItem( project, menu, itemName, siteUrl, moduleProject.getArtifactId() ); + } + } + else if ( decorationModel.getMenuRef( "modules" ).getInherit() == null ) + { + // only remove if project has no modules AND menu is not inherited, see MSHARED-174 + decorationModel.removeMenuRef( "modules" ); + } + } + + private static MavenProject getModuleFromReactor( MavenProject project, List reactorProjects, + String module ) + throws IOException + { + File moduleBasedir = new File( project.getBasedir(), module ).getCanonicalFile(); + + for ( MavenProject reactorProject : reactorProjects ) + { + if ( moduleBasedir.equals( reactorProject.getBasedir() ) ) + { + return reactorProject; + } + } + + // module not found in reactor + return null; + } + + /** {@inheritDoc} */ + public void populateReportsMenu( DecorationModel decorationModel, Locale locale, + Map> categories ) + { + checkNotNull( "decorationModel", decorationModel ); + checkNotNull( "categories", categories ); + + Menu menu = decorationModel.getMenuRef( "reports" ); + + if ( menu == null ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectdocumentation" ) ); + } + + boolean found = false; + if ( menu.getItems().isEmpty() ) + { + List categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_INFORMATION ); + if ( !isEmptyList( categoryReports ) ) + { + MenuItem item = createCategoryMenu( + i18n.getString( "site-tool", llocale, + "decorationModel.menu.projectinformation" ), + "/project-info.html", categoryReports, llocale ); + menu.getItems().add( item ); + found = true; + } + + categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_REPORTS ); + if ( !isEmptyList( categoryReports ) ) + { + MenuItem item = + createCategoryMenu( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectreports" ), + "/project-reports.html", categoryReports, llocale ); + menu.getItems().add( item ); + found = true; + } + } + if ( !found ) + { + decorationModel.removeMenuRef( "reports" ); + } + } + + /** {@inheritDoc} */ + public List getSiteLocales( String locales ) + { + if ( locales == null ) + { + return Collections.singletonList( DEFAULT_LOCALE ); + } + + String[] localesArray = StringUtils.split( locales, "," ); + List localesList = new ArrayList( localesArray.length ); + + for ( String localeString : localesArray ) + { + Locale locale = codeToLocale( localeString ); + + if ( locale == null ) + { + continue; + } + + if ( !Arrays.asList( Locale.getAvailableLocales() ).contains( locale ) ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "The locale defined by '" + locale + + "' is not available in this Java Virtual Machine (" + + System.getProperty( "java.version" ) + + " from " + System.getProperty( "java.vendor" ) + ") - IGNORING" ); + } + continue; + } + + // Default bundles are in English + if ( ( !locale.getLanguage().equals( DEFAULT_LOCALE.getLanguage() ) ) + && ( !i18n.getBundle( "site-tool", locale ).getLocale().getLanguage() + .equals( locale.getLanguage() ) ) ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "The locale '" + locale + "' (" + locale.getDisplayName( Locale.ENGLISH ) + + ") is not currently supported by Maven Site - IGNORING." + + "\nContributions are welcome and greatly appreciated!" + + "\nIf you want to contribute a new translation, please visit " + + "http://maven.apache.org/plugins/localization.html for detailed instructions." ); + } + + continue; + } + + localesList.add( locale ); + } + + if ( localesList.isEmpty() ) + { + localesList = Collections.singletonList( DEFAULT_LOCALE ); + } + + return localesList; + } + + /** + * Converts a locale code like "en", "en_US" or "en_US_win" to a java.util.Locale + * object. + *

If localeCode = default, return the current value of the default locale for this instance + * of the Java Virtual Machine.

+ * + * @param localeCode the locale code string. + * @return a java.util.Locale object instanced or null if errors occurred + * @see java.util.Locale#getDefault() + */ + private Locale codeToLocale( String localeCode ) + { + if ( localeCode == null ) + { + return null; + } + + if ( "default".equalsIgnoreCase( localeCode ) ) + { + return Locale.getDefault(); + } + + String language = ""; + String country = ""; + String variant = ""; + + StringTokenizer tokenizer = new StringTokenizer( localeCode, "_" ); + final int maxTokens = 3; + if ( tokenizer.countTokens() > maxTokens ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "Invalid java.util.Locale format for '" + localeCode + "' entry - IGNORING" ); + } + return null; + } + + if ( tokenizer.hasMoreTokens() ) + { + language = tokenizer.nextToken(); + if ( tokenizer.hasMoreTokens() ) + { + country = tokenizer.nextToken(); + if ( tokenizer.hasMoreTokens() ) + { + variant = tokenizer.nextToken(); + } + } + } + + return new Locale( language, country, variant ); + } + + // ---------------------------------------------------------------------- + // Protected methods + // ---------------------------------------------------------------------- + + /** + * @param path could be null. + * @return the path normalized, i.e. by eliminating "/../" and "/./" in the path. + * @see FilenameUtils#normalize(String) + */ + protected static String getNormalizedPath( String path ) + { + String normalized = FilenameUtils.normalize( path ); + if ( normalized == null ) + { + normalized = path; + } + return ( normalized == null ) ? null : normalized.replace( '\\', '/' ); + } + + // ---------------------------------------------------------------------- + // Private methods + // ---------------------------------------------------------------------- + + /** + * @param project not null + * @param localRepository not null + * @param repositories not null + * @param locale not null + * @return the resolved site descriptor + * @throws IOException if any + * @throws ArtifactResolutionException if any + * @throws ArtifactNotFoundException if any + */ + private File resolveSiteDescriptor( MavenProject project, ArtifactRepository localRepository, + List repositories, Locale locale ) + throws IOException, ArtifactResolutionException, ArtifactNotFoundException + { + File result; + + // TODO: this is a bit crude - proper type, or proper handling as metadata rather than an artifact in 2.1? + Artifact artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), + project.getArtifactId(), + project.getVersion(), "xml", + "site_" + locale.getLanguage() ); + + boolean found = false; + try + { + artifactResolver.resolve( artifact, repositories, localRepository ); + + result = artifact.getFile(); + + // we use zero length files to avoid re-resolution (see below) + if ( result.length() > 0 ) + { + found = true; + } + else + { + getLogger().debug( "No site descriptor found for " + project.getId() + " for locale " + + locale.getLanguage() + ", trying without locale..." ); + } + } + catch ( ArtifactNotFoundException e ) + { + getLogger().debug( "Unable to locate site descriptor for locale " + locale.getLanguage() + ": " + e ); + + // we can afford to write an empty descriptor here as we don't expect it to turn up later in the remote + // repository, because the parent was already released (and snapshots are updated automatically if changed) + result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); + result.getParentFile().mkdirs(); + result.createNewFile(); + } + + if ( !found ) + { + artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), project.getArtifactId(), + project.getVersion(), "xml", "site" ); + try + { + artifactResolver.resolve( artifact, repositories, localRepository ); + } + catch ( ArtifactNotFoundException e ) + { + // see above regarding this zero length file + result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); + result.getParentFile().mkdirs(); + result.createNewFile(); + + throw e; + } + + result = artifact.getFile(); + + // we use zero length files to avoid re-resolution (see below) + if ( result.length() == 0 ) + { + getLogger().debug( "No site descriptor found for " + project.getId() + " without locale." ); + result = null; + } + } + + return result; + } + + /** + * @param depth depth of project + * @param siteDirectory, can be null if project.basedir is null, ie POM from repository + * @param locale not null + * @param project not null + * @param reactorProjects not null + * @param localRepository not null + * @param repositories not null + * @param origProps not null + * @return the decoration model depending the locale and the parent project + * @throws SiteToolException if any + */ + private Map.Entry getDecorationModel( int depth, File siteDirectory, Locale locale, + MavenProject project, + List reactorProjects, + ArtifactRepository localRepository, + List repositories ) + throws SiteToolException + { + // 1. get site descriptor File + File siteDescriptor; + if ( project.getBasedir() == null ) + { + // POM is in the repository: look into the repository for site descriptor + try + { + siteDescriptor = getSiteDescriptorFromRepository( project, localRepository, repositories, locale ); + } + catch ( SiteToolException e ) + { + throw new SiteToolException( "The site descriptor cannot be resolved from the repository: " + + e.getMessage(), e ); + } + } + else + { + // POM is in build directory: look for site descriptor as local file + siteDescriptor = getSiteDescriptor( siteDirectory, locale ); + } + + // 2. read DecorationModel from site descriptor File and do early interpolation (${this.*}) + DecorationModel decoration = null; + Reader siteDescriptorReader = null; + try + { + if ( siteDescriptor != null && siteDescriptor.exists() ) + { + getLogger().debug( "Reading" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + + " site descriptor from " + siteDescriptor ); + + siteDescriptorReader = ReaderFactory.newXmlReader( siteDescriptor ); + + String siteDescriptorContent = readSiteDescriptor( siteDescriptorReader, project.getId() ); + + // interpolate ${this.*} = early interpolation + siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, true ); + + decoration = readDecorationModel( siteDescriptorContent ); + decoration.setLastModified( siteDescriptor.lastModified() ); + } + else + { + getLogger().debug( "No" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + " site descriptor." ); + } + } + catch ( IOException e ) + { + throw new SiteToolException( "The site descriptor for " + project.getId() + " cannot be read from " + + siteDescriptor, e ); + } + finally + { + IOUtil.close( siteDescriptorReader ); + } + + // 3. look for parent project + MavenProject parentProject = getParentProject( project, reactorProjects, localRepository ); + + // 4. merge with parent project DecorationModel + if ( parentProject != null && ( decoration == null || decoration.isMergeParent() ) ) + { + depth++; + getLogger().debug( "Looking for site descriptor of level " + depth + " parent project: " + + parentProject.getId() ); + + File parentSiteDirectory = null; + if ( parentProject.getBasedir() != null ) + { + // extrapolate parent project site directory + String siteRelativePath = getRelativeFilePath( project.getBasedir().getAbsolutePath(), + siteDescriptor.getParentFile().getAbsolutePath() ); + + parentSiteDirectory = new File( parentProject.getBasedir(), siteRelativePath ); + // notice: using same siteRelativePath for parent as current project; may be wrong if site plugin + // has different configuration. But this is a rare case (this only has impact if parent is from reactor) + } + + DecorationModel parentDecoration = + getDecorationModel( depth, parentSiteDirectory, locale, parentProject, reactorProjects, localRepository, + repositories ).getKey(); + + // MSHARED-116 requires an empty decoration model (instead of a null one) + // MSHARED-145 requires us to do this only if there is a parent to merge it with + if ( decoration == null && parentDecoration != null ) + { + // we have no site descriptor: merge the parent into an empty one + decoration = new DecorationModel(); + } + + String name = project.getName(); + if ( decoration != null && StringUtils.isNotEmpty( decoration.getName() ) ) + { + name = decoration.getName(); + } + + // Merge the parent and child DecorationModels + String projectDistMgmnt = getDistMgmntSiteUrl( project ); + String parentDistMgmnt = getDistMgmntSiteUrl( parentProject ); + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Site decoration model inheritance: assembling child with level " + depth + + " parent: distributionManagement.site.url child = " + projectDistMgmnt + " and parent = " + + parentDistMgmnt ); + } + assembler.assembleModelInheritance( name, decoration, parentDecoration, projectDistMgmnt, + parentDistMgmnt == null ? projectDistMgmnt : parentDistMgmnt ); + } + + return new AbstractMap.SimpleEntry( decoration, parentProject ); + } + + /** + * @param siteDescriptorContent not null + * @return the decoration model object + * @throws SiteToolException if any + */ + private DecorationModel readDecorationModel( String siteDescriptorContent ) + throws SiteToolException + { + try + { + return new DecorationXpp3Reader().read( new StringReader( siteDescriptorContent ) ); + } + catch ( XmlPullParserException e ) + { + throw new SiteToolException( "Error parsing site descriptor", e ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading site descriptor", e ); + } + } + + private String decorationModelToString( DecorationModel decoration ) + throws SiteToolException + { + StringWriter writer = new StringWriter(); + + try + { + new DecorationXpp3Writer().write( writer, decoration ); + return writer.toString(); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading site descriptor", e ); + } + finally + { + IOUtil.close( writer ); + } + } + + private static String buildRelativePath( final String toPath, final String fromPath, final char separatorChar ) + { + // use tokenizer to traverse paths and for lazy checking + StringTokenizer toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); + StringTokenizer fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); + + int count = 0; + + // walk along the to path looking for divergence from the from path + while ( toTokeniser.hasMoreTokens() && fromTokeniser.hasMoreTokens() ) + { + if ( separatorChar == '\\' ) + { + if ( !fromTokeniser.nextToken().equalsIgnoreCase( toTokeniser.nextToken() ) ) + { + break; + } + } + else + { + if ( !fromTokeniser.nextToken().equals( toTokeniser.nextToken() ) ) + { + break; + } + } + + count++; + } + + // reinitialize the tokenizers to count positions to retrieve the + // gobbled token + + toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); + fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); + + while ( count-- > 0 ) + { + fromTokeniser.nextToken(); + toTokeniser.nextToken(); + } + + StringBuilder relativePath = new StringBuilder(); + + // add back refs for the rest of from location. + while ( fromTokeniser.hasMoreTokens() ) + { + fromTokeniser.nextToken(); + + relativePath.append( ".." ); + + if ( fromTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + } + + if ( relativePath.length() != 0 && toTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + + // add fwd fills for whatever's left of to. + while ( toTokeniser.hasMoreTokens() ) + { + relativePath.append( toTokeniser.nextToken() ); + + if ( toTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + } + return relativePath.toString(); + } + + /** + * @param project not null + * @param menu not null + * @param name not null + * @param href could be null + * @param defaultHref not null + */ + private void appendMenuItem( MavenProject project, Menu menu, String name, String href, String defaultHref ) + { + String selectedHref = href; + + if ( selectedHref == null ) + { + selectedHref = defaultHref; + } + + MenuItem item = new MenuItem(); + item.setName( name ); + + String baseUrl = getDistMgmntSiteUrl( project ); + if ( baseUrl != null ) + { + selectedHref = getRelativePath( selectedHref, baseUrl ); + } + + if ( selectedHref.endsWith( "/" ) ) + { + item.setHref( selectedHref + "index.html" ); + } + else + { + item.setHref( selectedHref + "/index.html" ); + } + menu.addItem( item ); + } + + /** + * @param name not null + * @param href not null + * @param categoryReports not null + * @param locale not null + * @return the menu item object + */ + private MenuItem createCategoryMenu( String name, String href, List categoryReports, Locale locale ) + { + MenuItem item = new MenuItem(); + item.setName( name ); + item.setCollapse( true ); + item.setHref( href ); + + // MSHARED-172, allow reports to define their order in some other way? + //Collections.sort( categoryReports, new ReportComparator( locale ) ); + + for ( MavenReport report : categoryReports ) + { + MenuItem subitem = new MenuItem(); + subitem.setName( report.getName( locale ) ); + subitem.setHref( report.getOutputName() + ".html" ); + item.getItems().add( subitem ); + } + + return item; + } + + // ---------------------------------------------------------------------- + // static methods + // ---------------------------------------------------------------------- + + /** + * Convenience method. + * + * @param list could be null + * @return true if the list is null or empty + */ + private static boolean isEmptyList( List list ) + { + return list == null || list.isEmpty(); + } + + /** + * Return distributionManagement.site.url if defined, null otherwise. + * + * @param project not null + * @return could be null + */ + private static String getDistMgmntSiteUrl( MavenProject project ) + { + return getDistMgmntSiteUrl( project.getDistributionManagement() ); + } + + private static String getDistMgmntSiteUrl( DistributionManagement distMgmnt ) + { + if ( distMgmnt != null && distMgmnt.getSite() != null && distMgmnt.getSite().getUrl() != null ) + { + return urlEncode( distMgmnt.getSite().getUrl() ); + } + + return null; + } + + private static String urlEncode( final String url ) + { + if ( url == null ) + { + return null; + } + + try + { + return new File( url ).toURI().toURL().toExternalForm(); + } + catch ( MalformedURLException ex ) + { + return url; // this will then throw somewhere else + } + } + + private void checkNotNull( String name, Object value ) + { + if ( value == null ) + { + throw new IllegalArgumentException( "The parameter '" + name + "' cannot be null." ); + } + } + + /** + * Check the current Maven version to see if it's Maven 3.0 or newer. + */ + private static boolean isMaven3OrMore() + { + return new DefaultArtifactVersion( getMavenVersion() ).getMajorVersion() >= 3; + } + + private static String getMavenVersion() + { + // This relies on the fact that MavenProject is the in core classloader + // and that the core classloader is for the maven-core artifact + // and that should have a pom.properties file + // if this ever changes, we will have to revisit this code. + final Properties properties = new Properties(); + final String corePomProperties = "META-INF/maven/org.apache.maven/maven-core/pom.properties"; + final InputStream in = MavenProject.class.getClassLoader().getResourceAsStream( corePomProperties ); + try + { + properties.load( in ); + } + catch ( IOException ioe ) + { + return ""; + } + finally + { + IOUtil.close( in ); + } + + return properties.getProperty( "version" ).trim(); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/MojoLogWrapper.java b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/MojoLogWrapper.java new file mode 100644 index 000000000..ad8b28997 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/MojoLogWrapper.java @@ -0,0 +1,159 @@ +package org.apache.maven.doxia.tools; + +/* + * 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 org.apache.maven.doxia.logging.Log; + +/** + * Wrap a Mojo logger into a Doxia logger. + * + * @author Vincent Siveton + * @since 1.1 + * @see org.apache.maven.plugin.logging.Log + */ +public class MojoLogWrapper + implements Log +{ + private final org.apache.maven.plugin.logging.Log mojoLog; + + /** + * @param log a Mojo log + */ + public MojoLogWrapper( org.apache.maven.plugin.logging.Log log ) + { + this.mojoLog = log; + } + + /** {@inheritDoc} */ + public void setLogLevel( int level ) + { + // nop + } + + /** {@inheritDoc} */ + public void debug( CharSequence content ) + { + mojoLog.debug( toString( content ) ); + } + + /** {@inheritDoc} */ + public void debug( CharSequence content, Throwable error ) + { + mojoLog.debug( toString( content ), error ); + } + + /** {@inheritDoc} */ + public void debug( Throwable error ) + { + mojoLog.debug( "", error ); + } + + /** {@inheritDoc} */ + public void info( CharSequence content ) + { + mojoLog.info( toString( content ) ); + } + + /** {@inheritDoc} */ + public void info( CharSequence content, Throwable error ) + { + mojoLog.info( toString( content ), error ); + } + + /** {@inheritDoc} */ + public void info( Throwable error ) + { + mojoLog.info( "", error ); + } + + /** {@inheritDoc} */ + public void warn( CharSequence content ) + { + mojoLog.warn( toString( content ) ); + } + + /** {@inheritDoc} */ + public void warn( CharSequence content, Throwable error ) + { + mojoLog.warn( toString( content ), error ); + } + + /** {@inheritDoc} */ + public void warn( Throwable error ) + { + mojoLog.warn( "", error ); + } + + /** {@inheritDoc} */ + public void error( CharSequence content ) + { + mojoLog.error( toString( content ) ); + } + + /** {@inheritDoc} */ + public void error( CharSequence content, Throwable error ) + { + mojoLog.error( toString( content ), error ); + } + + /** {@inheritDoc} */ + public void error( Throwable error ) + { + mojoLog.error( "", error ); + } + + /** {@inheritDoc} */ + public boolean isDebugEnabled() + { + return mojoLog.isDebugEnabled(); + } + + /** {@inheritDoc} */ + public boolean isInfoEnabled() + { + return mojoLog.isInfoEnabled(); + } + + /** {@inheritDoc} */ + public boolean isWarnEnabled() + { + return mojoLog.isWarnEnabled(); + } + + /** {@inheritDoc} */ + public boolean isErrorEnabled() + { + return mojoLog.isErrorEnabled(); + } + + // ---------------------------------------------------------------------- + // Private methods + // ---------------------------------------------------------------------- + + private String toString( CharSequence content ) + { + if ( content == null ) + { + return ""; + } + + return content.toString(); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/ReportComparator.java b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/ReportComparator.java new file mode 100644 index 000000000..2a3b245f3 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/ReportComparator.java @@ -0,0 +1,61 @@ +package org.apache.maven.doxia.tools; + +/* + * 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.text.Collator; +import java.util.Comparator; +import java.util.Locale; + +import org.apache.maven.reporting.MavenReport; + +/** + * Sorts reports. + * + * @author Brett Porter + * @todo move to reporting API? + * @todo allow reports to define their order in some other way? + */ +public class ReportComparator + implements Comparator +{ + /** the local */ + private final Locale locale; + + /** + * Default constructor. + * + * @param locale not null + */ + public ReportComparator( Locale locale ) + { + if ( locale == null ) + { + throw new IllegalArgumentException( "locale should be defined" ); + } + this.locale = locale; + } + + /** {@inheritDoc} */ + public int compare( MavenReport r1, MavenReport r2 ) + { + Collator collator = Collator.getInstance( locale ); + return collator.compare( r1.getName( locale ), r2.getName( locale ) ); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/SiteTool.java b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/SiteTool.java new file mode 100644 index 000000000..e7ead6525 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/SiteTool.java @@ -0,0 +1,206 @@ +package org.apache.maven.doxia.tools; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.doxia.site.decoration.DecorationModel; +import org.apache.maven.project.MavenProject; +import org.apache.maven.reporting.MavenReport; + +/** + * Tool to play with Doxia objects + * like DecorationModel. + * + * @author Vincent Siveton + */ +public interface SiteTool +{ + /** Plexus Role */ + String ROLE = SiteTool.class.getName(); + + /** + * The locale by default for a Maven Site + * @see Locale#ENGLISH + */ + Locale DEFAULT_LOCALE = Locale.ENGLISH; + + /** + * Get a skin artifact from one of the repositories. + * + * @param localRepository the Maven local repository, not null. + * @param remoteArtifactRepositories the Maven remote repositories, not null. + * @param decoration the Doxia site descriptor model, not null. + * @return the Skin artifact defined in a DecorationModel from a given project and a + * local repository + * @throws SiteToolException if any + */ + Artifact getSkinArtifactFromRepository( ArtifactRepository localRepository, + List remoteArtifactRepositories, + DecorationModel decoration ) + throws SiteToolException; + + /** + * Get the default skin artifact for a project from one of the repositories. + * + * @param localRepository the Maven local repository, not null. + * @param remoteArtifactRepositories the Maven remote repositories, not null. + * @return the default Skin artifact from a given project and a local repository + * @throws SiteToolException if any + * @see org.apache.maven.doxia.site.decoration.Skin#getDefaultSkin() + * @see #getSkinArtifactFromRepository(ArtifactRepository, List, DecorationModel) + */ + Artifact getDefaultSkinArtifact( ArtifactRepository localRepository, + List remoteArtifactRepositories ) + throws SiteToolException; + + /** + * Get a site descriptor from the project's site directory. + * + * @param siteDirectory the site directory, not null + * @param locale the locale wanted for the site descriptor. If not null, searching for + * site_localeLanguage.xml, otherwise searching for site.xml. + * @return the site descriptor file + */ // used by maven-pdf-plugin (should not?) + File getSiteDescriptor( File siteDirectory, Locale locale ); + + /** + * Interpolating several expressions in the site descriptor content. Actually, the expressions can be in + * the project, the environment variables and the specific properties like encoding. + *

+ * For instance: + *

+ *
${project.name}
+ *
The value from the POM of: + *

+ * <project>
+ *   <name>myProjectName</name>
+ * </project> + *

+ *
${my.value}
+ *
The value from the POM of: + *

+ * <properties>
+ *   <my.value>hello</my.value>
+ * </properties> + *

+ *
${JAVA_HOME}
+ *
The value of JAVA_HOME in the environment variables
+ *
+ * + * @param props a map used for interpolation, not null. + * @param aProject a Maven project, not null. + * @param siteDescriptorContent the site descriptor file, not null. + * @return the interpolated site descriptor content. + * @throws SiteToolException if errors happened during the interpolation. + */ // used by maven-pdf-plugin (should not?) + String getInterpolatedSiteDescriptorContent( Map props, MavenProject aProject, + String siteDescriptorContent ) + throws SiteToolException; + + /** + * Get a decoration model for a project. + * + * @param siteDirectory the site directory, may be null if project from repository + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project the Maven project, not null. + * @param reactorProjects the Maven reactor projects, not null. + * @param localRepository the Maven local repository, not null. + * @param repositories the Maven remote repositories, not null. + * @return the DecorationModel object corresponding to the site.xml file with some + * interpolations. + * @throws SiteToolException if any + * @since 1.7, was previously with other parameter types and order + */ + DecorationModel getDecorationModel( File siteDirectory, Locale locale, MavenProject project, + List reactorProjects, ArtifactRepository localRepository, + List repositories ) + throws SiteToolException; + + /** + * Populate the pre-defined reports menu of the decoration model, + * if used through <menu ref="reports"/>. Notice this menu reference is translated into + * 2 separate menus: "Project Information" and "Project Reports". + * + * @param decorationModel the Doxia Sitetools DecorationModel, not null. + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param reportsPerCategory reports per category to put in "Reports" or "Information" menus, not null. + * @see MavenReport#CATEGORY_PROJECT_INFORMATION + * @see MavenReport#CATEGORY_PROJECT_REPORTS + */ + void populateReportsMenu( DecorationModel decorationModel, Locale locale, + Map> reportsPerCategory ); + + /** + * Extracts from a comma-separated list the locales that are available in site-tool + * resource bundle. Notice that default value will be changed to the default locale of + * the JVM. + * + * @param locales A comma separated list of locales + * @return a list of Locale, which at least contains the Maven default locale which is english + * @since 1.7, was previously getAvailableLocales(String) + */ + List getSiteLocales( String locales ); + + /** + * Calculate the relative path between two URLs or between two files. + * + * For example: + *
+ *
to = "http://maven.apache.org" and from = "http://maven.apache.org"
+ *
return ""
+ *
to = "http://maven.apache.org" and from = "http://maven.apache.org/plugins/maven-site-plugin/"
+ *
return "../.."
+ *
to = "http://maven.apache.org/plugins/maven-site-plugin/" and from = "http://maven.apache.org"
+ *
return "plugins/maven-site-plugin"
+ *
to = "/myproject/myproject-module1" and from = "/myproject/myproject"
+ *
return "../myproject-module1"
+ *
+ * Note: The file separator depends on the system. + * Maven-specific urls are supported, like dav:https://dav.codehaus.org/ or + * scm:svn:https://svn.apache.org/repos/asf. + * + * @param to the to url of file as string + * @param from the from url of file as string + * @return a relative path from from to to. + */ + String getRelativePath( String to, String from ); + + /** + * Returns the parent POM with interpolated URLs. + * If called from Maven 3, just returns project.getParent(), which is already + * interpolated. But when called from Maven 2, attempts to source this value from the + * reactorProjects parameters if available (reactor env model attributes + * are interpolated), or if the reactor is unavailable (-N) resorts to the + * project.getParent().getUrl() value which will NOT have been interpolated. + * + * @param aProject a Maven project, not null. + * @param reactorProjects the Maven reactor projects, not null. + * @param localRepository the Maven local repository, not null. + * @return the parent project with interpolated URLs. + */ + MavenProject getParentProject( MavenProject aProject, List reactorProjects, + ArtifactRepository localRepository ); +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/SiteToolException.java b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/SiteToolException.java new file mode 100644 index 000000000..1b2fd106d --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/SiteToolException.java @@ -0,0 +1,66 @@ +package org.apache.maven.doxia.tools; + +/* + * 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. + */ + +/** + * An exception occurring during the execution of this tool. + * + * @author Vincent Siveton + */ +public class SiteToolException + extends Exception +{ + /** serialVersionUID */ + static final long serialVersionUID = 2331441332996055959L; + + /** + * Construct a new SiteToolException exception wrapping an underlying Exception + * and providing a message. + * + * @param message could be null + * @param cause could be null + */ + public SiteToolException( String message, Exception cause ) + { + super( message, cause ); + } + + /** + * Construct a new SiteToolException exception wrapping an underlying Throwable + * and providing a message. + * + * @param message could be null + * @param cause could be null + */ + public SiteToolException( String message, Throwable cause ) + { + super( message, cause ); + } + + /** + * Construct a new SiteToolException exception providing a message. + * + * @param message could be null + */ + public SiteToolException( String message ) + { + super( message ); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/default-site.xml b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/default-site.xml new file mode 100644 index 000000000..5ba93bf99 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/default-site.xml @@ -0,0 +1,33 @@ + + + + + + ${project.name} + + + + + +
+ + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool.properties b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool.properties new file mode 100644 index 000000000..a3062d882 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool.properties @@ -0,0 +1,22 @@ +# 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. + +decorationModel.menu.parentproject = Parent Project +decorationModel.menu.projectdocumentation = Project Documentation +decorationModel.menu.projectinformation = Project Information +decorationModel.menu.projectmodules = Modules +decorationModel.menu.projectreports = Project Reports \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_ca.properties b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_ca.properties new file mode 100644 index 000000000..8e516e2a9 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_ca.properties @@ -0,0 +1,22 @@ +# 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. + +decorationModel.menu.parentproject = Projecte pare +decorationModel.menu.projectdocumentation = Documentaci\u00f3 del projecte +decorationModel.menu.projectinformation = Informaci\u00f3 del projecte +decorationModel.menu.projectmodules = M\u00f2duls +decorationModel.menu.projectreports = Informes del projecte diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_cs.properties b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_cs.properties new file mode 100644 index 000000000..70e234109 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_cs.properties @@ -0,0 +1,22 @@ +# 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. + +decorationModel.menu.parentproject = Nad\u0159azen\u00fd projekt +decorationModel.menu.projectdocumentation = Dokumentace projektu +decorationModel.menu.projectinformation = Informace o projektu +decorationModel.menu.projectmodules = Moduly +decorationModel.menu.projectreports = Souhrny projektu diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_da.properties b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_da.properties new file mode 100644 index 000000000..0f8d339b4 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_da.properties @@ -0,0 +1,22 @@ +# 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. + +decorationModel.menu.parentproject = Overordnet Projekt +decorationModel.menu.projectdocumentation = Projekt Dokumentation +decorationModel.menu.projectinformation = Projekt Information +decorationModel.menu.projectmodules = Moduler +decorationModel.menu.projectreports = Projekt Rapporter \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_de.properties b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_de.properties new file mode 100644 index 000000000..930249877 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_de.properties @@ -0,0 +1,22 @@ +# 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. + +decorationModel.menu.parentproject = \u00fcbergeordnetes Projekt +decorationModel.menu.projectdocumentation = Projektdokumentation +decorationModel.menu.projectinformation = Projektinformationen +decorationModel.menu.projectmodules = Module +decorationModel.menu.projectreports = Projektberichte diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_en.properties b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_en.properties new file mode 100644 index 000000000..0c479bc64 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_en.properties @@ -0,0 +1,23 @@ +# 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. + +# NOTE: +# This bundle is intentionally empty because English strings are provided by the base bundle via the parent chain. It +# must be provided nevertheless such that a request for locale "en" will not errorneously pick up the bundle for the +# JVM's default locale (which need not be "en"). See the method javadoc about +# ResourceBundle.getBundle(String, Locale, ClassLoader) +# for a full description of the lookup strategy. diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_es.properties b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_es.properties new file mode 100644 index 000000000..563f6ae29 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_es.properties @@ -0,0 +1,22 @@ +# 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. + +decorationModel.menu.parentproject = Proyecto padre +decorationModel.menu.projectdocumentation = Documentaci\u00f3n del proyecto +decorationModel.menu.projectinformation = Informaci\u00f3n del proyecto +decorationModel.menu.projectmodules = M\u00f3dulos +decorationModel.menu.projectreports = Informes del proyecto diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_fr.properties b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_fr.properties new file mode 100644 index 000000000..86c374fee --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_fr.properties @@ -0,0 +1,22 @@ +# 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. + +decorationModel.menu.parentproject = Projet parent +decorationModel.menu.projectdocumentation = Documentation sur le projet +decorationModel.menu.projectinformation = Info Projet +decorationModel.menu.projectmodules = Modules +decorationModel.menu.projectreports = Rapports Projet diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_gl.properties b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_gl.properties new file mode 100644 index 000000000..7e40badb1 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_gl.properties @@ -0,0 +1,22 @@ +# 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. + +decorationModel.menu.parentproject = Proxecto pai +decorationModel.menu.projectdocumentation = Documentaci\u00f3n do proxecto +decorationModel.menu.projectinformation = Informaci\u00f3n do proxecto +decorationModel.menu.projectmodules = M\u00f3dulos +decorationModel.menu.projectreports = Informes do proxecto diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_hu.properties b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_hu.properties new file mode 100644 index 000000000..732017552 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_hu.properties @@ -0,0 +1,22 @@ +# 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. + +decorationModel.menu.parentproject = Sz\u00fcl\u0151 projekt +decorationModel.menu.projectdocumentation = Projekt dokument\u00e1ci\u00f3 +decorationModel.menu.projectinformation = Projekt inform\u00e1ci\u00f3 +decorationModel.menu.projectmodules = Modulok +decorationModel.menu.projectreports = Projekt riportok diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_it.properties b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_it.properties new file mode 100644 index 000000000..23e45346e --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_it.properties @@ -0,0 +1,22 @@ +# 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. + +decorationModel.menu.parentproject = Progetto Padre +decorationModel.menu.projectdocumentation = Documentazione del Progetto +decorationModel.menu.projectinformation = Informazioni sul Progetto +decorationModel.menu.projectmodules = Moduli +decorationModel.menu.projectreports = Rapporti del Progetto diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_ja.properties b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_ja.properties new file mode 100644 index 000000000..89607d098 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_ja.properties @@ -0,0 +1,22 @@ +# 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. + +decorationModel.menu.parentproject = \u89aa\u30d7\u30ed\u30b8\u30a7\u30af\u30c8 +decorationModel.menu.projectdocumentation = \u30d7\u30ed\u30b8\u30a7\u30af\u30c8\u6587\u66f8 +decorationModel.menu.projectinformation = \u30d7\u30ed\u30b8\u30a7\u30af\u30c8\u60c5\u5831 +decorationModel.menu.projectmodules = \u30e2\u30b8\u30e5\u30fc\u30eb +decorationModel.menu.projectreports = \u30d7\u30ed\u30b8\u30a7\u30af\u30c8\u30ec\u30dd\u30fc\u30c8 diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_ko.properties b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_ko.properties new file mode 100644 index 000000000..472686a69 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_ko.properties @@ -0,0 +1,22 @@ +# 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. + +decorationModel.menu.parentproject = \ubd80\ubaa8 \ud504\ub85c\uc81d\ud2b8 +decorationModel.menu.projectdocumentation = \ud504\ub85c\uc81d\ud2b8 \ubb38\uc11c\ud654 +decorationModel.menu.projectinformation = \ud504\ub85c\uc81d\ud2b8 \uc815\ubcf4 +decorationModel.menu.projectmodules = \ubaa8\ub4c8 +decorationModel.menu.projectreports = \ud504\ub85c\uc81d\ud2b8 \ubcf4\uace0\uc11c diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_lt.properties b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_lt.properties new file mode 100644 index 000000000..9402a3763 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_lt.properties @@ -0,0 +1,22 @@ +# 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. + +decorationModel.menu.parentproject = T\u0117vinis projektas +decorationModel.menu.projectdocumentation = Projekt\u0173 dokumentacija +decorationModel.menu.projectinformation = Projekto informacija +decorationModel.menu.projectmodules = Moduliai +decorationModel.menu.projectreports = Projekto ataskaitos diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_nl.properties b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_nl.properties new file mode 100644 index 000000000..503545a1a --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_nl.properties @@ -0,0 +1,22 @@ +# 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. + +decorationModel.menu.parentproject = Bevattend project +decorationModel.menu.projectdocumentation = Project documentatie +decorationModel.menu.projectinformation = Project informatie +decorationModel.menu.projectmodules = Modulen +decorationModel.menu.projectreports = Project rapporten diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_no.properties b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_no.properties new file mode 100644 index 000000000..7504f3fde --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_no.properties @@ -0,0 +1,22 @@ +# 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. + +decorationModel.menu.parentproject = Hovedprosjekt +decorationModel.menu.projectdocumentation = Prosjekt dokumentasjon +decorationModel.menu.projectinformation = Prosjekt informasjon +decorationModel.menu.projectmodules = Moduler +decorationModel.menu.projectreports = Prosjekt rapporter diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_pl.properties b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_pl.properties new file mode 100644 index 000000000..1d0c490cd --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_pl.properties @@ -0,0 +1,22 @@ +# 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. + +decorationModel.menu.parentproject = Projekt nadrz\u0119dny +decorationModel.menu.projectdocumentation = Dokumentacja projektu +decorationModel.menu.projectinformation = Informacja o projekcie +decorationModel.menu.projectmodules = Modu\u0142y +decorationModel.menu.projectreports = Raporty projektu diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_pt.properties b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_pt.properties new file mode 100644 index 000000000..29b2dea82 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_pt.properties @@ -0,0 +1,22 @@ +# 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. + +decorationModel.menu.parentproject = Projecto Pai +decorationModel.menu.projectdocumentation = Documenta\u00e7\u00e3o do Projeto +decorationModel.menu.projectinformation = Informa\u00e7\u00e3o do Projeto +decorationModel.menu.projectmodules = M\u00f3dulos +decorationModel.menu.projectreports = Relat\u00f3rios do Projeto diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_pt_BR.properties b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_pt_BR.properties new file mode 100644 index 000000000..74785c6f8 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_pt_BR.properties @@ -0,0 +1,22 @@ +# 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. + +decorationModel.menu.parentproject = Projeto Pai +decorationModel.menu.projectdocumentation = Documenta\u00e7\u00e3o do Projeto +decorationModel.menu.projectinformation = Informa\u00e7\u00f5es sobre o Projeto +decorationModel.menu.projectmodules = M\u00f3dulo +decorationModel.menu.projectreports = Relat\u00f3rios do Projeto diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_ru.properties b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_ru.properties new file mode 100644 index 000000000..60506ebc6 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_ru.properties @@ -0,0 +1,22 @@ +# 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. + +decorationModel.menu.parentproject = \u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u041f\u0440\u043e\u0435\u043a\u0442 +decorationModel.menu.projectdocumentation = \u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f \u041f\u0440\u043e\u0435\u043a\u0442\u0430 +decorationModel.menu.projectinformation = \u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u041f\u0440\u043e\u0435\u043a\u0442\u0435 +decorationModel.menu.projectmodules = \u041c\u043e\u0434\u0443\u043b\u0438 +decorationModel.menu.projectreports = \u041e\u0442\u0447\u0435\u0442\u044b \u041f\u0440\u043e\u0435\u043a\u0442\u0430 \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_sk.properties b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_sk.properties new file mode 100644 index 000000000..e21927d50 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_sk.properties @@ -0,0 +1,22 @@ +# 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. + +decorationModel.menu.parentproject = Nadraden\u00fd projekt +decorationModel.menu.projectdocumentation = Projektov\u00e1 dokument\u00e1cia +decorationModel.menu.projectinformation = Inform\u00e1cie o projekte +decorationModel.menu.projectmodules = Moduly +decorationModel.menu.projectreports = Projektov\u00e9 zostavy diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_sv.properties b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_sv.properties new file mode 100644 index 000000000..4647faa5c --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_sv.properties @@ -0,0 +1,22 @@ +# 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. + +decorationModel.menu.parentproject = F\u00f6r\u00e4ldraprojekt +decorationModel.menu.projectdocumentation = Projektdokumentation +decorationModel.menu.projectinformation = Projektinformation +decorationModel.menu.projectmodules = Moduler +decorationModel.menu.projectreports = Projektrapporter \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_tr.properties b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_tr.properties new file mode 100644 index 000000000..2b5a3f2d4 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_tr.properties @@ -0,0 +1,22 @@ +# 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. + +decorationModel.menu.parentproject = Ana Proje +decorationModel.menu.projectdocumentation = Proje Dok\u00fcmantasyonu +decorationModel.menu.projectinformation = Proje Bilgileri +decorationModel.menu.projectmodules = Mod\u00fcller +decorationModel.menu.projectreports = Proje Raporlar\u00fd diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_zh_CN.properties b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_zh_CN.properties new file mode 100644 index 000000000..42794f85c --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_zh_CN.properties @@ -0,0 +1,22 @@ +# 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. + +decorationModel.menu.parentproject = \u7236\u9879\u76ee +decorationModel.menu.projectdocumentation = \u9879\u76ee\u6587\u6863 +decorationModel.menu.projectinformation = \u9879\u76ee\u4fe1\u606f +decorationModel.menu.projectmodules = \u6a21\u5757 +decorationModel.menu.projectreports = \u9879\u76ee\u62a5\u8868 diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_zh_TW.properties b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_zh_TW.properties new file mode 100644 index 000000000..f6ebd1860 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/main/resources/site-tool_zh_TW.properties @@ -0,0 +1,22 @@ +# 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. + +decorationModel.menu.parentproject = \u7236\u5c08\u6848 (Parent Project) +decorationModel.menu.projectdocumentation = \u5c08\u6848\u6587\u4ef6 (Project Documentation) +decorationModel.menu.projectinformation = \u5c08\u6848\u8cc7\u8a0a (Project Information) +decorationModel.menu.projectmodules = \u6a21\u7d44 (Modules) +decorationModel.menu.projectreports = \u5c08\u6848\u5831\u8868 (Project Reports) diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/site/apt/index.apt b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/site/apt/index.apt new file mode 100644 index 000000000..ff336c254 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/site/apt/index.apt @@ -0,0 +1,73 @@ + ------ + Introduction + ------ + Dennis Lundberg + Hervé Boutemy + ------ + 2016-03-27 + ------ + + ~~ 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. + + ~~ NOTE: For help with the syntax of this file, see: + ~~ http://maven.apache.org/doxia/references/apt-format.html + + +Maven Doxia Integration Tools + + This shared component has some utilities that are useful when integrating Doxia in Maven, + mainly for site generation and report creation. + + The main entry point is the + {{{./apidocs/org/apache/maven/doxia/tools/SiteTool.html}SiteTool}} Plexus component. + +* Usage + + Instructions on how to use the integration of Doxia in Maven can be found {{{./usage.html}here}}. + +* <<>> Decoration Model Interpolation + + Interpolation of {{{../doxia-decoration-model/decoration.html}<<>> decoration model}} injects + Maven project's information, replacing <<<$\{...\}>>> with calculated values + like it happens in {{{/ref/current/maven-model-builder/#Model_Interpolation}Maven model interpolation}}. + + Interpolation can be <> or <>: + + * with <> interpolation, replacement happens <> inheritance. This is the classical behaviour in Maven pom, + + * with <> interpolation, replacement happens <> inheritance: this was the default behaviour for <<>> + values until Doxia Sitetools 1.7, when these early and late interpolation definitions didn't exist. + Since Doxia Sitetools 1.7.1, early interpolation happens for <<>> values. + + [] + + Values are evaluated in sequence from different syntaxes: + +*-------------+--------------+--------------------+------------------+ +|| late value || early value || evaluation result || common examples || +*-------------+--------------+--------------------+------------------+ +| <<>>\ +<<<*>>> () | <<>> | POM content (see {{{/ref/current/maven-model/maven.html}POM reference}}) | <<<$\{project.version\}>>>\ + | | | <<<$\{this.url\}>>> +*-------------+--------------+--------------------+------------------+ +| <<<*>>> | <<>> | model properties, such as project properties set in the pom | <<<$\{any.key\}>>>\ + | | | <<<$\{this.any.key\}>>> +*-------------+--------------+--------------------+------------------+ +| <<>>\ +<<<*>>> | | environment variables | <<<$\{env.PATH\}>>>\ +*-------------+--------------+--------------------+------------------+ diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/site/apt/usage.apt b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/site/apt/usage.apt new file mode 100644 index 000000000..748b3a472 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/site/apt/usage.apt @@ -0,0 +1,71 @@ + ------ + Usage + ------ + Dennis Lundberg + Vincent Siveton + ------ + 2008-04-27 + ------ + + ~~ 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. + + ~~ NOTE: For help with the syntax of this file, see: + ~~ http://maven.apache.org/doxia/references/apt-format.html + +Using the SiteTool in a Mojo + ++----- +... +import org.apache.maven.doxia.tools.SiteTool; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.Mojo; +... + +/** + * Your own mojo. + */ +@Mojo( name = "your-own" ) +public class YourOwnMojo extends AbstractMojo +{ + ... + + /** + * SiteTool. + */ + @Component + protected SiteTool siteTool; + + ... + + public someMethod() + { + List localesList = siteTool.getSiteLocales( locales ); + String relativePath = siteTool.getRelativePath( "C:/foo/child", + "C:/foo/master" ); + ... + } + + ... +} ++----- + +* References + + [[1]] {{{./apidocs/org/apache/maven/doxia/tools/SiteTool.html}SiteTool API}} + + [] \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/site/resources/download.cgi b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/site/resources/download.cgi new file mode 100644 index 000000000..1b178d2e6 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/site/resources/download.cgi @@ -0,0 +1,22 @@ +#!/bin/sh +# +# 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. +# +# Just call the standard mirrors.cgi script. It will use download.html +# as the input template. +exec /www/www.apache.org/dyn/mirrors/mirrors.cgi $* \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/site/site.xml b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/site/site.xml new file mode 100644 index 000000000..e2e57b3bb --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/site/site.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/site/xdoc/download.xml.vm b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/site/xdoc/download.xml.vm new file mode 100644 index 000000000..252fdae39 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/site/xdoc/download.xml.vm @@ -0,0 +1,126 @@ + + + + + + + Download ${project.name} Source + + +
+ +

${project.name} ${project.version} is distributed in source format. Use a source archive if you intend to build + ${project.name} yourself. Otherwise, simply use the ready-made binary artifacts from central repository.

+ +

You will be prompted for a mirror - if the file is not found on yours, please be patient, as it may take 24 + hours to reach all mirrors.

+ +

In order to guard against corrupted downloads/installations, it is highly recommended to + verify the signature + of the release bundles against the public KEYS used by the Apache Maven + developers.

+ +

${project.name} is distributed under the Apache License, version 2.0.

+ +

We strongly encourage our users to configure a Maven repository mirror closer to their location, please read How to Use Mirrors for Repositories.

+ + + + +

+ [if-any logo] + + logo + + [end] + The currently selected mirror is + [preferred]. + If you encounter a problem with this mirror, + please select another mirror. + If all mirrors are failing, there are + backup + mirrors + (at the end of the mirrors list) that should be available. +

+ +
+ Other mirrors: + + +
+ +

+ You may also consult the + complete list of + mirrors. +

+ + + + + +

This is the current stable version of ${project.name}.

+ + + + + + + + + + + + + + + + + + +
LinkChecksumSignature
${project.name} ${project.version} (Source zip)maven/doxia/${project.artifactId}-${project.version}-source-release.zipmaven/doxia/${project.artifactId}-${project.version}-source-release.zip.sha512maven/doxia/${project.artifactId}-${project.version}-source-release.zip.asc
+
+ + + +

Older non-recommended releases can be found on our archive site.

+ +
+
+ +
+ diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/java/org/apache/maven/doxia/tools/DefaultSiteToolTest.java b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/java/org/apache/maven/doxia/tools/DefaultSiteToolTest.java new file mode 100644 index 000000000..1698e2d45 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/java/org/apache/maven/doxia/tools/DefaultSiteToolTest.java @@ -0,0 +1,116 @@ +package org.apache.maven.doxia.tools; + +/* + * 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 org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; + +import org.codehaus.plexus.logging.Logger; +import org.codehaus.plexus.logging.console.ConsoleLogger; +import org.junit.Before; + +/** + * @author Vincent Siveton + */ +public class DefaultSiteToolTest +{ + + private DefaultSiteTool tool = new DefaultSiteTool(); + + @Before + public void setUp() { + Logger logger = new ConsoleLogger(Logger.LEVEL_WARN, "tool"); + tool.enableLogging(logger); + } + + /** + * test getNormalizedPath(). + */ + @Test + public void testGetNormalizedPath() + { + assertNull( DefaultSiteTool.getNormalizedPath( null ) ); + assertEquals( "", DefaultSiteTool.getNormalizedPath( "" ) ); + assertEquals( "", DefaultSiteTool.getNormalizedPath( "." ) ); + assertEquals( "", DefaultSiteTool.getNormalizedPath( "./" ) ); + assertEquals( "foo", DefaultSiteTool.getNormalizedPath( "foo" ) ); + assertEquals( "foo/bar", DefaultSiteTool.getNormalizedPath( "foo/bar" ) ); + assertEquals( "foo/bar", DefaultSiteTool.getNormalizedPath( "foo\\bar" ) ); + assertEquals( "foo/bar", DefaultSiteTool.getNormalizedPath( "foo/./bar" ) ); + assertEquals( "foo/bar", DefaultSiteTool.getNormalizedPath( "foo//bar" ) ); + assertEquals( "", DefaultSiteTool.getNormalizedPath( "foo/../" ) ); + assertEquals( "", DefaultSiteTool.getNormalizedPath( "foo/.." ) ); + assertEquals( "bar", DefaultSiteTool.getNormalizedPath( "foo/../bar" ) ); + assertEquals( "foo", DefaultSiteTool.getNormalizedPath( "./foo" ) ); + assertEquals( "../foo", DefaultSiteTool.getNormalizedPath( "../foo" ) ); + assertEquals( "../../foo", DefaultSiteTool.getNormalizedPath( "../../foo" ) ); + assertEquals( "index.html", DefaultSiteTool.getNormalizedPath( "./foo/../index.html" ) ); + + // note: space is preserved and double slash is removed! + assertEquals( "file:/Documents and Settings/", + DefaultSiteTool.getNormalizedPath( "file://Documents and Settings/" ) ); + } + + @SuppressWarnings("deprecation") + @Test + public void testGetRelativePath() + { + assertEquals( + ".." + File.separator + "bar.html", + tool.getRelativePath("http://example.com/foo/bar.html", "http://example.com/foo/baz.html")); + } + + @SuppressWarnings("deprecation") + @Test + public void testGetRelativePath_same() + { + assertTrue( + tool.getRelativePath( "http://example.com/foo/bar.html", "http://example.com/foo/bar.html" ).isEmpty() ); + } + + @SuppressWarnings("deprecation") + @Test + public void testGetRelativePath_differentSchemes() { + assertEquals( + "scp://example.com/foo/bar.html", + tool.getRelativePath( "scp://example.com/foo/bar.html", "http://example.com/foo/bar.html" ) ); + assertEquals( + "file:///tmp/bloop", + tool.getRelativePath( "file:///tmp/bloop", "scp://localhost:/tmp/blop" ) ); + } + + @SuppressWarnings("deprecation") + @Test + public void testGetRelativePath_differentDomains() { + assertEquals( + "https://example.org/bar.html", + tool.getRelativePath( "https://example.org/bar.html", "https://example.com/bar.html" ) ); + assertEquals( + "dav:https://nexus2.mysite.net:123/nexus/content/sites/site/mysite-child/2.0.0/", + tool.getRelativePath( + "dav:https://nexus2.mysite.net:123/nexus/content/sites/site/mysite-child/2.0.0/", + "dav:https://nexus1.mysite.net:123/nexus/content/sites/site/mysite-parent/1.0.0/" )); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/java/org/apache/maven/doxia/tools/SiteToolTest.java b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/java/org/apache/maven/doxia/tools/SiteToolTest.java new file mode 100644 index 000000000..6e12e4c35 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/java/org/apache/maven/doxia/tools/SiteToolTest.java @@ -0,0 +1,409 @@ +package org.apache.maven.doxia.tools; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; + +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.artifact.repository.ArtifactRepositoryFactory; +import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy; +import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout; +import org.apache.maven.doxia.site.decoration.DecorationModel; +import org.apache.maven.doxia.site.decoration.LinkItem; +import org.apache.maven.doxia.site.decoration.Skin; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Writer; +import org.apache.maven.doxia.tools.stubs.SiteToolMavenProjectStub; +import org.apache.maven.project.MavenProject; + +import org.codehaus.plexus.PlexusTestCase; +import org.codehaus.plexus.util.FileUtils; +import org.codehaus.plexus.util.IOUtil; +import org.codehaus.plexus.util.WriterFactory; + +/** + * @author Vincent Siveton + */ +public class SiteToolTest + extends PlexusTestCase +{ + /** + * @return the repo. + * + * @throws Exception + */ + protected ArtifactRepository getLocalRepo() + throws Exception + { + String updatePolicyFlag = ArtifactRepositoryPolicy.UPDATE_POLICY_ALWAYS; + String checksumPolicyFlag = ArtifactRepositoryPolicy.CHECKSUM_POLICY_WARN; + ArtifactRepositoryPolicy snapshotsPolicy = new ArtifactRepositoryPolicy( true, updatePolicyFlag, + checksumPolicyFlag ); + ArtifactRepositoryPolicy releasesPolicy = new ArtifactRepositoryPolicy( true, updatePolicyFlag, + checksumPolicyFlag ); + ArtifactRepositoryFactory artifactRepositoryFactory = (ArtifactRepositoryFactory) lookup( ArtifactRepositoryFactory.ROLE ); + ArtifactRepositoryLayout defaultArtifactRepositoryLayout = (ArtifactRepositoryLayout) lookup( + ArtifactRepositoryLayout.ROLE, + "default" ); + return artifactRepositoryFactory.createArtifactRepository( "local", getTestFile( "target/local-repo" ).toURI().toURL() + .toString(), defaultArtifactRepositoryLayout, snapshotsPolicy, releasesPolicy ); + } + + /** + * @return the local repo directory. + * + * @throws Exception + */ + protected File getLocalRepoDir() + throws Exception + { + return new File( getLocalRepo().getBasedir() ); + } + + /** + * @throws Exception + */ + public void testGetDefaultSkinArtifact() + throws Exception + { + SiteTool tool = (SiteTool) lookup( SiteTool.ROLE ); + assertNotNull( tool ); + + SiteToolMavenProjectStub project = new SiteToolMavenProjectStub( "site-tool-test" ); + assertNotNull( tool.getDefaultSkinArtifact( getLocalRepo(), project.getRemoteArtifactRepositories() ) ); + } + + /** + * @throws Exception + */ + public void testGetSkinArtifactFromRepository() + throws Exception + { + SiteTool tool = (SiteTool) lookup( SiteTool.ROLE ); + assertNotNull( tool ); + + SiteToolMavenProjectStub project = new SiteToolMavenProjectStub( "site-tool-test" ); + DecorationModel decorationModel = new DecorationModel(); + Skin skin = new Skin(); + skin.setGroupId( "org.apache.maven.skins" ); + skin.setArtifactId( "maven-stylus-skin" ); + decorationModel.setSkin( skin ); + assertNotNull( tool.getSkinArtifactFromRepository( getLocalRepo(), project.getRemoteArtifactRepositories(), + decorationModel ) ); + } + + private void checkGetRelativePathDirectory( SiteTool tool, String relative, String to, String from ) + { + assertEquals( relative, tool.getRelativePath( to, from ) ); + assertEquals( relative, tool.getRelativePath( to + '/', from ) ); + assertEquals( relative, tool.getRelativePath( to, from + '/' ) ); + assertEquals( relative, tool.getRelativePath( to + '/', from + '/' ) ); + } + + /** + * @throws Exception + */ + public void testGetRelativePath() + throws Exception + { + SiteTool tool = (SiteTool) lookup( SiteTool.ROLE ); + assertNotNull( tool ); + + checkGetRelativePathDirectory( tool, "", "http://maven.apache.org", "http://maven.apache.org" ); + + checkGetRelativePathDirectory( tool, ".." + File.separator + "..", "http://maven.apache.org", + "http://maven.apache.org/plugins/maven-site-plugin" ); + + checkGetRelativePathDirectory( tool, "plugins" + File.separator + "maven-site-plugin", + "http://maven.apache.org/plugins/maven-site-plugin", "http://maven.apache.org" ); + + checkGetRelativePathDirectory( tool, "", "dav:https://maven.apache.org", "dav:https://maven.apache.org" ); + + checkGetRelativePathDirectory( tool, "plugins" + File.separator + "maven-site-plugin", + "dav:http://maven.apache.org/plugins/maven-site-plugin", + "dav:http://maven.apache.org" ); + + checkGetRelativePathDirectory( tool, "", "scm:svn:https://maven.apache.org", "scm:svn:https://maven.apache.org" ); + + checkGetRelativePathDirectory( tool, "plugins" + File.separator + "maven-site-plugin", + "scm:svn:https://maven.apache.org/plugins/maven-site-plugin", + "scm:svn:https://maven.apache.org" ); + + String to = "http://maven.apache.org/downloads.html"; + String from = "http://maven.apache.org/index.html"; + + // MSITE-600, MSHARED-203 + to = "file:///tmp/bloop"; + from = "scp://localhost:/tmp/blop"; + assertEquals( tool.getRelativePath( to, from ), to ); + + // note: 'tmp' is the host here which is probably not the intention, but at least the result is correct + to = "file://tmp/bloop"; + from = "scp://localhost:/tmp/blop"; + assertEquals( to, tool.getRelativePath( to, from ) ); + + // Tests between files as described in MIDEA-102 + to = "C:/dev/voca/gateway/parser/gateway-parser.iml"; + from = "C:/dev/voca/gateway/"; + assertEquals( "Child file using Windows drive letter", + "parser" + File.separator + "gateway-parser.iml", tool.getRelativePath( to, from ) ); + to = "C:/foo/child"; + from = "C:/foo/master"; + assertEquals( "Sibling directory using Windows drive letter", + ".." + File.separator + "child", tool.getRelativePath( to, from ) ); + to = "/myproject/myproject-module1"; + from = "/myproject/myproject"; + assertEquals( "Sibling directory with similar name", + ".." + File.separator + "myproject-module1", tool.getRelativePath( to, from ) ); + + // Normalized paths as described in MSITE-284 + assertEquals( ".." + File.separator + "project-module-1" + File.separator + "src" + File.separator + "site", + tool.getRelativePath( "Z:\\dir\\project\\project-module-1\\src\\site", + "Z:\\dir\\project\\project-module-1\\..\\project-parent" ) ); + assertEquals( ".." + File.separator + ".." + File.separator + ".." + File.separator + "project-parent", + tool.getRelativePath( "Z:\\dir\\project\\project-module-1\\..\\project-parent", + "Z:\\dir\\project\\project-module-1\\src\\site" ) ); + + assertEquals( ".." + File.separator + "foo", tool.getRelativePath( "../../foo/foo", "../../foo/bar" ) ); + } + + /** + * @throws Exception + */ + public void testGetSiteDescriptorFromBasedir() + throws Exception + { + SiteTool tool = (SiteTool) lookup( SiteTool.ROLE ); + assertNotNull( tool ); + + SiteToolMavenProjectStub project = new SiteToolMavenProjectStub( "site-tool-test" ); + assertEquals( tool.getSiteDescriptor( new File( project.getBasedir(), "src/site" ), null ).toString(), + project.getBasedir() + File.separator + "src" + File.separator + "site" + File.separator + "site.xml" ); + assertEquals( tool.getSiteDescriptor( new File( project.getBasedir(), "src/site" ), Locale.ENGLISH ).toString(), + project.getBasedir() + File.separator + "src" + File.separator + "site" + File.separator + "site.xml" ); + String siteDir = "src/blabla"; + assertEquals( tool.getSiteDescriptor( new File( project.getBasedir(), siteDir ), null ).toString(), + project.getBasedir() + File.separator + "src" + File.separator + "blabla" + File.separator + "site.xml" ); + } + + /** + * @throws Exception + */ + public void testGetSiteDescriptorFromRepository() + throws Exception + { + DefaultSiteTool tool = (DefaultSiteTool) lookup( SiteTool.ROLE ); + assertNotNull( tool ); + + SiteToolMavenProjectStub project = new SiteToolMavenProjectStub( "site-tool-test" ); + project.setGroupId( "org.apache.maven" ); + project.setArtifactId( "maven-site" ); + project.setVersion( "1.0" ); + String result = getLocalRepoDir() + File.separator + "org" + File.separator + "apache" + File.separator + + "maven" + File.separator + "maven-site" + File.separator + "1.0" + File.separator + + "maven-site-1.0-site.xml"; + + assertEquals( tool.getSiteDescriptorFromRepository( project, getLocalRepo(), + project.getRemoteArtifactRepositories(), Locale.ENGLISH ) + .toString(), result ); + } + + /** + * @throws Exception + */ + public void testGetDecorationModel() + throws Exception + { + SiteTool tool = (SiteTool) lookup( SiteTool.ROLE ); + assertNotNull( tool ); + + SiteToolMavenProjectStub project = new SiteToolMavenProjectStub( "site-tool-test" ); + List reactorProjects = new ArrayList(); + + // model from current local build + DecorationModel model = + tool.getDecorationModel( new File( project.getBasedir(), "src/site" ), Locale.getDefault(), project, + reactorProjects, getLocalRepo(), project.getRemoteArtifactRepositories() ); + assertNotNull( model ); + assertNotNull( model.getBannerLeft() ); + assertEquals( "Maven Site", model.getBannerLeft().getName() ); + assertEquals( "http://maven.apache.org/images/apache-maven-project.png", model.getBannerLeft().getSrc() ); + assertEquals( "http://maven.apache.org/", model.getBannerLeft().getHref() ); + assertNotNull( model.getBannerRight() ); + assertNull( model.getBannerRight().getName() ); + assertEquals( "http://maven.apache.org/images/maven-small.gif", model.getBannerRight().getSrc() ); + assertNull( model.getBannerRight().getHref() ); + + // model from repo: https://repo1.maven.org/maven2/org/apache/maven/maven-site/1.0/maven-site-1.0-site.xml + // TODO Enable this test as soon as we haven a site.xml with head content as string + /*project.setBasedir( null ); + project.setGroupId( "org.apache.maven" ); + project.setArtifactId( "maven-site" ); + project.setVersion( "1.0" ); + DecorationModel modelFromRepo = + tool.getDecorationModel( null, Locale.getDefault(), project, reactorProjects, getLocalRepo(), + project.getRemoteArtifactRepositories() ); + assertNotNull( modelFromRepo ); + assertNotNull( modelFromRepo.getBannerLeft() ); + assertEquals( "Maven", modelFromRepo.getBannerLeft().getName() ); + assertEquals( "images/apache-maven-project-2.png", modelFromRepo.getBannerLeft().getSrc() ); + assertEquals( "http://maven.apache.org/", modelFromRepo.getBannerLeft().getHref() ); + assertNotNull( modelFromRepo.getBannerRight() ); + assertNull( modelFromRepo.getBannerRight().getName() ); + assertEquals( "images/maven-logo-2.gif", modelFromRepo.getBannerRight().getSrc() ); + assertNull( modelFromRepo.getBannerRight().getHref() );*/ + } + + /** + * @throws Exception + */ + public void testGetDefaultDecorationModel() + throws Exception + { + SiteTool tool = (SiteTool) lookup( SiteTool.ROLE ); + assertNotNull( tool ); + + SiteToolMavenProjectStub project = new SiteToolMavenProjectStub( "no-site-test" ); + String siteDirectory = "src/site"; + List reactorProjects = new ArrayList(); + + DecorationModel model = + tool.getDecorationModel( new File( project.getBasedir(), siteDirectory ), Locale.getDefault(), project, + reactorProjects, getLocalRepo(), project.getRemoteArtifactRepositories() ); + assertNotNull( model ); + } + + public void testGetAvailableLocales() + throws Exception + { + SiteTool tool = (SiteTool) lookup( SiteTool.ROLE ); + + assertEquals( Arrays.asList( new Locale[] { SiteTool.DEFAULT_LOCALE } ), tool.getSiteLocales( "en" ) ); + + assertEquals( Arrays.asList( new Locale[] { SiteTool.DEFAULT_LOCALE, Locale.FRENCH, Locale.ITALIAN } ), + tool.getSiteLocales( "en,fr,it" ) ); + + // by default, only DEFAULT_LOCALE + assertEquals( Arrays.asList( new Locale[] { SiteTool.DEFAULT_LOCALE } ), tool.getSiteLocales( "" ) ); + } + + public void testGetInterpolatedSiteDescriptorContent() + throws Exception + { + SiteTool tool = (SiteTool) lookup( SiteTool.ROLE ); + assertNotNull( tool ); + + File pomXmlFile = getTestFile( "src/test/resources/unit/interpolated-site/pom.xml" ); + assertNotNull( pomXmlFile ); + assertTrue( pomXmlFile.exists() ); + + File descriptorFile = getTestFile( "src/test/resources/unit/interpolated-site/src/site/site.xml" ); + assertNotNull( descriptorFile ); + assertTrue( descriptorFile.exists() ); + + String siteDescriptorContent = FileUtils.fileRead( descriptorFile ); + assertNotNull( siteDescriptorContent ); + assertTrue( siteDescriptorContent.contains( "${project.name}" ) ); + assertFalse( siteDescriptorContent.contains( "Interpolatesite" ) ); + + SiteToolMavenProjectStub project = new SiteToolMavenProjectStub( "interpolated-site" ); + + SiteTool siteTool = (SiteTool) lookup( SiteTool.ROLE ); + siteDescriptorContent = + siteTool.getInterpolatedSiteDescriptorContent( new HashMap(), project, + siteDescriptorContent ); + assertNotNull( siteDescriptorContent ); + assertFalse( siteDescriptorContent.contains( "${project.name}" ) ); + assertTrue( siteDescriptorContent.contains( "Interpolatesite" ) ); + } + + // MSHARED-217 -> DOXIATOOLS-34 -> DOXIASITETOOLS-118 + public void testDecorationModelInheritanceAndInterpolation() + throws Exception + { + SiteTool tool = (SiteTool) lookup( SiteTool.ROLE ); + assertNotNull( tool ); + + SiteToolMavenProjectStub parentProject = new SiteToolMavenProjectStub( "interpolation-parent-test" ); + parentProject.setDistgributionManagementSiteUrl( "dav:https://davs.codehaus.org/site" ); + + SiteToolMavenProjectStub childProject = new SiteToolMavenProjectStub( "interpolation-child-test" ); + childProject.setParent( parentProject ); + childProject.setDistgributionManagementSiteUrl( "dav:https://davs.codehaus.org/site/child" ); + + List reactorProjects = Collections.singletonList( parentProject ); + + DecorationModel model = tool.getDecorationModel( new File( childProject.getBasedir(), "src/site" ), + Locale.getDefault(), childProject, reactorProjects, + getLocalRepo(), childProject.getRemoteArtifactRepositories() ); + assertNotNull( model ); + + writeModel( model, "unit/interpolation-child-test/effective-site.xml" ); + + assertEquals( "MSHARED-217 Child", model.getName() ); + // late (classical) interpolation + assertEquals( "project.artifactId = mshared-217-child", model.getBannerLeft().getName() ); + // early interpolation: DOXIASITETOOLS-158 + assertEquals( "this.artifactId = mshared-217-parent", model.getBannerRight().getName() ); + // href rebase + assertEquals( "../../index.html", model.getBody().getBreadcrumbs().iterator().next().getHref() ); + Iterator links = model.getBody().getLinks().iterator(); + // late interpolation of pom content (which happens first: properties can't override) + assertEquals( "project.name = MSHARED-217 Child", links.next().getName() ); + assertEquals( "name = MSHARED-217 Child", links.next().getName() ); + // early interpolation: DOXIASITETOOLS-158 + assertEquals( "this.name = MSHARED-217 Parent", links.next().getName() ); + + // late interpolation of project properties + assertEquals( "my_property = from child pom.xml", links.next().getName() ); + // early interpolation of project properties: DOXIASITETOOLS-158 + assertEquals( "this.my_property = from parent pom.xml", links.next().getName() ); + + // Env Var interpolation + String envPath = links.next().getName(); + assertTrue( envPath.startsWith( "env.PATH = " ) ); + assertFalse( envPath.contains( "${" ) ); + assertNotSame( "env.PATH = PATH property from pom", envPath ); + + // property overrides env + assertEquals( "PATH = PATH property from pom", links.next().getName() ); + } + + private void writeModel( DecorationModel model, String to ) + throws Exception + { + Writer writer = WriterFactory.newXmlWriter( getTestFile( "target/test-classes/" + to ) ); + try + { + new DecorationXpp3Writer().write( writer, model ); + } + finally + { + IOUtil.close( writer ); + } + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/java/org/apache/maven/doxia/tools/stubs/SiteToolMavenProjectStub.java b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/java/org/apache/maven/doxia/tools/stubs/SiteToolMavenProjectStub.java new file mode 100644 index 000000000..4a30d7d6e --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/java/org/apache/maven/doxia/tools/stubs/SiteToolMavenProjectStub.java @@ -0,0 +1,152 @@ +package org.apache.maven.doxia.tools.stubs; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.FileReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Properties; + +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.artifact.repository.DefaultArtifactRepository; +import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout; +import org.apache.maven.model.Build; +import org.apache.maven.model.DistributionManagement; +import org.apache.maven.model.Model; +import org.apache.maven.model.Site; +import org.apache.maven.model.io.xpp3.MavenXpp3Reader; +import org.apache.maven.plugin.testing.stubs.MavenProjectStub; + +/** + * @author Vincent Siveton + */ +public class SiteToolMavenProjectStub + extends MavenProjectStub +{ + private Build build; + + private File basedir; + + private DistributionManagement distributionManagement; + + private Properties properties; + + public SiteToolMavenProjectStub( String projectName ) + { + basedir = new File( super.getBasedir() + "/src/test/resources/unit/" + projectName ); + + Model model = null; + + try + { + model = new MavenXpp3Reader().read( new FileReader( new File( getBasedir(), "pom.xml" ) ) ); + setModel( model ); + } + catch ( Exception e ) + { + throw new RuntimeException( e ); + } + + setGroupId( model.getGroupId() ); + setArtifactId( model.getArtifactId() ); + setVersion( model.getVersion() ); + setName( model.getName() ); + setUrl( model.getUrl() ); + setPackaging( model.getPackaging() ); + setProperties( model.getProperties() ); + + build = new Build(); + build.setFinalName( model.getArtifactId() ); + build.setDirectory( super.getBasedir() + "/target/test/unit/" + projectName + "/target" ); + build.setSourceDirectory( getBasedir() + "/src/main/java" ); + build.setOutputDirectory( build.getDirectory() + "/classes" ); + build.setTestSourceDirectory( getBasedir() + "/src/test/java" ); + build.setTestOutputDirectory( build.getDirectory() + "/test-classes" ); + + List compileSourceRoots = new ArrayList(); + compileSourceRoots.add( getBasedir() + "/src/main/java" ); + setCompileSourceRoots( compileSourceRoots ); + + List testCompileSourceRoots = new ArrayList(); + testCompileSourceRoots.add( getBasedir() + "/src/test/java" ); + setTestCompileSourceRoots( testCompileSourceRoots ); + } + + /** {@inheritDoc} */ + public Build getBuild() + { + return build; + } + + /** {@inheritDoc} */ + public void setBuild( Build build ) + { + this.build = build; + } + + /** {@inheritDoc} */ + public File getBasedir() + { + return basedir; + } + + /** {@inheritDoc} */ + public void setBasedir( File basedir ) + { + this.basedir = basedir; + } + + /** {@inheritDoc} */ + public List getRemoteArtifactRepositories() + { + ArtifactRepository repository = new DefaultArtifactRepository( "central", "https://repo1.maven.org/maven2", + new DefaultRepositoryLayout() ); + + return Collections.singletonList( repository ); + } + + /** {@inheritDoc} */ + public Properties getProperties() + { + return properties; + } + + /** {@inheritDoc} */ + public void setProperties( Properties properties ) + { + this.properties = properties; + } + + public void setDistgributionManagementSiteUrl( String url ) + { + Site site = new Site(); + site.setUrl( url ); + distributionManagement = new DistributionManagement(); + distributionManagement.setSite( site ); + } + + /** {@inheritDoc} */ + public DistributionManagement getDistributionManagement() + { + return distributionManagement; + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/interpolated-site/pom.xml b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/interpolated-site/pom.xml new file mode 100644 index 000000000..a135e210c --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/interpolated-site/pom.xml @@ -0,0 +1,80 @@ + + + + + + 4.0.0 + org.apache.maven.plugin.site.unit + interpolatesite + jar + 1.0-SNAPSHOT + 2006 + Interpolatesite + Test interpolation for site.xml. + http://maven.apache.org + + + junit + junit + 3.8.1 + test + + + + + vsiveton + Vincent Siveton + vsiveton@apache.org + Apache Software Foundation + + Java Developer + + -5 + + + + + + org.apache.maven.plugins + maven-project-info-reports-plugin + + + + + + + org.apache.maven.plugins + maven-site-plugin + + + ISO-8859-1 + ISO-8859-1 + + + + + + + foo + bar + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/interpolated-site/src/site/apt/test.apt b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/interpolated-site/src/site/apt/test.apt new file mode 100644 index 000000000..90671376d --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/interpolated-site/src/site/apt/test.apt @@ -0,0 +1,29 @@ + ------ + Configuring Site Plugin + ------ + Vincent Siveton + ------ + November 2006 + ------ + + ~~ 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. + + +Site Plugin Test + + Test diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/interpolated-site/src/site/site.xml b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/interpolated-site/src/site/site.xml new file mode 100644 index 000000000..58a27760a --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/interpolated-site/src/site/site.xml @@ -0,0 +1,43 @@ + + + + + + + Maven Site + http://maven.apache.org/images/apache-maven-project.png + http://maven.apache.org/ + + + http://maven.apache.org/images/maven-small.gif + + + + + + + + + + + + ${reports} + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/interpolation-child-test/pom.xml b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/interpolation-child-test/pom.xml new file mode 100644 index 000000000..b27a81443 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/interpolation-child-test/pom.xml @@ -0,0 +1,37 @@ + + + + + 4.0.0 + + org.apache.maven.shared.its + mshared-217-child + 1.0-SNAPSHOT + + MSHARED-217 Child + http://maven.apache.org/mshared-217/child + + + from child pom.xml + PATH property from pom + name property + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/interpolation-child-test/src/site/site.xml b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/interpolation-child-test/src/site/site.xml new file mode 100644 index 000000000..091ed5199 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/interpolation-child-test/src/site/site.xml @@ -0,0 +1,24 @@ + + + + + \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/interpolation-parent-test/pom.xml b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/interpolation-parent-test/pom.xml new file mode 100644 index 000000000..28545f85d --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/interpolation-parent-test/pom.xml @@ -0,0 +1,36 @@ + + + + + 4.0.0 + + org.apache.maven.shared.its + mshared-217-parent + 1.0-SNAPSHOT + + MSHARED-217 Parent + http://maven.apache.org/mshared-217 + + + from parent pom.xml + name property + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/interpolation-parent-test/src/site/site.xml b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/interpolation-parent-test/src/site/site.xml new file mode 100644 index 000000000..27a291361 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/interpolation-parent-test/src/site/site.xml @@ -0,0 +1,52 @@ + + + + + + + project.artifactId = ${project.artifactId} + + + this.artifactId = ${this.artifactId} + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/no-site-test/pom.xml b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/no-site-test/pom.xml new file mode 100644 index 000000000..6d048ee0a --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/no-site-test/pom.xml @@ -0,0 +1,40 @@ + + + + + 4.0.0 + + org.apache.maven.shared + no-site-test + 1.0-SNAPSHOT + jar + + dummy + + + + junit + junit + 3.8.2 + test + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/site-tool-test/pom.xml b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/site-tool-test/pom.xml new file mode 100644 index 000000000..fb40c17fe --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/site-tool-test/pom.xml @@ -0,0 +1,40 @@ + + + + + 4.0.0 + + org.apache.maven.shared + site-tool-test + 1.0-SNAPSHOT + jar + + dummy + + + + junit + junit + 3.8.1 + test + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/site-tool-test/src/site/site.xml b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/site-tool-test/src/site/site.xml new file mode 100644 index 000000000..41db45ef7 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-integration-tools/src/test/resources/unit/site-tool-test/src/site/site.xml @@ -0,0 +1,45 @@ + + + + + + + Maven Site + http://maven.apache.org/images/apache-maven-project.png + http://maven.apache.org/ + + + http://maven.apache.org/images/maven-small.gif + + + org.apache.maven.skins + maven-stylus-skin + + + + + + + + + + ${reports} + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/pom.xml b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/pom.xml new file mode 100644 index 000000000..ba4c0f6a4 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/pom.xml @@ -0,0 +1,233 @@ + + + + + + 4.0.0 + + + org.apache.maven.doxia + doxia-sitetools + 1.9.3-SNAPSHOT + ../pom.xml + + + doxia-site-renderer + + Doxia Sitetools :: Site Renderer + The Site Renderer handles the rendering of sites, merging site decoration with document content. + + + + + org.apache.maven + maven-artifact + 3.0 + + + + + org.apache.maven.doxia + doxia-core + + + org.apache.maven.doxia + doxia-logging-api + + + org.apache.maven.doxia + doxia-sink-api + + + org.apache.maven.doxia + doxia-decoration-model + + + org.apache.maven.doxia + doxia-skin-model + + + + org.apache.maven.doxia + doxia-module-apt + test + + + org.apache.maven.doxia + doxia-module-confluence + test + + + org.apache.maven.doxia + doxia-module-docbook-simple + test + + + org.apache.maven.doxia + doxia-module-xdoc + test + + + org.apache.maven.doxia + doxia-module-xhtml + + + org.apache.maven.doxia + doxia-module-xhtml5 + + + org.apache.maven.doxia + doxia-module-fml + test + + + + + org.codehaus.plexus + plexus-component-annotations + + + org.codehaus.plexus + plexus-i18n + + + org.codehaus.plexus + plexus-container-default + + + org.codehaus.plexus + plexus-velocity + + + org.codehaus.plexus + plexus-utils + + + + + org.apache.velocity + velocity + + + org.apache.velocity + velocity-tools + 2.0 + + + + javax.servlet + servlet-api + + + org.apache.struts + struts-core + + + org.apache.struts + struts-taglib + + + org.apache.struts + struts-tiles + + + sslext + sslext + + + commons-validator + commons-validator + + + + + commons-collections + commons-collections + 3.2.2 + + + org.apache.commons + commons-lang3 + 3.8.1 + + + + + junit + junit + test + + + commons-io + commons-io + test + + + org.apache.maven.doxia + doxia-core + ${doxiaVersion} + test-jar + test + + + net.sourceforge.htmlunit + htmlunit + 2.24 + test + + + org.mockito + mockito-core + 2.28.2 + test + + + + + + reporting + + + + org.codehaus.mojo + l10n-maven-plugin + 1.0-alpha-2 + + + de + en + es + fr + it + ja + nl + pl + pt_BR + sv + zh_CN + + + + + + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java new file mode 100644 index 000000000..6950b60f3 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java @@ -0,0 +1,1192 @@ +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} */ + 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 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/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DocumentContent.java b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DocumentContent.java new file mode 100644 index 000000000..4bcbb9edd --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DocumentContent.java @@ -0,0 +1,66 @@ +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.util.List; + +/** + * Document content, that will be merged into a site template. + * + * @since 1.8 + */ +public interface DocumentContent +{ + /** + * Get the title of the document. + * @return the document title + */ + String getTitle(); + + /** + * Get the date of the document. + * @return the document date + */ + String getDate(); + + /** + * Get the authors of the document. + * @return the document authors + */ + List getAuthors(); + + /** + * Get the html head of the document. + * @return the document html head + */ + String getHead(); + + /** + * Get the html body of the document. + * @return the document body head + */ + String getBody(); + + /** + * Get the document rendering context. + * @return the document rendering context + */ + RenderingContext getRenderingContext(); +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DocumentRenderer.java b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DocumentRenderer.java new file mode 100644 index 000000000..74485a65d --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DocumentRenderer.java @@ -0,0 +1,75 @@ +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.FileNotFoundException; +import java.io.UnsupportedEncodingException; +import java.io.Writer; + +/** + * Renders a page in a site, whatever the source is: a Doxia source file, a report or anything else. + * + * @author Brett Porter + * @see RenderingContext document rendering context + */ +public interface DocumentRenderer +{ + /** + * Render a document in a site. + * + * @param writer the Writer for the document output. + * @param siteRenderer the site renderer to merge document content to. + * @param siteRenderingContext the site rendering context. + * @throws org.apache.maven.doxia.siterenderer.RendererException if it bombs. + * @throws java.io.FileNotFoundException if it bombs. + * @throws java.io.UnsupportedEncodingException if it bombs. + */ + void renderDocument( Writer writer, Renderer siteRenderer, SiteRenderingContext siteRenderingContext ) + throws RendererException, FileNotFoundException, UnsupportedEncodingException; + + /** + * The name of the output document. + * + * @return the name of the output document. + */ + String getOutputName(); + + /** + * Return the RenderingContext of the document. + * + * @return RenderingContext. + */ + RenderingContext getRenderingContext(); + + /** + * Whether to always overwrite the document, or only do so when it is changed. + * + * @return whether to overwrite + */ + boolean isOverwrite(); + + /** + * Whether this document is an external report, independent from the site templating. + * + * @return {@code true} if report is external, otherwise {@code false} + * @since 1.7 + */ + boolean isExternalReport(); +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DoxiaDocumentRenderer.java b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DoxiaDocumentRenderer.java new file mode 100644 index 000000000..30ffda062 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DoxiaDocumentRenderer.java @@ -0,0 +1,78 @@ +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.FileNotFoundException; +import java.io.UnsupportedEncodingException; +import java.io.Writer; + +/** + * Renderer for a document that has a source file to be parsed by Doxia. + * Details about the source file are in {@link RenderingContext}, which is expected to have + * a non-null parserId and extension. + * + * @author Brett Porter + */ +public class DoxiaDocumentRenderer + implements DocumentRenderer +{ + private RenderingContext renderingContext; + + /** + * Constructor. + * + * @param renderingContext the document's RenderingContext to use. + */ + public DoxiaDocumentRenderer( RenderingContext renderingContext ) + { + this.renderingContext = renderingContext; + } + + /** {@inheritDoc} */ + public void renderDocument( Writer writer, Renderer siteRenderer, SiteRenderingContext siteRenderingContext ) + throws RendererException, FileNotFoundException, UnsupportedEncodingException + { + siteRenderer.renderDocument( writer, renderingContext, siteRenderingContext ); + } + + /** {@inheritDoc} */ + public String getOutputName() + { + return renderingContext.getOutputName(); + } + + /** {@inheritDoc} */ + public RenderingContext getRenderingContext() + { + return renderingContext; + } + + /** {@inheritDoc} */ + public boolean isOverwrite() + { + return false; + } + + public boolean isExternalReport() + { + return false; + } + +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/ExtraDoxiaModuleReference.java b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/ExtraDoxiaModuleReference.java new file mode 100644 index 000000000..42cab469e --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/ExtraDoxiaModuleReference.java @@ -0,0 +1,60 @@ +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.File; + +/** + * Holds an extra Doxia source module reference in the list of added modules to the site rendering context. + * + * @author Brett Porter + */ +class ExtraDoxiaModuleReference +{ + private final String parserId; + + private final File basedir; + + ExtraDoxiaModuleReference( String parserId, File basedir ) + { + this.parserId = parserId; + this.basedir = basedir; + } + + /** + *

Getter for the field parserId.

+ * + * @return Doxia parser id associated to this source module. + */ + String getParserId() + { + return parserId; + } + + /** + *

Getter for the field basedir.

+ * + * @return The base directory for module's source files. + */ + File getBasedir() + { + return basedir; + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/Renderer.java b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/Renderer.java new file mode 100644 index 000000000..148cef953 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/Renderer.java @@ -0,0 +1,185 @@ +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.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.net.MalformedURLException; +import java.util.Collection; +import java.util.Locale; +import java.util.Map; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.doxia.site.decoration.DecorationModel; +import org.apache.maven.doxia.siterenderer.sink.SiteRendererSink; + +/** + *

Site Renderer interface: render a collection of documents into a site, ie decored with a site template + * (eventually packaged as skin).

+ * + * @author Emmanuel Venisse + */ +public interface Renderer // TODO rename to SiteRenderer +{ + /** + * Plexus lookup role. + */ + String ROLE = Renderer.class.getName(); + + /** + * Render a collection of documents into a site. + * + * @param documents the documents to render. + * @param siteRenderingContext the SiteRenderingContext to use. + * @param outputDirectory the output directory to write results. + * @throws RendererException if it bombs. + * @throws IOException if it bombs. + */ + void render( Collection documents, SiteRenderingContext siteRenderingContext, + File outputDirectory ) + throws RendererException, IOException; + + /** + * Generate a document output from a Doxia SiteRenderer Sink, i.e. merge the document content into + * the site template. + * + * @param writer the Writer to use. + * @param sink the Site Renderer Sink that received the Doxia events during document content rendering. + * @param siteRenderingContext the SiteRenderingContext to use. + * @throws RendererException if it bombs. + * @deprecated since 1.8, use mergeDocumentIntoSite + */ + void generateDocument( Writer writer, SiteRendererSink sink, SiteRenderingContext siteRenderingContext ) + throws RendererException; + + /** + * Generate a document output integrated in a site from a document content, + * i.e. merge the document content into the site template. + * + * @param writer the Writer to use. + * @param content the document content to be merged + * @param siteRenderingContext the SiteRenderingContext to use. + * @throws RendererException if it bombs. + * @since 1.8 + */ + void mergeDocumentIntoSite( Writer writer, DocumentContent content, SiteRenderingContext siteRenderingContext ) + throws RendererException; + + /** + * Create a Site Rendering Context for a site using a skin. + * + * @param skin + * @param attributes + * @param decoration + * @param defaultWindowTitle + * @param locale + * @return a SiteRenderingContext. + * @throws java.io.IOException if it bombs. + * @since 1.7.3 was previously with skin as File instead of Artifact + */ + SiteRenderingContext createContextForSkin( Artifact skin, Map attributes, DecorationModel decoration, + String defaultWindowTitle, Locale locale ) + throws RendererException, IOException; + + /** + * Create a Site Rendering Context for a site using a local template. + * + * @param templateFile + * @param attributes + * @param decoration + * @param defaultWindowTitle + * @param locale + * @return a SiteRenderingContext. + * @throws MalformedURLException if it bombs. + * @since 1.7, had an additional skinFile parameter before + * @deprecated Deprecated without replacement, use skins only. + * @see #createContextForSkin(File, Map, DecorationModel, String, Locale) + */ + @Deprecated + SiteRenderingContext createContextForTemplate( File templateFile, Map attributes, + DecorationModel decoration, String defaultWindowTitle, + Locale locale ) + throws MalformedURLException; + + /** + * Copy resource files. + * + * @param siteRenderingContext + * @param resourcesDirectory + * @param outputDirectory + * @throws IOException if it bombs. + * @deprecated since 1.7, use copyResources without resourcesDirectory parameter + */ + void copyResources( SiteRenderingContext siteRenderingContext, File resourcesDirectory, File outputDirectory ) + throws IOException; + + /** + * Copy resource files from skin, template, and site resources. + * + * @param siteRenderingContext + * @param outputDirectory + * @throws IOException if it bombs. + * @since 1.7 + */ + void copyResources( SiteRenderingContext siteRenderingContext, File outputDirectory ) + throws IOException; + + /** + * Locate Doxia document source files in the site source context. + * + * @param siteRenderingContext + * @return the Doxia document renderers in a Map keyed by output file name. + * @throws IOException if it bombs. + * @throws RendererException if it bombs. + * @deprecated since 1.8, use locateDocumentFiles with editable parameter + */ + Map locateDocumentFiles( SiteRenderingContext siteRenderingContext ) + throws IOException, RendererException; + + /** + * Locate Doxia document source files in the site source context. + * + * @param siteRenderingContext + * @param mark Doxia document renderer as editable? (should not mark editable if generated Doxia source) + * @return the Doxia document renderers in a Map keyed by output file name. + * @throws IOException if it bombs. + * @throws RendererException if it bombs. + * @since 1.8 + */ + Map locateDocumentFiles( SiteRenderingContext siteRenderingContext, boolean editable ) + throws IOException, RendererException; + + /** + * Render a document written in a Doxia markup language. This method is an internal method, used by + * {@link DoxiaDocumentRenderer}. + * + * @param writer the writer to render the document to. + * @param docRenderingContext the document's rendering context, which is expected to have a non-null parser id. + * @param siteContext the site's rendering context + * @throws RendererException if it bombs. + * @throws FileNotFoundException if it bombs. + * @throws UnsupportedEncodingException if it bombs. + */ + void renderDocument( Writer writer, RenderingContext docRenderingContext, SiteRenderingContext siteContext ) + throws RendererException, FileNotFoundException, UnsupportedEncodingException; +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/RendererException.java b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/RendererException.java new file mode 100644 index 000000000..b664eaa2c --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/RendererException.java @@ -0,0 +1,53 @@ +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. + */ + +/** + *

RendererException class.

+ * + * @author Emmanuel Venisse + */ +public class RendererException + extends Exception +{ + + private static final long serialVersionUID = 3141592653589793238L; + + /** + * Construct a RendererException with a message. + * + * @param message a custom message. + */ + public RendererException( String message ) + { + super( message ); + } + + /** + * Construct a RendererException with a message and a cause. + * + * @param message a custom message. + * @param t the cause. + */ + public RendererException( String message, Throwable t ) + { + super( message, t ); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/RenderingContext.java b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/RenderingContext.java new file mode 100644 index 000000000..239e6ec56 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/RenderingContext.java @@ -0,0 +1,295 @@ +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.File; +import java.util.HashMap; +import java.util.Map; + +import org.codehaus.plexus.util.PathTool; +import org.codehaus.plexus.util.StringUtils; + +/** + * The rendering context of a document. + * If not rendered from a Doxia markup source, parserId and extension will be null. + * + * @author Jason van Zyl + * @since 1.5 (was since 1.1 in o.a.m.d.sink.render) + */ +public class RenderingContext // TODO rename to DocumentRenderingContext +{ + private final File basedir; + + private final String basedirRelativePath; + + private final String inputName; + + private final String outputName; + + private final String parserId; + + private final String relativePath; + + private final String extension; + + private Map attributes; + + private final boolean editable; + + private final String generator; + + @Deprecated + public RenderingContext( File basedir, String document ) + { + this( basedir, null, document, null, null, false, null ); + } + + /** + *

+ * Constructor for RenderingContext when document is not rendered from a Doxia markup source. + *

+ * + * @param basedir the pseudo-source base directory. + * @param document the pseudo-source document name: will be used to compute output name (same name with extension + * replaced with .html). + * @param generator the generator (in general a reporting goal: groupId:artifactId:version:goal) + * @since 1.8 + */ + public RenderingContext( File basedir, String document, String generator ) + { + this( basedir, null, document, null, null, false, generator ); + } + + @Deprecated + public RenderingContext( File basedir, String document, String parserId, String extension ) + { + this( basedir, null, document, parserId, extension, false, null ); + } + + public RenderingContext( File basedir, String basedirRelativePath, String document, String parserId, + String extension, boolean editable ) + { + this( basedir, basedirRelativePath, document, parserId, extension, editable, null ); + } + + /** + *

+ * Constructor for document RenderingContext. + *

+ * + * @param basedir the source base directory (not null, pseudo value when not a Doxia source). + * @param basedirRelativePath the relative path from root (null if not Doxia source) + * @param document the source document name. + * @param parserId the Doxia module parser id associated to this document, may be null if document not rendered from + * a Doxia source. + * @param extension the source document filename extension, may be null if document not rendered from + * a Doxia source. + * @param editable is the document editable as source, i.e. not generated? + * @param generator the generator (in general a reporting goal: groupId:artifactId:version:goal) + * @since 1.8 + */ + public RenderingContext( File basedir, String basedirRelativePath, String document, String parserId, + String extension, boolean editable, String generator ) + { + this.basedir = basedir; + this.basedirRelativePath = basedirRelativePath; + this.inputName = document; + this.parserId = parserId; + this.extension = extension; + this.generator = generator; + this.attributes = new HashMap(); + + if ( StringUtils.isNotEmpty( extension ) ) + { + // document comes from a Doxia source: see DoxiaDocumentRenderer + this.editable = editable; + + // here we know the parserId and extension, we can play with this to get output name from document: + // - index.xml -> index.html + // - index.xml.vm -> index.html + // - download.apt.vm --> download.html + if ( DefaultSiteRenderer.endsWithIgnoreCase( document, ".vm" ) ) + { + document = document.substring( 0, document.length() - 3 ); + } + String fileNameWithoutExt = document.substring( 0, document.length() - extension.length() - 1 ); + this.outputName = fileNameWithoutExt + ".html"; + } + else + { + // document does not come from a Doxia source but direct Sink API + this.editable = false; + // make sure output name ends in .html + this.outputName = document.substring( 0, document.lastIndexOf( '.' ) ).replace( '\\', '/' ) + ".html"; + } + + this.relativePath = PathTool.getRelativePath( basedir.getPath(), new File( basedir, inputName ).getPath() ); + } + + /** + *

Getter for the field basedir.

+ * + * @return a {@link java.io.File} object. + */ + public File getBasedir() + { + return basedir; + } + + /** + *

Getter for the field inputName.

+ * + * @return a {@link java.lang.String} object. + */ + public String getInputName() + { + return inputName; + } + + /** + * Get html output name, relative to site root. + * + * @return html output name + * @see PathTool#getRelativePath(String) + */ + public String getOutputName() + { + return outputName; + } + + /** + * Get the parserId when document comes from a Doxia source. + * + * @return parser id, or null if not froma DOxia source. + */ + public String getParserId() + { + return parserId; + } + + /** + * Get the relative path to site root. + * + * @return the relative path to site root + */ + public String getRelativePath() + { + return relativePath; + } + + /** + *

setAttribute.

+ * + * @param key a {@link java.lang.String} object. + * @param value a {@link java.lang.String} object. + */ + public void setAttribute( String key, String value ) + { + attributes.put( key, value ); + } + + /** + *

getAttribute.

+ * + * @param key a {@link java.lang.String} object. + * @return a {@link java.lang.String} object. + */ + public String getAttribute( String key ) + { + return attributes.get( key ); + } + + /** + * Get the source document filename extension (when a Doxia source) + * + * @return the source document filename extension when a Doxia source, or null if not a Doxia source + */ + public String getExtension() + { + return extension; + } + + /** + * Is the source document editable? + * + * @return true if comes from an editable Doxia source (not generated one). + * @since 1.8 + */ + public boolean isEditable() + { + return editable; + } + + /** + * Is the document rendered from a Doxia source? + * + * @return true if comes from a Doxia source. + * @since 1.8 + */ + public boolean isDoxiaSource() + { + return StringUtils.isNotEmpty( extension ); + } + + /** + * What is the generator (if any)? + * + * @return null if no known generator + * @since 1.8 + */ + public String getGenerator() + { + return generator; + } + + /** + * Get the relative path of basedir (when a Doxia source) + * + * @return the relative path of basedir when a Doxia source, or null if not a Doxia source + * @since 1.8 + */ + public String getBasedirRelativePath() + { + return basedirRelativePath; + } + + /** + * Get the relative path to Doxia source from build root. + * + * @return the relative path to Doxia source from build root, or null if not a Doxia source + * @since 1.8 + */ + public String getDoxiaSourcePath() + { + return isDoxiaSource() ? ( basedirRelativePath + '/' + inputName ) : null; + } + + /** + * Get url of the Doxia source calculate from given base url. + * + * @param base the base url to use + * @return the resulting url + * @since 1.8 + */ + public String getDoxiaSourcePath( String base ) + { + return PathTool.calculateLink( getDoxiaSourcePath(), base ); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/SiteRenderingContext.java b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/SiteRenderingContext.java new file mode 100644 index 000000000..994e2a1e2 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/SiteRenderingContext.java @@ -0,0 +1,473 @@ +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.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.doxia.site.decoration.DecorationModel; +import org.apache.maven.doxia.site.skin.SkinModel; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.WriterFactory; + +/** + * Context for a site rendering. + * + * @author Brett Porter + */ +public class SiteRenderingContext +{ + private String inputEncoding = ReaderFactory.FILE_ENCODING; + + private String outputEncoding = WriterFactory.UTF_8; + + private String templateName; + + private ClassLoader templateClassLoader; + + private Map templateProperties; + + private Locale locale = Locale.getDefault(); + + private List siteLocales = new ArrayList(); + + private DecorationModel decoration; + + private String defaultWindowTitle; + + private Artifact skin; + + private SkinModel skinModel; + + private boolean usingDefaultTemplate; + + private File rootDirectory; + + private List siteDirectories = new ArrayList(); + + private Map moduleExcludes; + + private List modules = new ArrayList(); + + private boolean validate; + + private Date publishDate; + + private File processedContentOutput; + + /** + * If input documents should be validated before parsing. + * By default no validation is performed. + * + * @return true if validation is switched on. + * @since 1.1.3 + */ + public boolean isValidate() + { + return validate; + } + + /** + * Switch on/off validation. + * + * @param validate true to switch on validation. + * @since 1.1.3 + */ + public void setValidate( boolean validate ) + { + this.validate = validate; + } + + /** + *

Getter for the field templateName.

+ * + * @return a {@link java.lang.String} object. + */ + public String getTemplateName() + { + return templateName; + } + + /** + *

Getter for the field templateClassLoader.

+ * + * @return a {@link java.lang.ClassLoader} object. + */ + public ClassLoader getTemplateClassLoader() + { + return templateClassLoader; + } + + /** + *

Setter for the field templateClassLoader.

+ * + * @param templateClassLoader a {@link java.lang.ClassLoader} object. + */ + public void setTemplateClassLoader( ClassLoader templateClassLoader ) + { + this.templateClassLoader = templateClassLoader; + } + + /** + *

Getter for the field templateProperties.

+ * + * @return a {@link java.util.Map} object. + */ + public Map getTemplateProperties() + { + return templateProperties; + } + + /** + *

Setter for the field templateProperties.

+ * + * @param templateProperties a {@link java.util.Map} object. + */ + public void setTemplateProperties( Map templateProperties ) + { + this.templateProperties = Collections.unmodifiableMap( templateProperties ); + } + + /** + *

Getter for the field locale.

+ * + * @return a {@link java.util.Locale} object. + */ + public Locale getLocale() + { + return locale; + } + + /** + *

Setter for the field locale.

+ * + * @param locale a {@link java.util.Locale} object. + */ + public void setLocale( Locale locale ) + { + this.locale = locale; + } + + /** + *

Getter for the field siteLocales - + * a list of locales available for this site context.

+ * + * @return a {@link java.util.List} object with {@link java.util.Locale} objects. + */ + public List getSiteLocales() + { + return siteLocales; + } + + /** + *

Adds passed locales to the list of site locales.

+ * + * @param locales List of {@link java.util.Locale} objects to add to the site locales list. + */ + public void addSiteLocales( List locales ) + { + siteLocales.addAll( locales ); + } + + /** + *

Getter for the field decoration.

+ * + * @return a {@link org.apache.maven.doxia.site.decoration.DecorationModel} object. + */ + public DecorationModel getDecoration() + { + return decoration; + } + + /** + *

Setter for the field decoration.

+ * + * @param decoration a {@link org.apache.maven.doxia.site.decoration.DecorationModel} object. + */ + public void setDecoration( DecorationModel decoration ) + { + this.decoration = decoration; + } + + /** + *

Setter for the field defaultWindowTitle.

+ * + * @param defaultWindowTitle a {@link java.lang.String} object. + */ + public void setDefaultWindowTitle( String defaultWindowTitle ) + { + this.defaultWindowTitle = defaultWindowTitle; + } + + /** + *

Getter for the field defaultWindowTitle.

+ * + * @return a {@link java.lang.String} object. + */ + public String getDefaultWindowTitle() + { + return defaultWindowTitle; + } + + /** + *

Getter for the field skin.

+ * + * @return a {@link Artifact} object. + */ + public Artifact getSkin() + { + return skin; + } + + /** + *

Setter for the field skinJarFile.

+ * + * @param skin an {@link Artifact} object. + */ + public void setSkin( Artifact skin ) + { + this.skin = skin; + } + + /** + *

Getter for the field skinModel.

+ * + * @return a {@link SkinModel} object. + */ + public SkinModel getSkinModel() + { + return skinModel; + } + + /** + *

Setter for the field skinModel.

+ * + * @param skinModel a {@link SkinModel} object. + */ + public void setSkinModel( SkinModel skinModel ) + { + this.skinModel = skinModel; + } + + /** + *

Setter for the field templateName.

+ * + * @param templateName a {@link java.lang.String} object. + */ + public void setTemplateName( String templateName ) + { + this.templateName = templateName; + } + + /** + *

Setter for the field usingDefaultTemplate.

+ * + * @param usingDefaultTemplate a boolean. + */ + public void setUsingDefaultTemplate( boolean usingDefaultTemplate ) + { + this.usingDefaultTemplate = usingDefaultTemplate; + } + + /** + *

isUsingDefaultTemplate.

+ * + * @return a boolean. + */ + public boolean isUsingDefaultTemplate() + { + return usingDefaultTemplate; + } + + /** + * Add a site directory, expected to have a Doxia Site layout, ie one directory per Doxia parser module containing + * files with parser extension. Typical values are src/site or target/generated-site. + * + * @param siteDirectory a {@link java.io.File} object. + */ + public void addSiteDirectory( File siteDirectory ) + { + this.siteDirectories.add( siteDirectory ); + } + + /** + * Add a extra-module source directory: used for Maven 1.x ${basedir}/xdocs layout, which contains + * xdoc and fml. + * + * @param moduleBasedir The base directory for module's source files. + * @param moduleParserId module's Doxia parser id. + */ + public void addModuleDirectory( File moduleBasedir, String moduleParserId ) + { + this.modules.add( new ExtraDoxiaModuleReference( moduleParserId, moduleBasedir ) ); + } + + /** + *

Getter for the field siteDirectories.

+ * + * @return List of site directories files. + */ + public List getSiteDirectories() + { + return siteDirectories; + } + + /** + *

Getter for the field modules.

+ * + * @return a {@link java.util.List} object. + */ + public List getModules() + { + return modules; + } + + /** + *

Getter for the field moduleExcludes.

+ * + * @return a map defining exclude patterns (comma separated) by parser id. + */ + public Map getModuleExcludes() + { + return moduleExcludes; + } + + /** + *

Setter for the field moduleExcludes.

+ * + * @param moduleExcludes a {@link java.util.Map} object. + */ + public void setModuleExcludes( Map moduleExcludes ) + { + this.moduleExcludes = moduleExcludes; + } + + /** + *

Getter for the field inputEncoding.

+ * + * @return a {@link java.lang.String} object. + */ + public String getInputEncoding() + { + return inputEncoding; + } + + /** + *

Setter for the field inputEncoding.

+ * + * @param inputEncoding a {@link java.lang.String} object. + */ + public void setInputEncoding( String inputEncoding ) + { + this.inputEncoding = inputEncoding; + } + + /** + *

Getter for the field outputEncoding.

+ * + * @return a {@link java.lang.String} object. + */ + public String getOutputEncoding() + { + return outputEncoding; + } + + /** + *

Setter for the field outputEncoding.

+ * + * @param outputEncoding a {@link java.lang.String} object. + */ + public void setOutputEncoding( String outputEncoding ) + { + this.outputEncoding = outputEncoding; + } + + /** + *

If you want to specify a specific publish date instead of the current date.

+ * + * @return the publish date, can be {@code null} + */ + public Date getPublishDate() + { + return publishDate; + } + + /** + *

Specify a specific publish date instead of the current date.

+ * + * @param publishDate the publish date + */ + public void setPublishDate( Date publishDate ) + { + this.publishDate = publishDate; + } + + /** + * Directory where to save content after Velocity processing (*.vm), but before parsing it with Doxia. + * + * @return not null if the documents are to be saved + * @since 1.7 + */ + public File getProcessedContentOutput() + { + return processedContentOutput; + } + + /** + * Where to (eventually) save content after Velocity processing (*.vm), but before parsing it with + * Doxia? + * + * @param processedContentOutput not null if the documents are to be saved + * @since 1.7 + */ + public void setProcessedContentOutput( File processedContentOutput ) + { + this.processedContentOutput = processedContentOutput; + } + + /** + * Root directory, to calculate relative path to every site directories. + * Corresponds to the pom.xml directory for Maven build. + * + * @return the root directory + * @since 1.8 + */ + public File getRootDirectory() + { + return rootDirectory; + } + + /** + * Set the root directory. + * + * @param rootDirectory + * @since 1.8 + */ + public void setRootDirectory( File rootDirectory ) + { + this.rootDirectory = rootDirectory; + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/SkinResourceLoader.java b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/SkinResourceLoader.java new file mode 100644 index 000000000..e8870de15 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/SkinResourceLoader.java @@ -0,0 +1,125 @@ +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.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.velocity.runtime.resource.Resource; +import org.apache.velocity.runtime.resource.loader.ResourceLoader; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.commons.collections.ExtendedProperties; +import org.codehaus.plexus.util.IOUtil; + +/** + * Skin resource loader: gets content from context classloader, which should contain skin artifact, + * and normalizes newlines + * (see DOXIASITETOOLS-87). + * + * @author Hervé Boutemy + */ +@Deprecated +public class SkinResourceLoader + extends ResourceLoader +{ + public void init( ExtendedProperties configuration ) + { + } + + public synchronized InputStream getResourceStream( String name ) + throws ResourceNotFoundException + { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + + if ( name.startsWith( "/" ) ) + { + name = name.substring( 1 ); + } + + return normalizeNewline( classLoader.getResourceAsStream( name ) ); + } + + InputStream normalizeNewline( InputStream in ) + throws ResourceNotFoundException + { + if ( in == null ) + { + return null; + } + + try + { + byte[] content = IOUtil.toByteArray( in ); + + // following code based on org.apache.maven.doxia.sink.AbstractSink.unifyEOLs(String) + + byte[] eol = System.getProperty( "line.separator" ).getBytes(); + + final int size = content.length; + + ByteArrayOutputStream out = new ByteArrayOutputStream( size ); + + for ( int i = 0; i < size; i++ ) + { + byte b = content[i]; + + if ( b == '\r' ) + { + if ( ( i + 1 ) < size && content[i + 1] == '\n' ) + { + i++; + } + + out.write( eol ); + } + else if ( b == '\n' ) + { + out.write( eol ); + } + else + { + out.write( b ); + } + } + + return new ByteArrayInputStream( out.toByteArray() ); + } + catch ( IOException ioe ) + { + throw new ResourceNotFoundException( "cannot read resource", ioe ); + } + finally + { + IOUtil.close( in ); + } + } + + public boolean isSourceModified( Resource resource ) + { + return false; + } + + public long getLastModified( Resource resource ) + { + return 0; + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/sink/SiteRendererSink.java b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/sink/SiteRendererSink.java new file mode 100644 index 000000000..91a349b10 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/sink/SiteRendererSink.java @@ -0,0 +1,353 @@ +package org.apache.maven.doxia.siterenderer.sink; + +/* + * 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.StringWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.swing.text.html.HTML.Attribute; + +import org.apache.maven.doxia.markup.HtmlMarkup; +import org.apache.maven.doxia.module.xhtml5.Xhtml5Sink; +import org.apache.maven.doxia.sink.Sink; +import org.apache.maven.doxia.sink.SinkEventAttributes; +import org.apache.maven.doxia.siterenderer.DocumentContent; +import org.apache.maven.doxia.siterenderer.RenderingContext; +import org.apache.maven.doxia.util.HtmlTools; +import org.codehaus.plexus.util.StringUtils; + +/** + * Sink for site rendering of a document, to allow later merge document's output with a template. + * During raw Doxia rendering, content is stored in multiple fields for later use when incorporating + * into skin or template: title, date, authors, head, body + * + * @author Emmanuel Venisse + */ +@SuppressWarnings( "checkstyle:methodname" ) +public class SiteRendererSink + extends Xhtml5Sink + implements Sink, org.codehaus.doxia.sink.Sink, DocumentContent +{ + private String date = ""; + + private String title = ""; + + private List authors = new ArrayList(); + + private final StringWriter headWriter; + + private StringBuilder sectionTitleBuffer; + + private StringBuilder sectionTitleWriteBuffer; + + private boolean sectionHasID; + + private boolean isSectionTitle; + + private Set anchorsInSectionTitle; + + private final Writer writer; + + private RenderingContext renderingContext; + + /** + * Construct a new SiteRendererSink for a document. + * + * @param renderingContext the document's RenderingContext. + */ + public SiteRendererSink( RenderingContext renderingContext ) + { + this( new StringWriter(), renderingContext ); + } + + /** + * Construct a new SiteRendererSink for a document. + * + * @param writer the writer for the sink. + * @param renderingContext the document's RenderingContext. + */ + private SiteRendererSink( StringWriter writer, RenderingContext renderingContext ) + { + super( writer ); + + this.writer = writer; + this.headWriter = new StringWriter(); + this.renderingContext = renderingContext; + + /* the template is expected to have used the main tag, which can be used only once */ + super.contentStack.push( HtmlMarkup.MAIN ); + } + + /** {@inheritDoc} */ + @Override + public void title_() + { + if ( getTextBuffer().length() > 0 ) + { + title = getTextBuffer().toString(); + } + + resetTextBuffer(); + } + + /** + * {@inheritDoc} + * + * Reset text buffer, since text content before title mustn't be in title. + * @see org.apache.maven.doxia.module.xhtml5.Xhtml5Sink#title() + */ + @Override + public void title() + { + resetTextBuffer(); + } + + /** {@inheritDoc} */ + @Override + public void author() + { + resetTextBuffer(); + } + + /** {@inheritDoc} */ + @Override + public void author_() + { + if ( getTextBuffer().length() > 0 ) + { + String text = HtmlTools.escapeHTML( getTextBuffer().toString() ); + text = StringUtils.replace( text, "&#", "&#" ); + authors.add( text.trim() ); + } + + resetTextBuffer(); + } + + /** {@inheritDoc} */ + @Override + public void date() + { + resetTextBuffer(); + } + + /** {@inheritDoc} */ + @Override + public void date_() + { + if ( getTextBuffer().length() > 0 ) + { + date = getTextBuffer().toString().trim(); + } + + resetTextBuffer(); + } + + /** + * {@inheritDoc} + * + * Do nothing. + * @see org.apache.maven.doxia.module.xhtml5.Xhtml5Sink#body_() + */ + @Override + public void body_() + { + // nop + } + + /** + * {@inheritDoc} + * + * Do nothing. + * @see org.apache.maven.doxia.module.xhtml5.Xhtml5Sink#body() + */ + @Override + public void body() + { + // nop + } + + /** {@inheritDoc} */ + @Override + public void head_() + { + setHeadFlag( false ); + } + + /** {@inheritDoc} */ + @Override + public void head() + { + setHeadFlag( true ); + } + + /** {@inheritDoc} */ + @Override + public void anchor( String name, SinkEventAttributes attributes ) + { + super.anchor( name, attributes ); + if ( isSectionTitle ) + { + if ( anchorsInSectionTitle == null ) + { + anchorsInSectionTitle = new HashSet(); + } + anchorsInSectionTitle.add( name ); + } + } + + /** {@inheritDoc} */ + @Override + protected void onSectionTitle( int depth, SinkEventAttributes attributes ) + { + sectionHasID = ( attributes != null && attributes.isDefined ( Attribute.ID.toString() ) ); + isSectionTitle = true; + + super.onSectionTitle( depth, attributes ); + + this.sectionTitleBuffer = new StringBuilder(); + this.sectionTitleWriteBuffer = new StringBuilder(); + } + + /** {@inheritDoc} */ + @Override + protected void onSectionTitle_( int depth ) + { + String sectionTitle = sectionTitleBuffer.toString(); + this.sectionTitleBuffer = null; + String sectionWriteTitle = sectionTitleWriteBuffer.toString(); + this.sectionTitleWriteBuffer = null; + + if ( !StringUtils.isEmpty( sectionTitle ) ) + { + if ( sectionHasID ) + { + sectionHasID = false; + } + else + { + String id = HtmlTools.encodeId( sectionTitle ); + if ( ( anchorsInSectionTitle == null ) || ( !anchorsInSectionTitle.contains( id ) ) ) + { + anchor( id ); + anchor_(); + } + } + } + + super.write( sectionWriteTitle ); + + this.isSectionTitle = false; + anchorsInSectionTitle = null; + super.onSectionTitle_( depth ); + } + + /** {@inheritDoc} */ + @Override + public void text( String text ) + { + if ( sectionTitleBuffer != null ) + { + // this implies we're inside a section title, collect text events for anchor generation + sectionTitleBuffer.append( text ); + } + + super.text( text ); + } + + /** {@inheritDoc} */ + @Override + protected void write( String text ) + { + String txt = text; + + if ( isHeadFlag() ) + { + headWriter.write( unifyEOLs( txt ) ); + + return; + } + + if ( renderingContext != null ) + { + String relativePathToBasedir = renderingContext.getRelativePath(); + + if ( relativePathToBasedir == null ) + { + txt = StringUtils.replace( txt, "$relativePath", "." ); + } + else + { + txt = StringUtils.replace( txt, "$relativePath", relativePathToBasedir ); + } + } + + if ( sectionTitleWriteBuffer != null ) + { + // this implies we're inside a section title, collect text events for anchor generation + sectionTitleWriteBuffer.append( txt ); + } + else + { + super.write( txt ); + } + } + + // DocumentContent interface + + /** {@inheritDoc} */ + public String getTitle() + { + return title; + } + + /** {@inheritDoc} */ + public List getAuthors() + { + return authors; + } + + /** {@inheritDoc} */ + public String getDate() + { + return date; + } + + /** {@inheritDoc} */ + public String getBody() + { + return writer.toString(); + } + + /** {@inheritDoc} */ + public String getHead() + { + return headWriter.toString(); + } + + /** {@inheritDoc} */ + public RenderingContext getRenderingContext() + { + return renderingContext; + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/css/maven-base.css b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/css/maven-base.css new file mode 100644 index 000000000..322efae73 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/css/maven-base.css @@ -0,0 +1,168 @@ +/* + * 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. + */ + +body { + margin: 0px; + padding: 0px; +} +table { + padding:0px; + width: 100%; + margin-left: -2px; + margin-right: -2px; +} +acronym { + cursor: help; + border-bottom: 1px dotted #feb; +} +table.bodyTable th, table.bodyTable td { + padding: 2px 4px 2px 4px; + vertical-align: top; +} +div.clear{ + clear:both; + visibility: hidden; +} +div.clear hr{ + display: none; +} +#bannerLeft, #bannerRight { + font-size: xx-large; + font-weight: bold; +} +#bannerLeft img, #bannerRight img { + margin: 0px; +} +.xleft, #bannerLeft img { + float:left; +} +.xright, #bannerRight { + float:right; +} +#banner { + padding: 0px; +} +#breadcrumbs { + padding: 3px 10px 3px 10px; +} +#leftColumn { + width: 170px; + float:left; + overflow: auto; +} +#bodyColumn { + margin-right: 1.5em; + margin-left: 197px; +} +#legend { + padding: 8px 0 8px 0; +} +#navcolumn { + padding: 8px 4px 0 8px; +} +#navcolumn h5 { + margin: 0; + padding: 0; + font-size: small; +} +#navcolumn ul { + margin: 0; + padding: 0; + font-size: small; +} +#navcolumn li { + list-style-type: none; + background-image: none; + background-repeat: no-repeat; + background-position: 0 0.4em; + padding-left: 16px; + list-style-position: outside; + line-height: 1.2em; + font-size: smaller; +} +#navcolumn li.expanded { + background-image: url(../images/expanded.gif); +} +#navcolumn li.collapsed { + background-image: url(../images/collapsed.gif); +} +#navcolumn li.none { + text-indent: -1em; + margin-left: 1em; +} +#poweredBy { + text-align: center; +} +#navcolumn img { + margin-top: 10px; + margin-bottom: 3px; +} +#poweredBy img { + display:block; + margin: 20px 0 20px 17px; +} +#search img { + margin: 0px; + display: block; +} +#search #q, #search #btnG { + border: 1px solid #999; + margin-bottom:10px; +} +#search form { + margin: 0px; +} +#lastPublished { + font-size: x-small; +} +.navSection { + margin-bottom: 2px; + padding: 8px; +} +.navSectionHead { + font-weight: bold; + font-size: x-small; +} +.section { + padding: 4px; +} +#footer { + padding: 3px 10px 3px 10px; + font-size: x-small; +} +#breadcrumbs { + font-size: x-small; + margin: 0pt; +} +.source { + padding: 12px; + margin: 1em 7px 1em 7px; +} +.source pre { + margin: 0px; + padding: 0px; +} +#navcolumn img.imageLink, .imageLink { + padding-left: 0px; + padding-bottom: 0px; + padding-top: 0px; + padding-right: 2px; + border: 0px; + margin: 0px; +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/css/print.css b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/css/print.css new file mode 100644 index 000000000..18fcbad70 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/css/print.css @@ -0,0 +1,26 @@ +/* + * 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. + */ + +#banner, #footer, #leftcol, #breadcrumbs, .docs #toc, .docs .courtesylinks, #leftColumn, #navColumn { + display: none !important; +} +#bodyColumn, body.docs div.docs { + margin: 0 !important; + border: none !important +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/default-site-macros.vm b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/default-site-macros.vm new file mode 100644 index 000000000..3f55b30cd --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/default-site-macros.vm @@ -0,0 +1,494 @@ +## 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. +## +#macro ( link $href $name $target $img $position $alt $border $width $height $title ) +#**##set ( $linkTitle = ' title="' + $name + '"' ) +#**##if( $target ) +#* *##set ( $linkTarget = ' target="' + $target + '"' ) +#**##else +#* *##set ( $linkTarget = "" ) +#**##end +#**##if ( $decoration.isLink( $href ) ) +#* *##set ( $linkClass = ' class="externalLink"' ) +#**##else +#* *##set ( $linkClass = "" ) +#**##end +#**##if ( $img ) +#* *##if ( $position == "left" ) +#* *##image($img $alt $border $width $height $title)$name## +#* *##else +#* *#$name #image($img $alt $border $width $height $title)## +#* *##end +#**##else +#* *#$name## +#**##end +#end +## +#macro ( image $img $alt $border $width $height $title ) +#**##if( $img ) +#* *##if ( !$decoration.isLink( $img ) ) +#* *##set ( $imgSrc = $PathTool.calculateLink( $img, $relativePath ) ) +#* *##set ( $imgSrc = $imgSrc.replaceAll( '\\', '/' ) ) +#* *##set ( $imgSrc = ' src="' + $imgSrc + '"' ) +#* *##else +#* *##set ( $imgSrc = ' src="' + $img + '"' ) +#* *##end +#* *##if( $alt ) +#* *##set ( $imgAlt = ' alt="' + $alt + '"' ) +#* *##else +#* *##set ( $imgAlt = ' alt=""' ) +#* *##end +#* *##if( $border ) +#* *##set ( $imgBorder = ' border="' + $border + '"' ) +#* *##else +#* *##set ( $imgBorder = "" ) +#* *##end +#* *##if( $width ) +#* *##set ( $imgWidth = ' width="' + $width + '"' ) +#* *##else +#* *##set ( $imgWidth = "" ) +#* *##end +#* *##if( $height ) +#* *##set ( $imgHeight = ' height="' + $height + '"' ) +#* *##else +#* *##set ( $imgHeight = "" ) +#* *##end +#* *##if( $title ) +#* *##set ( $imgTitle = ' title="' + $title + '"' ) +#* *##else +#* *##set ( $imgTitle = "" ) +#* *##end +#* *### +#**##end +#end +## +#macro ( banner $banner $id ) +#**##if ( $banner ) +#* *##if( $banner.href ) +#* *##set ( $hrf = $banner.href ) +#* *##if ( !$decoration.isLink( $hrf ) ) +#* *##set ( $hrf = $PathTool.calculateLink( $hrf, $relativePath ) ) +#* *##set ( $hrf = $hrf.replaceAll( '\\', '/' ) ) +#* *##if ( ( $hrf == '' ) ) +#* *##set ( $hrf = './' ) +#* *##end +#* *##end +#* *### +#* *##else +#* *# +#* *##end +#**##end +#end +## +#macro ( links $links ) +#**##set ( $counter = 0 ) +#**##foreach( $item in $links ) +#* *##set ( $counter = $counter + 1 ) +#* *##set ( $currentItemHref = $PathTool.calculateLink( $item.href, $relativePath ) ) +#* *##set ( $currentItemHref = $currentItemHref.replaceAll( '\\', '/' ) ) +#* *##link( $currentItemHref $item.name $item.target $item.img $item.position $item.alt $item.border $item.width $item.height $item.title ) +#* *##if ( $links.size() > $counter ) +#* *# | +#* *##end +#**##end +#end +## +#macro ( breadcrumbs $breadcrumbs ) +#**##foreach( $item in $breadcrumbs ) +#* *##set ( $currentItemHref = $PathTool.calculateLink( $item.href, $relativePath ) ) +#* *##set ( $currentItemHref = $currentItemHref.replaceAll( '\\', '/' ) ) +#* *##if ( ( $currentItemHref == '' ) ) +#* *##set ( $currentItemHref = './' ) +#* *##end +## +#* *##link( $currentItemHref $item.name $item.target $item.img $item.position $item.alt $item.border $item.width $item.height $item.title ) +#**# > +#**##end +#**#$shortTitle +#**##if( $decoration.edit && $docRenderingContext.editable ) +#* *# [edit] +#**##end +#end +## +#macro ( displayTree $display $item ) +#**##if ( $item && $item.items && $item.items.size() > 0 ) +#* *##foreach( $subitem in $item.items ) +#* *##set ( $subitemHref = $PathTool.calculateLink( $subitem.href, $relativePath ) ) +#* *##set ( $subitemHref = $subitemHref.replaceAll( '\\', '/' ) ) +## +#* *##if ( $alignedFileName == $subitemHref ) +#* *##set ( $display = true ) +#* *##end +## +#* *##displayTree( $display $subitem ) +#* *##end +#**##end +#end +## +#macro ( menuItem $item $indent ) +#**##set ( $collapseClass = "none" ) +#**##set ( $currentItemHref = $PathTool.calculateLink( $item.href, $relativePath ) ) +#**##set ( $currentItemHref = $currentItemHref.replaceAll( '\\', '/' ) ) +## +#**##if ( $item && $item.items && $item.items.size() > 0 ) +#* *##if ( $item.collapse == false ) +#* *##set ( $collapseClass = "expanded" ) +#* *##else +#* *### By default collapsed +#* *##set ( $collapseClass = "collapsed" ) +#* *##end +## +#* *##set ( $display = false ) +#* *##displayTree( $display $item ) +## +#* *##if ( $alignedFileName == $currentItemHref || $display ) +#* *##set ( $collapseClass = "expanded" ) +#* *##end +#**##end +$indent
  • ## +#**##if ( $item.img ) +#* *##if ( $item.position == "left" ) +#* *##if ( $alignedFileName == $currentItemHref ) +#* *##image($item.img $item.alt $item.border $item.width $item.height $item.title) $item.name +#* *##else +#* *##link($currentItemHref $item.name $item.target $item.img $item.position $item.alt $item.border $item.width $item.height $item.title) +#* *##end +#* *##else +#* *##if ( $alignedFileName == $currentItemHref ) +#* *#$item.name #image($item.img $item.alt $item.border $item.width $item.height $item.title) +#* *##else +#* *##link($currentItemHref $item.name $item.target $item.img $item.position $item.alt $item.border $item.width $item.height $item.title) +#* *##end +#* *##end +#**##else +#* *##if ( $alignedFileName == $currentItemHref ) +#* *#$item.name## +#* *##else +#* *##link( $currentItemHref $item.name $item.target $item.img $item.position $item.alt $item.border $item.width $item.height $item.title ) +#* *##end +#**##end +#**##if ( $item && $item.items && $item.items.size() > 0 ) +#* *##if ( $collapseClass == "expanded" ) + +$indent
      +#* *##foreach( $subitem in $item.items ) +#* *##menuItem( $subitem "$indent " ) +#* *##end +$indent
    ## +#* *##end +#**##end +#**#
  • +#end +## +#macro ( mainMenu $menus ) +#**##foreach( $menu in $menus ) +#* *##if ( $menu.name ) +#* *##if ( $menu.img ) +#* *##if( $menu.position ) +#* *##set ( $position = $menu.position ) +#* *##else +#* *##set ( $position = "left" ) +#* *##end +## +#* *##if ( !$decoration.isLink( $menu.img ) ) +#* *##set ( $src = $PathTool.calculateLink( $menu.img, $relativePath ) ) +#* *##set ( $src = $src.replaceAll( '\\', '/' ) ) +#* *##set ( $src = ' src="' + $src + '"' ) +#* *##else +#* *##set ( $src = ' src="' + $menu.img + '"' ) +#* *##end +## +#* *##if( $menu.alt ) +#* *##set ( $alt = ' alt="' + $menu.alt + '"' ) +#* *##else +#* *##set ( $alt = ' alt="' + $menu.name + '"' ) +#* *##end +## +#* *##if( $menu.border ) +#* *##set ( $border = ' border="' + $menu.border + '"' ) +#* *##else +#* *##set ( $border = ' border="0"' ) +#* *##end +## +#* *##if( $menu.width ) +#* *##set ( $width = ' width="' + $menu.width + '"' ) +#* *##else +#* *##set ( $width = "" ) +#* *##end +#* *##if( $menu.height ) +#* *##set ( $height = ' height="' + $menu.height + '"' ) +#* *##else +#* *##set ( $height = "" ) +#* *##end +#* *##if( $menu.title ) +#* *##set ( $title = ' title="' + $menu.title + '"' ) +#* *##else +#* *##set ( $title = "" ) +#* *##end +## +#* *##set ( $img = '" ) +## +#* *##if ( $position == "left" ) +
    $img $menu.name
    +#* *##else +
    $menu.name $img
    +#* *##end +#* *##else +
    $menu.name
    +#* *##end +#* *##end +#* *##if ( $menu.items && $menu.items.size() > 0 ) +
      +#* *##foreach( $item in $menu.items ) +#* *##menuItem( $item '' ) +#* *##end +
    +#* *##end +#**##end +#end +## +#macro ( copyright ) +#**##if ( $project ) +#* *##if ( ${project.organization} && ${project.organization.name} ) +#* *##set ( $period = "" ) +#* *##else +#* *##set ( $period = "." ) +#* *##end +## +#* *##set ( $currentYear = ${currentDate.year} + 1900 ) +## +#* *##if ( ${project.inceptionYear} && ( ${project.inceptionYear} != ${currentYear.toString()} ) ) + ${project.inceptionYear}–${currentYear}${period}## +#* *##else + ${currentYear}${period}## +#* *##end +## +#* *##if ( ${project.organization} ) +#* *##if ( ${project.organization.name} && ${project.organization.url} ) +#* *#${project.organization.name}. +#* *##elseif ( ${project.organization.name} ) +#* *#${project.organization.name}. +#* *##end +#* *##end +#**##end +#end +## +#macro ( publishDate $position $decorationPublishDate $version ) +#**##if ( $publishDate ) +#* *##set ( $dateValue = $dateFormat.format( $publishDate ) ) +#**##elseif ( $decoration.custom.getChild( 'publishDate' ) ) +#* *##set ( $dateValue = $decoration.custom.getChild( 'publishDate' ).getValue() ) +#**##else +#* *##set ( $dateValue = $dateFormat.format( $currentDate ) ) +#**##end +## +#**##set ( $datePosition = $decorationPublishDate.position ) +#**##set ( $versionPosition = $version.position ) +## +#**##set ( $breadcrumbs = $decoration.body.breadcrumbs ) +#**##set ( $links = $decoration.body.links ) +## +#**##if ( $datePosition.equalsIgnoreCase( "right" ) && $links && $links.size() > 0 ) +#* *##set ( $prefix = " |" ) +#**##else +#* *##set ( $prefix = "" ) +#**##end +## +#**##if ( $datePosition.equalsIgnoreCase( $position ) ) +#* *##if ( ( $datePosition.equalsIgnoreCase( "right" ) ) || ( $datePosition.equalsIgnoreCase( "bottom" ) ) ) + $prefix $i18n.getString( "site-renderer", $locale, "template.lastpublished" ): $dateValue +#* *##if ( $versionPosition.equalsIgnoreCase( $position ) ) +  | $i18n.getString( "site-renderer", $locale, "template.version" ): ${project.version} +#* *##end +#* *##elseif ( ( $datePosition.equalsIgnoreCase( "navigation-bottom" ) ) || ( $datePosition.equalsIgnoreCase( "navigation-top" ) ) ) +
    + $i18n.getString( "site-renderer", $locale, "template.lastpublished" ): $dateValue +#* *##if ( $versionPosition.equalsIgnoreCase( $position ) ) +  | $i18n.getString( "site-renderer", $locale, "template.version" ): ${project.version} +#* *##end +
    +#* *##elseif ( $datePosition.equalsIgnoreCase( "left" ) ) +
    + $i18n.getString( "site-renderer", $locale, "template.lastpublished" ): $dateValue +#* *##if ( $versionPosition.equalsIgnoreCase( $position ) ) +  | $i18n.getString( "site-renderer", $locale, "template.version" ): ${project.version} +#* *##end +#* *##if ( $breadcrumbs && $breadcrumbs.size() > 0 ) + | #breadcrumbs( $breadcrumbs ) +#* *##end +
    +#* *##end +#**##elseif ( $versionPosition.equalsIgnoreCase( $position ) ) +#* *##if ( ( $versionPosition.equalsIgnoreCase( "right" ) ) || ( $versionPosition.equalsIgnoreCase( "bottom" ) ) ) + $prefix $i18n.getString( "site-renderer", $locale, "template.version" ): ${project.version} +#* *##elseif ( ( $versionPosition.equalsIgnoreCase( "navigation-bottom" ) ) || ( $versionPosition.equalsIgnoreCase( "navigation-top" ) ) ) +
    + $i18n.getString( "site-renderer", $locale, "template.version" ): ${project.version} +
    +#* *##elseif ( $versionPosition.equalsIgnoreCase( "left" ) ) +
    + $i18n.getString( "site-renderer", $locale, "template.version" ): ${project.version} +#* *##if ( $breadcrumbs && $breadcrumbs.size() > 0 ) + | #breadcrumbs( $breadcrumbs ) +#* *##end +
    +#* *##end +#**##elseif ( $position.equalsIgnoreCase( "left" ) ) +#* *##if ( $breadcrumbs && $breadcrumbs.size() > 0 ) +
    +#* *##breadcrumbs( $breadcrumbs ) +
    +#* *##end +#**##end +#end +## +#macro ( poweredByLogo $poweredBy ) +#**##if( $poweredBy ) +#* *##foreach ($item in $poweredBy) +#* *##if( $item.href ) +#* *##set ( $href = $PathTool.calculateLink( $item.href, $relativePath ) ) +#* *##set ( $href = $href.replaceAll( '\\', '/' ) ) +#* *##else +#* *##set ( $href="https://maven.apache.org/" ) +#* *##end +## +#* *##if( $item.name ) +#* *##set ( $name = $item.name ) +#* *##else +#* *##set ( $name = $i18n.getString( "site-renderer", $locale, "template.builtby" ) ) +#* *##set ( $name = "${name} Maven" ) +#* *##end +## +#* *##if( $item.img ) +#* *##set ( $img = $item.img ) +#* *##else +#* *##set ( $img = "images/logos/maven-feather.png" ) +#* *##end +## +#* *##if ( !$decoration.isLink( $img ) ) +#* *##set ( $img = $PathTool.calculateLink( $img, $relativePath ) ) +#* *##set ( $img = $img.replaceAll( '\\', '/' ) ) +#* *##end +## +#* *##if( $item.alt ) +#* *##set ( $alt = ' alt="' + $item.alt + '"' ) +#* *##else +#* *##set ( $alt = ' alt="' + $name + '"' ) +#* *##end +## +#* *##if( $item.border ) +#* *##set ( $border = ' border="' + $item.border + '"' ) +#* *##else +#* *##set ( $border = "" ) +#* *##end +## +#* *##if( $item.width ) +#* *##set ( $width = ' width="' + $item.width + '"' ) +#* *##else +#* *##set ( $width = "" ) +#* *##end +#* *##if( $item.height ) +#* *##set ( $height = ' height="' + $item.height + '"' ) +#* *##else +#* *##set ( $height = "" ) +#* *##end +#* *##if( $item.title ) +#* *##set ( $title = ' title="' + $item.title + '"' ) +#* *##else +#* *##set ( $title = "" ) +#* *##end +## + + + +#* *##end +#* *##if( $poweredBy.isEmpty() ) + + $i18n.getString( + +#* *##end +#**##else + + $i18n.getString( + +#**##end +#end +## +#macro ( googleAnalytics $accountId ) +#**##if( $accountId && $accountId != "" ) + + +#**##end +#end +## +#macro( generatedBy ) +Apache Maven Doxia Site Renderer#if( $doxiaSiteRendererVersion ) $doxiaSiteRendererVersion#end## +#end +## \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/default-site.vm b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/default-site.vm new file mode 100644 index 000000000..6f5e76d2c --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/default-site.vm @@ -0,0 +1,100 @@ + +## 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. +## +#parse( "default-site-macros.vm" ) + +## + + + + +## put meta together + +#foreach( $author in $authors ) + +#end +#if ( $documentDate ) + +#end + $title + + + + +#**##if( $decoration.body.head ) +#* *#$render.eval( $decoration.body.head ) +#**##end +#**##if( $headContent )$headContent#end +#**##googleAnalytics( $decoration.googleAnalyticsAccountId ) + + + + +
    + +
    +
    +
    +#* *#$bodyContent +
    +
    +
    +
    +
    + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/images/collapsed.gif b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/images/collapsed.gif new file mode 100644 index 000000000..6e7108406 Binary files /dev/null and b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/images/collapsed.gif differ diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/images/expanded.gif b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/images/expanded.gif new file mode 100644 index 000000000..0fef3d89e Binary files /dev/null and b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/images/expanded.gif differ diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/images/logos/build-by-maven-black.png b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/images/logos/build-by-maven-black.png new file mode 100644 index 000000000..919fd0f66 Binary files /dev/null and b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/images/logos/build-by-maven-black.png differ diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/images/logos/build-by-maven-white.png b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/images/logos/build-by-maven-white.png new file mode 100644 index 000000000..7d44c9c2e Binary files /dev/null and b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/images/logos/build-by-maven-white.png differ diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/images/logos/maven-feather.png b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/images/logos/maven-feather.png new file mode 100644 index 000000000..b5ada836e Binary files /dev/null and b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/images/logos/maven-feather.png differ diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/resources.txt b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/resources.txt new file mode 100644 index 000000000..0384226cc --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/resources.txt @@ -0,0 +1,24 @@ +# 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. + +images/expanded.gif +images/collapsed.gif +images/logos/maven-feather.png +images/logos/build-by-maven-white.png +images/logos/build-by-maven-black.png +css/maven-base.css +css/print.css diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer.properties b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer.properties new file mode 100644 index 000000000..3dc6ce342 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer.properties @@ -0,0 +1,20 @@ +# 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. + +template.lastpublished=Last Published +template.version=Version +template.builtby=Built by diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_de.properties b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_de.properties new file mode 100644 index 000000000..7620924ca --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_de.properties @@ -0,0 +1,19 @@ +# 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. + +template.lastpublished=Zuletzt ver\u00F6ffentlicht +template.builtby=Erstellt von diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_en.properties b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_en.properties new file mode 100644 index 000000000..0c479bc64 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_en.properties @@ -0,0 +1,23 @@ +# 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. + +# NOTE: +# This bundle is intentionally empty because English strings are provided by the base bundle via the parent chain. It +# must be provided nevertheless such that a request for locale "en" will not errorneously pick up the bundle for the +# JVM's default locale (which need not be "en"). See the method javadoc about +# ResourceBundle.getBundle(String, Locale, ClassLoader) +# for a full description of the lookup strategy. diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_es.properties b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_es.properties new file mode 100644 index 000000000..00c73d808 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_es.properties @@ -0,0 +1,20 @@ +# 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. + +template.lastpublished=Publicado el +template.version=Versión +template.builtby=Generado por diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_fr.properties b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_fr.properties new file mode 100644 index 000000000..5281316a2 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_fr.properties @@ -0,0 +1,19 @@ +# 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. + +template.lastpublished=Dernière publication +template.builtby=Produit par diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_it.properties b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_it.properties new file mode 100644 index 000000000..7eb73cdc9 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_it.properties @@ -0,0 +1,20 @@ +# 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. + +template.lastpublished=Ultima Pubblicazione +template.version=Versione +template.builtby=Costruito da diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_ja.properties b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_ja.properties new file mode 100644 index 000000000..3414d000a --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_ja.properties @@ -0,0 +1,20 @@ +# 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. + +template.lastpublished=\u6700\u7d42\u66f4\u65b0 +template.version=\u30d0\u30fc\u30b8\u30e7\u30f3 +template.builtby=Built by diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_nl.properties b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_nl.properties new file mode 100644 index 000000000..d77251cea --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_nl.properties @@ -0,0 +1,20 @@ +# 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. + +template.lastpublished=Meest recente publicatie +template.version=Versie +template.builtby=Gemaakt door diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_pl.properties b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_pl.properties new file mode 100644 index 000000000..d7f2e6a06 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_pl.properties @@ -0,0 +1,19 @@ +# 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. + +template.lastpublished=Ostatnio opublikowano +template.builtby=Skompilowane przez diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_pt_BR.properties b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_pt_BR.properties new file mode 100644 index 000000000..6dd269b26 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_pt_BR.properties @@ -0,0 +1,20 @@ +# 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. + +template.lastpublished=Última atualização +template.version=Versão +template.builtby=Construído por diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_sv.properties b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_sv.properties new file mode 100644 index 000000000..1a21ec17c --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_sv.properties @@ -0,0 +1,20 @@ +# 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. + +template.lastpublished=Senast publicerat +template.version=Version +template.builtby=Byggt av diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_zh_CN.properties b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_zh_CN.properties new file mode 100644 index 000000000..25aedebba --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/main/resources/site-renderer_zh_CN.properties @@ -0,0 +1,20 @@ +# 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. + +template.builtby = \u6784\u5EFA\u4F9D\u9760 +template.lastpublished = \u6700\u8FD1\u66F4\u65B0 +template.version = \u7248\u672C diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/site/apt/doxia-site-renderer.odg b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/site/apt/doxia-site-renderer.odg new file mode 100644 index 000000000..2a22fbfcf Binary files /dev/null and b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/site/apt/doxia-site-renderer.odg differ diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/site/apt/index.apt.vm b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/site/apt/index.apt.vm new file mode 100644 index 000000000..c8eb0ccf9 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/site/apt/index.apt.vm @@ -0,0 +1,193 @@ + ----- + Introduction + ----- + Hervé Boutemy + Dennis Lundberg + ------ + 2015-12-20 + ------ + +~~ 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. + +~~ NOTE: For help with the syntax of this file, see: +~~ http://maven.apache.org/doxia/references/apt-format.html + +Doxia Sitetools - Site Renderer + + The Site Renderer handles the rendering of sites, assembling a common site decoration template (also called ) + with a collection of documents. + + Documents can be dynamically generated with {{{/doxia/doxia/doxia-sink-api/}Doxia Sink API}}, like Maven reports, + or simply read from static files written in {{{/doxia/references/index.html}markup supported by Doxia Parsers}}, + eventually processed by {{{http://velocity.apache.org/engine/1.7/}Velocity}} + if their file names end in <<<.vm>>>. + +[doxia-site-renderer.png] + +* Doxia Site Skins + + A default site decoration template is included (see <<>>), but other decoration templates can be used at will, + either as a standalone template or packaged in a {{{../doxia-skin-model/}<> artifact}}. + + Maven team provides {{{/skins/}a collection of skins}} for projects use. + + Some documentation is available on {{{/plugins/maven-site-plugin/examples/creatingskins.html}how to create a new skin}}. + +* Velocity processing + + Site decoration and documents with file names ending in <<<.vm>>> are processed by Velocity. + + The Velocity context contains some variables related to rendering context that you can use: + +*---------------------------------+----------------------+-------------------------------+ +|| Variable || Type || Description || +*---------------------------------+----------------------+-------------------------------+ +| <<>> | <<>> | The file name of the (HTML) document being rendered, relative to the document being rendered. | +*---------------------------------+----------------------+-------------------------------+ +| <<>> | {{{../doxia-decoration-model/apidocs/org/apache/maven/doxia/site/decoration/DecorationModel.html}<<>>}} | This is a model that represents the data in your {{{../doxia-decoration-model/decoration.html}<<>>}}. | +*---------------------------------+----------------------+-------------------------------+ +| <<>> | <<>> | <>: use <<>> or <<>>. The date when the site is rendered. | +*---------------------------------+----------------------+-------------------------------+ +| <<>> | <<>> | The file name of the (HTML) document being rendered, relative to the site root. | +*---------------------------------+----------------------+-------------------------------+ +| <<>> | <<>> | <>: use <<>>, <<>>, or <<>>. An instance of the date format as defined in <<>> (default: An instance of the default date format for the locale of the document being rendered. | +*---------------------------------+----------------------+-------------------------------+ +| <<>> | <<>> | <>: same as <<>>. The date when the site is rendered, in the format "yyyyMMdd". | +*---------------------------------+----------------------+-------------------------------+ +| <<>> | <<>> | The version of the Doxia Site Renderer in use. | +*---------------------------------+----------------------+-------------------------------+ +| <<>> | <<>> | The locale for the document being rendered. | +*---------------------------------+----------------------+-------------------------------+ +| <<>> | <<>> | An optional hardcoded publish date that has been set programmatically. | +*---------------------------------+----------------------+-------------------------------+ +| <<>> | <<>> | The path to the site root from the document being rendered. | +*---------------------------------+----------------------+-------------------------------+ +| <<>> | <<>>> | The list of locales that the site will contain. | +*---------------------------------+----------------------+-------------------------------+ + + There are also some tools for general use: + +#set( $plexus = "http://codehaus-plexus.github.io/plexus-utils/apidocs/org/codehaus/plexus" ) +*---------------------------------+------------------------------------------------------+-------------------------------+ +|| Variable || Type || Description || +*---------------------------------+------------------------------------------------------+-------------------------------+ +| <<>> | {{{$plexus/util/FileUtils.html}<<>>}} | <> | +*---------------------------------+------------------------------------------------------+-------------------------------+ +| <<>> | {{{$plexus/i18n/I18N.html}<<>>}} | <>: use <<>>. | +*---------------------------------+------------------------------------------------------+-------------------------------+ +| <<>> | {{{$plexus/util/PathTool.html}<<>>}} | | +*---------------------------------+------------------------------------------------------+-------------------------------+ +| <<>> | {{{$plexus/util/StringUtils.html}<<>>}} | | +*---------------------------------+------------------------------------------------------+-------------------------------+ +| <<>> | {{{http://git.eclipse.org/c/sisu/org.eclipse.sisu.plexus.git/tree/org.eclipse.sisu.plexus/src/org/codehaus/plexus/PlexusContainer.java}<<>>}} | | +*---------------------------------+------------------------------------------------------+-------------------------------+ + + Additionally, there are {{{http://velocity.apache.org/tools/releases/2.0/generic.html} Velocity Generic Tools}} populated + with the site locale, the decoration model's date format, and site renderer's resource bundle: + +#set( $generic = "http://velocity.apache.org/tools/releases/2.0/javadoc/org/apache/velocity/tools/generic" ) +*------------------+----------------------------------------------------------+-------------------------------+ +|| Variable || Type || Description || +*------------------+----------------------------------------------------------+-------------------------------+ +| <<>> | {{{$generic/AlternatorTool.html}AlternatorTool}} | For creating alternators to easily alternate over a set of values. +*------------------+----------------------------------------------------------+-------------------------------+ +| <<>> | {{{$generic/ClassTool.html}ClassTool}} | For simplifying reflective lookup of information about classes and their fields, methods and constructors. +*------------------+----------------------------------------------------------+-------------------------------+ +| <<>> | {{{$generic/ContextTool.html}ContextTool}} | For convenient access to context data and metadata. +*------------------+----------------------------------------------------------+-------------------------------+ +| <<>> | {{{$generic/ConversionTool.html}ConversionTool}} | For converting String values to richer object Types. +*------------------+----------------------------------------------------------+-------------------------------+ +| <<>> | {{{$generic/ComparisonDateTool.html}ComparisonDateTool}} | For manipulating, formatting, and comparing dates. +*------------------+----------------------------------------------------------+-------------------------------+ +| <<>> | {{{$generic/DisplayTool.html}DisplayTool}} | For controlling display of references (e.g., truncating values, "pretty printing" lists, and displaying alternates when a reference is null). +*------------------+----------------------------------------------------------+-------------------------------+ +| <<>> | {{{$generic/EscapeTool.html}EscapeTool}} | For common escaping needs in Velocity templates (e.g. escaping html, xml, javascript etc.). +*------------------+----------------------------------------------------------+-------------------------------+ +| <<>> | {{{$generic/FieldTool.html}FieldTool}} | For (easy) access to static fields in a class, such as string constants. +*------------------+----------------------------------------------------------+-------------------------------+ +| <<>> | {{{$generic/LinkTool.html}LinkTool}} | For creating and manipulating URIs and URLs. The API for this tool is designed to closely resemble that of the VelocityView tool of the same name. +*------------------+----------------------------------------------------------+-------------------------------+ +| <<>> | {{{$generic/LoopTool.html}LoopTool}} | A convenience tool to use with \#foreach loops. It wraps a list with a custom iterator to provide greater control, allowing loops to end early, skip ahead and more. +*------------------+----------------------------------------------------------+-------------------------------+ +| <<>> | {{{$generic/MathTool.html}MathTool}} | For performing math functions. +*------------------+----------------------------------------------------------+-------------------------------+ +| <<>> | {{{$generic/NumberTool.html}NumberTool}} | For formatting and converting numbers. +*------------------+----------------------------------------------------------+-------------------------------+ +| <<>> | {{{$generic/RenderTool.html}RenderTool}} | To evaluate and render arbitrary strings of VTL, including recursive rendering. +*------------------+----------------------------------------------------------+-------------------------------+ +| <<>> | {{{$generic/ResourceTool.html}ResourceTool}} | For simplified access to resource bundles for internationalization or other dynamic content needs. +*------------------+----------------------------------------------------------+-------------------------------+ +| <<>> | {{{$generic/SortTool.html}SortTool}} | Used to sort collections (or arrays, iterators, etc) on any arbitary set of properties exposed by the objects contained within the collection. +*------------------+----------------------------------------------------------+-------------------------------+ +| <<>> | {{{$generic/XmlTool.html}XmlTool}} | For reading/navigating XML files. This uses dom4j under the covers and provides complete XPath support. +*------------------+----------------------------------------------------------+-------------------------------+ + + If you intend to use custom Velocity tools, add them to the Maven Site Plugin's dependency list and make sure + that they have a bundled configuration file in <<>>. + + See <<<{{{./xref/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.html\#L488}DefaultSiteRenderer.createToolManagedVelocityContext(...)}}>>> + source for more details and the {{{http://velocity.apache.org/tools/devel/summary.html}tools usage summary}}. + +** Maven Site Plugin + + When <<>> is used by <<>>, the following template properties are defined: + +*---------------------------------+----------------------+-------------------------------+ +|| Variable || Type || Description || +*---------------------------------+----------------------+-------------------------------+ +| <<>> | <<>> | | +*---------------------------------+----------------------+-------------------------------+ +| <<>> | <<>> | | +*---------------------------------+----------------------+-------------------------------+ +| <<>> | {{{/ref/current/maven-core/apidocs/org/apache/maven/project/MavenProject.html}<<>>}} | The current project. | +*---------------------------------+----------------------+-------------------------------+ +| | <<>> | Properties defined in POM are directly available. | +*---------------------------------+----------------------+-------------------------------+ + + See <<<{{{/plugins/maven-site-plugin/apidocs/org/apache/maven/plugins/site/render/AbstractSiteRenderingMojo.html\#createSiteRenderingContext(java.util.Locale)}AbstractSiteRenderingMojo.createSiteRenderingContext(...)}}>>> + source for more details. + +** Site Template + + When Velocity is processing the site template (be it from skin or local template), there are a few complementary variables + available that give access to information taken from document being merged into the template: + +*---------------------------------+----------------------+-------------------------------+ +|| Variable || Type || Description || +*---------------------------------+----------------------+-------------------------------+ +| <<>> | <<>>> | A list of authors from the source document. | +*---------------------------------+----------------------+-------------------------------+ +| <<>> | <<>> | HTML body content of the Doxia generated output. | +*---------------------------------+----------------------+-------------------------------+ +| <<>> | <<>> | <>: use <<>>. The date specified in the source document, in the format "yyyyMMdd". | +*---------------------------------+----------------------+-------------------------------+ +| <<>> | <<>> | <>: use <<>>. The date specified in the source document. | +*---------------------------------+----------------------+-------------------------------+ +| <<>> | <<>> | The date specified in the source document: semantics has to be chosen by document writer (document creation date, or document last modification date, or ...), and format is not enforced. | +*---------------------------------+----------------------+-------------------------------+ +| <<>> | <<>> | HTML head content of the Doxia generated output. | +*---------------------------------+----------------------+-------------------------------+ +| <<>> | <<>> | The title of the document, excluding the project or site name. | +*---------------------------------+----------------------+-------------------------------+ +| <<>> | <<>> | The title of the document, including the project or site name. | +*---------------------------------+----------------------+-------------------------------+ +| <<>> | {{{./apidocs/org/apache/maven/doxia/siterenderer/RenderingContext.html}<<>>}} | (since 1.8) The document rendering context. | +*---------------------------------+----------------------+-------------------------------+ + + See <<<{{{./xref/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.html\#L616}DefaultSiteRenderer.createSiteTemplateVelocityContext(...)}}>>> + source for more details. diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/site/resources/doxia-site-renderer.png b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/site/resources/doxia-site-renderer.png new file mode 100644 index 000000000..5c1eebf32 Binary files /dev/null and b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/site/resources/doxia-site-renderer.png differ diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/site/site.xml b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/site/site.xml new file mode 100644 index 000000000..ebe51fe63 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/site/site.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/AbstractVerifier.java b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/AbstractVerifier.java new file mode 100644 index 000000000..b52fd9924 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/AbstractVerifier.java @@ -0,0 +1,70 @@ +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 com.gargoylesoftware.htmlunit.WebClient; +import com.gargoylesoftware.htmlunit.html.HtmlPage; + +import java.io.File; + +import org.codehaus.plexus.PlexusTestCase; + +/** + * Abstract base class for verifiers. + * + * @author ltheussl + */ +public abstract class AbstractVerifier + extends PlexusTestCase +{ + /** + * Get a HtmlPage from a file. + * + * @param htmlFile the file to parse. + * + * @return a HtmlPage. + * + * @throws Exception if something goes wrong. + */ + protected HtmlPage htmlPage( String htmlFile ) + throws Exception + { + File file = getTestFile( htmlFile ); + assertNotNull( file ); + assertTrue( file.exists() ); + + // HtmlUnit + try ( WebClient webClient = new WebClient() ) { + webClient.getOptions().setCssEnabled( false ); + + return (HtmlPage) webClient.getPage( file.toURI().toURL() ); + } + } + + /** + * Verify a HtmlPage. + * + * @param file the file to verify. + * + * @throws java.lang.Exception if something goes wrong + */ + public abstract void verify( String file ) + throws Exception; +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/AptVerifier.java b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/AptVerifier.java new file mode 100644 index 000000000..b8369ae2f --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/AptVerifier.java @@ -0,0 +1,180 @@ +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 com.gargoylesoftware.htmlunit.html.HtmlAnchor; +import com.gargoylesoftware.htmlunit.html.HtmlBold; +import com.gargoylesoftware.htmlunit.html.HtmlCode; +import com.gargoylesoftware.htmlunit.html.HtmlDivision; +import com.gargoylesoftware.htmlunit.html.HtmlElement; +import com.gargoylesoftware.htmlunit.html.HtmlHeading2; +import com.gargoylesoftware.htmlunit.html.HtmlHeading3; +import com.gargoylesoftware.htmlunit.html.HtmlItalic; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import com.gargoylesoftware.htmlunit.html.HtmlParagraph; +import com.gargoylesoftware.htmlunit.html.HtmlSection; + +import java.util.Iterator; + + +/** + * Verifies apt transformations. + * + * @author ltheussl + */ +public class AptVerifier + extends AbstractVerifier +{ + /** {@inheritDoc} */ + public void verify( String file ) + throws Exception + { + HtmlPage page = htmlPage( file ); + assertNotNull( page ); + + HtmlElement element = page.getHtmlElementById( "contentBox" ); + assertNotNull( element ); + HtmlDivision division = (HtmlDivision) element; + assertNotNull( division ); + + Iterator elementIterator = division.getHtmlElementDescendants().iterator(); + + // ---------------------------------------------------------------------- + // + // ---------------------------------------------------------------------- + + HtmlSection section = (HtmlSection) elementIterator.next(); + + HtmlHeading2 h2 = (HtmlHeading2) elementIterator.next(); + assertNotNull( h2 ); + assertEquals( "Links", h2.asText().trim() ); + + HtmlAnchor a = (HtmlAnchor) elementIterator.next(); + assertEquals( "Links", a.getAttribute( "name" ) ); + + HtmlParagraph p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + + // Expected log: [APT Parser] Ambiguous link: 'cdc.html'. If this is a local link, prepend "./"! + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "Anchor", a.getAttribute( "name" ) ); + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "cdc.html", a.getAttribute( "name" ) ); + + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "#Anchor", a.getAttribute( "href" ) ); + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "#Anchor", a.getAttribute( "href" ) ); + + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "Anchor_with_space", a.getAttribute( "name" ) ); + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "#Anchor_with_space", a.getAttribute( "href" ) ); + + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "http://maven.apache.org/", a.getAttribute( "href" ) ); + assertEquals( "externalLink", a.getAttribute( "class" ) ); + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "http://maven.apache.org/", a.getAttribute( "href" ) ); + assertEquals( "externalLink", a.getAttribute( "class" ) ); + + // Expected log: [APT Parser] Ambiguous link: 'cdc.html'. If this is a local link, prepend "./"! + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "./cdc.html", a.getAttribute( "href" ) ); + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "#cdc.html", a.getAttribute( "href" ) ); + + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "/index.html", a.getAttribute( "href" ) ); + + section = (HtmlSection) elementIterator.next(); + assertNotNull( section ); + + h2 = (HtmlHeading2) elementIterator.next(); + assertNotNull( h2 ); + // Note: htmlunit strips the white space, actual result is ok + assertEquals( "Section formatting: italic bold mono", h2.asText().trim() ); + + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "Section_formatting:_italic_bold_mono", a.getAttribute( "name" ) ); + + HtmlItalic italic = (HtmlItalic) elementIterator.next(); + assertEquals( "i", italic.getTagName() ); + assertEquals( "italic", italic.asText().trim() ); + + HtmlBold bold = (HtmlBold) elementIterator.next(); + assertEquals( "b", bold.getTagName() ); + assertEquals( "bold", bold.asText().trim() ); + + HtmlCode code = (HtmlCode) elementIterator.next(); + assertEquals( "code", code.getTagName() ); + assertEquals( "mono", code.asText().trim() ); + + section = (HtmlSection) elementIterator.next(); + assertNotNull( section ); + + HtmlHeading3 h3 = (HtmlHeading3) elementIterator.next(); + assertNotNull( h3 ); + // Note: htmlunit strips the white space, actual result is ok + assertEquals( "SubSection formatting: italic bold mono", h3.asText().trim() ); + + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "SubSection_formatting:_italic_bold_mono", a.getAttribute( "name" ) ); + + italic = (HtmlItalic) elementIterator.next(); + assertEquals( "i", italic.getTagName() ); + assertEquals( "italic", italic.asText().trim() ); + + bold = (HtmlBold) elementIterator.next(); + assertEquals( "b", bold.getTagName() ); + assertEquals( "bold", bold.asText().trim() ); + + code = (HtmlCode) elementIterator.next(); + assertEquals( "code", code.getTagName() ); + assertEquals( "mono", code.asText().trim() ); + + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + + italic = (HtmlItalic) elementIterator.next(); + assertEquals( "i", italic.getTagName() ); + assertEquals( "italic", italic.asText().trim() ); + + bold = (HtmlBold) elementIterator.next(); + assertEquals( "b", bold.getTagName() ); + assertEquals( "bold", bold.asText().trim() ); + + code = (HtmlCode) elementIterator.next(); + assertEquals( "code", code.getTagName() ); + assertEquals( "mono", code.asText().trim() ); + + section = (HtmlSection) elementIterator.next(); + assertNotNull( section ); + + h2 = (HtmlHeading2) elementIterator.next(); + assertNotNull( h2 ); + assertEquals( "No Default Anchor in Section Title with Explicit Anchor", h2.asText().trim() ); + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "No_Default_Anchor_in_Section_Title_with_Explicit_Anchor", a.getAttribute( "name" ) ); + + section = (HtmlSection) elementIterator.next(); + assertNotNull( section ); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/AttributesVerifier.java b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/AttributesVerifier.java new file mode 100644 index 000000000..0865d9227 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/AttributesVerifier.java @@ -0,0 +1,246 @@ +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 com.gargoylesoftware.htmlunit.html.HtmlAnchor; +import com.gargoylesoftware.htmlunit.html.HtmlBold; +import com.gargoylesoftware.htmlunit.html.HtmlDivision; +import com.gargoylesoftware.htmlunit.html.HtmlElement; +import com.gargoylesoftware.htmlunit.html.HtmlHeading2; +import com.gargoylesoftware.htmlunit.html.HtmlHeading3; +import com.gargoylesoftware.htmlunit.html.HtmlImage; +import com.gargoylesoftware.htmlunit.html.HtmlItalic; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import com.gargoylesoftware.htmlunit.html.HtmlParagraph; +import com.gargoylesoftware.htmlunit.html.HtmlPreformattedText; +import com.gargoylesoftware.htmlunit.html.HtmlS; +import com.gargoylesoftware.htmlunit.html.HtmlSection; +import com.gargoylesoftware.htmlunit.html.HtmlSubscript; +import com.gargoylesoftware.htmlunit.html.HtmlSuperscript; +import com.gargoylesoftware.htmlunit.html.HtmlTable; +import com.gargoylesoftware.htmlunit.html.HtmlTableDataCell; +import com.gargoylesoftware.htmlunit.html.HtmlTableHeaderCell; +import com.gargoylesoftware.htmlunit.html.HtmlTableRow; +import com.gargoylesoftware.htmlunit.html.HtmlUnderlined; + +import java.util.Iterator; + + +/** + * + * + * @author ltheussl + */ +public class AttributesVerifier + extends AbstractVerifier +{ + /** {@inheritDoc} */ + public void verify( String file ) + throws Exception + { + HtmlPage page = htmlPage( file ); + assertNotNull( page ); + + HtmlElement element = page.getHtmlElementById( "contentBox" ); + assertNotNull( element ); + HtmlDivision division = (HtmlDivision) element; + assertNotNull( division ); + + Iterator elementIterator = division.getHtmlElementDescendants().iterator(); + + // ---------------------------------------------------------------------- + // + // ---------------------------------------------------------------------- + + HtmlSection section = (HtmlSection) elementIterator.next(); + + HtmlHeading2 h2 = (HtmlHeading2) elementIterator.next(); + assertNotNull( h2 ); + assertEquals( "section", h2.asText().trim() ); + + HtmlAnchor a = (HtmlAnchor) elementIterator.next(); + assertNotNull( a ); + assertEquals( "section", a.getAttribute( "name" ) ); + + HtmlParagraph p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + + assertEquals( "ID", p.getAttribute( "id" ) ); + assertEquals( "CLASS", p.getAttribute( "class" ) ); + assertEquals( "TITLE", p.getAttribute( "title" ) ); + assertEquals( "STYLE", p.getAttribute( "style" ) ); + assertEquals( "LANG", p.getAttribute( "lang" ) ); + + HtmlImage img = (HtmlImage) elementIterator.next(); + assertNotNull( img ); + + assertEquals( "project.png", img.getAttribute( "src" ) ); + assertEquals( "150", img.getAttribute( "width" ) ); + assertEquals( "93", img.getAttribute( "height" ) ); + assertEquals( "border: 1px solid silver", img.getAttribute( "style" ) ); + assertEquals( "Project", img.getAttribute( "alt" ) ); + + // test object identity to distinguish the case ATTRIBUTE_VALUE_EMPTY + assertSame( img.getAttribute( "dummy" ), HtmlElement.ATTRIBUTE_NOT_DEFINED ); + + HtmlTable table = (HtmlTable) elementIterator.next(); + assertEquals( "1", table.getAttribute( "border" ) ); + assertEquals( "none", table.getAttribute( "class" ) ); + + element = elementIterator.next(); + // this is a htmlunit bug + assertEquals( "tbody", element.getTagName() ); + + HtmlTableRow tr = (HtmlTableRow) elementIterator.next(); + HtmlTableHeaderCell th = (HtmlTableHeaderCell) elementIterator.next(); + + th = (HtmlTableHeaderCell) elementIterator.next(); + assertEquals( "center", th.getAttribute( "align" ) ); + assertEquals( "2", th.getAttribute( "colspan" ) ); + assertEquals( "50%", th.getAttribute( "width" ) ); + + tr = (HtmlTableRow) elementIterator.next(); + + th = (HtmlTableHeaderCell) elementIterator.next(); + assertEquals( "2", th.getAttribute( "rowspan" ) ); + assertEquals( "middle", th.getAttribute( "valign" ) ); + + HtmlTableDataCell td = (HtmlTableDataCell) elementIterator.next(); + td = (HtmlTableDataCell) elementIterator.next(); + tr = (HtmlTableRow) elementIterator.next(); + td = (HtmlTableDataCell) elementIterator.next(); + td = (HtmlTableDataCell) elementIterator.next(); + + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + + HtmlUnderlined u = (HtmlUnderlined) elementIterator.next(); + assertEquals( "u", u.getTagName() ); + HtmlS s = (HtmlS) elementIterator.next(); + assertEquals( "s", s.getTagName() ); + HtmlSubscript sub = (HtmlSubscript) elementIterator.next(); + assertEquals( "sub", sub.getTagName() ); + HtmlSuperscript sup = (HtmlSuperscript) elementIterator.next(); + assertEquals( "sup", sup.getTagName() ); + + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + + HtmlBold b = (HtmlBold) elementIterator.next(); + assertEquals( "b", b.getTagName() ); + HtmlItalic i = (HtmlItalic) elementIterator.next(); + assertEquals( "i", i.getTagName() ); + i = (HtmlItalic) elementIterator.next(); + assertEquals( "i", i.getTagName() ); + b = (HtmlBold) elementIterator.next(); + assertEquals( "b", b.getTagName() ); + + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "color: red; margin-left: 20px", p.getAttribute( "style" ) ); + + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "Anchor", a.getAttribute( "name" ) ); + + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "#Anchor", a.getAttribute( "href" ) ); + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "#Anchor", a.getAttribute( "href" ) ); + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "http://maven.apache.org/", a.getAttribute( "href" ) ); + assertEquals( "externalLink", a.getAttribute( "class" ) ); + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "./cdc.html", a.getAttribute( "href" ) ); + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "cdc.html", a.getAttribute( "href" ) ); + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "cdc.pdf", a.getAttribute( "href" ) ); + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "./cdc.txt", a.getAttribute( "href" ) ); + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "/index.html", a.getAttribute( "href" ) ); + + HtmlDivision div = (HtmlDivision) elementIterator.next(); + assertEquals( "source", div.getAttribute( "class" ) ); + HtmlPreformattedText pre = (HtmlPreformattedText) elementIterator.next(); + assertEquals( "pretty", pre.getAttribute( "class" ) ); + + div = (HtmlDivision) elementIterator.next(); + assertEquals( "source", div.getAttribute( "class" ) ); + assertEquals( "", div.getAttribute( "id" ) ); + pre = (HtmlPreformattedText) elementIterator.next(); + assertEquals( "pretty", pre.getAttribute( "id" ) ); + + section = (HtmlSection) elementIterator.next(); + h2 = (HtmlHeading2) elementIterator.next(); + assertEquals( "Section without id", h2.asText().trim() ); + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "Section_without_id", a.getAttribute( "name" ) ); + + section = (HtmlSection) elementIterator.next(); + HtmlHeading3 h3 = (HtmlHeading3) elementIterator.next(); + assertEquals( "Subsection without id", h3.asText().trim() ); + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "Subsection_without_id", a.getAttribute( "name" ) ); + + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "section-id", a.getAttribute( "name" ) ); + section = (HtmlSection) elementIterator.next(); + h2 = (HtmlHeading2) elementIterator.next(); + assertEquals( "Section with id", h2.asText().trim() ); + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "Section_with_id", a.getAttribute( "name" ) ); + + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "subsection-id", a.getAttribute( "name" ) ); + section = (HtmlSection) elementIterator.next(); + h3 = (HtmlHeading3) elementIterator.next(); + assertEquals( "Subsection with id", h3.asText().trim() ); + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "Subsection_with_id", a.getAttribute( "name" ) ); + + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "foo", a.getAttribute( "name" ) ); + section = (HtmlSection) elementIterator.next(); + assertEquals( "bar", section.getAttribute( "class" ) ); + assertEquals( "foo", section.getAttribute( "id" ) ); + h2 = (HtmlHeading2) elementIterator.next(); + assertEquals( "Section name", h2.asText().trim() ); + assertEquals( "", h2.getAttribute( "class" ) ); + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "Section_name", a.getAttribute( "name" ) ); + + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "subfoo", a.getAttribute( "name" ) ); + section = (HtmlSection) elementIterator.next(); + assertEquals( "subbar", section.getAttribute( "class" ) ); + assertEquals( "subfoo", section.getAttribute( "id" ) ); + h3 = (HtmlHeading3) elementIterator.next(); + assertEquals( "Subsection name", h3.asText().trim() ); + assertEquals( "", h3.getAttribute( "class" ) ); + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "Subsection_name", a.getAttribute( "name" ) ); + + assertFalse( elementIterator.hasNext() ); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/CommentsVerifier.java b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/CommentsVerifier.java new file mode 100644 index 000000000..82c48e6b5 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/CommentsVerifier.java @@ -0,0 +1,41 @@ +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.File; + +import org.codehaus.plexus.util.FileUtils; + +/** + * Verifies that there are no DOXIASITETOOLS-146 comments + */ +public class CommentsVerifier + extends AbstractVerifier +{ + /** {@inheritDoc} */ + public void verify( String file ) + throws Exception + { + String content = FileUtils.fileRead( new File( file ), "UTF-8" ); + + assertTrue( file + " should not contain 'DOXIASITETOOLS-146' text in comments", + content.indexOf( "DOXIASITETOOLS-146" ) < 0 ); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/DefaultSiteRendererTest.java b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/DefaultSiteRendererTest.java new file mode 100644 index 000000000..9570bcb21 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/DefaultSiteRendererTest.java @@ -0,0 +1,671 @@ +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 static org.mockito.Matchers.eq; +import static org.mockito.Matchers.isNull; +import static org.mockito.Mockito.*; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.StringWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystem; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.jar.JarOutputStream; +import java.util.zip.ZipEntry; + +import org.apache.commons.io.IOUtils; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.DefaultArtifact; +import org.apache.maven.doxia.Doxia; +import org.apache.maven.doxia.parser.ParseException; +import org.apache.maven.doxia.parser.Parser; +import org.apache.maven.doxia.sink.Sink; +import org.apache.maven.doxia.site.decoration.DecorationModel; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Reader; +import org.apache.maven.doxia.siterenderer.sink.SiteRendererSink; +import org.apache.maven.doxia.xsd.AbstractXmlValidator; +import org.codehaus.plexus.PlexusTestCase; +import org.codehaus.plexus.util.FileUtils; +import org.codehaus.plexus.util.IOUtil; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.ReflectionUtils; +import org.codehaus.plexus.util.StringUtils; +import org.mockito.Mockito; +import org.xml.sax.EntityResolver; + +/** + * @author Vincent Siveton + * @author Emmanuel Venisse + */ +public class DefaultSiteRendererTest + extends PlexusTestCase +{ + /** + * All output produced by this test will go here. + */ + private static final String OUTPUT = "target/output"; + + /** + * The renderer used to produce output. + */ + private Renderer renderer; + + /** + * The locale before executing tests. + */ + private Locale oldLocale; + + private File skinJar = new File( getBasedir(), "target/test-classes/skin.jar" ); + + /** + * @throws java.lang.Exception if something goes wrong. + * @see org.codehaus.plexus.PlexusTestCase#setUp() + */ + @Override + protected void setUp() + throws Exception + { + super.setUp(); + + renderer = (Renderer) lookup( Renderer.ROLE ); + + // copy the default-site.vm and default-site-macros.vm + copyVm( "default-site.vm", "\n\n\n\r\n\r\n\r\n" ); + copyVm( "default-site-macros.vm", "" ); + + InputStream skinIS = this.getResourceAsStream( "velocity-toolmanager.vm" ); + JarOutputStream jarOS = new JarOutputStream( new FileOutputStream( skinJar ) ); + try + { + jarOS.putNextEntry( new ZipEntry( "META-INF/maven/site.vm" ) ); + IOUtil.copy( skinIS, jarOS ); + jarOS.closeEntry(); + } + finally + { + IOUtil.close( skinIS ); + IOUtil.close( jarOS ); + } + + oldLocale = Locale.getDefault(); + Locale.setDefault( Locale.ENGLISH ); + } + + private void copyVm( String filename, String append ) + throws IOException + { + InputStream is = this.getResourceAsStream( "/org/apache/maven/doxia/siterenderer/resources/" + filename ); + assertNotNull( is ); + OutputStream os = new FileOutputStream( new File( getBasedir(), "target/test-classes/" + filename ) ); + try + { + IOUtil.copy( is, os ); + os.write( append.getBytes( "ISO-8859-1" ) ); + } + finally + { + IOUtil.close( is ); + IOUtil.close( os ); + } + } + + /** + * @throws java.lang.Exception if something goes wrong. + * @see org.codehaus.plexus.PlexusTestCase#tearDown() + */ + @Override + protected void tearDown() + throws Exception + { + release( renderer ); + super.tearDown(); + + Locale.setDefault( oldLocale ); + } + + /** + * @throws Exception if something goes wrong. + */ + public void testRenderExceptionMessageWhenLineNumberIsNotAvailable() + throws Exception + { + final File testBasedir = getTestFile( "src/test/resources/site/xdoc" ); + final String testDocumentName = "head.xml"; + final String exceptionMessage = "parse error occurred"; + + Doxia doxiaInstance = (Doxia) lookup( Doxia.class ); + Doxia doxiaSpy = spy( doxiaInstance ); + Mockito.doThrow( new ParseException( exceptionMessage ) ) + .when( doxiaSpy ) + .parse( Mockito.any(), Mockito.anyString(), Mockito.any() ); + Renderer renderer = (Renderer) lookup( Renderer.class ); + ReflectionUtils.setVariableValueInObject( renderer, "doxia", doxiaSpy ); + + RenderingContext renderingContext = new RenderingContext( testBasedir, "", testDocumentName, "xdoc", "", + false ); + + try + { + renderer.renderDocument( null, renderingContext, new SiteRenderingContext() ); + fail( "should fail with exception" ); + } + catch ( RendererException e ) + { + assertEquals( + String.format( "Error parsing '%s%s%s': %s", + testBasedir.getAbsolutePath(), File.separator, testDocumentName, exceptionMessage ), + e.getMessage() ); + } + } + + /** + * @throws Exception if something goes wrong. + */ + public void testRenderExceptionMessageWhenLineNumberIsAvailable() + throws Exception + { + final File testBasedir = getTestFile( "src/test/resources/site/xdoc" ); + final String testDocumentName = "head.xml"; + final String exceptionMessage = "parse error occurred"; + + Doxia doxiaInstance = (Doxia) lookup( Doxia.class ); + Doxia doxiaSpy = spy( doxiaInstance ); + Mockito.doThrow( new ParseException( exceptionMessage, 42, 36 ) ) + .when( doxiaSpy ) + .parse( Mockito.any(), Mockito.anyString(), Mockito.any() ); + Renderer renderer = (Renderer) lookup( Renderer.class ); + ReflectionUtils.setVariableValueInObject( renderer, "doxia", doxiaSpy ); + + RenderingContext renderingContext = new RenderingContext( testBasedir, "", testDocumentName, "xdoc", "", + false ); + + try + { + renderer.renderDocument( null, renderingContext, new SiteRenderingContext() ); + fail( "should fail with exception" ); + } + catch ( RendererException e ) + { + assertEquals( + String.format( "Error parsing '%s%s%s': line [42] %s", + testBasedir.getAbsolutePath(), File.separator, testDocumentName, exceptionMessage ), + e.getMessage() ); + } + } + + /** + * @throws Exception if something goes wrong. + */ + public void testRender() + throws Exception + { + // Safety + FileUtils.deleteDirectory( getTestFile( OUTPUT ) ); + + // ---------------------------------------------------------------------- + // Render the site from src/test/resources/site to OUTPUT + // ---------------------------------------------------------------------- + DecorationModel decoration = new DecorationXpp3Reader() + .read( new FileReader( getTestFile( "src/test/resources/site/site.xml" ) ) ); + + SiteRenderingContext ctxt = getSiteRenderingContext( decoration, "src/test/resources/site", false ); + ctxt.setRootDirectory( getTestFile( "" ) ); + renderer.render( renderer.locateDocumentFiles( ctxt, true ).values(), ctxt, getTestFile( OUTPUT ) ); + + ctxt = getSiteRenderingContext( decoration, "src/test/resources/site-validate", true ); + ctxt.setRootDirectory( getTestFile( "" ) ); + renderer.render( renderer.locateDocumentFiles( ctxt, true ).values(), ctxt, getTestFile( OUTPUT ) ); + + // ---------------------------------------------------------------------- + // Verify specific pages + // ---------------------------------------------------------------------- + verifyHeadPage(); + verifyCdcPage(); + verifyNestedItemsPage(); + verifyMultipleBlock(); + verifyMacro(); + verifyEntitiesPage(); + verifyJavascriptPage(); + verifyFaqPage(); + verifyAttributes(); + verifyMisc(); + verifyDocbookPageExists(); + verifyApt(); + verifyExtensionInFilename(); + verifyNewlines(); + + // ---------------------------------------------------------------------- + // Validate the rendering pages + // ---------------------------------------------------------------------- + validatePages(); + } + + public void testExternalReport() + throws Exception + { + DocumentRenderer docRenderer = mock( DocumentRenderer.class ); + when( docRenderer.isExternalReport() ).thenReturn( true ); + when( docRenderer.getOutputName() ).thenReturn( "external/index" ); + when( docRenderer.getRenderingContext() ).thenReturn( new RenderingContext( new File( "" ), "index.html", + "generator:external" ) ); + + SiteRenderingContext context = new SiteRenderingContext(); + + renderer.render( Collections.singletonList( docRenderer ), context, new File( "target/output" ) ); + + verify( docRenderer ).renderDocument( isNull( Writer.class ), eq( renderer ), eq( context ) ); + } + + public void testVelocityToolManager() + throws Exception + { + StringWriter writer = new StringWriter(); + + SiteRenderingContext siteRenderingContext = new SiteRenderingContext(); + siteRenderingContext.setDecoration( new DecorationModel() ); + + Map attributes = new HashMap(); + + /* + * We need to add doxiaSiteRendererVersion manually because version property from pom.properties + * is not available at test time in some cases. + */ + attributes.put( "doxiaSiteRendererVersion", "1.7-bogus" ); + + siteRenderingContext.setTemplateProperties( attributes ); + + siteRenderingContext.setTemplateName( "org/apache/maven/doxia/siterenderer/velocity-toolmanager.vm" ); + RenderingContext context = new RenderingContext( new File( "" ), "document.html", "generator" ); + SiteRendererSink sink = new SiteRendererSink( context ); + renderer.mergeDocumentIntoSite( writer, sink, siteRenderingContext ); + + String renderResult = writer.toString(); + String expectedResult = + IOUtils.toString( + getClass().getResourceAsStream( "velocity-toolmanager.expected.txt" ), + StandardCharsets.UTF_8 ); + expectedResult = StringUtils.unifyLineSeparators( expectedResult ); + assertEquals( expectedResult, renderResult ); + } + + public void testVelocityToolManagerForTemplate() + throws Exception + { + StringWriter writer = new StringWriter(); + + File templateFile = + new File( getBasedir(), "target/test-classes/org/apache/maven/doxia/siterenderer/velocity-toolmanager.vm" ); + Map attributes = new HashMap(); + + /* + * We need to add doxiaSiteRendererVersion manually because version property from pom.properties + * is not available at test time in some cases. + */ + attributes.put( "doxiaSiteRendererVersion", "1.7-bogus" ); + + SiteRenderingContext siteRenderingContext = + renderer.createContextForTemplate( templateFile, attributes, new DecorationModel(), + "defaultWindowTitle", Locale.ENGLISH ); + RenderingContext context = new RenderingContext( new File( "" ), "document.html", "generator" ); + SiteRendererSink sink = new SiteRendererSink( context ); + renderer.mergeDocumentIntoSite( writer, sink, siteRenderingContext ); + + String renderResult = writer.toString(); + InputStream in = getClass().getResourceAsStream( "velocity-toolmanager.expected.txt" ); + String expectedResult = StringUtils.unifyLineSeparators( IOUtils.toString( in, StandardCharsets.UTF_8 ) ); + assertEquals( expectedResult, renderResult ); + } + + public void testVelocityToolManagerForSkin() + throws Exception + { + StringWriter writer = new StringWriter(); + + File skinFile = skinJar; + + Map attributes = new HashMap(); + + /* + * We need to add doxiaSiteRendererVersion manually because version property from pom.properties + * is not available at test time in some cases. + */ + attributes.put( "doxiaSiteRendererVersion", "1.7-bogus" ); + + Artifact skin = new DefaultArtifact( "org.group", "artifact", "1.1", null, "jar", "", null ); + skin.setFile( skinFile ); + SiteRenderingContext siteRenderingContext = + renderer.createContextForSkin( skin, attributes, new DecorationModel(), "defaultWindowTitle", + Locale.ENGLISH ); + RenderingContext context = new RenderingContext( new File( "" ), "document.html", "generator" ); + SiteRendererSink sink = new SiteRendererSink( context ); + renderer.mergeDocumentIntoSite( writer, sink, siteRenderingContext ); + String renderResult = writer.toString(); + String expectedResult = StringUtils.unifyLineSeparators( + IOUtils.toString( + getClass().getResourceAsStream( "velocity-toolmanager.expected.txt" ), + StandardCharsets.UTF_8 ) ); + assertEquals( expectedResult, renderResult ); + } + + public void testMatchVersion() + throws Exception + { + DefaultSiteRenderer r = (DefaultSiteRenderer) renderer; + assertTrue( r.matchVersion( "1.7", "1.7" ) ); + assertFalse( r.matchVersion( "1.7", "1.8" ) ); + } + + private SiteRenderingContext getSiteRenderingContext( DecorationModel decoration, String siteDir, boolean validate ) + { + SiteRenderingContext ctxt = new SiteRenderingContext(); + ctxt.setTemplateName( "default-site.vm" ); + ctxt.setTemplateClassLoader( getClassLoader() ); + ctxt.setUsingDefaultTemplate( true ); + final Map templateProp = new HashMap(); + templateProp.put( "outputEncoding", "UTF-8" ); + ctxt.setTemplateProperties( templateProp ); + ctxt.setDecoration( decoration ); + ctxt.addSiteDirectory( getTestFile( siteDir ) ); + ctxt.setValidate( validate ); + + return ctxt; + } + + /** + * @throws Exception if something goes wrong. + */ + public void verifyHeadPage() + throws Exception + { + new HeadVerifier().verify( "target/output/head.html" ); + } + + /** + * @throws Exception if something goes wrong. + */ + public void verifyCdcPage() + throws Exception + { + File nestedItems = getTestFile( "target/output/cdc.html" ); + assertNotNull( nestedItems ); + assertTrue( nestedItems.exists() ); + } + + /** + * @throws Exception if something goes wrong. + */ + public void verifyNestedItemsPage() + throws Exception + { + NestedItemsVerifier verifier = new NestedItemsVerifier(); + verifier.verify( "target/output/nestedItems.html" ); + } + + /** + * @throws Exception if something goes wrong. + */ + public void verifyMultipleBlock() + throws Exception + { + MultipleBlockVerifier verifier = new MultipleBlockVerifier(); + verifier.verify( "target/output/multipleblock.html" ); + } + + /** + * @throws Exception if something goes wrong. + */ + public void verifyMacro() + throws Exception + { + File macro = getTestFile( "target/output/macro.html" ); + assertNotNull( macro ); + assertTrue( macro.exists() ); + + Reader reader = null; + try + { + reader = ReaderFactory.newXmlReader( macro ); + String content = IOUtil.toString( reader ); + assertEquals( content.indexOf( "" ), -1 ); + } + finally + { + IOUtil.close( reader ); + } + } + + /** + * @throws Exception if something goes wrong. + */ + public void verifyEntitiesPage() + throws Exception + { + EntitiesVerifier verifier = new EntitiesVerifier(); + verifier.verify( "target/output/entityTest.html" ); + } + + /** + * @throws Exception if something goes wrong. + */ + public void verifyJavascriptPage() + throws Exception + { + JavascriptVerifier verifier = new JavascriptVerifier(); + verifier.verify( "target/output/javascript.html" ); + } + + /** + * @throws Exception if something goes wrong. + */ + public void verifyFaqPage() + throws Exception + { + FaqVerifier verifier = new FaqVerifier(); + verifier.verify( "target/output/faq.html" ); + } + + /** + * @throws Exception if something goes wrong. + */ + public void verifyAttributes() + throws Exception + { + AttributesVerifier verifier = new AttributesVerifier(); + verifier.verify( "target/output/attributes.html" ); + } + + /** + * @throws Exception if something goes wrong. + */ + public void verifyMisc() + throws Exception + { + AbstractVerifier verifier = new MiscVerifier(); + verifier.verify( "target/output/misc.html" ); + + verifier = new CommentsVerifier(); + verifier.verify( "target/output/misc.html" ); + } + + /** + * @throws Exception if something goes wrong. + */ + public void verifyDocbookPageExists() + throws Exception + { + File output = getTestFile( "target/output/docbook.html" ); + assertNotNull( output ); + assertTrue( output.exists() ); + } + + /** + * @throws Exception if something goes wrong. + */ + public void verifyApt() + throws Exception + { + AbstractVerifier verifier = new AptVerifier(); + verifier.verify( "target/output/apt.html" ); + + verifier = new CommentsVerifier(); + verifier.verify( "target/output/apt.html" ); + } + + /** + * @throws Exception if something goes wrong. + */ + public void verifyExtensionInFilename() + throws Exception + { + File output = getTestFile( "target/output/extension.apt.not.at.end.html" ); + assertNotNull( output ); + assertTrue( output.exists() ); + } + + /** + * @throws Exception if something goes wrong. + */ + public void verifyNewlines() + throws Exception + { + /* apt */ + checkNewlines( FileUtils.fileRead( getTestFile( "target/output/apt.html" ), "ISO-8859-1" ) ); + checkNewlines( FileUtils.fileRead( getTestFile( "target/output/cdc.html" ), "ISO-8859-1" ) ); + checkNewlines( FileUtils.fileRead( getTestFile( "target/output/interpolation.html" ), "ISO-8859-1" ) ); + /* confluence */ + checkNewlines( FileUtils.fileRead( getTestFile( "target/output/confluence/anchor.html" ), "ISO-8859-1" ) ); + checkNewlines( FileUtils.fileRead( getTestFile( "target/output/confluence/code.html" ), "ISO-8859-1" ) ); + checkNewlines( FileUtils.fileRead( getTestFile( "target/output/confluence/table.html" ), "ISO-8859-1" ) ); + /* docbook */ + checkNewlines( FileUtils.fileRead( getTestFile( "target/output/docbook.html" ), "ISO-8859-1" ) ); + checkNewlines( FileUtils.fileRead( getTestFile( "target/output/sdocbook_full.html" ), "ISO-8859-1" ) ); + /* fml */ + checkNewlines( FileUtils.fileRead( getTestFile( "target/output/faq.html" ), "ISO-8859-1" ) ); + /* xdoc */ + checkNewlines( FileUtils.fileRead( getTestFile( "target/output/attributes.html" ), "ISO-8859-1" ) ); + checkNewlines( FileUtils.fileRead( getTestFile( "target/output/javascript.html" ), "ISO-8859-1" ) ); + checkNewlines( FileUtils.fileRead( getTestFile( "target/output/head.html" ), "ISO-8859-1" ) ); + checkNewlines( FileUtils.fileRead( getTestFile( "target/output/macro.html" ), "ISO-8859-1" ) ); + } + + private void checkNewlines( String content ) + { + int cr = StringUtils.countMatches( content, "\r" ); + int lf = StringUtils.countMatches( content, "\n" ); + assertTrue( "Should contain only Windows or Unix newlines: cr = " + cr + ", lf = " + lf, ( cr == 0 ) + || ( cr == lf ) ); + } + + /** + * Validate the generated pages. + * + * @throws Exception if something goes wrong. + * @since 1.1.1 + */ + public void validatePages() + throws Exception + { + new Xhtml5ValidatorTest().validateGeneratedPages(); + } + + protected static class Xhtml5ValidatorTest + extends AbstractXmlValidator + { + /** + * Validate the generated documents. + * + * @throws Exception + */ + public void validateGeneratedPages() + throws Exception + { + setValidate( false ); + setUp(); + testValidateFiles(); + tearDown(); + } + + private static String[] getIncludes() + { + return new String[] { "**/*.html" }; + } + + /** {@inheritDoc} */ + protected String addNamespaces( String content ) + { + return content; + } + + /** {@inheritDoc} */ + protected EntityResolver getEntityResolver() + { + /* HTML5 restricts use of entities to XML only */ + return null; + } + + /** {@inheritDoc} */ + protected Map getTestDocuments() + throws IOException + { + Map testDocs = new HashMap(); + + File dir = new File( getBasedir(), "target/output" ); + + List l = + FileUtils.getFileNames( dir, getIncludes()[0], FileUtils.getDefaultExcludesAsString(), true ); + + for ( String file : l ) + { + file = StringUtils.replace( file, "\\", "/" ); + + Reader reader = ReaderFactory.newXmlReader( new File( file ) ); + try + { + testDocs.put( file, IOUtil.toString( reader ) ); + } + finally + { + IOUtil.close( reader ); + } + } + + return testDocs; + } + + /** {@inheritDoc} */ + @Override + protected boolean isFailErrorMessage( String message ) + { + return true; + } + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/EntitiesVerifier.java b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/EntitiesVerifier.java new file mode 100644 index 000000000..0195bd238 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/EntitiesVerifier.java @@ -0,0 +1,189 @@ +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 com.gargoylesoftware.htmlunit.html.HtmlAnchor; +import com.gargoylesoftware.htmlunit.html.HtmlDivision; +import com.gargoylesoftware.htmlunit.html.HtmlElement; +import com.gargoylesoftware.htmlunit.html.HtmlHeading2; +import com.gargoylesoftware.htmlunit.html.HtmlHeading3; +import com.gargoylesoftware.htmlunit.html.HtmlHeading4; +import com.gargoylesoftware.htmlunit.html.HtmlMeta; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import com.gargoylesoftware.htmlunit.html.HtmlParagraph; +import com.gargoylesoftware.htmlunit.html.HtmlPreformattedText; +import com.gargoylesoftware.htmlunit.html.HtmlSection; + +import java.util.Iterator; + +/** + * Verify the site/xdoc/entityTest.xml + * + * @author ltheussl + */ +public class EntitiesVerifier + extends AbstractVerifier +{ + /** {@inheritDoc} */ + public void verify( String file ) + throws Exception + { + HtmlPage page = htmlPage( file ); + assertNotNull( page ); + + HtmlMeta author = (HtmlMeta) page.getElementsByName( "author" ).get( 0 ); + assertNotNull( author ); + assertTrue( author.toString().indexOf( "Ligature \u00C6" ) > 0 ); + assertEquals( "Ligature \u00C6", author.getContentAttribute() ); + + author = (HtmlMeta) page.getElementsByName( "author" ).get( 1 ); + assertNotNull( author ); + assertTrue( author.toString().indexOf( "Ampersand &" ) > 0 ); + assertEquals( "Ampersand &", author.getContentAttribute() ); + + author = (HtmlMeta) page.getElementsByName( "author" ).get( 2 ); + assertNotNull( author ); + assertTrue( author.toString().indexOf( "Less than <" ) > 0 ); + assertEquals( "Less than <", author.getContentAttribute() ); + + author = (HtmlMeta) page.getElementsByName( "author" ).get( 3 ); + assertNotNull( author ); + assertTrue( author.toString().indexOf( "Greater than >" ) > 0 ); + assertEquals( "Greater than >", author.getContentAttribute() ); + + author = (HtmlMeta) page.getElementsByName( "author" ).get( 4 ); + assertNotNull( author ); + assertEquals( "Apostrophe '", author.getContentAttribute() ); + + author = (HtmlMeta) page.getElementsByName( "author" ).get( 5 ); + assertNotNull( author ); + assertTrue( author.toString().indexOf( "Quote "" ) > 0 ); + assertEquals( "Quote \"", author.getContentAttribute() ); + + author = (HtmlMeta) page.getElementsByName( "author" ).get( 6 ); + assertNotNull( author ); + assertTrue( author.toString().indexOf( "test@email.com" ) > 0 ); + assertEquals( "test@email.com", author.getContentAttribute() ); + + author = (HtmlMeta) page.getElementsByName( "author" ).get( 7 ); + assertNotNull( author ); + assertTrue( author.toString().indexOf( "test\u00A9email.com" ) > 0 ); + assertEquals( "test\u00A9email.com", author.getContentAttribute() ); + + HtmlElement element = page.getHtmlElementById( "contentBox" ); + assertNotNull( element ); + HtmlDivision division = (HtmlDivision) element; + assertNotNull( division ); + + Iterator elementIterator = division.getHtmlElementDescendants().iterator(); + + // ---------------------------------------------------------------------- + // + // ---------------------------------------------------------------------- + + HtmlSection section = (HtmlSection) elementIterator.next(); + + HtmlHeading2 h2 = (HtmlHeading2) elementIterator.next(); + assertNotNull( h2 ); + assertEquals( h2.asText().trim(), "section name with entities: '&' '\u0391' ' ' '\uD835\uDFED'" ); + + HtmlAnchor a = (HtmlAnchor) elementIterator.next(); + assertNotNull( a ); + assertEquals( "section_name_with_entities:_.27.26.27_.27.CE.91.27_.27.C2.A0.27_.27.3F.3F.27", + a.getAttribute( "name" ) ); + + section = (HtmlSection) elementIterator.next(); + assertNotNull( section ); + + section = (HtmlSection) elementIterator.next(); + assertNotNull( section ); + + HtmlHeading4 h4 = (HtmlHeading4) elementIterator.next(); + assertNotNull( h4 ); + assertEquals( "Entities", h4.asText().trim() ); + + a = (HtmlAnchor) elementIterator.next(); + assertNotNull( a ); + assertEquals( "Entities", a.getAttribute( "name" ) ); + + section = (HtmlSection) elementIterator.next(); + assertNotNull( section ); + + HtmlHeading3 h3 = (HtmlHeading3) elementIterator.next(); + assertNotNull( h3 ); + assertEquals( "Generic Entities: '&' '<' '>' '\"' '''", h3.asText().trim() ); + + a = (HtmlAnchor) elementIterator.next(); + + HtmlParagraph p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "'&' '<' '>' '\"' '''", p.asText().trim() ); + + section = (HtmlSection) elementIterator.next(); + assertNotNull( section ); + + h3 = (HtmlHeading3) elementIterator.next(); + assertNotNull( h3 ); + assertEquals( "Local Entities: '\u0391' '\u0392' '\u0393' '\uD835\uDFED'", h3.asText().trim() ); + + a = (HtmlAnchor) elementIterator.next(); + + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "'\u0391' '\u0392' '\u0393' '\uD835\uDFED\uD835\uDFED' '\u0159\u0159' '\u0159'", p.asText().trim() ); + + section = (HtmlSection) elementIterator.next(); + assertNotNull( section ); + + h3 = (HtmlHeading3) elementIterator.next(); + assertNotNull( h3 ); + assertEquals( "DTD Entities: ' ' '\u00A1' '\u00A2'", h3.asText().trim() ); + + a = (HtmlAnchor) elementIterator.next(); + + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "' ' '\u00A1' '\u00A2'", p.asText().trim() ); + + section = (HtmlSection) elementIterator.next(); + assertNotNull( section ); + + h4 = (HtmlHeading4) elementIterator.next(); + assertNotNull( h4 ); + assertEquals( "CDATA", h4.asText().trim() ); + + a = (HtmlAnchor) elementIterator.next(); + assertNotNull( a ); + assertEquals( "CDATA", a.getAttribute( "name" ) ); + + HtmlDivision div = (HtmlDivision) elementIterator.next(); + assertNotNull( div ); + + HtmlPreformattedText pre = (HtmlPreformattedText) elementIterator.next(); + assertNotNull( pre ); + assertEquals( "", pre.asText().trim() ); + + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "' ' '¡'", p.asText().trim() ); + + assertFalse( elementIterator.hasNext() ); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/FaqVerifier.java b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/FaqVerifier.java new file mode 100644 index 000000000..39a25bb44 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/FaqVerifier.java @@ -0,0 +1,198 @@ +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 com.gargoylesoftware.htmlunit.html.HtmlAnchor; +import com.gargoylesoftware.htmlunit.html.HtmlDefinitionDescription; +import com.gargoylesoftware.htmlunit.html.HtmlDefinitionList; +import com.gargoylesoftware.htmlunit.html.HtmlDefinitionTerm; +import com.gargoylesoftware.htmlunit.html.HtmlDivision; +import com.gargoylesoftware.htmlunit.html.HtmlElement; +import com.gargoylesoftware.htmlunit.html.HtmlHeading2; +import com.gargoylesoftware.htmlunit.html.HtmlListItem; +import com.gargoylesoftware.htmlunit.html.HtmlOrderedList; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import com.gargoylesoftware.htmlunit.html.HtmlParagraph; +import com.gargoylesoftware.htmlunit.html.HtmlPreformattedText; +import com.gargoylesoftware.htmlunit.html.HtmlSection; + +import java.util.Iterator; + +/** + * + * @author ltheussl + */ +public class FaqVerifier + extends AbstractVerifier +{ + /** {@inheritDoc} */ + public void verify( String file ) + throws Exception + { + HtmlPage page = htmlPage( file ); + assertNotNull( page ); + + HtmlElement element = page.getHtmlElementById( "contentBox" ); + assertNotNull( element ); + HtmlDivision division = (HtmlDivision) element; + assertNotNull( division ); + + Iterator elementIterator = division.getHtmlElementDescendants().iterator(); + + // ---------------------------------------------------------------------- + // + // ---------------------------------------------------------------------- + + HtmlSection section = (HtmlSection) elementIterator.next(); + + HtmlHeading2 h2 = (HtmlHeading2) elementIterator.next(); + assertEquals( "Oft Asked Questions", h2.asText().trim() ); + + HtmlAnchor a = (HtmlAnchor) elementIterator.next(); + assertEquals( a.getAttribute( "name" ), "Oft_Asked_Questions" ); + + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "top", a.getAttribute( "name" ) ); + + HtmlParagraph p = (HtmlParagraph) elementIterator.next(); + element = elementIterator.next(); + assertEquals( "b", element.getTagName() ); + assertEquals( "Contributing", element.asText().trim() ); + + HtmlOrderedList ol = (HtmlOrderedList) elementIterator.next(); + assertEquals( "One stupid question & a silly answer?", ol.getFirstElementChild().asText().trim() ); + + HtmlListItem li = (HtmlListItem) elementIterator.next(); + assertEquals( "One stupid question & a silly answer?", li.getFirstElementChild().asText().trim() ); + + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "#stupid-question", a.getAttribute( "href" ) ); + + element = elementIterator.next(); + assertEquals( "b", element.getTagName() ); + assertEquals( "stupid", element.asText().trim() ); + + p = (HtmlParagraph) elementIterator.next(); + element = elementIterator.next(); + assertEquals( "b", element.getTagName() ); + assertEquals( "Using Maven", element.asText().trim() ); + + ol = (HtmlOrderedList) elementIterator.next(); + assertEquals( "How do I disable a report on my site?", ol.getFirstElementChild().asText().trim() ); + + li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + assertEquals( "How do I disable a report on my site?", li.getFirstElementChild().asText().trim() ); + + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "#disable-reports", a.getAttribute( "href" ) ); + + section = (HtmlSection) elementIterator.next(); + + h2 = (HtmlHeading2) elementIterator.next(); + assertEquals( "Contributing", h2.asText().trim() ); + + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "Contributing", a.getAttribute( "name" ) ); + + HtmlDefinitionList dl = (HtmlDefinitionList) elementIterator.next(); + + HtmlDefinitionTerm dt = (HtmlDefinitionTerm) elementIterator.next(); + assertEquals( "One stupid question & a silly answer?", dt.getFirstChild().asText().trim() ); + + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "stupid-question", a.getAttribute( "name" ) ); + + element = elementIterator.next(); + assertEquals( "b", element.getTagName() ); + assertEquals( "stupid", element.asText().trim() ); + + HtmlDefinitionDescription dd = (HtmlDefinitionDescription) elementIterator.next(); + + p = (HtmlParagraph) elementIterator.next(); + + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "#Using_Maven", a.getAttribute( "href" ) ); + assertEquals( "local link", a.asText().trim() ); + + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "./cdc.html", a.getAttribute( "href" ) ); + assertEquals( "source document", a.asText().trim() ); + + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "http://maven.apache.org/?l=a&m=b", a.getAttribute( "href" ) ); + assertEquals( "external link", a.asText().trim() ); + + element = elementIterator.next(); + assertEquals( "i", element.getTagName() ); + assertEquals( "italic", element.asText().trim() ); + + element = elementIterator.next(); + assertEquals( "b", element.getTagName() ); + assertEquals( "non-US-ASCII characters: àéèç", element.asText().trim() ); + + p = (HtmlParagraph) elementIterator.next(); + assertEquals( "right", p.getAttribute( "align" ) ); + + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "#top", a.getAttribute( "href" ) ); + assertEquals( "[top]", a.asText().trim() ); + + + section = (HtmlSection) elementIterator.next(); + + h2 = (HtmlHeading2) elementIterator.next(); + assertEquals( "Using Maven", h2.asText().trim() ); + + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "Using_Maven", a.getAttribute( "name" ) ); + + dl = (HtmlDefinitionList) elementIterator.next(); + + dt = (HtmlDefinitionTerm) elementIterator.next(); + assertEquals( "How do I disable a report on my site?", dt.getFirstChild().asText().trim() ); + + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "disable-reports", a.getAttribute( "name" ) ); + + dd = (HtmlDefinitionDescription) elementIterator.next(); + + p = (HtmlParagraph) elementIterator.next(); + + element = elementIterator.next(); + assertEquals( "code", element.getTagName() ); + assertEquals( "", element.asText().trim() ); + + HtmlDivision div = (HtmlDivision) elementIterator.next(); + assertEquals( "source", div.getAttribute( "class" ) ); + + HtmlPreformattedText pre = (HtmlPreformattedText) elementIterator.next(); + assertEquals( "1.5", pre.asText().trim() ); + + p = (HtmlParagraph) elementIterator.next(); + assertEquals( "right", p.getAttribute( "align" ) ); + + a = (HtmlAnchor) elementIterator.next(); + assertEquals( "#top", a.getAttribute( "href" ) ); + assertEquals( "[top]", a.asText().trim() ); + + assertFalse( elementIterator.hasNext() ); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/HeadVerifier.java b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/HeadVerifier.java new file mode 100644 index 000000000..babd6e1f8 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/HeadVerifier.java @@ -0,0 +1,102 @@ +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 com.gargoylesoftware.htmlunit.html.HtmlBase; +import com.gargoylesoftware.htmlunit.html.HtmlElement; +import com.gargoylesoftware.htmlunit.html.HtmlLink; +import com.gargoylesoftware.htmlunit.html.HtmlMeta; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import com.gargoylesoftware.htmlunit.html.HtmlTitle; + +import java.util.Iterator; +import java.util.List; + +/** + * Verify correct rendering of site/xdoc/head.xml. + * + * @author ltheussl + */ +public class HeadVerifier + extends AbstractVerifier +{ + + /** {@inheritDoc} */ + public void verify( String file ) + throws Exception + { + HtmlPage page = htmlPage( file ); + assertNotNull( page ); + + HtmlElement html = page.getDocumentElement(); + assertNotNull( html ); + + assertEquals( "en", html.getAttribute( "lang" ) ); + + List heads = html.getElementsByTagName( "head" ); + assertEquals( 1, heads.size() ); + HtmlElement head = heads.get( 0 ); + assertNotNull( head ); + + Iterator elementIterator = head.getHtmlElementDescendants().iterator(); + + // ---------------------------------------------------------------------- + // + // ---------------------------------------------------------------------- + + HtmlMeta meta = (HtmlMeta) elementIterator.next(); + assertEquals( "UTF-8", meta.getAttribute( "charset" ) ); + + // Skip viewport + elementIterator.next(); + + meta = (HtmlMeta) elementIterator.next(); + assertEquals( "Unexpected meta entry found generated resource " + file, "generator", meta.getAttribute( "name" ) ); + String generator = meta.getAttribute("content"); + assertEquals("Unexpected value found for generator meta entry in generated resource " + file, "Apache Maven Doxia Site Renderer", generator); + + meta = (HtmlMeta) elementIterator.next(); + assertEquals( "author", meta.getAttribute( "name" ) ); + assertEquals( "John Doe", meta.getAttribute( "content" ).trim() ); + + HtmlTitle title = (HtmlTitle) elementIterator.next(); + assertNotNull( title ); + + HtmlLink link = (HtmlLink) elementIterator.next(); + assertNotNull( link ); + link = (HtmlLink) elementIterator.next(); + assertNotNull( link ); + link = (HtmlLink) elementIterator.next(); + assertNotNull( link ); + link = (HtmlLink) elementIterator.next(); + assertNotNull( link ); + + meta = (HtmlMeta) elementIterator.next(); + assertEquals( "description", meta.getAttribute( "name" ) ); + assertEquals( "Free Web tutorials", meta.getAttribute( "content" ) ); + + meta = (HtmlMeta) elementIterator.next(); + assertEquals( "keywords", meta.getAttribute( "name" ) ); + assertEquals( "HTML,CSS,XML,JavaScript", meta.getAttribute( "content" ) ); + + HtmlBase base = (HtmlBase) elementIterator.next(); + assertEquals( "http://maven.apache.org/", base.getAttribute( "href" ) ); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/JavascriptVerifier.java b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/JavascriptVerifier.java new file mode 100644 index 000000000..bbc38d60c --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/JavascriptVerifier.java @@ -0,0 +1,106 @@ +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 com.gargoylesoftware.htmlunit.CollectingAlertHandler; +import com.gargoylesoftware.htmlunit.WebClient; +import com.gargoylesoftware.htmlunit.html.HtmlAnchor; +import com.gargoylesoftware.htmlunit.html.HtmlDivision; +import com.gargoylesoftware.htmlunit.html.HtmlElement; +import com.gargoylesoftware.htmlunit.html.HtmlHeading2; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import com.gargoylesoftware.htmlunit.html.HtmlParagraph; +import com.gargoylesoftware.htmlunit.html.HtmlScript; +import com.gargoylesoftware.htmlunit.html.HtmlSection; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + + +/** + * Verify javascript code. + * + * @author ltheussl + */ +public class JavascriptVerifier + extends AbstractVerifier +{ + /** + * Verifies a HtmlPage. + * + * @param file the file to verify. + * + * @throws Exception if something goes wrong. + */ + public void verify( String file ) + throws Exception + { + File jsTest = getTestFile( "target/output/javascript.html" ); + assertNotNull( jsTest ); + assertTrue( jsTest.exists() ); + + // HtmlUnit + try ( WebClient webClient = new WebClient() ) { + webClient.getOptions().setCssEnabled( false ); + + final List collectedAlerts = new ArrayList( 4 ); + webClient.setAlertHandler( new CollectingAlertHandler( collectedAlerts ) ); + + HtmlPage page = (HtmlPage) webClient.getPage( jsTest.toURI().toURL() ); + assertNotNull( page ); + + HtmlElement element = page.getHtmlElementById( "contentBox" ); + assertNotNull( element ); + HtmlDivision division = (HtmlDivision) element; + assertNotNull( division ); + + Iterator elementIterator = division.getHtmlElementDescendants().iterator(); + + // ---------------------------------------------------------------------- + // + // ---------------------------------------------------------------------- + + HtmlSection section = (HtmlSection) elementIterator.next(); + assertNotNull( section ); + + HtmlHeading2 h2 = (HtmlHeading2) elementIterator.next(); + assertNotNull( h2 ); + assertEquals( "Test", h2.asText().trim() ); + + HtmlAnchor a = (HtmlAnchor) elementIterator.next(); + assertNotNull( a ); + assertEquals( "Test", a.getAttribute( "name" ) ); + + HtmlParagraph p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "You should see a JavaScript alert...", p.asText().trim() ); + + HtmlScript script = (HtmlScript) elementIterator.next(); + assertNotNull( script ); + assertEquals( "text/javascript", script.getAttribute( "type" ) ); + assertEquals( "", script.asText().trim() ); + List expectedAlerts = Collections.singletonList( "Hello!" ); + assertEquals( expectedAlerts, collectedAlerts ); + } + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/MiscVerifier.java b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/MiscVerifier.java new file mode 100644 index 000000000..e1664d13b --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/MiscVerifier.java @@ -0,0 +1,64 @@ +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 com.gargoylesoftware.htmlunit.html.HtmlDivision; +import com.gargoylesoftware.htmlunit.html.HtmlElement; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import com.gargoylesoftware.htmlunit.html.HtmlParameter; +import com.gargoylesoftware.htmlunit.html.HtmlUnknownElement; + +import java.util.Iterator; + + +/** + * + * @author ltheussl + */ +public class MiscVerifier + extends AbstractVerifier +{ + /** {@inheritDoc} */ + public void verify( String file ) + throws Exception + { + HtmlPage page = htmlPage( file ); + assertNotNull( page ); + + HtmlElement element = page.getHtmlElementById( "contentBox" ); + assertNotNull( element ); + HtmlDivision division = (HtmlDivision) element; + assertNotNull( division ); + + Iterator elementIterator = division.getHtmlElementDescendants().iterator(); + + // ---------------------------------------------------------------------- + // + // ---------------------------------------------------------------------- + + HtmlUnknownElement applet = (HtmlUnknownElement) elementIterator.next(); + assertEquals( "org.micro.applet.Main", applet.getAttribute( "code" ) ); + assertEquals( "micro-applet.jar", applet.getAttribute( "archive" ) ); + + HtmlParameter param = (HtmlParameter) elementIterator.next(); + assertEquals( "midlet", param.getAttribute( "name" ) ); + assertEquals( "org.micro.applet.SimpleDemoMIDlet", param.getAttribute( "value" ) ); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/MultipleBlockVerifier.java b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/MultipleBlockVerifier.java new file mode 100644 index 000000000..3820e533f --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/MultipleBlockVerifier.java @@ -0,0 +1,132 @@ +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 com.gargoylesoftware.htmlunit.html.HtmlAnchor; +import com.gargoylesoftware.htmlunit.html.HtmlDivision; +import com.gargoylesoftware.htmlunit.html.HtmlElement; +import com.gargoylesoftware.htmlunit.html.HtmlHeading2; +import com.gargoylesoftware.htmlunit.html.HtmlListItem; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import com.gargoylesoftware.htmlunit.html.HtmlParagraph; +import com.gargoylesoftware.htmlunit.html.HtmlSection; +import com.gargoylesoftware.htmlunit.html.HtmlUnorderedList; + +import java.util.Iterator; + +/** + * + * @author ltheussl + */ +public class MultipleBlockVerifier + extends AbstractVerifier +{ + /** {@inheritDoc} */ + public void verify( String file ) + throws Exception + { + HtmlPage page = htmlPage( file ); + assertNotNull( page ); + + HtmlElement element = page.getHtmlElementById( "contentBox" ); + assertNotNull( element ); + HtmlDivision division = (HtmlDivision) element; + assertNotNull( division ); + + Iterator elementIterator = division.getHtmlElementDescendants().iterator(); + + // ---------------------------------------------------------------------- + // Verify link + // ---------------------------------------------------------------------- + + HtmlSection section = (HtmlSection) elementIterator.next(); + assertNotNull( section ); + + HtmlHeading2 h2 = (HtmlHeading2) elementIterator.next(); + assertNotNull( h2 ); + assertEquals( "section name", h2.asText().trim() ); + + HtmlAnchor a = (HtmlAnchor) elementIterator.next(); + assertNotNull( a ); + assertEquals( "section_name", a.getAttribute( "name" ) ); + + // ---------------------------------------------------------------------- + // Paragraph + // ---------------------------------------------------------------------- + + HtmlParagraph p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "text", p.asText().trim() ); + + // ---------------------------------------------------------------------- + // Unordered list + // ---------------------------------------------------------------------- + + HtmlUnorderedList ul = (HtmlUnorderedList) elementIterator.next(); + assertNotNull( ul ); + + HtmlListItem li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + assertEquals( "list1", li.getFirstChild().asText().trim() ); + + // ---------------------------------------------------------------------- + // Paragraph + // ---------------------------------------------------------------------- + + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "text2", p.asText().trim() ); + + // ---------------------------------------------------------------------- + // Unordered list + // ---------------------------------------------------------------------- + + ul = (HtmlUnorderedList) elementIterator.next(); + assertNotNull( ul ); + + li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + assertEquals( "list1", li.getFirstChild().asText().trim() ); + + // ---------------------------------------------------------------------- + // Paragraph + // ---------------------------------------------------------------------- + + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "text3", p.asText().trim() ); + + // ---------------------------------------------------------------------- + // Unordered list + // ---------------------------------------------------------------------- + + ul = (HtmlUnorderedList) elementIterator.next(); + assertNotNull( ul ); + + li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "list1", p.getFirstChild().asText().trim() ); + + assertFalse( elementIterator.hasNext() ); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/NestedItemsVerifier.java b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/NestedItemsVerifier.java new file mode 100644 index 000000000..92b64723b --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/NestedItemsVerifier.java @@ -0,0 +1,320 @@ +package org.apache.maven.doxia.siterenderer; + +import java.util.Iterator; + +/* + * 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 com.gargoylesoftware.htmlunit.html.HtmlAnchor; +import com.gargoylesoftware.htmlunit.html.HtmlDefinitionDescription; +import com.gargoylesoftware.htmlunit.html.HtmlDefinitionList; +import com.gargoylesoftware.htmlunit.html.HtmlDefinitionTerm; +import com.gargoylesoftware.htmlunit.html.HtmlDivision; +import com.gargoylesoftware.htmlunit.html.HtmlElement; +import com.gargoylesoftware.htmlunit.html.HtmlHeading2; +import com.gargoylesoftware.htmlunit.html.HtmlHeading4; +import com.gargoylesoftware.htmlunit.html.HtmlListItem; +import com.gargoylesoftware.htmlunit.html.HtmlOrderedList; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import com.gargoylesoftware.htmlunit.html.HtmlParagraph; +import com.gargoylesoftware.htmlunit.html.HtmlSection; +import com.gargoylesoftware.htmlunit.html.HtmlUnorderedList; + +/** + * + * @author ltheussl + */ +public class NestedItemsVerifier + extends AbstractVerifier +{ + /** {@inheritDoc} */ + public void verify( String file ) + throws Exception + { + HtmlPage page = htmlPage( file ); + assertNotNull( page ); + + HtmlElement element = page.getHtmlElementById( "contentBox" ); + assertNotNull( element ); + HtmlDivision division = (HtmlDivision) element; + assertNotNull( division ); + + Iterator elementIterator = division.getHtmlElementDescendants().iterator(); + + // ---------------------------------------------------------------------- + // Verify link + // ---------------------------------------------------------------------- + + HtmlSection section = (HtmlSection) elementIterator.next(); + assertNotNull( section ); + + HtmlHeading2 h2 = (HtmlHeading2) elementIterator.next(); + assertNotNull( h2 ); + assertEquals( "List Section", h2.asText().trim() ); + + HtmlAnchor a = (HtmlAnchor) elementIterator.next(); + assertNotNull( a ); + assertEquals( "List_Section", a.getAttribute( "name" ) ); + + // ---------------------------------------------------------------------- + // Unordered lists + // ---------------------------------------------------------------------- + + section = (HtmlSection) elementIterator.next(); + assertNotNull( section ); + + section = (HtmlSection) elementIterator.next(); + assertNotNull( section ); + + HtmlHeading4 h4 = (HtmlHeading4) elementIterator.next(); + assertNotNull( h4 ); + assertEquals( "Unordered lists", h4.asText().trim() ); + + a = (HtmlAnchor) elementIterator.next(); + assertNotNull( a ); + assertEquals( "Unordered_lists", a.getAttribute( "name" ) ); + + HtmlParagraph p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "Below is an unordered list, followed by six paragraphs.", p.asText().trim() ); + + HtmlUnorderedList ul = (HtmlUnorderedList) elementIterator.next(); + assertNotNull( ul ); + + HtmlListItem li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + assertEquals( "Item 1.", li.getFirstChild().asText().trim() ); + + ul = (HtmlUnorderedList) elementIterator.next(); + assertNotNull( ul ); + + li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "Item 11.", p.getFirstChild().asText().trim() ); + li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "Item 12.", p.getFirstChild().asText().trim() ); + li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + assertEquals( "Item 13.", li.getFirstChild().asText().trim() ); + li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + assertEquals( "Item 14.", li.getFirstChild().asText().trim() ); + li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + assertEquals( "Item 2.", li.getFirstChild().asText().trim() ); + li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + assertEquals( "Item 3.", li.getFirstChild().asText().trim() ); + li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + assertEquals( "Item 4.", li.getFirstChild().asText().trim() ); + + ul = (HtmlUnorderedList) elementIterator.next(); + assertNotNull( ul ); + + li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + assertEquals( "Item 41.", li.getFirstChild().asText().trim() ); + li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + assertEquals( "Item 42.", li.getFirstChild().asText().trim() ); + li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + assertEquals( "Item 43.", li.getFirstChild().asText().trim() ); + li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + assertEquals( "Item 44.", li.getFirstChild().asText().trim() ); + + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "Paragraph 1 below list.", p.asText().trim() ); + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "Paragraph 2 below list.", p.asText().trim() ); + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "Paragraph 3 below list.", p.asText().trim() ); + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "Paragraph 4 below list.", p.asText().trim() ); + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "Paragraph 5 below list.", p.asText().trim() ); + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "Paragraph 6 below list.", p.asText().trim() ); + + // ---------------------------------------------------------------------- + // Ordered lists + // ---------------------------------------------------------------------- + + section = (HtmlSection) elementIterator.next(); + assertNotNull( section ); + + h4 = (HtmlHeading4) elementIterator.next(); + assertNotNull( h4 ); + assertEquals( "Ordered lists", h4.asText().trim() ); + + a = (HtmlAnchor) elementIterator.next(); + assertNotNull( a ); + assertEquals( "Ordered_lists", a.getAttribute( "name" ) ); + + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "Below is an ordered list, followed by six paragraphs.", p.asText().trim() ); + + HtmlOrderedList ol = (HtmlOrderedList) elementIterator.next(); + assertNotNull( ol ); + + li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + assertEquals( "Item 1.", li.getFirstChild().asText().trim() ); + + ol = (HtmlOrderedList) elementIterator.next(); + assertNotNull( ol ); + + li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + assertEquals( "Item 11.", li.getFirstChild().asText().trim() ); + li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + assertEquals( "Item 12.", li.getFirstChild().asText().trim() ); + li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + assertEquals( "Item 13.", li.getFirstChild().asText().trim() ); + li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + assertEquals( "Item 14.", li.getFirstChild().asText().trim() ); + li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + assertEquals( "Item 2.", li.getFirstChild().asText().trim() ); + li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + assertEquals( "Item 3.", li.getFirstChild().asText().trim() ); + li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + assertEquals( "Item 4.", li.getFirstChild().asText().trim() ); + + ol = (HtmlOrderedList) elementIterator.next(); + assertNotNull( ol ); + + li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + assertEquals( "Item 41.", li.getFirstChild().asText().trim() ); + li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + assertEquals( "Item 42.", li.getFirstChild().asText().trim() ); + li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + assertEquals( "Item 43.", li.getFirstChild().asText().trim() ); + li = (HtmlListItem) elementIterator.next(); + assertNotNull( li ); + assertEquals( "Item 44.", li.getFirstChild().asText().trim() ); + + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "Paragraph 1 below list.", p.asText().trim() ); + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "Paragraph 2 below list.", p.asText().trim() ); + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "Paragraph 3 below list.", p.asText().trim() ); + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "Paragraph 4 below list.", p.asText().trim() ); + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "Paragraph 5 below list.", p.asText().trim() ); + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "Paragraph 6 below list.", p.asText().trim() ); + + // ---------------------------------------------------------------------- + // Definition lists + // ---------------------------------------------------------------------- + + section = (HtmlSection) elementIterator.next(); + assertNotNull( section ); + + h4 = (HtmlHeading4) elementIterator.next(); + assertNotNull( h4 ); + assertEquals( "Definition lists", h4.asText().trim() ); + + a = (HtmlAnchor) elementIterator.next(); + assertNotNull( a ); + assertEquals( "Definition_lists", a.getAttribute( "name" ) ); + + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "Below is a definition list, followed by six paragraphs.", p.asText().trim() ); + + HtmlDefinitionList dl = (HtmlDefinitionList) elementIterator.next(); + assertNotNull( dl ); + + HtmlDefinitionTerm dt = (HtmlDefinitionTerm) elementIterator.next(); + assertNotNull( dt ); + assertEquals( "Term 1.", dt.getFirstChild().asText().trim() ); + HtmlDefinitionDescription dd = (HtmlDefinitionDescription) elementIterator.next(); + assertNotNull( dd ); + assertEquals( "Description 1.", dd.getFirstChild().asText().trim() ); + + dt = (HtmlDefinitionTerm) elementIterator.next(); + assertNotNull( dt ); + assertEquals( "Term 2.", dt.getFirstChild().asText().trim() ); + dd = (HtmlDefinitionDescription) elementIterator.next(); + assertNotNull( dd ); + assertEquals( "Description 2.", dd.getFirstChild().asText().trim() ); + + dl = (HtmlDefinitionList) elementIterator.next(); + assertNotNull( dl ); + dt = (HtmlDefinitionTerm) elementIterator.next(); + assertNotNull( dt ); + assertEquals( "Term 21.", dt.getFirstChild().asText().trim() ); + dd = (HtmlDefinitionDescription) elementIterator.next(); + assertNotNull( dd ); + assertEquals( "Description 21.", dd.getFirstChild().asText().trim() ); + + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "Paragraph 1 below list.", p.asText().trim() ); + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "Paragraph 2 below list.", p.asText().trim() ); + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "Paragraph 3 below list.", p.asText().trim() ); + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "Paragraph 4 below list.", p.asText().trim() ); + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "Paragraph 5 below list.", p.asText().trim() ); + p = (HtmlParagraph) elementIterator.next(); + assertNotNull( p ); + assertEquals( "Paragraph 6 below list.", p.asText().trim() ); + + assertFalse( elementIterator.hasNext() ); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/RenderingContextTest.java b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/RenderingContextTest.java new file mode 100644 index 000000000..2435847e5 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/RenderingContextTest.java @@ -0,0 +1,76 @@ +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.File; + +import org.apache.maven.doxia.siterenderer.RenderingContext; +import org.codehaus.plexus.PlexusTestCase; + +/** + * @author olamy + * @since 20 oct. 07 + */ +public class RenderingContextTest + extends PlexusTestCase +{ + + /** + * Test getRelativePath() with various file names. + * + * @throws java.lang.Exception if any. + */ + public void testFileNameWithDot() + throws Exception + { + File baseDir = new File( getBasedir() + File.separatorChar + "test" + File.separatorChar + "resources" ); + String docName = "file.with.dot.in.name.xml"; + + RenderingContext renderingContext = new RenderingContext( baseDir, "test", docName, "", "xml", false ); + assertEquals( "file.with.dot.in.name.html", renderingContext.getOutputName() ); + assertEquals( ".", renderingContext.getRelativePath() ); + + renderingContext = new RenderingContext( baseDir, docName, "generator" ); // not Doxia source + assertEquals( "file.with.dot.in.name.html", renderingContext.getOutputName() ); + assertEquals( ".", renderingContext.getRelativePath() ); + + docName = "index.xml.vm"; + + renderingContext = new RenderingContext( baseDir, "test", docName, "", "xml", false ); + assertEquals( "index.html", renderingContext.getOutputName() ); + assertEquals( ".", renderingContext.getRelativePath() ); + + docName = "download.apt.vm"; + + renderingContext = new RenderingContext( baseDir, "test", docName, "", "apt", false ); + assertEquals( "download.html", renderingContext.getOutputName() ); + assertEquals( ".", renderingContext.getRelativePath() ); + + docName = "path/file.apt"; + renderingContext = new RenderingContext( baseDir, "test", docName, "", "apt", false ); + assertEquals( "path/file.html", renderingContext.getOutputName() ); + assertEquals( "..", renderingContext.getRelativePath() ); + + renderingContext = new RenderingContext( baseDir, docName, "generator" ); + assertEquals( "path/file.html", renderingContext.getOutputName() ); + assertEquals( "..", renderingContext.getRelativePath() ); + } + +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/SkinResourceLoaderTest.java b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/SkinResourceLoaderTest.java new file mode 100644 index 000000000..6f7a5e976 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/SkinResourceLoaderTest.java @@ -0,0 +1,63 @@ +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.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.maven.doxia.sink.impl.AbstractSink; +import org.codehaus.plexus.util.IOUtil; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class SkinResourceLoaderTest +{ + private SkinResourceLoader skinResourceLoader = new SkinResourceLoader(); + + @Test + public void testNormalizeNewline() throws Exception + { + String EOL = AbstractSink.EOL; + String EOL_MACOS9 = "\r"; + String EOL_UNIX = "\n"; + String EOL_WIN = "\r\n"; + + assertEquals( "Hello " + EOL + " world", normalizeNewline( "Hello " + EOL_MACOS9 + " world" ) ); + assertEquals( "Hello " + EOL + " world", normalizeNewline( "Hello " + EOL_UNIX + " world" ) ); + assertEquals( "Hello " + EOL + " world", normalizeNewline( "Hello " + EOL_WIN + " world" ) ); + + assertEquals( "Hello world" + EOL, normalizeNewline( "Hello world" + EOL_MACOS9 ) ); + assertEquals( "Hello world" + EOL, normalizeNewline( "Hello world" + EOL_UNIX ) ); + assertEquals( "Hello world" + EOL, normalizeNewline( "Hello world" + EOL_WIN ) ); + + assertEquals( EOL + "Hello world", normalizeNewline( EOL_MACOS9 + "Hello world" ) ); + assertEquals( EOL + "Hello world", normalizeNewline( EOL_UNIX + "Hello world" ) ); + assertEquals( EOL + "Hello world", normalizeNewline( EOL_WIN + "Hello world" ) ); + } + + private String normalizeNewline( String text ) throws IOException + { + InputStream in = new ByteArrayInputStream( text.getBytes() ); + InputStream out = skinResourceLoader.normalizeNewline( in ); + return IOUtil.toString( out ); + } +} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/dtd/xhtml-lat1.ent b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/dtd/xhtml-lat1.ent new file mode 100644 index 000000000..ffee223eb --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/dtd/xhtml-lat1.ent @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/dtd/xhtml-special.ent b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/dtd/xhtml-special.ent new file mode 100644 index 000000000..ca358b2fe --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/dtd/xhtml-special.ent @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/dtd/xhtml-symbol.ent b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/dtd/xhtml-symbol.ent new file mode 100644 index 000000000..63c2abfa6 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/dtd/xhtml-symbol.ent @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/dtd/xhtml1-transitional.dtd b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/dtd/xhtml1-transitional.dtd new file mode 100644 index 000000000..c62c02a5f --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/dtd/xhtml1-transitional.dtd @@ -0,0 +1,1201 @@ + + + + + +%HTMLlat1; + + +%HTMLsymbol; + + +%HTMLspecial; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/log4j.properties b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/log4j.properties new file mode 100644 index 000000000..45cd5b389 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/log4j.properties @@ -0,0 +1,24 @@ +# 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. + +log4j.rootCategory=INFO, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%5p [%c{1}:%L] %d{ISO8601} - %m%n + +log4j.logger.com.gargoylesoftware.htmlunit=ERROR diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/org/apache/maven/doxia/siterenderer/velocity-toolmanager.expected.txt b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/org/apache/maven/doxia/siterenderer/velocity-toolmanager.expected.txt new file mode 100644 index 000000000..eb6fed940 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/org/apache/maven/doxia/siterenderer/velocity-toolmanager.expected.txt @@ -0,0 +1,42 @@ +Maven 2 & 3 +This is... + +We have the following keys in the context: + 1. FileUtils + 2. PathTool + 3. StringUtils + 4. alignedFileName + 5. alternator + 6. authors + 7. bodyContent + 8. class + 9. context + 10. convert + 11. currentDate + 12. currentFileName + 13. date + 14. dateFormat + 15. dateRevision + 16. decoration + 17. display + 18. docRenderingContext + 19. doxiaSiteRendererVersion + 20. esc + 21. field + 22. headContent + 23. i18n + 24. link + 25. locale + 26. loop + 27. math + 28. number + 29. plexus + 30. publishDate + 31. relativePath + 32. render + 33. shortTitle + 34. sorter + 35. supportedLocales + 36. text + 37. title + 38. xml diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/org/apache/maven/doxia/siterenderer/velocity-toolmanager.vm b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/org/apache/maven/doxia/siterenderer/velocity-toolmanager.vm new file mode 100644 index 000000000..64a430af3 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/org/apache/maven/doxia/siterenderer/velocity-toolmanager.vm @@ -0,0 +1,7 @@ +$esc.html('Maven 2 & 3') +$display.truncate("This is a long string.", 10) + +We have the following keys in the context: +#foreach( $key in $sorter.sort( $context.keys ) ) + $display.printf( "%2s", ${foreach.count} ). $key +#end diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site-plugin.properties b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site-plugin.properties new file mode 100644 index 000000000..9e19dc3f7 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site-plugin.properties @@ -0,0 +1,19 @@ +# 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. + +template.lastpublished=Last Published +template.builtby=Built by diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site-validate/xdoc/entityTest.xml b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site-validate/xdoc/entityTest.xml new file mode 100644 index 000000000..48460b7d8 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site-validate/xdoc/entityTest.xml @@ -0,0 +1,71 @@ + + + + + +%HTMLlat1; + + + + + + + +]> + + + Test entities, cdatas and comments + + Ligature Æ + Ampersand & + Less than < + Greater than > + Apostrophe ' + Quote " + test@email.com + test©email.com + + + + +
    + +

    Entities

    +

    Generic Entities: '&' '<' '>' '"' '''

    +

    '&' '<' '>' '"' '''

    + +

    Local Entities: 'Α' 'Β' 'Γ' '&tritPos;'

    +

    'Α' 'Β' 'Γ' '&tritPos;𝟭' '&flo;ř' '&myCustomEntity;'

    + +

    DTD Entities: ' ' '¡' '¢'

    +

    ' ' '¡' '¢'

    + +

    CDATA

    + ]]> +

    + +
    + + + +
    diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site-validate/xdoc/xdoc-2.0.xsd b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site-validate/xdoc/xdoc-2.0.xsd new file mode 100644 index 000000000..065b90e53 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site-validate/xdoc/xdoc-2.0.xsd @@ -0,0 +1,2963 @@ + + + + + + + + + Xdoc 2.0 XML Schema. + + This is based on: Extensible HTML version 1.0 Transitional XML Schema + http://www.w3.org/2002/08/xhtml/xhtml1-transitional.xsd + + For further information, see: + http://maven.apache.org/doxia/references/xdoc-format.html + + + + + + + + ================ Character mnemonic entities ========================= + + XHTML entity sets are identified by the PUBLIC and SYSTEM identifiers: + + PUBLIC "-//W3C//ENTITIES Latin 1 for XHTML//EN" + SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent" + + PUBLIC "-//W3C//ENTITIES Special for XHTML//EN" + SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent" + + PUBLIC "-//W3C//ENTITIES Symbols for XHTML//EN" + SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent" + + + + + + ================== Imported Names ==================================== + + + + + + + media type, as per [RFC2045] + + + + + + + + + comma-separated list of media types, as per [RFC2045] + + + + + + + + + a character encoding, as per [RFC2045] + + + + + + + + + a space separated list of character encodings, as per [RFC2045] + + + + + + + + + a language code, as per [RFC3066] + + + + + + + + + a single character, as per section 2.2 of [XML] + + + + + + + + + + + one or more digits + + + + + + + + + + + tabindex attribute specifies the position of the current element + in the tabbing order for the current document. This value must be + a number between 0 and 32767. User agents should ignore leading zeros. + + + + + + + + + + + + space-separated list of link types + + + + + + + + + single or comma-separated list of media descriptors + + + + + + + + + + + a Uniform Resource Identifier, see [RFC2396] + + + + + + + + + a space separated list of Uniform Resource Identifiers + + + + + + + + + date and time information. ISO date format + + + + + + + + + script expression + + + + + + + + + style sheet data + + + + + + + + + used for titles etc. + + + + + + + + + render in this frame + + + + + + + + + + + nn for pixels or nn% for percentage length + + + + + + + + + + + pixel, percentage, or relative + + + + + + + + + + + integer representing length in pixels + + + + + + + + these are used for image maps + + + + + + + + + + + + + + + + comma separated list of lengths + + + + + + + + + + + used for object, applet, img, input and iframe + + + + + + + + + + + + + + + a color using sRGB: #RRGGBB as Hex values + + There are also 16 widely known color names with their sRGB values: + + Black = #000000 Green = #008000 + Silver = #C0C0C0 Lime = #00FF00 + Gray = #808080 Olive = #808000 + White = #FFFFFF Yellow = #FFFF00 + Maroon = #800000 Navy = #000080 + Red = #FF0000 Blue = #0000FF + Purple = #800080 Teal = #008080 + Fuchsia= #FF00FF Aqua = #00FFFF + + + + + + + + + + =================== Generic Attributes =============================== + + + + + + + core attributes common to most elements + id document-wide unique id + class space separated list of classes + style associated style info + title advisory title/amplification + + + + + + + + + + + + internationalization attributes + lang language code (backwards compatible) + xml:lang language code (as per XML 1.0 spec) + dir direction for weak/neutral text + + + + + + + + + + + + + + + + + + attributes for common UI events + onclick a pointer button was clicked + ondblclick a pointer button was double clicked + onmousedown a pointer button was pressed down + onmouseup a pointer button was released + onmousemove a pointer was moved onto the element + onmouseout a pointer was moved away from the element + onkeypress a key was pressed and released + onkeydown a key was pressed down + onkeyup a key was released + + + + + + + + + + + + + + + + + + attributes for elements that can get the focus + accesskey accessibility key character + tabindex position in tabbing order + onfocus the element got the focus + onblur the element lost the focus + + + + + + + + + + + + + + + + + + text alignment for p, div, h1-h6. The default is + align="left" for ltr headings, "right" for rtl + + + + + + + + + + + + + + + + + =================== Text Elements ==================================== + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + these can only occur at block level + + + + + + + + + + + + + these can only occur at block level + + + + + + + + + + + + + + + + + + + + + + "Inline" covers inline or "text-level" element + + + + + + + + + + + ================== Block level elements ============================== + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "Flow" mixes block and inline and is used for list items etc. + + + + + + + + + + + + + ================== Content models for exclusions ===================== + + + + + + + a elements use "Inline" excluding a + + + + + + + + + + + + + + + pre uses "Inline" excluding img, object, applet, big, small, + font, or basefont + + + + + + + + + + + + + + + + form uses "Flow" excluding form + + + + + + + + + + + + + button uses "Flow" but excludes a, form, form controls, iframe + + + + + + + + + + + + + + + + + + + + + + + + + ================ Document Head ======================================= + + + + + + + + + + + + + + + + + + + + content model is "head.misc" combined with a single + title and an optional base element in any order + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The title element is not considered part of the flow of text. + It should be displayed, for example as the page header or + window title. Exactly one title is required per document. + + + + + + + + + + + + document base URI + + + + + + + + + + + + + generic metainformation + + + + + + + + + + + + + + + + Relationship values can be used in principle: + + a) for document specific toolbars/menus when used + with the link element in document head e.g. + start, contents, previous, next, index, end, help + b) to link to a separate style sheet (rel="stylesheet") + c) to make a link to a script (rel="script") + d) by stylesheets to control how collections of + html nodes are rendered into printed documents + e) to make a link to a printable version of this document + e.g. a PostScript or PDF version (rel="alternate" media="print") + + + + + + + + + + + + + + + + + + + style info, which may include CDATA sections + + + + + + + + + + + + + + + + script statements, which may include CDATA sections + + + + + + + + + + + + + + + + + + + + + + + alternate content container for non script-based rendering + + + + + + + + + + + + + + ======================= Frames ======================================= + + + + + + + inline subwindow + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + alternate content container for non frame-based rendering + + + + + + + + + + + + + + =================== Document Body ==================================== + + + + + + + generic language/style container + + + + + + + + + + + + + + + =================== Paragraphs ======================================= + + + + + + + + + + + + + + + + + =================== Headings ========================================= + + There are six levels of headings from h1 (the most important) + to h6 (the least important). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + =================== Lists ============================================ + + + + + + + Unordered list bullet styles + + + + + + + + + + + + + Unordered list + + + + + + + + + + + + + + + + + + + + + + Ordered list numbering style + + 1 arabic numbers 1, 2, 3, ... + a lower alpha a, b, c, ... + A upper alpha A, B, C, ... + i lower roman i, ii, iii, ... + I upper roman I, II, III, ... + + The style is applied to the sequence number which by default + is reset to 1 for the first list item in an ordered list. + + + + + + + + + Ordered (numbered) list + + + + + + + + + + + + + + + + + + + + + + + single column list (DEPRECATED) + + + + + + + + + + + + + + + + + + + + + multiple column list (DEPRECATED) + + + + + + + + + + + + + + + + + + + + + LIStyle is constrained to: "(ULStyle|OLStyle)" + + + + + + + + + list item + + + + + + + + + + + + + + + + definition lists - dt for term, dd for its definition + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + =================== Address ========================================== + + + + + + + information on author + + + + + + + + + + + + + + + =================== Horizontal Rule ================================== + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + =================== Preformatted Text ================================ + + + + + + + content is "Inline" excluding + "img|object|applet|big|small|sub|sup|font|basefont" + + + + + + + + + + + + + + + + =================== Block-like Quotes ================================ + + + + + + + + + + + + + + + + + =================== Text alignment =================================== + + + + + + + center content + + + + + + + + + + + + + + =================== Inserted/Deleted Text ============================ + + ins/del are allowed in block and inline content, but its + inappropriate to include block content within an ins element + occurring in inline content. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ================== The Anchor Element ================================ + + + + + + + content is "Inline" except that anchors shouldn't be nested + + + + + + + + + + + + + + + + + + + + + + + + + ===================== Inline Elements ================================ + + + + + + + generic language/style container + + + + + + + + + + + + + + + I18N BiDi over-ride + + + + + + + + + + + + + + + + + + + + + + + + + + forced line break + + + + + + + + + + + + + + + + + + + + + emphasis + + + + + + + + + + + + + + + strong emphasis + + + + + + + + + + + + + + + definitional + + + + + + + + + + + + + + + program code + + + + + + + + + + + + + + + sample + + + + + + + + + + + + + + + something user would type + + + + + + + + + + + + + + + variable + + + + + + + + + + + + + + + citation + + + + + + + + + + + + + + + abbreviation + + + + + + + + + + + + + + + acronym + + + + + + + + + + + + + + + inlined quote + + + + + + + + + + + + + + + + subscript + + + + + + + + + + + + + + + superscript + + + + + + + + + + + + + + + fixed pitch font + + + + + + + + + + + + + + + italic font + + + + + + + + + + + + + + + bold font + + + + + + + + + + + + + + + bigger font + + + + + + + + + + + + + + + smaller font + + + + + + + + + + + + + + + underline + + + + + + + + + + + + + + + strike-through + + + + + + + + + + + + + + + strike-through + + + + + + + + + + + + + + + base font size + + + + + + + + + + + + + + local change to font + + + + + + + + + + + + + + + + + + ==================== Object ====================================== + + object is used to embed objects as part of HTML pages. + param elements should precede other content. Parameters + can also be expressed as attribute/value pairs on the + object element itself when brevity is desired. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + param is used to supply a named property value. + In XML it would seem natural to follow RDF and support an + abbreviated syntax where the param elements are replaced + by attribute value pairs on the object start tag. + + + + + + + + + + + + + + + + + + + + + + =================== Java applet ================================== + + One of code or object attributes must be present. + Place param elements before other content. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + =================== Images =========================================== + + To avoid accessibility problems for people who aren't + able to see the image, you should provide a text + description using the alt and longdesc attributes. + In addition, avoid the use of server-side image maps. + + + + + + + + + + + + + + + + usemap points to a map element which may be in this document + or an external document, although the latter is not widely supported + + + + + + + + + + + + + + + + + + + + ================== Client-side image maps ============================ + + These can be placed in the same document or grouped in a + separate document although this isn't yet widely supported + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ================ Forms =============================================== + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Each label must not contain more than ONE field + Label elements shouldn't be nested. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + form control + + + + + + + + + + the name attribute is required for all but submit & reset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + option selector + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + option group + + + + + + + + + + + + + + + + + + + + + + selectable choice + + + + + + + + + + + + + + + + + + + + + + + + + + + multi-line text field + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The fieldset element is used to group form fields. + Only one legend element should occur in the content + and if present should only be preceded by whitespace. + + NOTE: this content model is different from the XHTML 1.0 DTD, + closer to the intended content model in HTML4 DTD + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fieldset label + + + + + + + + + + + + + + + + + Content is "Flow" excluding a, form and form controls + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + single-line text input control (DEPRECATED) + + + + + + + + + + + + ======================= Tables ======================================= + + Derived from IETF HTML table standard, see [RFC1942] + + + + + + + The border attribute sets the thickness of the frame around the + table. The default units are screen pixels. + + The frame attribute specifies which parts of the frame around + the table should be rendered. The values are not the same as + CALS to avoid a name clash with the valign attribute. + + + + + + + + + + + + + + + + + + + The rules attribute defines which rules to draw between cells: + + If rules is absent then assume: + "none" if border is absent or border="0" otherwise "all" + + + + + + + + + + + + + + + horizontal placement of table relative to document + + + + + + + + + + + + + horizontal alignment attributes for cell contents + + char alignment char, e.g. char=':' + charoff offset for alignment char + + + + + + + + + + + + + + + + + + + + + vertical alignment attributes for cell contents + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Use thead to duplicate headers when breaking table + across page boundaries, or for static headers when + tbody sections are rendered in scrolling panel. + + Use tfoot to duplicate footers when breaking table + across page boundaries, or for static footers when + tbody sections are rendered in scrolling panel. + + Use multiple tbody sections when rules are needed + between groups of table rows. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + colgroup groups a set of col elements. It allows you to group + several semantically related columns together. + + + + + + + + + + + + + + + + + + col elements define the alignment properties for cells in + one or more columns. + + The width attribute specifies the width of the columns, e.g. + + width=64 width in screen pixels + width=0.5* relative width of 0.5 + + The span attribute causes the attributes of one + col element to apply to more than one column. + + + + + + + + + + + + + + + + + + + + + + + + + + + + Scope is simpler than headers attribute for common tables + + + + + + + + + + + + + th is for headers, td for data and for cells acting as both + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ================== Xdoc Specific ===================================== + + + + + + ================ Document Structure ================================== + + + + + + 2.0.0 + + The <document/> element is the root of the Xdoc descriptor. + + + + + + + 2.0.0 + + Optional properties element for this document element. + + + + + + 2.0.0 + + Optional head element for this document element. + + + + + + 2.0.0 + + Required body element for this document element. + + + + + + + + 2.0.0 + + The identifier of this document element. + + + + + + + + + ================ Document Properties ================================= + + + + + + + + + 2.0.0 + + Required title element for the document element. + + + + + + 2.0.0 + + Optional author element for the document element. + + + + + + 2.0.0 + + Optional creation/last updated date for the document element. + + + + + + + + + + 2.0.0 + + An author element. + + + + + + 2.0.0 + + The email attribute for the author element. + + + + + + + + + 2.0.0 + + An date element. The date is recommended (but it is not a requirement) to be align to + the ISO-8601 standard, i.e.: YYYY-MM-DD. + + + + + + + + =================== Document Body ==================================== + + + + + + 2.0.0 + + The body element. + + + + + + + 2.0.0 + + A section in the body element. + + + + + + + + + + + 2.0.0 + + A section element. + + + + + + + + + + + 2.0.0 + + A subsection in the section element. + + + + + + + + + + + + 2.0.0 + + A subsection element. + + + + + + + + + + + + + + + + + ================ Document Addons ===================================== + See "special.extra" group. + + + + + + 2.0.0 + + A source element. + + + + + + + + + + + + 2.0.0 + + A macro element. + + + + + + + + + + \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/apt/apt.apt b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/apt/apt.apt new file mode 100644 index 000000000..77a214ffc --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/apt/apt.apt @@ -0,0 +1,61 @@ + ----- + Anchors / Links in APT + ----- + Lukas Theussl + ----- + May 2008 + ----- + +~~ 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. + +~~ NOTE: For help with the syntax of this file, see: +~~ http://maven.apache.org/doxia/references/apt-format.html + +Links + + {Anchor} + {cdc.html} + Link to {{Anchor}}. + Link to anchor {{{Anchor}showing alternate text}}. + {Anchor with space} and {{{Anchor_with_space}link to it}}. + Link to {{http://maven.apache.org/}}. + Link to {{{http://maven.apache.org/}Maven home page}}. + Link to {{{./cdc.html}other document}} and within {{{cdc.html}same document}}. + Link to {{{/index.html}root document}}. + +Section formatting: <> <<>> + +* SubSection formatting: <> <<>> + + <> <<>> + +{No Default Anchor in Section Title with Explicit Anchor} + +~~ DOXIASITETOOLS-146 comments are not rendered + +TOC Macro + +%{toc} + +Echo Macro + +%{echo|param1=value1|param2=value2} + +Snippet Macro + +%{snippet|id=macrotest|file=src/test/resources/site/site.xml} diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/apt/cdc.apt b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/apt/cdc.apt new file mode 100644 index 000000000..b61ce893c --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/apt/cdc.apt @@ -0,0 +1,107 @@ + ----- + Plexus Component Descriptor Creator + ----- + Trygve Laugstøl + ----- + 25th May 2005 + ----- + +~~ 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. + +~~ NOTE: For help with the syntax of this file, see: +~~ http://maven.apache.org/doxia/references/apt-format.html + +Plexus Component Descriptor Creator + + <> + + The Component Descriptor Creator (or CDC for short) is a tool that will create + the <<>> file from your Java code. It uses a set of JavaDoc + tags to gather the information it needs to create the component descriptor. + +* Component Tags + +*-------------------------------*--------------*---------------------------------------------* +| <> | <> | <> | +*-------------------------------*---*--------------------------------------------------------* +| @plexus.component | y | Marker tag to mark a class as a Plexus component. | +*-------------------------------*---*--------------------------------------------------------* +| @plexus.role | y | The role of the component. | +*-------------------------------*---*--------------------------------------------------------* +| @plexus.version | n | The component version. | +*-------------------------------*---*--------------------------------------------------------* +| @plexus.lifecycle-handler | n | The lifecycle handler of the component. | +*-------------------------------*---*--------------------------------------------------------* +| @plexus.instatiation-strategy | n | The instantiation strategy of the component. | +*-------------------------------*---*--------------------------------------------------------* + ++---+ +/** + * @plexus.component + * @plexus.lifecycle-handler plexus-configurable + */ +public class DefaultActionManager + extends AbstractLogEnabled + implements ActionManager, Serviceable +{ ++---+ +~~ [example-class-tags] Example Class Tags + +* Requirement Tags + + These tags are used on fields to state requirements. + +*-------------------------------*----------------*-------------------------------------------* +| <> | <> | <> | +*-------------------------------*-----*------------------------------------------------------* +| @plexus.requirement | y | Marker tag to mark this field as requirement | +*-------------------------------*-----*------------------------------------------------------* +| @plexus.role | y/n | Only required if the field is a List or Map | +*-------------------------------*-----*------------------------------------------------------* +| @plexus.role-hint | n | | +*-------------------------------*-----*------------------------------------------------------* + ++---+ +/** + * @plexus.requirement + */ +private ActionManager actionManager; ++---+ +~~ [example-requirement-tags] Example Requirement Tags + +* Configuration Tags + + Note that the default value tag won't be required in the future when these + tags will be used to generate a separate configuration declaration section. + +*-------------------------------*--------------*---------------------------------------------* +| <> | <> | <> | +*-------------------------------*---*--------------------------------------------------------* +| @plexus.configuration | y | Marker tag to mark this field as configureable | +*-------------------------------*---*--------------------------------------------------------* +| @plexus.default-value | y | Sets the default value for the configuration field | +*-------------------------------*---*--------------------------------------------------------* + ++---+ +/** + * @plexus.requirement + * @plexus.role-hint velocity + */ +private Renderer renderer; ++---+ +~~ [example-requirement-tags] Example Requirement Tags diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/apt/extension.apt.not.at.end.apt b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/apt/extension.apt.not.at.end.apt new file mode 100644 index 000000000..01472896b --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/apt/extension.apt.not.at.end.apt @@ -0,0 +1,32 @@ +----- + extension in filename + ----- + Hervé Boutemy + ----- + 2015-12-19 + ----- + +~~ 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. + +~~ NOTE: For help with the syntax of this file, see: +~~ http://maven.apache.org/doxia/references/apt-format.html + +RenderingContext + + When transforming source file name into output file name, it is important to use last ".apt" and not first one + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/apt/interpolation.apt b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/apt/interpolation.apt new file mode 100644 index 000000000..1c938bb5a --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/apt/interpolation.apt @@ -0,0 +1,33 @@ + ----- + Project goodies that are available in the project + ----- + Maven Monkey + ----- + + ----- + +~~ 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. + +~~ NOTE: For help with the syntax of this file, see: +~~ http://maven.apache.org/doxia/references/apt-format.html + +Things you can do with Velocity + + * project = ${project.name} ${project.version} + * $currentFileName + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/anchor.confluence b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/anchor.confluence new file mode 100644 index 000000000..425dcbabd --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/anchor.confluence @@ -0,0 +1,7 @@ +h1. Section Title + +{anchor:start}Simple paragraph. + +Simple paragraph{anchor:end} + +Simple {anchor:middle} paragraph. diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/code.confluence b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/code.confluence new file mode 100644 index 000000000..d4f73d53a --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/code.confluence @@ -0,0 +1,21 @@ +Simple paragraph. + +{code:title=Cat.java} +public class Cat { + public void sitOn(Mat mat) { + // ... code here + } +} +{code} + +Another paragraph (the title of the code block is ignored). + +Simple paragraph with embedded code +{code} +public class Cat { + public void sitOn(Mat mat) { + // ... code here + } +} +{code} +in the same paragraph (this didn't work until DOXIA-181). See also DOXIA-302. \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/escapes.confluence b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/escapes.confluence new file mode 100644 index 000000000..82e453369 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/escapes.confluence @@ -0,0 +1,6 @@ +Simple paragraph with asterisk \* and underline \_. + +Simple paragraph with asterisk \*not bold\* and underline \_not italic\_. + +Simple paragraph with normal \character escaped. + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/figure.confluence b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/figure.confluence new file mode 100644 index 000000000..5a35dca45 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/figure.confluence @@ -0,0 +1,22 @@ +Simple paragraph. + +!images/build-by-maven-white.png! + +Simple paragraph with attempted inline !image.jpg! (should fail). + +DOXIA-361: + +!images/build-by-maven-white.png! With caption on same line + +!images/build-by-maven-white.png!\\ +With caption underneath and linebreak + +!images/build-by-maven-white.png! +With caption underneath and no linebreak + +!images/build-by-maven-white.png! +With *bold* caption underneath + +DOXIA-303: + +!images/build-by-maven-white.png|align=right, vspace=4! \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/linebreak.confluence b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/linebreak.confluence new file mode 100644 index 000000000..d076a6dae --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/linebreak.confluence @@ -0,0 +1,5 @@ +Line\\ +break. + +Line\\with 2\\inline\\ +Breaks. \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/link.confluence b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/link.confluence new file mode 100644 index 000000000..7d543680e --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/link.confluence @@ -0,0 +1,5 @@ +Line with [middle] link + +Line with link at the [end] + +Line with [link#anchor] and [#simple] anchor and [alias|link] \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/nested-format.confluence b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/nested-format.confluence new file mode 100644 index 000000000..9dbbefb5b --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/nested-format.confluence @@ -0,0 +1,6 @@ + +A paragraph with *_bold italic_* _*italic bold*_ *{{bold monospaced}}* + +A paragraph with *bold _italic_* _italic *bold*_ *bold {{monospaced}}* + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/nested-list-heterogenous.confluence b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/nested-list-heterogenous.confluence new file mode 100644 index 000000000..37884187c --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/nested-list-heterogenous.confluence @@ -0,0 +1,4 @@ +* A top level list item +*# A nested list item +*# Another nested list item +* Back at the top level diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/nested-list.confluence b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/nested-list.confluence new file mode 100644 index 000000000..2a4e719aa --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/nested-list.confluence @@ -0,0 +1,8 @@ + +A paragraph + +* A top level list item +** A nested list item +** Another nested list item +* Back at the top level + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/note-tip-info.confluence b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/note-tip-info.confluence new file mode 100644 index 000000000..75e83a8d5 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/note-tip-info.confluence @@ -0,0 +1,27 @@ +Simple paragraph. + +{note:title=Be Careful} +The body of the note here.. +{note} + +Tip + +{tip:title=Guess What?} +The body of the tip here.. +{tip} + +Info + +{info:title=Some +Info} +The body of the info here.. +{info} + +Quote + +{quote:title=Simon Says} +The body of the *quote* here.. +{quote} + +(In all cases there is no way to reverse the Doxia output +back to confluence because they are all rendered as a defitionList.) \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/paragraph-figure.confluence b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/paragraph-figure.confluence new file mode 100644 index 000000000..6d3b02756 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/paragraph-figure.confluence @@ -0,0 +1,6 @@ + +A paragraph +!images/logo.png! +with a figure + +Another paragraph diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/paragraph-header.confluence b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/paragraph-header.confluence new file mode 100644 index 000000000..5467b0bef --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/paragraph-header.confluence @@ -0,0 +1,6 @@ + +A paragraph +h2. A header +with a header + +Another paragraph diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/paragraph-list.confluence b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/paragraph-list.confluence new file mode 100644 index 000000000..df0d9f87d --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/paragraph-list.confluence @@ -0,0 +1,8 @@ + +A paragraph +* A nested list item +* Another nested list item +with two lines + +Back at the top level + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/section.confluence b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/section.confluence new file mode 100644 index 000000000..d28fe4ccf --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/section.confluence @@ -0,0 +1,9 @@ +h1. Section1 + +h2. Section2 +h3. Section3 + +h4. Section4 +h5. Section5 + +h1. TitleWithLeadingSpace diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/simple-list.confluence b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/simple-list.confluence new file mode 100644 index 000000000..7b213a408 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/simple-list.confluence @@ -0,0 +1,14 @@ +* Simple paragraph with *bold* and _italic_ text. +* Here is a link to [JIRA|http://jira.codehaus.org] +* Here is a link with no text [http://jira.codehaus.org] +* This is some {{monospaced}} text. +* Item with no formatting + +Paragraph + +* One bullet + +* A list item with +more than one line + +*bold text, not a list!* \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/simple-paragraph.confluence b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/simple-paragraph.confluence new file mode 100644 index 000000000..00d15f231 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/simple-paragraph.confluence @@ -0,0 +1,8 @@ +Simple paragraph with *bold* and _italic_ text. + +Here is a link to [JIRA|http://jira.codehaus.org] + +Here is a link with no text [http://jira.codehaus.org] + +This is some {{monospaced}} text. + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/table-link.confluence b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/table-link.confluence new file mode 100644 index 000000000..cdab3a2ae --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/table-link.confluence @@ -0,0 +1,4 @@ +|| Version || Download || Format || +|0.1.1 |[Download|http://example.com/release.0.1.1/ex-win32-win32.x86.zip]| 12-12-2008 | zip | +|0.1.2 |[http://example.com/release.0.1.2/ex-win32-win32.x86.zip]| 04-12-2008 | zip | +|0.1.3 |[Download|http://example.com/release.0.1.3/ex-win32-win32.x86.zip]| 03-11-2008 | zip | \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/table.confluence b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/table.confluence new file mode 100644 index 000000000..227a8e0fc --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/table.confluence @@ -0,0 +1,13 @@ + +Simple table: + +||heading 1||heading 2||heading 3|| +|col A1|col A2|col A3| +|col B1|col B2|col B3| + +Table with links (DOXIA-301): + +|| Version || Download || Date || Format || +|0.1.1 | [Download|http://example.com/release.0.1.1/ex-win32-win32.x86.zip] | 12-12-2008 | zip | +|0.1.2 | [Download|http://example.com/release.0.1.2/ex-win32-win32.x86.zip] | 04-12-2008 | zip | +|0.1.3 | [Download|http://example.com/release.0.1.3/ex-win32-win32.x86.zip] | 03-11-2008 | zip | \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/test.confluence b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/test.confluence new file mode 100644 index 000000000..3d5daeac1 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/test.confluence @@ -0,0 +1,98 @@ +This the way that we would like to *translate* sites that are primarily +authored in confluence while at the same time having the site be rendered in +a _standard way on a static website_. + +---- + +Here is a link to [JIRA|http://jira.codehaus.org] + +Here is a link with no text [http://jira.codehaus.org] + +This is some {{monospaced}} text. + +* item one +** foo +** bar +* item two +* item three + +Some more text + +# number one +# number two +# number three + +||one||two||three|| +|foo|bar|baz| + +h1. I am h1 + +this is how you would code a mojo! + +{code} +public class MyMojo + extends AbstractMojo +{ + /** + * @parameter expression="${plugin.artifacts}" + * @required + */ + private List pluginArtifacts; + + public void execute() + throws MojoExecutionException + { + ... + for ( Iterator i = pluginArtifacts.iterator(); i.hasNext(); ) + { + Artifact pluginArtifact = (Artifact) i.next(); + } + ... + } +} +{code} + +h2. I am h2 + +this is the way of the world + +{noformat} +public class MyMojo + extends AbstractMojo +{ + /** + * @parameter expression="${plugin.artifacts}" + * @required + */ + private List pluginArtifacts; + + public void execute() + throws MojoExecutionException + { + ... + for ( Iterator i = pluginArtifacts.iterator(); i.hasNext(); ) + { + Artifact pluginArtifact = (Artifact) i.next(); + } + ... + } +} +{noformat} + +h3. I am h3 + +this is the way of the world + +h4. I am h4 + +this is the way of the world + +h5. I am h5 + +this is the way of the world + +h1. Answered Questions + +h3. What can I do to get the Maven love? + +Well, you just have to be calm and the maven love will come your way. diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/unknown-macro.confluence b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/unknown-macro.confluence new file mode 100644 index 000000000..3b84a0c6c --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/confluence/confluence/unknown-macro.confluence @@ -0,0 +1,5 @@ +{unknown:start}Simple paragraph. + +Simple paragraph{unknown:end}. + +Simple {unknown:middle} paragraph. diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/docbook/docbook.xml b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/docbook/docbook.xml new file mode 100644 index 000000000..585096525 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/docbook/docbook.xml @@ -0,0 +1,31 @@ + + + + +
    + Title + +
    + Section title + + Some content +
    +
    \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/docbook/sdocbook_full.xml b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/docbook/sdocbook_full.xml new file mode 100644 index 000000000..085eeda36 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/docbook/sdocbook_full.xml @@ -0,0 +1,546 @@ + + + + + + +
    + + + + Title + Or How I Learned to Live + citetitle + + Configuring Menus + + CorpAuthor + + Date + + Assn. + + + articleinfo Abstract Title + articleinfo Abstract Content + + + + PhD + Main + Author + Sr. + D + + Director of Cooperative Efforts + Cooperative Engineering + + + Title + super hero. + + + + Dr + Peter + Doe + Sr. + Spiderman + + Director of Cooperative Efforts + Cooperative Engineering + + + Title + super hero. + + + + Authorgroup CorpAuthor + + + Dr + Authorgroup + Author + Sr. + D + + Director of Cooperative Efforts + Cooperative Engineering + + + authorblurb title + He's a super hero in his spare time. + + + + + Dr + Authorgroup + Editor + Sr. + Spiderman + + Director of Cooperative Efforts + Cooperative Engineering + + + authorblurb title + He's a super hero in his spare time. + + + + + Dr + Authorgroup + OtherCredit + Sr. + AlterEgo + + Director of Cooperative Efforts + Cooperative Engineering + + + authorblurb title + He's a super hero in his spare time. + + + + + + + + 1996 + 1997 + me + + + Edition + + + Dr + Peter + Parker + Sr. + Spiderman + + Director of Cooperative Efforts + Cooperative Engineering + + + Title + Peter's a super hero in his spare time. + + + + + key1 + key2 + + + + The Eiffel Tower + + + + Dr + John + Doe + Sr. + Spiderman + + Director of Cooperative Efforts + Cooperative Engineering + + + Title + Peter's a super hero in his spare time. + + + + 1994 + The TeX User's Group + beta + 15 + 3 + No notice is required. + + + + 0.91 + 11 Dec 1996 + ndw + Bug fixes + + + 0.90 + 30 Nov 1996 + ndw + long write up + + + + + Electronic Publishing + SGML (Computer program language) + + + + + + + Abstract Title + In brief. + + +
    + + A trivial example of recursive sections. + + Section title +
    Sub-section title +
    Sub-sub-section title +
    Sub-sub-sub-section title +
    Sub-sub-sub-sub-section title + + + William Shakespeare + Epigraph: What say you? + + + + The Assn. of Computing Machinery + has published Developing SGML DTDs. + + + + In UNIX command ls + with the option is used to get a directory listing. + + + + The output from the date command is eg + Sun Nov 16, 1997 21:03:29. + + + + An email address is eg yo@tu.com. + + + + The symbolic constants for error numbers are defined in + errno.h in + /usr/include/sys. + + + + This software is provided as is. + + + + Execute the command with + command + + filename. + + + + This is hosted at helio.com. + The name pipipo is traded. + The name pipipo is registered. + The name pipipo is copyrighted. + The name pipipo is serviced. + The name pipipo is traded. + + + + At the system prompt, enter xyzzy. + + + + A Sidebar + Sidebar content. + + + + A link to an element with an XRefLabel: . + A straight link generates the cross-reference text: . + + + + + Phaser sound effect + + + A sound effect. + + + + + + + + Italic font. + Bold font. + Monospaced font. + + + + A Real Function + + real function()Error! + + + +
    + William Shakespeare + Blockquote: What say you? +
    + + + An annual percentage rate of + 3.27%The prime rate. + will be charged on all balances. + + + + List item 1. + List item 2.Paragraph contained in list item 2. + + Sub-list item 1. + Sub-list item 2. + + + List item 3. Force end of list: + + + Verbatim text not contained in list item 3 + + + Numbered item 1. + + Numbered item A. + Numbered item B. + + + Numbered item 2. + + + List numbering schemes: [[1]], [[a]], [[A]], [[i]], [[I]]. + + + Defined term 1of definition list. + Defined term 2of definition list.Verbatim text + in a box + + +
    + Figure title + + + Figure caption + +
    + + + Here is an inline image: . + + + + + No grid, no caption: + + + + + + + cell + cell + + + cell + cell + + + + + + + + + + + header + header + + + + + cell + cell + + + + + + + + + + + + + + Horizontal Span + a3a4a5 + + + + + f1f2f3f4f5 + + + + + b1b2b3b4 + + Vertical Span + + + c1 + Span Both + c4 + + + d1d4d5 + + + + + + + + + + + + + + + + a1a2a5 + + + b1b2b3b4b5 + + + c1c2c5 + + + + + + + + + + a1b1c1 + + + a2 + + + + b2a1b2b1b2c1 + + + b2a2b2b2b2c2 + + + b2a3b2b3b2c3 + + + + c2 + + + a3b3c3 + + + + + + + Anchor. + Link to Anchor. + Link to http://www.pixware.fr. + Link to showing alternate text. + Link to Pixware home page. + + + + Upcoming Changes + Future versions of this feature may not be backward-compatible. + +
    +
    +
    +
    +
    + + + A Test Bibliography + Books + + + The World Wide Web Journal + 21. + O'Reilly & Associates, Inc. and + Winter, 1996. + + + + + + Demonstration Appendix + This appendix demonstrates an appendix. + + +
    \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/fml/faq.fml b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/fml/faq.fml new file mode 100644 index 000000000..a6843c541 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/fml/faq.fml @@ -0,0 +1,60 @@ + + + + + + + + Contributing + + + One stupid question & a silly answer? + +

    + A paragraph with a local link, + a link to another source document, + an external link + with entities, an italic text and non-US-ASCII characters: àéèç. +

    +
    +
    + +
    + + + Using Maven + + + How do I disable a report on my site? + +

    + Test nested <source></source> tags (DOXIA-16): +

    + 1.5]]> +
    +
    + +
    + +
    + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/resources/confluence/images/build-by-maven-white.png b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/resources/confluence/images/build-by-maven-white.png new file mode 100644 index 000000000..7d44c9c2e Binary files /dev/null and b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/resources/confluence/images/build-by-maven-white.png differ diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/site.xml b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/site.xml new file mode 100644 index 000000000..d18c154a9 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/site.xml @@ -0,0 +1,57 @@ + + + + + + + + + + Plexus + http://plexus.codehaus.org/images/plexus-logo.png + http://plexus.codehaus.org + + + http://media.codehaus.org/images/unity-codehaus-logo.png + http://www.codehaus.org + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/xdoc/attributes.xml b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/xdoc/attributes.xml new file mode 100644 index 000000000..efbee99e5 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/xdoc/attributes.xml @@ -0,0 +1,93 @@ + + + + + + +
    +

    + + Project +

    + + + + + + + + + + + + + + + +
    NameTelephone
    Telephone555 77 854555 77 855
    555 77 854555 77 855
    +

    + + underline strike-through subscript superscript +

    +

    + + bold-italic italic-bold +

    + +

    + + + +

    + Link to anchor. + Link to anchor showing alternate text. + Link to Maven home page. + Link to other document. + Link to other document. + Link to other document. + Link to other document. + Link to root document. +

    + + + some source + some source + +
    + + +
    + + +
    + +
    + + +
    + + +
    + + +
    + + +
    diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/xdoc/head.xml b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/xdoc/head.xml new file mode 100644 index 000000000..dccf3526a --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/xdoc/head.xml @@ -0,0 +1,39 @@ + + + + + + + + Page Title + John Doe + + + + + + + + + +

    Oi!

    + + +
    diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/xdoc/javascript.xml b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/xdoc/javascript.xml new file mode 100644 index 000000000..16114a46a --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/xdoc/javascript.xml @@ -0,0 +1,45 @@ + + + + + + + Hello + + + + +
    + +

    You should see a JavaScript alert...

    + + + +
    + + + +
    \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/xdoc/macro.xml b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/xdoc/macro.xml new file mode 100644 index 000000000..b57d9c50b --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/xdoc/macro.xml @@ -0,0 +1,75 @@ + + + + + + Test DOXIA-96 + Vincent Siveton + + + + +
    + +
    + +
    + + + + +
    + + +
    + + + + +
    + +
    +

    bla bla...

    +

    bla bla...

    +

    SUB SUB TITLE 1.2.1

    +
    +

    bla bla...

    +
    + +
    +

    bla bla...

    +

    bla bla...

    +

    SUB SUB TITLE 2.2.1

    +
    +

    bla bla...

    +
    + +
    +

    bla bla...

    +

    bla bla...

    +

    SUB SUB TITLE 3.2.1

    +
    +

    bla bla...

    +
    + + +
    diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/xdoc/misc.xml b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/xdoc/misc.xml new file mode 100644 index 000000000..352f24be2 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/xdoc/misc.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/xdoc/multipleblock.xml b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/xdoc/multipleblock.xml new file mode 100644 index 000000000..b9cc6d7ec --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/xdoc/multipleblock.xml @@ -0,0 +1,44 @@ + + + + + + Test DOXIA-93 + Vincent Siveton + + +
    +

    text

    +
      +
    • list1
    • +
    +

    text2

    +
      +
    • list1
    • +
    +

    text3

    +
      +
    • list1

    • +
    +
    + +
    diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/xdoc/nestedItems.xml b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/xdoc/nestedItems.xml new file mode 100644 index 000000000..5ffa9b83d --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/site/xdoc/nestedItems.xml @@ -0,0 +1,195 @@ + + + + + + Test List Issue + + + +
    +

    Unordered lists

    +

    + Below is an unordered list, followed by six paragraphs. +

    +
      +
    • + Item 1. +
        +
      • +

        Item 11.

        +
      • +
      • +

        Item 12.

        +
      • +
      • + Item 13. +
      • +
      • + Item 14. +
      • +
      +
    • +
    • + Item 2. +
    • +
    • + Item 3. +
    • +
    • + Item 4. +
        +
      • + Item 41. +
      • +
      • + Item 42. +
      • +
      • + Item 43. +
      • +
      • + Item 44. +
      • +
      +
    • +
    +

    + Paragraph 1 below list. +

    +

    + Paragraph 2 below list. +

    +

    + Paragraph 3 below list. +

    +

    + Paragraph 4 below list. +

    +

    + Paragraph 5 below list. +

    +

    + Paragraph 6 below list. +

    + +

    Ordered lists

    +

    + Below is an ordered list, followed by six paragraphs. +

    +
      +
    1. + Item 1. +
        +
      1. + Item 11. +
      2. +
      3. + Item 12. +
      4. +
      5. + Item 13. +
      6. +
      7. + Item 14. +
      8. +
      +
    2. +
    3. + Item 2. +
    4. +
    5. + Item 3. +
    6. +
    7. + Item 4. +
        +
      1. + Item 41. +
      2. +
      3. + Item 42. +
      4. +
      5. + Item 43. +
      6. +
      7. + Item 44. +
      8. +
      +
    8. +
    +

    + Paragraph 1 below list. +

    +

    + Paragraph 2 below list. +

    +

    + Paragraph 3 below list. +

    +

    + Paragraph 4 below list. +

    +

    + Paragraph 5 below list. +

    +

    + Paragraph 6 below list. +

    + +

    Definition lists

    +

    + Below is a definition list, followed by six paragraphs. +

    +
    +
    Term 1.
    +
    Description 1.
    +
    Term 2.
    +
    Description 2. +
    +
    Term 21.
    +
    Description 21.
    +
    +
    +
    +

    + Paragraph 1 below list. +

    +

    + Paragraph 2 below list. +

    +

    + Paragraph 3 below list. +

    +

    + Paragraph 4 below list. +

    +

    + Paragraph 5 below list. +

    +

    + Paragraph 6 below list. +

    +
    + +
    diff --git a/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/xhtml-lat1.ent b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/xhtml-lat1.ent new file mode 100644 index 000000000..ffee223eb --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-site-renderer/src/test/resources/xhtml-lat1.ent @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-skin-model/pom.xml b/Java-base/maven-doxia-sitetools/src/doxia-skin-model/pom.xml new file mode 100644 index 000000000..1ea9e4e06 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-skin-model/pom.xml @@ -0,0 +1,88 @@ + + + + + + 4.0.0 + + + org.apache.maven.doxia + doxia-sitetools + 1.9.3-SNAPSHOT + ../pom.xml + + + doxia-skin-model + + Doxia Sitetools :: Skin Model + The Skin Model defines metadata for Doxia Sitetools skins. + + + + org.codehaus.plexus + plexus-utils + + + + + + + org.codehaus.modello + modello-maven-plugin + + + src/main/mdo/skin.mdo + + + 1.7.0 + 1.7.0 + + + + descriptor + generate-sources + + java + xpp3-reader + xsd + + + + descriptor-xdoc + pre-site + + xdoc + + + + descriptor-xsd + pre-site + + xsd + + + ${project.build.directory}/generated-site/resources/xsd + + + + + + + diff --git a/Java-base/maven-doxia-sitetools/src/doxia-skin-model/src/main/mdo/skin.mdo b/Java-base/maven-doxia-sitetools/src/doxia-skin-model/src/main/mdo/skin.mdo new file mode 100644 index 000000000..99b7eeb28 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-skin-model/src/main/mdo/skin.mdo @@ -0,0 +1,101 @@ + + + + + + skin + Skin + This is a reference for the skin descriptor used in Doxia Sitetools, stored as META-INF/maven/skin.xml.

    +

    An XSD is available at:

    + + ]]>
    + + + + package + org.apache.maven.doxia.site.skin + + + + + + SkinModel + <skin> element is the root of the skin descriptor. + ]]> + 1.7.0+ + + + + + + + prerequisites + 1.7.0+ + Describes the prerequisites in the build environment for using this skin. + + Prerequisites + + + + encoding + 1.7.0+ + Encoding of text content, like the Velocity template itself. + String + false + + + + + 1.7.0+ + + + + + + + + Prerequisites + 1.7.0+ + Describes the prerequisites a skin can have. + + + doxiaSitetools + 1.7.0+ + String + 1.7 + + + false + + + + +
    diff --git a/Java-base/maven-doxia-sitetools/src/doxia-skin-model/src/site/apt/index.apt b/Java-base/maven-doxia-sitetools/src/doxia-skin-model/src/site/apt/index.apt new file mode 100644 index 000000000..0aa95a5ef --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-skin-model/src/site/apt/index.apt @@ -0,0 +1,54 @@ + ----- + Doxia Sitetools Skin Model + ----- + Hervé Boutemy + ----- + 2016-02-07 + ----- + + ~~ 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. + + ~~ NOTE: For help with the syntax of this file, see: + ~~ http://maven.apache.org/doxia/references/apt-format.html + +Doxia Sitetools Skin Model + + This is strictly the model for Doxia Sitetools Skin Model, used in skins in <<>>. + + The following are generated from this model: + + * {{{./apidocs/index.html}Java sources}} with Reader for the Xpp3 XML parser + + * A {{{./skin.html}Descriptor Reference}} + + * An XSD referenced in the {{{./skin.html}Descriptor Reference}}. + +* Doxia Sitetools Skin + + A Doxia Sitetools skin must contain a Velocity template named <<>>: it will be called + by {{{../doxia-site-renderer/index.html}Doxia Sitetools – Site Renderer}} with additional variables about the + rendered document as documented in the {{{../doxia-site-renderer/index.html#Site_Template}Site Template section}}, + the main variable being <<>>. + + Maven team provides {{{/skins/}a collection of skins}} for projects use. + + Some documentation is available on {{{/plugins/maven-site-plugin/examples/creatingskins.html}how to create a new skin}}, + by copying other skins to benefit from examples of breadcrumbs or menu generation from + {{{../doxia-decoration-model/index.html}decoration model}}. + + Since Doxia Sitetools 1.7, a skin descriptor can be added in <<>>. diff --git a/Java-base/maven-doxia-sitetools/src/doxia-skin-model/src/site/site.xml b/Java-base/maven-doxia-sitetools/src/doxia-skin-model/src/site/site.xml new file mode 100644 index 000000000..4e1193a07 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/doxia-skin-model/src/site/site.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/maven-doxia-sitetools/src/pom.xml b/Java-base/maven-doxia-sitetools/src/pom.xml new file mode 100644 index 000000000..f1cf8c081 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/pom.xml @@ -0,0 +1,411 @@ + + + + + + 4.0.0 + + + org.apache.maven + maven-parent + 34 + ../../pom/maven/pom.xml + + + org.apache.maven.doxia + doxia-sitetools + 1.9.3-SNAPSHOT + pom + + Doxia Sitetools + Doxia Sitetools generates sites, consisting of static and dynamic content that was generated by Doxia. + https://maven.apache.org/doxia/doxia-sitetools/ + 2005 + + + doxia-decoration-model + doxia-skin-model + doxia-integration-tools + doxia-site-renderer + doxia-doc-renderer + + + + scm:git:https://gitbox.apache.org/repos/asf/maven-doxia-sitetools.git + scm:git:https://gitbox.apache.org/repos/asf/maven-doxia-sitetools.git + https://github.com/apache/maven-doxia-sitetools/tree/${project.scm.tag} + master + + + jira + https://issues.apache.org/jira/browse/DOXIASITETOOLS + + + Jenkins + https://builds.apache.org/job/doxia-sitetools/ + + + + apache.website + scm:svn:https://svn.apache.org/repos/asf/maven/doxia/website/components/${maven.site.path} + + + + + 7 + 1.9.1 + doxia-sitetools-archives/doxia-sitetools-LATEST + 2020-02-19T07:03:09Z + + + + + + + org.apache.maven.doxia + doxia-logging-api + ${doxiaVersion} + + + org.apache.maven.doxia + doxia-sink-api + ${doxiaVersion} + + + org.apache.maven.doxia + doxia-core + ${doxiaVersion} + + + org.apache.maven.doxia + doxia-module-apt + ${doxiaVersion} + + + org.apache.maven.doxia + doxia-module-confluence + ${doxiaVersion} + + + org.apache.maven.doxia + doxia-module-docbook-simple + ${doxiaVersion} + + + org.apache.maven.doxia + doxia-module-fml + ${doxiaVersion} + + + org.apache.maven.doxia + doxia-module-markdown + ${doxiaVersion} + + + org.apache.maven.doxia + doxia-module-xdoc + ${doxiaVersion} + + + org.apache.maven.doxia + doxia-module-xhtml + ${doxiaVersion} + + + org.apache.maven.doxia + doxia-module-xhtml5 + ${doxiaVersion} + + + org.apache.maven.doxia + doxia-decoration-model + ${project.version} + + + org.apache.maven.doxia + doxia-skin-model + ${project.version} + + + + commons-io + commons-io + 2.6 + + + + org.codehaus.plexus + plexus-container-default + 1.0-alpha-30 + + + org.codehaus.plexus + plexus-i18n + 1.0-beta-10 + + + org.codehaus.plexus + plexus-component-api + + + + + org.codehaus.plexus + plexus-velocity + 1.2 + + + org.codehaus.plexus + plexus-utils + 3.0.22 + + + + org.apache.velocity + velocity + 1.7 + + + + junit + junit + 4.13 + + + + + + + + src/main/resources + + + ${project.build.directory}/generated-site/xsd + + **/*.xsd + + + + + + + + org.codehaus.mojo + clirr-maven-plugin + + 1.1 + + + + org.apache.maven.plugins + maven-site-plugin + + scm:svn:https://svn.apache.org/repos/asf/maven/doxia/website/components/${maven.site.path} + + + + org.apache.maven.plugins + maven-scm-publish-plugin + + ${maven.site.cache}/doxia/${maven.site.path} + + + + org.apache.rat + apache-rat-plugin + + + src/test/resources/site/confluence/confluence/*.confluence + src/test/resources/org/apache/maven/doxia/siterenderer/velocity-toolmanager.vm + src/test/resources/org/apache/maven/doxia/siterenderer/velocity-toolmanager.expected.txt + src/test/resources/dtd/xhtml-*.ent + src/test/resources/dtd/xhtml1-transitional.dtd + src/test/resources/xhtml-lat1.ent + + + + + + + + org.codehaus.plexus + plexus-component-metadata + + + + generate-metadata + + + + + + org.codehaus.mojo + clirr-maven-plugin + + + verify + + check + + + + org/apache/maven/doxia/site/decoration/* + org/apache/maven/doxia/site/decoration/inheritance/*URLContainer + + org/apache/maven/doxia/siterenderer/*Renderer + org/apache/maven/doxia/siterenderer/sink/* + + org/apache/maven/doxia/docrenderer/AbstractDocumentRenderer + org/apache/maven/doxia/docrenderer/itext/AbstractITextRender + + org/apache/maven/doxia/siterenderer/SiteRenderingContext + + + + + + + maven-enforcer-plugin + + + enforce-bytecode-version + + enforce + + + + + ${maven.compiler.target} + + + true + + + + + + org.codehaus.mojo + extra-enforcer-rules + 1.2 + compile + + + + + + + + + reporting + + + + org.apache.maven.plugins + maven-changes-plugin + 2.6 + false + + Type,Key,Summary,Resolution,Assignee + 1000 + true + Key + + + + + jira-report + + + + + + org.apache.maven.plugins + maven-jxr-plugin + + + non-aggregate + + jxr + + + + aggregate + + aggregate + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + Doxia Site Renderer + org.apache.maven.doxia.siterenderer* + + + Doxia Decoration Model + org.apache.maven.doxia.site.decoration* + + + Doxia Skin Model + org.apache.maven.doxia.site.skin* + + + Doxia Integration Tools + org.apache.maven.doxia.tools* + + + Doxia Document Renderer + org.apache.maven.doxia.docrenderer* + + + + + + non-aggregate + + javadoc + + + + aggregate + + aggregate + + + + + + + + + jigsaw + + [1.9,) + + + true + + + + diff --git a/Java-base/maven-doxia-sitetools/src/src/site/resources/download.cgi b/Java-base/maven-doxia-sitetools/src/src/site/resources/download.cgi new file mode 100644 index 000000000..1b178d2e6 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/src/site/resources/download.cgi @@ -0,0 +1,22 @@ +#!/bin/sh +# +# 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. +# +# Just call the standard mirrors.cgi script. It will use download.html +# as the input template. +exec /www/www.apache.org/dyn/mirrors/mirrors.cgi $* \ No newline at end of file diff --git a/Java-base/maven-doxia-sitetools/src/src/site/resources/images/doxia-sitetools-deps.png b/Java-base/maven-doxia-sitetools/src/src/site/resources/images/doxia-sitetools-deps.png new file mode 100644 index 000000000..410e33a76 Binary files /dev/null and b/Java-base/maven-doxia-sitetools/src/src/site/resources/images/doxia-sitetools-deps.png differ diff --git a/Java-base/maven-doxia-sitetools/src/src/site/site.xml b/Java-base/maven-doxia-sitetools/src/src/site/site.xml new file mode 100644 index 000000000..db676592a --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/src/site/site.xml @@ -0,0 +1,53 @@ + + + + + + + + Doxia + https://maven.apache.org/doxia/images/doxia-logo.png + https://maven.apache.org/doxia/ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Java-base/maven-doxia-sitetools/src/src/site/xdoc/download.xml.vm b/Java-base/maven-doxia-sitetools/src/src/site/xdoc/download.xml.vm new file mode 100644 index 000000000..252fdae39 --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/src/site/xdoc/download.xml.vm @@ -0,0 +1,126 @@ + + + + + + + Download ${project.name} Source + + +
    + +

    ${project.name} ${project.version} is distributed in source format. Use a source archive if you intend to build + ${project.name} yourself. Otherwise, simply use the ready-made binary artifacts from central repository.

    + +

    You will be prompted for a mirror - if the file is not found on yours, please be patient, as it may take 24 + hours to reach all mirrors.

    + +

    In order to guard against corrupted downloads/installations, it is highly recommended to + verify the signature + of the release bundles against the public KEYS used by the Apache Maven + developers.

    + +

    ${project.name} is distributed under the Apache License, version 2.0.

    + +

    We strongly encourage our users to configure a Maven repository mirror closer to their location, please read How to Use Mirrors for Repositories.

    + + + + +

    + [if-any logo] + + logo + + [end] + The currently selected mirror is + [preferred]. + If you encounter a problem with this mirror, + please select another mirror. + If all mirrors are failing, there are + backup + mirrors + (at the end of the mirrors list) that should be available. +

    + +
    + Other mirrors: + + +
    + +

    + You may also consult the + complete list of + mirrors. +

    + + + + + +

    This is the current stable version of ${project.name}.

    + + + + + + + + + + + + + + + + + + +
    LinkChecksumSignature
    ${project.name} ${project.version} (Source zip)maven/doxia/${project.artifactId}-${project.version}-source-release.zipmaven/doxia/${project.artifactId}-${project.version}-source-release.zip.sha512maven/doxia/${project.artifactId}-${project.version}-source-release.zip.asc
    +
    + + + +

    Older non-recommended releases can be found on our archive site.

    + +
    +
    + +
    + diff --git a/Java-base/maven-doxia-sitetools/src/src/site/xdoc/doxia-sitetools-deps.odg b/Java-base/maven-doxia-sitetools/src/src/site/xdoc/doxia-sitetools-deps.odg new file mode 100644 index 000000000..af3825b44 Binary files /dev/null and b/Java-base/maven-doxia-sitetools/src/src/site/xdoc/doxia-sitetools-deps.odg differ diff --git a/Java-base/maven-doxia-sitetools/src/src/site/xdoc/index.xml b/Java-base/maven-doxia-sitetools/src/src/site/xdoc/index.xml new file mode 100644 index 000000000..818a2a65a --- /dev/null +++ b/Java-base/maven-doxia-sitetools/src/src/site/xdoc/index.xml @@ -0,0 +1,64 @@ + + + + + + + + Doxia Sitetools + Hervé Boutemy + + + + +
    + +

    Doxia Sitetools is an extension of base Doxia component that generates:

    +
      +
    • either HTML sites, adding decoration (header, footer, navigation bar, menu, ...) to content that was generated by Doxia: + a decoration is defined in a site skin,
    • +
    • or documents like RTF or PDF.
    • +
    +

    In addition, Doxia Sitetools processes files with extra .vm extension with Velocity.

    + +

    + Doxia Sitetools Dependencies + + Doxia Site Renderer + Doxia Integration Tools + Doxia Decoration Model + Doxia Skin Model + Doxia Document Renderer + Plexus Velocity + Velocity + Doxia Module XHTML + Doxia Module FO + Doxia Module iText + Doxia Modules + +

    + +
    + + + +
    diff --git a/Java/maven-doxia-sitetools-AbstractDocumentRenderer_532/Dockerfile b/Java/maven-doxia-sitetools-AbstractDocumentRenderer_532/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-AbstractDocumentRenderer_532/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-AbstractDocumentRenderer_532/buggy.java b/Java/maven-doxia-sitetools-AbstractDocumentRenderer_532/buggy.java new file mode 100644 index 000000000..707694137 --- /dev/null +++ b/Java/maven-doxia-sitetools-AbstractDocumentRenderer_532/buggy.java @@ -0,0 +1,713 @@ +package org.apache.maven.doxia.docrenderer; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.apache.maven.doxia.Doxia; +import org.apache.maven.doxia.document.DocumentModel; +import org.apache.maven.doxia.document.io.xpp3.DocumentXpp3Reader; +import org.apache.maven.doxia.sink.Sink; +import org.apache.maven.doxia.parser.ParseException; +import org.apache.maven.doxia.parser.Parser; +import org.apache.maven.doxia.parser.manager.ParserNotFoundException; +import org.apache.maven.doxia.logging.PlexusLoggerWrapper; +import org.apache.maven.doxia.parser.module.ParserModule; +import org.apache.maven.doxia.parser.module.ParserModuleManager; +import org.apache.maven.doxia.util.XmlValidator; + +import org.apache.velocity.VelocityContext; +import org.apache.velocity.context.Context; + +import org.codehaus.plexus.component.annotations.Requirement; +import org.codehaus.plexus.logging.AbstractLogEnabled; + +import org.codehaus.plexus.util.DirectoryScanner; +import org.codehaus.plexus.util.FileUtils; +import org.codehaus.plexus.util.IOUtil; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.xml.XmlStreamReader; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; +import org.codehaus.plexus.velocity.SiteResourceLoader; +import org.codehaus.plexus.velocity.VelocityComponent; + +/** + * Abstract document renderer. + * + * @author Vincent Siveton + * @author ltheussl + * @since 1.1 + */ +public abstract class AbstractDocumentRenderer + extends AbstractLogEnabled + implements DocumentRenderer +{ + @Requirement + protected ParserModuleManager parserModuleManager; + + @Requirement + protected Doxia doxia; + + @Requirement + private VelocityComponent velocity; + + /** + * The common base directory of source files. + */ + private String baseDir; + + //-------------------------------------------- + // + //-------------------------------------------- + + /** + * Render an aggregate document from the files found in a Map. + * + * @param filesToProcess the Map of Files to process. The Map should contain as keys the paths of the + * source files (relative to {@link #getBaseDir() baseDir}), and the corresponding ParserModule as values. + * @param outputDirectory the output directory where the aggregate document should be generated. + * @param documentModel the document model, containing all the metadata, etc. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + * @throws java.io.IOException if any + * @deprecated since 1.1.2, use {@link #render(Map, File, DocumentModel, DocumentRendererContext)} + */ + public abstract void render( Map filesToProcess, File outputDirectory, + DocumentModel documentModel ) + throws DocumentRendererException, IOException; + + //-------------------------------------------- + // + //-------------------------------------------- + + /** {@inheritDoc} */ + public void render( Collection files, File outputDirectory, DocumentModel documentModel ) + throws DocumentRendererException, IOException + { + render( getFilesToProcess( files ), outputDirectory, documentModel, null ); + } + + /** {@inheritDoc} */ + public void render( File baseDirectory, File outputDirectory, DocumentModel documentModel ) + throws DocumentRendererException, IOException + { + render( baseDirectory, outputDirectory, documentModel, null ); + } + + /** + * Render an aggregate document from the files found in a Map. + * + * @param filesToProcess the Map of Files to process. The Map should contain as keys the paths of the + * source files (relative to {@link #getBaseDir() baseDir}), and the corresponding ParserModule as values. + * @param outputDirectory the output directory where the aggregate document should be generated. + * @param documentModel the document model, containing all the metadata, etc. + * @param context the rendering context when processing files. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + * @throws java.io.IOException if any + */ + public void render( Map filesToProcess, File outputDirectory, DocumentModel documentModel, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + // nop + } + + /** + * Render a document from the files found in a source directory, depending on a rendering context. + * + * @param baseDirectory the directory containing the source files. + * This should follow the standard Maven convention, ie containing all the site modules. + * @param outputDirectory the output directory where the document should be generated. + * @param documentModel the document model, containing all the metadata, etc. + * If the model contains a TOC, only the files found in this TOC are rendered, + * otherwise all files found under baseDirectory will be processed. + * If the model is null, render all files from baseDirectory individually. + * @param context the rendering context when processing files. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + * @throws java.io.IOException if any + * @since 1.1.2 + */ + public void render( File baseDirectory, File outputDirectory, DocumentModel documentModel, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + render( getFilesToProcess( baseDirectory ), outputDirectory, documentModel, context ); + } + + /** + * Render a document from the files found in baseDirectory. This just forwards to + * {@link #render(File,File,DocumentModel)} with a new DocumentModel. + * + * @param baseDirectory the directory containing the source files. + * This should follow the standard Maven convention, ie containing all the site modules. + * @param outputDirectory the output directory where the document should be generated. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + * @throws java.io.IOException if any + * @see #render(File, File, DocumentModel) + */ + public void render( File baseDirectory, File outputDirectory ) + throws DocumentRendererException, IOException + { + render( baseDirectory, outputDirectory, (DocumentModel) null ); + } + + /** + * Render a document from the files found in baseDirectory. + * + * @param baseDirectory the directory containing the source files. + * This should follow the standard Maven convention, ie containing all the site modules. + * @param outputDirectory the output directory where the document should be generated. + * @param documentDescriptor a file containing the document model. + * If this file does not exist or is null, some default settings will be used. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + * @throws java.io.IOException if any + * @see #render(File, File) if documentDescriptor does not exist or is null + * @see #render(Map, File, DocumentModel) otherwise + */ + public void render( File baseDirectory, File outputDirectory, File documentDescriptor ) + throws DocumentRendererException, IOException + { + if ( ( documentDescriptor == null ) || ( !documentDescriptor.exists() ) ) + { + getLogger().warn( "No documentDescriptor found: using default settings!" ); + + render( baseDirectory, outputDirectory ); + } + else + { + render( getFilesToProcess( baseDirectory ), outputDirectory, readDocumentModel( documentDescriptor ), + null ); + } + } + + /** + * Render documents separately for each file found in a Map. + * + * @param filesToProcess the Map of Files to process. The Map should contain as keys the paths of the + * source files (relative to {@link #getBaseDir() baseDir}), and the corresponding ParserModule as values. + * @param outputDirectory the output directory where the documents should be generated. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + * @throws java.io.IOException if any + * @since 1.1.1 + * @deprecated since 1.1.2, use {@link #renderIndividual(Map, File, DocumentRendererContext)} + */ + public void renderIndividual( Map filesToProcess, File outputDirectory ) + throws DocumentRendererException, IOException + { + // nop + } + + /** + * Render documents separately for each file found in a Map. + * + * @param filesToProcess the Map of Files to process. The Map should contain as keys the paths of the + * source files (relative to {@link #getBaseDir() baseDir}), and the corresponding ParserModule as values. + * @param outputDirectory the output directory where the documents should be generated. + * @param context the rendering context. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + * @throws java.io.IOException if any + * @since 1.1.2 + */ + public void renderIndividual( Map filesToProcess, File outputDirectory, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + // nop + } + + /** + * Returns a Map of files to process. The Map contains as keys the paths of the source files + * (relative to {@link #getBaseDir() baseDir}), and the corresponding ParserModule as values. + * + * @param baseDirectory the directory containing the source files. + * This should follow the standard Maven convention, ie containing all the site modules. + * @return a Map of files to process. + * @throws java.io.IOException in case of a problem reading the files under baseDirectory. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any + */ + public Map getFilesToProcess( File baseDirectory ) + throws IOException, DocumentRendererException + { + if ( !baseDirectory.isDirectory() ) + { + getLogger().warn( "No files found to process!" ); + + return new HashMap(); + } + + setBaseDir( baseDirectory.getAbsolutePath() ); + + Map filesToProcess = new LinkedHashMap(); + Map duplicatesFiles = new LinkedHashMap(); + + Collection modules = parserModuleManager.getParserModules(); + for ( ParserModule module : modules ) + { + File moduleBasedir = new File( baseDirectory, module.getSourceDirectory() ); + + if ( moduleBasedir.exists() ) + { + // TODO: handle in/excludes + List allFiles = FileUtils.getFileNames( moduleBasedir, "**/*.*", null, false ); + + String[] extensions = getExtensions( module ); + List docs = new LinkedList( allFiles ); + // Take care of extension case + for ( Iterator it = docs.iterator(); it.hasNext(); ) + { + String name = it.next().trim(); + + if ( !endsWithIgnoreCase( name, extensions ) ) + { + it.remove(); + } + } + + String[] vmExtensions = new String[extensions.length]; + for ( int i = 0; i < extensions.length; i++ ) + { + vmExtensions[i] = extensions[i] + ".vm"; + } + List velocityFiles = new LinkedList( allFiles ); + // *.xml.vm + for ( Iterator it = velocityFiles.iterator(); it.hasNext(); ) + { + String name = it.next().trim(); + + if ( !endsWithIgnoreCase( name, vmExtensions ) ) + { + it.remove(); + } + } + docs.addAll( velocityFiles ); + + for ( String filePath : docs ) + { + filePath = filePath.trim(); + + if ( filePath.lastIndexOf( '.' ) > 0 ) + { + String key = filePath.substring( 0, filePath.lastIndexOf( '.' ) ); + + if ( duplicatesFiles.containsKey( key ) ) + { + throw new DocumentRendererException( "Files '" + module.getSourceDirectory() + + File.separator + filePath + "' clashes with existing '" + + duplicatesFiles.get( key ) + "'." ); + } + + duplicatesFiles.put( key, module.getSourceDirectory() + File.separator + filePath ); + } + + filesToProcess.put( filePath, module ); + } + } + } + + return filesToProcess; + } + + protected static String[] getExtensions( ParserModule module ) + { + String[] extensions = new String[module.getExtensions().length]; + for ( int i = module.getExtensions().length - 1; i >= 0; i-- ) + { + extensions[i] = '.' + module.getExtensions()[i]; + } + return extensions; + } + + // TODO replace with StringUtils.endsWithIgnoreCase() from maven-shared-utils 0.7 + protected 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() ); + } + + protected static boolean endsWithIgnoreCase( String str, String[] searchStrs ) + { + for ( String searchStr : searchStrs ) + { + if ( endsWithIgnoreCase( str, searchStr ) ) + { + return true; + } + } + return false; + } + + /** + * Returns a Map of files to process. The Map contains as keys the paths of the source files + * (relative to {@link #getBaseDir() baseDir}), and the corresponding ParserModule as values. + * + * @param files The Collection of source files. + * @return a Map of files to process. + */ + public Map getFilesToProcess( Collection files ) + { + // ---------------------------------------------------------------------- + // Map all the file names to parser ids + // ---------------------------------------------------------------------- + + Map filesToProcess = new HashMap(); + + Collection modules = parserModuleManager.getParserModules(); + for ( ParserModule module : modules ) + { + String[] extensions = getExtensions( module ); + + String sourceDirectory = File.separator + module.getSourceDirectory() + File.separator; + + for ( String file : files ) + { + // first check if the file path contains one of the recognized source dir identifiers + // (there's trouble if a pathname contains 2 identifiers), then match file extensions (not unique). + + if ( file.indexOf( sourceDirectory ) != -1 ) + { + filesToProcess.put( file, module ); + } + else + { + // don't overwrite if it's there already + if ( endsWithIgnoreCase( file, extensions ) && !filesToProcess.containsKey( file ) ) + { + filesToProcess.put( file, module ); + } + } + } + } + + return filesToProcess; + } + + /** {@inheritDoc} */ + public DocumentModel readDocumentModel( File documentDescriptor ) + throws DocumentRendererException, IOException + { + DocumentModel documentModel; + + Reader reader = null; + try + { + reader = ReaderFactory.newXmlReader( documentDescriptor ); + documentModel = new DocumentXpp3Reader().read( reader ); + } + catch ( XmlPullParserException e ) + { + throw new DocumentRendererException( "Error parsing document descriptor", e ); + } + finally + { + IOUtil.close( reader ); + } + + return documentModel; + } + + /** + * Sets the current base directory. + * + * @param newDir the absolute path to the base directory to set. + */ + public void setBaseDir( String newDir ) + { + this.baseDir = newDir; + } + + /** + * Return the current base directory. + * + * @return the current base directory. + */ + public String getBaseDir() + { + return this.baseDir; + } + + //-------------------------------------------- + // + //-------------------------------------------- + + /** + * Parse a source document into a sink. + * + * @param fullDocPath absolute path to the source document. + * @param parserId determines the parser to use. + * @param sink the sink to receive the events. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException in case of a parsing error. + * @throws java.io.IOException if the source document cannot be opened. + * @deprecated since 1.1.2, use {@link #parse(String, String, Sink, DocumentRendererContext)} + */ + protected void parse( String fullDocPath, String parserId, Sink sink ) + throws DocumentRendererException, IOException + { + parse( fullDocPath, parserId, sink, null ); + } + + /** + * Parse a source document into a sink. + * + * @param fullDocPath absolute path to the source document. + * @param parserId determines the parser to use. + * @param sink the sink to receive the events. + * @param context the rendering context. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException in case of a parsing error. + * @throws java.io.IOException if the source document cannot be opened. + */ +/** + * Parse a source document into a sink. + * + * @param fullDocPath + * absolute path to the source document. + * @param parserId + * determines the parser to use. + * @param sink + * the sink to receive the events. + * @param context + * the rendering context. + * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException + * in case of a parsing error. + * @throws java.io.IOException + * if the source document cannot be opened. + */ +protected void parse(java.lang.String fullDocPath, java.lang.String parserId, org.apache.maven.doxia.sink.Sink sink, org.apache.maven.doxia.docrenderer.DocumentRendererContext context) throws org.apache.maven.doxia.docrenderer.DocumentRendererException, java.io.IOException { + if (getLogger().isDebugEnabled()) { + getLogger().debug("Parsing file " + fullDocPath); + } + java.io.Reader reader = null; + try { + java.io.File f = new java.io.File(fullDocPath); + org.apache.maven.doxia.parser.Parser parser = doxia.getParser(parserId); + switch (parser.getType()) { + case org.apache.maven.doxia.parser.Parser.XML_TYPE : + reader = org.codehaus.plexus.util.ReaderFactory.newXmlReader(f); + if (org.apache.maven.doxia.docrenderer.AbstractDocumentRenderer.isVelocityFile(f)) { + reader = getVelocityReader(f, ((org.codehaus.plexus.util.xml.XmlStreamReader) (reader)).getEncoding(), context); + } + if ((context != null) && java.lang.Boolean.TRUE.equals(((java.lang.Boolean) (context.get("validate"))))) { + reader = validate(reader, fullDocPath); + } + break; + case org.apache.maven.doxia.parser.Parser.TXT_TYPE : + case org.apache.maven.doxia.parser.Parser.UNKNOWN_TYPE : + default : + if (org.apache.maven.doxia.docrenderer.AbstractDocumentRenderer.isVelocityFile(f)) { + reader = getVelocityReader(f, context == null ? org.codehaus.plexus.util.ReaderFactory.FILE_ENCODING : context.getInputEncoding(), context); + } else { + { + reader = org.codehaus.plexus.util.ReaderFactory.newReader(f, /* NPEX_NULL_EXP */ + context.getInputEncoding()); + } + } + } + sink.enableLogging(new org.apache.maven.doxia.logging.PlexusLoggerWrapper(getLogger())); + doxia.parse(reader, parserId, sink); + } catch (org.apache.maven.doxia.parser.manager.ParserNotFoundException e) { + throw new org.apache.maven.doxia.docrenderer.DocumentRendererException((((("No parser '" + parserId) + "' found for ") + fullDocPath) + ": ") + e.getMessage(), e); + } catch (org.apache.maven.doxia.parser.ParseException e) { + throw new org.apache.maven.doxia.docrenderer.DocumentRendererException((("Error parsing " + fullDocPath) + ": ") + e.getMessage(), e); + } finally { + org.codehaus.plexus.util.IOUtil.close(reader); + sink.flush(); + } +} + + /** + * Copies the contents of the resource directory to an output folder. + * + * @param outputDirectory the destination folder. + * @throws java.io.IOException if any. + */ + protected void copyResources( File outputDirectory ) + throws IOException + { + File resourcesDirectory = new File( getBaseDir(), "resources" ); + + if ( !resourcesDirectory.isDirectory() ) + { + return; + } + + if ( !outputDirectory.exists() ) + { + outputDirectory.mkdirs(); + } + + copyDirectory( resourcesDirectory, outputDirectory ); + } + + /** + * Copy content of a directory, excluding scm-specific files. + * + * @param source directory that contains the files and sub-directories to be copied. + * @param destination destination folder. + * @throws java.io.IOException if any. + */ + protected void copyDirectory( File source, File destination ) + throws IOException + { + if ( source.isDirectory() && destination.isDirectory() ) + { + DirectoryScanner scanner = new DirectoryScanner(); + + String[] includedResources = {"**/**"}; + + scanner.setIncludes( includedResources ); + + scanner.addDefaultExcludes(); + + scanner.setBasedir( source ); + + scanner.scan(); + + List includedFiles = Arrays.asList( scanner.getIncludedFiles() ); + + for ( String name : includedFiles ) + { + File sourceFile = new File( source, name ); + + File destinationFile = new File( destination, name ); + + FileUtils.copyFile( sourceFile, destinationFile ); + } + } + } + + /** + * @param documentModel not null + * @return the output name defined in the documentModel without the output extension. If the output name is not + * defined, return target by default. + * @since 1.1.1 + * @see org.apache.maven.doxia.document.DocumentModel#getOutputName() + * @see #getOutputExtension() + */ + protected String getOutputName( DocumentModel documentModel ) + { + String outputName = documentModel.getOutputName(); + if ( outputName == null ) + { + getLogger().info( "No outputName is defined in the document descriptor. Using 'target'" ); + + documentModel.setOutputName( "target" ); + } + + outputName = outputName.trim(); + if ( outputName.toLowerCase( Locale.ENGLISH ).endsWith( "." + getOutputExtension() ) ) + { + outputName = + outputName.substring( 0, outputName.toLowerCase( Locale.ENGLISH ) + .lastIndexOf( "." + getOutputExtension() ) ); + } + documentModel.setOutputName( outputName ); + + return documentModel.getOutputName(); + } + + /** + * TODO: DOXIA-111: we need a general filter here that knows how to alter the context + * + * @param f the file to process, not null + * @param encoding the wanted encoding, not null + * @param context the current render document context not null + * @return a reader with + * @throws DocumentRendererException + */ + private Reader getVelocityReader( File f, String encoding, DocumentRendererContext context ) + throws DocumentRendererException + { + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Velocity render for " + f.getAbsolutePath() ); + } + + SiteResourceLoader.setResource( f.getAbsolutePath() ); + + Context velocityContext = new VelocityContext(); + + if ( context.getKeys() != null ) + { + for ( int i = 0; i < context.getKeys().length; i++ ) + { + String key = (String) context.getKeys()[i]; + + velocityContext.put( key, context.get( key ) ); + } + } + + StringWriter sw = new StringWriter(); + try + { + velocity.getEngine().mergeTemplate( f.getAbsolutePath(), encoding, velocityContext, sw ); + } + catch ( Exception e ) + { + throw new DocumentRendererException( "Error whenn parsing Velocity file " + f.getAbsolutePath() + ": " + + e.getMessage(), e ); + } + + return new StringReader( sw.toString() ); + } + + /** + * @param f not null + * @return true if file has a vm extension, false otherwise. + */ + private static boolean isVelocityFile( File f ) + { + return FileUtils.getExtension( f.getAbsolutePath() ).toLowerCase( Locale.ENGLISH ).endsWith( "vm" ); + } + + 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 ); + } + } +} diff --git a/Java/maven-doxia-sitetools-AbstractDocumentRenderer_532/metadata.json b/Java/maven-doxia-sitetools-AbstractDocumentRenderer_532/metadata.json new file mode 100644 index 000000000..55f30cbbc --- /dev/null +++ b/Java/maven-doxia-sitetools-AbstractDocumentRenderer_532/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-AbstractDocumentRenderer_532", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/AbstractDocumentRenderer.java", + "line": 534, + "npe_method": "parse", + "deref_field": "context", + "npe_class": "AbstractDocumentRenderer", + "repo": "maven-doxia-sitetools", + "bug_id": "AbstractDocumentRenderer_532" + } +} diff --git a/Java/maven-doxia-sitetools-AbstractDocumentRenderer_532/npe.json b/Java/maven-doxia-sitetools-AbstractDocumentRenderer_532/npe.json new file mode 100644 index 000000000..e5d1ce951 --- /dev/null +++ b/Java/maven-doxia-sitetools-AbstractDocumentRenderer_532/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/AbstractDocumentRenderer.java", + "line": 534, + "npe_method": "parse", + "deref_field": "context", + "npe_class": "AbstractDocumentRenderer" +} \ No newline at end of file diff --git a/Java/maven-doxia-sitetools-DecorationUtils_113/Dockerfile b/Java/maven-doxia-sitetools-DecorationUtils_113/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-DecorationUtils_113/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-DecorationUtils_113/buggy.java b/Java/maven-doxia-sitetools-DecorationUtils_113/buggy.java new file mode 100644 index 000000000..3aa767149 --- /dev/null +++ b/Java/maven-doxia-sitetools-DecorationUtils_113/buggy.java @@ -0,0 +1,127 @@ +package org.apache.maven.doxia.site.decoration; + +/* + * 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 org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.util.xml.Xpp3Dom; + +/** + * Decoration model utilities. + * + * @since 1.7 + */ +public class DecorationUtils +{ + public static boolean isLink( String href ) + { + return StringUtils.isNotBlank( href ) + && ( startsWithAnyIgnoreCase( href, "http:/", "https:/", "ftp:/", "mailto:", "file:/" ) + || href.contains( "://" ) ); + } + + private static boolean startsWithIgnoreCase( String str, String prefix ) + { + if ( str == null || prefix == null ) + { + return ( str == null && prefix == null ); + } + if ( prefix.length() > str.length() ) + { + return false; + } + return str.regionMatches( true, 0, prefix, 0, prefix.length() ); + } + + public static boolean startsWithAnyIgnoreCase( String string, String... searchStrings ) + { + for ( int i = 0; i < searchStrings.length; i++ ) + { + String searchString = searchStrings[i]; + if ( startsWithIgnoreCase( string, searchString ) ) + { + return true; + } + } + return false; + } + + /** + * Helper to get decoration custom DOM element by simply specifying a dotted path. + * + * @param custom the custom DOM element + * @param path the dotted path to the child + * @return null if any element in the path does not exist + * @since 1.8 + */ + public static Xpp3Dom getCustomChild( Xpp3Dom custom, String path ) + { + String[] elements = path.split( "\\." ); + for ( String element : elements ) + { + if ( custom == null ) + { + return null; + } + custom = custom.getChild( element ); + } + return custom; + } + + /** + * Helper to get decoration custom DOM element value by simply specifying a dotted path. + * + * @param custom the custom DOM element + * @param path the dotted path to the child + * @return the element value or null if any element in the path does not exist + * @since 1.8 + */ + public static String getCustomValue( Xpp3Dom custom, String path ) + { + custom = getCustomChild( custom, path ); + return ( custom == null ) ? null : custom.getValue(); + } + + /** + * Helper to get decoration custom DOM element value by simply specifying a dotted path. + * + * @param custom the custom DOM element + * @param path the dotted path to the child + * @param defaultValue default value + * @return the element value or the default value if any element in the path does not exist + * @since 1.8 + */ +/** + * Helper to get decoration custom DOM element value by simply specifying a dotted path. + * + * @param custom + * the custom DOM element + * @param path + * the dotted path to the child + * @param defaultValue + * default value + * @return the element value or the default value if any element in the path does not exist + * @since 1.8 + */ +public static java.lang.String getCustomValue(org.codehaus.plexus.util.xml.Xpp3Dom custom, java.lang.String path, java.lang.String defaultValue) { + custom = org.apache.maven.doxia.site.decoration.DecorationUtils.getCustomChild(/* NPEX_NULL_EXP */ + custom, path); + return custom.getValue(); +} +} diff --git a/Java/maven-doxia-sitetools-DecorationUtils_113/metadata.json b/Java/maven-doxia-sitetools-DecorationUtils_113/metadata.json new file mode 100644 index 000000000..db8aa0486 --- /dev/null +++ b/Java/maven-doxia-sitetools-DecorationUtils_113/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-DecorationUtils_113", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/DecorationUtils.java", + "line": 124, + "npe_method": "getCustomValue", + "deref_field": "custom", + "npe_class": "DecorationUtils", + "repo": "maven-doxia-sitetools", + "bug_id": "DecorationUtils_113" + } +} diff --git a/Java/maven-doxia-sitetools-DecorationUtils_113/npe.json b/Java/maven-doxia-sitetools-DecorationUtils_113/npe.json new file mode 100644 index 000000000..1bc58a283 --- /dev/null +++ b/Java/maven-doxia-sitetools-DecorationUtils_113/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/DecorationUtils.java", + "line": 124, + "npe_method": "getCustomValue", + "deref_field": "custom", + "npe_class": "DecorationUtils" +} \ No newline at end of file diff --git a/Java/maven-doxia-sitetools-DecorationUtils_78/Dockerfile b/Java/maven-doxia-sitetools-DecorationUtils_78/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-DecorationUtils_78/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-DecorationUtils_78/buggy.java b/Java/maven-doxia-sitetools-DecorationUtils_78/buggy.java new file mode 100644 index 000000000..75dd00cef --- /dev/null +++ b/Java/maven-doxia-sitetools-DecorationUtils_78/buggy.java @@ -0,0 +1,122 @@ +package org.apache.maven.doxia.site.decoration; + +/* + * 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 org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.util.xml.Xpp3Dom; + +/** + * Decoration model utilities. + * + * @since 1.7 + */ +public class DecorationUtils +{ + public static boolean isLink( String href ) + { + return StringUtils.isNotBlank( href ) + && ( startsWithAnyIgnoreCase( href, "http:/", "https:/", "ftp:/", "mailto:", "file:/" ) + || href.contains( "://" ) ); + } + + private static boolean startsWithIgnoreCase( String str, String prefix ) + { + if ( str == null || prefix == null ) + { + return ( str == null && prefix == null ); + } + if ( prefix.length() > str.length() ) + { + return false; + } + return str.regionMatches( true, 0, prefix, 0, prefix.length() ); + } + + public static boolean startsWithAnyIgnoreCase( String string, String... searchStrings ) + { + for ( int i = 0; i < searchStrings.length; i++ ) + { + String searchString = searchStrings[i]; + if ( startsWithIgnoreCase( string, searchString ) ) + { + return true; + } + } + return false; + } + + /** + * Helper to get decoration custom DOM element by simply specifying a dotted path. + * + * @param custom the custom DOM element + * @param path the dotted path to the child + * @return null if any element in the path does not exist + * @since 1.8 + */ +/** + * Helper to get decoration custom DOM element by simply specifying a dotted path. + * + * @param custom + * the custom DOM element + * @param path + * the dotted path to the child + * @return null if any element in the path does not exist + * @since 1.8 + */ +public static org.codehaus.plexus.util.xml.Xpp3Dom getCustomChild(org.codehaus.plexus.util.xml.Xpp3Dom custom, java.lang.String path) { + java.lang.String[] elements = path.split("\\."); + for (java.lang.String element : elements) { + { + custom = /* NPEX_NULL_EXP */ + custom.getChild(element); + } + } + return custom; +} + + /** + * Helper to get decoration custom DOM element value by simply specifying a dotted path. + * + * @param custom the custom DOM element + * @param path the dotted path to the child + * @return the element value or null if any element in the path does not exist + * @since 1.8 + */ + public static String getCustomValue( Xpp3Dom custom, String path ) + { + custom = getCustomChild( custom, path ); + return ( custom == null ) ? null : custom.getValue(); + } + + /** + * Helper to get decoration custom DOM element value by simply specifying a dotted path. + * + * @param custom the custom DOM element + * @param path the dotted path to the child + * @param defaultValue default value + * @return the element value or the default value if any element in the path does not exist + * @since 1.8 + */ + public static String getCustomValue( Xpp3Dom custom, String path, String defaultValue ) + { + custom = getCustomChild( custom, path ); + return ( custom == null ) ? defaultValue : custom.getValue(); + } +} diff --git a/Java/maven-doxia-sitetools-DecorationUtils_78/metadata.json b/Java/maven-doxia-sitetools-DecorationUtils_78/metadata.json new file mode 100644 index 000000000..f4fcfd2d3 --- /dev/null +++ b/Java/maven-doxia-sitetools-DecorationUtils_78/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-DecorationUtils_78", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/DecorationUtils.java", + "line": 88, + "npe_method": "getCustomChild", + "deref_field": "custom", + "npe_class": "DecorationUtils", + "repo": "maven-doxia-sitetools", + "bug_id": "DecorationUtils_78" + } +} diff --git a/Java/maven-doxia-sitetools-DecorationUtils_78/npe.json b/Java/maven-doxia-sitetools-DecorationUtils_78/npe.json new file mode 100644 index 000000000..94ad4391a --- /dev/null +++ b/Java/maven-doxia-sitetools-DecorationUtils_78/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/DecorationUtils.java", + "line": 88, + "npe_method": "getCustomChild", + "deref_field": "custom", + "npe_class": "DecorationUtils" +} \ No newline at end of file diff --git a/Java/maven-doxia-sitetools-DecorationUtils_98/Dockerfile b/Java/maven-doxia-sitetools-DecorationUtils_98/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-DecorationUtils_98/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-DecorationUtils_98/buggy.java b/Java/maven-doxia-sitetools-DecorationUtils_98/buggy.java new file mode 100644 index 000000000..b829f8b02 --- /dev/null +++ b/Java/maven-doxia-sitetools-DecorationUtils_98/buggy.java @@ -0,0 +1,125 @@ +package org.apache.maven.doxia.site.decoration; + +/* + * 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 org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.util.xml.Xpp3Dom; + +/** + * Decoration model utilities. + * + * @since 1.7 + */ +public class DecorationUtils +{ + public static boolean isLink( String href ) + { + return StringUtils.isNotBlank( href ) + && ( startsWithAnyIgnoreCase( href, "http:/", "https:/", "ftp:/", "mailto:", "file:/" ) + || href.contains( "://" ) ); + } + + private static boolean startsWithIgnoreCase( String str, String prefix ) + { + if ( str == null || prefix == null ) + { + return ( str == null && prefix == null ); + } + if ( prefix.length() > str.length() ) + { + return false; + } + return str.regionMatches( true, 0, prefix, 0, prefix.length() ); + } + + public static boolean startsWithAnyIgnoreCase( String string, String... searchStrings ) + { + for ( int i = 0; i < searchStrings.length; i++ ) + { + String searchString = searchStrings[i]; + if ( startsWithIgnoreCase( string, searchString ) ) + { + return true; + } + } + return false; + } + + /** + * Helper to get decoration custom DOM element by simply specifying a dotted path. + * + * @param custom the custom DOM element + * @param path the dotted path to the child + * @return null if any element in the path does not exist + * @since 1.8 + */ + public static Xpp3Dom getCustomChild( Xpp3Dom custom, String path ) + { + String[] elements = path.split( "\\." ); + for ( String element : elements ) + { + if ( custom == null ) + { + return null; + } + custom = custom.getChild( element ); + } + return custom; + } + + /** + * Helper to get decoration custom DOM element value by simply specifying a dotted path. + * + * @param custom the custom DOM element + * @param path the dotted path to the child + * @return the element value or null if any element in the path does not exist + * @since 1.8 + */ +/** + * Helper to get decoration custom DOM element value by simply specifying a dotted path. + * + * @param custom + * the custom DOM element + * @param path + * the dotted path to the child + * @return the element value or null if any element in the path does not exist + * @since 1.8 + */ +public static java.lang.String getCustomValue(org.codehaus.plexus.util.xml.Xpp3Dom custom, java.lang.String path) { + custom = org.apache.maven.doxia.site.decoration.DecorationUtils.getCustomChild(/* NPEX_NULL_EXP */ + custom, path); + return custom.getValue(); +} + + /** + * Helper to get decoration custom DOM element value by simply specifying a dotted path. + * + * @param custom the custom DOM element + * @param path the dotted path to the child + * @param defaultValue default value + * @return the element value or the default value if any element in the path does not exist + * @since 1.8 + */ + public static String getCustomValue( Xpp3Dom custom, String path, String defaultValue ) + { + custom = getCustomChild( custom, path ); + return ( custom == null ) ? defaultValue : custom.getValue(); + } +} diff --git a/Java/maven-doxia-sitetools-DecorationUtils_98/metadata.json b/Java/maven-doxia-sitetools-DecorationUtils_98/metadata.json new file mode 100644 index 000000000..8259d6269 --- /dev/null +++ b/Java/maven-doxia-sitetools-DecorationUtils_98/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-DecorationUtils_98", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/DecorationUtils.java", + "line": 107, + "npe_method": "getCustomValue", + "deref_field": "custom", + "npe_class": "DecorationUtils", + "repo": "maven-doxia-sitetools", + "bug_id": "DecorationUtils_98" + } +} diff --git a/Java/maven-doxia-sitetools-DecorationUtils_98/npe.json b/Java/maven-doxia-sitetools-DecorationUtils_98/npe.json new file mode 100644 index 000000000..43859cec3 --- /dev/null +++ b/Java/maven-doxia-sitetools-DecorationUtils_98/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/DecorationUtils.java", + "line": 107, + "npe_method": "getCustomValue", + "deref_field": "custom", + "npe_class": "DecorationUtils" +} \ No newline at end of file diff --git a/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_126/Dockerfile b/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_126/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_126/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_126/buggy.java b/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_126/buggy.java new file mode 100644 index 000000000..6f5f43f73 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_126/buggy.java @@ -0,0 +1,458 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.util.ArrayList; +import java.util.List; + +import org.apache.maven.doxia.site.decoration.Banner; +import org.apache.maven.doxia.site.decoration.Body; +import org.apache.maven.doxia.site.decoration.DecorationModel; +import org.apache.maven.doxia.site.decoration.LinkItem; +import org.apache.maven.doxia.site.decoration.Logo; +import org.apache.maven.doxia.site.decoration.Menu; +import org.apache.maven.doxia.site.decoration.MenuItem; + +import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.util.xml.Xpp3Dom; + +/** + * Manage inheritance of the decoration model. + * + * @author Brett Porter + * @author Henning P. Schmiedehausen + */ +@Component( role = DecorationModelInheritanceAssembler.class ) +public class DefaultDecorationModelInheritanceAssembler + implements DecorationModelInheritanceAssembler +{ + /** {@inheritDoc} */ + public void assembleModelInheritance( String name, DecorationModel child, DecorationModel parent, + String childBaseUrl, String parentBaseUrl ) + { + if ( parent == null || !child.isMergeParent() ) + { + return; + } + + child.setCombineSelf( parent.getCombineSelf() ); + + URLRebaser urlContainer = new URLRebaser( parentBaseUrl, childBaseUrl ); + + if ( child.getBannerLeft() == null && parent.getBannerLeft() != null ) + { + child.setBannerLeft( parent.getBannerLeft().clone() ); + rebaseBannerPaths( child.getBannerLeft(), urlContainer ); + } + + if ( child.getBannerRight() == null && parent.getBannerRight() != null ) + { + child.setBannerRight( parent.getBannerRight().clone() ); + rebaseBannerPaths( child.getBannerRight(), urlContainer ); + } + + if ( child.isDefaultPublishDate() && parent.getPublishDate() != null ) + { + child.setPublishDate( parent.getPublishDate().clone() ); + } + + if ( child.isDefaultVersion() && parent.getVersion() != null ) + { + child.setVersion( parent.getVersion().clone() ); + } + + if ( child.getEdit() == null && parent.getEdit() != null ) + { + child.setEdit( parent.getEdit() ); + } + + if ( child.getSkin() == null && parent.getSkin() != null ) + { + child.setSkin( parent.getSkin().clone() ); + } + + child.setPoweredBy( mergePoweredByLists( child.getPoweredBy(), parent.getPoweredBy(), urlContainer ) ); + + if ( parent.getLastModified() > child.getLastModified() ) + { + child.setLastModified( parent.getLastModified() ); + } + + if ( child.getGoogleAdSenseClient() == null && parent.getGoogleAdSenseClient() != null ) + { + child.setGoogleAdSenseClient( parent.getGoogleAdSenseClient() ); + } + + if ( child.getGoogleAdSenseSlot() == null && parent.getGoogleAdSenseSlot() != null ) + { + child.setGoogleAdSenseSlot( parent.getGoogleAdSenseSlot() ); + } + + if ( child.getGoogleAnalyticsAccountId() == null && parent.getGoogleAnalyticsAccountId() != null ) + { + child.setGoogleAnalyticsAccountId( parent.getGoogleAnalyticsAccountId() ); + } + + assembleBodyInheritance( name, child, parent, urlContainer ); + + assembleCustomInheritance( child, parent ); + } + + /** {@inheritDoc} */ +/** + * {@inheritDoc } + */ +public void resolvePaths(final org.apache.maven.doxia.site.decoration.DecorationModel decoration, final java.lang.String baseUrl) { + if (baseUrl == null) { + return; + } + { + relativizeBannerPaths(/* NPEX_NULL_EXP */ + decoration.getBannerLeft(), baseUrl); + } + if (decoration.getBannerRight() != null) { + relativizeBannerPaths(decoration.getBannerRight(), baseUrl); + } + for (org.apache.maven.doxia.site.decoration.Logo logo : decoration.getPoweredBy()) { + relativizeLogoPaths(logo, baseUrl); + } + if (decoration.getBody() != null) { + for (org.apache.maven.doxia.site.decoration.LinkItem linkItem : decoration.getBody().getLinks()) { + relativizeLinkItemPaths(linkItem, baseUrl); + } + for (org.apache.maven.doxia.site.decoration.LinkItem linkItem : decoration.getBody().getBreadcrumbs()) { + relativizeLinkItemPaths(linkItem, baseUrl); + } + for (org.apache.maven.doxia.site.decoration.Menu menu : decoration.getBody().getMenus()) { + relativizeMenuPaths(menu.getItems(), baseUrl); + } + } +} + + /** + * Resolves all relative paths between the elements in a banner. The banner element might contain relative paths + * to the oldBaseUrl, these are changed to the newBannerUrl. + * + * @param banner + * @param baseUrl + */ + private void relativizeBannerPaths( final Banner banner, final String baseUrl ) + { + // banner has been checked to be not null, both href and src may be empty or null + banner.setHref( relativizeLink( banner.getHref(), baseUrl ) ); + banner.setSrc( relativizeLink( banner.getSrc(), baseUrl ) ); + } + + private void rebaseBannerPaths( final Banner banner, final URLRebaser urlContainer ) + { + if ( banner.getHref() != null ) // it may be empty + { + banner.setHref( urlContainer.rebaseLink( banner.getHref() ) ); + } + + if ( banner.getSrc() != null ) + { + banner.setSrc( urlContainer.rebaseLink( banner.getSrc() ) ); + } + } + + private void assembleCustomInheritance( final DecorationModel child, final DecorationModel parent ) + { + if ( child.getCustom() == null ) + { + child.setCustom( parent.getCustom() ); + } + else + { + child.setCustom( Xpp3Dom.mergeXpp3Dom( (Xpp3Dom) child.getCustom(), (Xpp3Dom) parent.getCustom() ) ); + } + } + + private void assembleBodyInheritance( final String name, final DecorationModel child, final DecorationModel parent, + final URLRebaser urlContainer ) + { + Body cBody = child.getBody(); + Body pBody = parent.getBody(); + + if ( cBody != null || pBody != null ) + { + if ( cBody == null ) + { + cBody = new Body(); + child.setBody( cBody ); + } + + if ( pBody == null ) + { + pBody = new Body(); + } + + if ( cBody.getHead() == null && pBody.getHead() != null ) + { + cBody.setHead( pBody.getHead() ); + } + + cBody.setLinks( mergeLinkItemLists( cBody.getLinks(), pBody.getLinks(), urlContainer, false ) ); + + if ( cBody.getBreadcrumbs().isEmpty() && !pBody.getBreadcrumbs().isEmpty() ) + { + LinkItem breadcrumb = new LinkItem(); + breadcrumb.setName( name ); + breadcrumb.setHref( "index.html" ); + cBody.getBreadcrumbs().add( breadcrumb ); + } + cBody.setBreadcrumbs( mergeLinkItemLists( cBody.getBreadcrumbs(), pBody.getBreadcrumbs(), urlContainer, + true ) ); + + cBody.setMenus( mergeMenus( cBody.getMenus(), pBody.getMenus(), urlContainer ) ); + + if ( cBody.getFooter() == null && pBody.getFooter() != null ) + { + cBody.setFooter( pBody.getFooter() ); + } + } + } + + private List mergeMenus( final List childMenus, final List parentMenus, + final URLRebaser urlContainer ) + { + List menus = new ArrayList( childMenus.size() + parentMenus.size() ); + + for ( Menu menu : childMenus ) + { + menus.add( menu ); + } + + int topCounter = 0; + for ( Menu menu : parentMenus ) + { + if ( "top".equals( menu.getInherit() ) ) + { + final Menu clone = menu.clone(); + + rebaseMenuPaths( clone.getItems(), urlContainer ); + + menus.add( topCounter, clone ); + topCounter++; + } + else if ( "bottom".equals( menu.getInherit() ) ) + { + final Menu clone = menu.clone(); + + rebaseMenuPaths( clone.getItems(), urlContainer ); + + menus.add( clone ); + } + } + + return menus; + } + + private void relativizeMenuPaths( final List items, final String baseUrl ) + { + for ( MenuItem item : items ) + { + relativizeLinkItemPaths( item, baseUrl ); + relativizeMenuPaths( item.getItems(), baseUrl ); + } + } + + private void rebaseMenuPaths( final List items, final URLRebaser urlContainer ) + { + for ( MenuItem item : items ) + { + rebaseLinkItemPaths( item, urlContainer ); + rebaseMenuPaths( item.getItems(), urlContainer ); + } + } + + private void relativizeLinkItemPaths( final LinkItem item, final String baseUrl ) + { + item.setHref( relativizeLink( item.getHref(), baseUrl ) ); + } + + private void rebaseLinkItemPaths( final LinkItem item, final URLRebaser urlContainer ) + { + item.setHref( urlContainer.rebaseLink( item.getHref() ) ); + } + + private void relativizeLogoPaths( final Logo logo, final String baseUrl ) + { + logo.setImg( relativizeLink( logo.getImg(), baseUrl ) ); + relativizeLinkItemPaths( logo, baseUrl ); + } + + private void rebaseLogoPaths( final Logo logo, final URLRebaser urlContainer ) + { + logo.setImg( urlContainer.rebaseLink( logo.getImg() ) ); + rebaseLinkItemPaths( logo, urlContainer ); + } + + private List mergeLinkItemLists( final List childList, final List parentList, + final URLRebaser urlContainer, boolean cutParentAfterDuplicate ) + { + List items = new ArrayList( childList.size() + parentList.size() ); + + for ( LinkItem item : parentList ) + { + if ( !items.contains( item ) && !childList.contains( item ) ) + { + final LinkItem clone = item.clone(); + + rebaseLinkItemPaths( clone, urlContainer ); + + items.add( clone ); + } + else if ( cutParentAfterDuplicate ) + { + // if a parent item is found in child, ignore next items (case for breadcrumbs) + // merge ( "B > E", "A > B > C > D" ) -> "A > B > E" (notice missing "C > D") + // see https://issues.apache.org/jira/browse/DOXIASITETOOLS-62 + break; + } + } + + for ( LinkItem item : childList ) + { + if ( !items.contains( item ) ) + { + items.add( item ); + } + } + + return items; + } + + private List mergePoweredByLists( final List childList, final List parentList, + final URLRebaser urlContainer ) + { + List logos = new ArrayList( 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/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_126/metadata.json b/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_126/metadata.json new file mode 100644 index 000000000..8a4d24eb5 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_126/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_126", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/DefaultDecorationModelInheritanceAssembler.java", + "line": 128, + "npe_method": "resolvePaths", + "deref_field": "getBannerLeft", + "npe_class": "DefaultDecorationModelInheritanceAssembler", + "repo": "maven-doxia-sitetools", + "bug_id": "DefaultDecorationModelInheritanceAssembler_126" + } +} diff --git a/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_126/npe.json b/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_126/npe.json new file mode 100644 index 000000000..9b36f9053 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_126/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/DefaultDecorationModelInheritanceAssembler.java", + "line": 128, + "npe_method": "resolvePaths", + "deref_field": "getBannerLeft", + "npe_class": "DefaultDecorationModelInheritanceAssembler" +} \ No newline at end of file diff --git a/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_131/Dockerfile b/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_131/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_131/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_131/buggy.java b/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_131/buggy.java new file mode 100644 index 000000000..31a154fe2 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_131/buggy.java @@ -0,0 +1,458 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.util.ArrayList; +import java.util.List; + +import org.apache.maven.doxia.site.decoration.Banner; +import org.apache.maven.doxia.site.decoration.Body; +import org.apache.maven.doxia.site.decoration.DecorationModel; +import org.apache.maven.doxia.site.decoration.LinkItem; +import org.apache.maven.doxia.site.decoration.Logo; +import org.apache.maven.doxia.site.decoration.Menu; +import org.apache.maven.doxia.site.decoration.MenuItem; + +import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.util.xml.Xpp3Dom; + +/** + * Manage inheritance of the decoration model. + * + * @author Brett Porter + * @author Henning P. Schmiedehausen + */ +@Component( role = DecorationModelInheritanceAssembler.class ) +public class DefaultDecorationModelInheritanceAssembler + implements DecorationModelInheritanceAssembler +{ + /** {@inheritDoc} */ + public void assembleModelInheritance( String name, DecorationModel child, DecorationModel parent, + String childBaseUrl, String parentBaseUrl ) + { + if ( parent == null || !child.isMergeParent() ) + { + return; + } + + child.setCombineSelf( parent.getCombineSelf() ); + + URLRebaser urlContainer = new URLRebaser( parentBaseUrl, childBaseUrl ); + + if ( child.getBannerLeft() == null && parent.getBannerLeft() != null ) + { + child.setBannerLeft( parent.getBannerLeft().clone() ); + rebaseBannerPaths( child.getBannerLeft(), urlContainer ); + } + + if ( child.getBannerRight() == null && parent.getBannerRight() != null ) + { + child.setBannerRight( parent.getBannerRight().clone() ); + rebaseBannerPaths( child.getBannerRight(), urlContainer ); + } + + if ( child.isDefaultPublishDate() && parent.getPublishDate() != null ) + { + child.setPublishDate( parent.getPublishDate().clone() ); + } + + if ( child.isDefaultVersion() && parent.getVersion() != null ) + { + child.setVersion( parent.getVersion().clone() ); + } + + if ( child.getEdit() == null && parent.getEdit() != null ) + { + child.setEdit( parent.getEdit() ); + } + + if ( child.getSkin() == null && parent.getSkin() != null ) + { + child.setSkin( parent.getSkin().clone() ); + } + + child.setPoweredBy( mergePoweredByLists( child.getPoweredBy(), parent.getPoweredBy(), urlContainer ) ); + + if ( parent.getLastModified() > child.getLastModified() ) + { + child.setLastModified( parent.getLastModified() ); + } + + if ( child.getGoogleAdSenseClient() == null && parent.getGoogleAdSenseClient() != null ) + { + child.setGoogleAdSenseClient( parent.getGoogleAdSenseClient() ); + } + + if ( child.getGoogleAdSenseSlot() == null && parent.getGoogleAdSenseSlot() != null ) + { + child.setGoogleAdSenseSlot( parent.getGoogleAdSenseSlot() ); + } + + if ( child.getGoogleAnalyticsAccountId() == null && parent.getGoogleAnalyticsAccountId() != null ) + { + child.setGoogleAnalyticsAccountId( parent.getGoogleAnalyticsAccountId() ); + } + + assembleBodyInheritance( name, child, parent, urlContainer ); + + assembleCustomInheritance( child, parent ); + } + + /** {@inheritDoc} */ +/** + * {@inheritDoc } + */ +public void resolvePaths(final org.apache.maven.doxia.site.decoration.DecorationModel decoration, final java.lang.String baseUrl) { + if (baseUrl == null) { + return; + } + if (decoration.getBannerLeft() != null) { + relativizeBannerPaths(decoration.getBannerLeft(), baseUrl); + } + { + relativizeBannerPaths(/* NPEX_NULL_EXP */ + decoration.getBannerRight(), baseUrl); + } + for (org.apache.maven.doxia.site.decoration.Logo logo : decoration.getPoweredBy()) { + relativizeLogoPaths(logo, baseUrl); + } + if (decoration.getBody() != null) { + for (org.apache.maven.doxia.site.decoration.LinkItem linkItem : decoration.getBody().getLinks()) { + relativizeLinkItemPaths(linkItem, baseUrl); + } + for (org.apache.maven.doxia.site.decoration.LinkItem linkItem : decoration.getBody().getBreadcrumbs()) { + relativizeLinkItemPaths(linkItem, baseUrl); + } + for (org.apache.maven.doxia.site.decoration.Menu menu : decoration.getBody().getMenus()) { + relativizeMenuPaths(menu.getItems(), baseUrl); + } + } +} + + /** + * Resolves all relative paths between the elements in a banner. The banner element might contain relative paths + * to the oldBaseUrl, these are changed to the newBannerUrl. + * + * @param banner + * @param baseUrl + */ + private void relativizeBannerPaths( final Banner banner, final String baseUrl ) + { + // banner has been checked to be not null, both href and src may be empty or null + banner.setHref( relativizeLink( banner.getHref(), baseUrl ) ); + banner.setSrc( relativizeLink( banner.getSrc(), baseUrl ) ); + } + + private void rebaseBannerPaths( final Banner banner, final URLRebaser urlContainer ) + { + if ( banner.getHref() != null ) // it may be empty + { + banner.setHref( urlContainer.rebaseLink( banner.getHref() ) ); + } + + if ( banner.getSrc() != null ) + { + banner.setSrc( urlContainer.rebaseLink( banner.getSrc() ) ); + } + } + + private void assembleCustomInheritance( final DecorationModel child, final DecorationModel parent ) + { + if ( child.getCustom() == null ) + { + child.setCustom( parent.getCustom() ); + } + else + { + child.setCustom( Xpp3Dom.mergeXpp3Dom( (Xpp3Dom) child.getCustom(), (Xpp3Dom) parent.getCustom() ) ); + } + } + + private void assembleBodyInheritance( final String name, final DecorationModel child, final DecorationModel parent, + final URLRebaser urlContainer ) + { + Body cBody = child.getBody(); + Body pBody = parent.getBody(); + + if ( cBody != null || pBody != null ) + { + if ( cBody == null ) + { + cBody = new Body(); + child.setBody( cBody ); + } + + if ( pBody == null ) + { + pBody = new Body(); + } + + if ( cBody.getHead() == null && pBody.getHead() != null ) + { + cBody.setHead( pBody.getHead() ); + } + + cBody.setLinks( mergeLinkItemLists( cBody.getLinks(), pBody.getLinks(), urlContainer, false ) ); + + if ( cBody.getBreadcrumbs().isEmpty() && !pBody.getBreadcrumbs().isEmpty() ) + { + LinkItem breadcrumb = new LinkItem(); + breadcrumb.setName( name ); + breadcrumb.setHref( "index.html" ); + cBody.getBreadcrumbs().add( breadcrumb ); + } + cBody.setBreadcrumbs( mergeLinkItemLists( cBody.getBreadcrumbs(), pBody.getBreadcrumbs(), urlContainer, + true ) ); + + cBody.setMenus( mergeMenus( cBody.getMenus(), pBody.getMenus(), urlContainer ) ); + + if ( cBody.getFooter() == null && pBody.getFooter() != null ) + { + cBody.setFooter( pBody.getFooter() ); + } + } + } + + private List mergeMenus( final List childMenus, final List parentMenus, + final URLRebaser urlContainer ) + { + List menus = new ArrayList( childMenus.size() + parentMenus.size() ); + + for ( Menu menu : childMenus ) + { + menus.add( menu ); + } + + int topCounter = 0; + for ( Menu menu : parentMenus ) + { + if ( "top".equals( menu.getInherit() ) ) + { + final Menu clone = menu.clone(); + + rebaseMenuPaths( clone.getItems(), urlContainer ); + + menus.add( topCounter, clone ); + topCounter++; + } + else if ( "bottom".equals( menu.getInherit() ) ) + { + final Menu clone = menu.clone(); + + rebaseMenuPaths( clone.getItems(), urlContainer ); + + menus.add( clone ); + } + } + + return menus; + } + + private void relativizeMenuPaths( final List items, final String baseUrl ) + { + for ( MenuItem item : items ) + { + relativizeLinkItemPaths( item, baseUrl ); + relativizeMenuPaths( item.getItems(), baseUrl ); + } + } + + private void rebaseMenuPaths( final List items, final URLRebaser urlContainer ) + { + for ( MenuItem item : items ) + { + rebaseLinkItemPaths( item, urlContainer ); + rebaseMenuPaths( item.getItems(), urlContainer ); + } + } + + private void relativizeLinkItemPaths( final LinkItem item, final String baseUrl ) + { + item.setHref( relativizeLink( item.getHref(), baseUrl ) ); + } + + private void rebaseLinkItemPaths( final LinkItem item, final URLRebaser urlContainer ) + { + item.setHref( urlContainer.rebaseLink( item.getHref() ) ); + } + + private void relativizeLogoPaths( final Logo logo, final String baseUrl ) + { + logo.setImg( relativizeLink( logo.getImg(), baseUrl ) ); + relativizeLinkItemPaths( logo, baseUrl ); + } + + private void rebaseLogoPaths( final Logo logo, final URLRebaser urlContainer ) + { + logo.setImg( urlContainer.rebaseLink( logo.getImg() ) ); + rebaseLinkItemPaths( logo, urlContainer ); + } + + private List mergeLinkItemLists( final List childList, final List parentList, + final URLRebaser urlContainer, boolean cutParentAfterDuplicate ) + { + List items = new ArrayList( childList.size() + parentList.size() ); + + for ( LinkItem item : parentList ) + { + if ( !items.contains( item ) && !childList.contains( item ) ) + { + final LinkItem clone = item.clone(); + + rebaseLinkItemPaths( clone, urlContainer ); + + items.add( clone ); + } + else if ( cutParentAfterDuplicate ) + { + // if a parent item is found in child, ignore next items (case for breadcrumbs) + // merge ( "B > E", "A > B > C > D" ) -> "A > B > E" (notice missing "C > D") + // see https://issues.apache.org/jira/browse/DOXIASITETOOLS-62 + break; + } + } + + for ( LinkItem item : childList ) + { + if ( !items.contains( item ) ) + { + items.add( item ); + } + } + + return items; + } + + private List mergePoweredByLists( final List childList, final List parentList, + final URLRebaser urlContainer ) + { + List logos = new ArrayList( 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/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_131/metadata.json b/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_131/metadata.json new file mode 100644 index 000000000..a253185bc --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_131/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_131", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/DefaultDecorationModelInheritanceAssembler.java", + "line": 131, + "npe_method": "resolvePaths", + "deref_field": "getBannerRight", + "npe_class": "DefaultDecorationModelInheritanceAssembler", + "repo": "maven-doxia-sitetools", + "bug_id": "DefaultDecorationModelInheritanceAssembler_131" + } +} diff --git a/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_131/npe.json b/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_131/npe.json new file mode 100644 index 000000000..a41cc7848 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_131/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/DefaultDecorationModelInheritanceAssembler.java", + "line": 131, + "npe_method": "resolvePaths", + "deref_field": "getBannerRight", + "npe_class": "DefaultDecorationModelInheritanceAssembler" +} \ No newline at end of file diff --git a/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_141/Dockerfile b/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_141/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_141/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_141/buggy.java b/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_141/buggy.java new file mode 100644 index 000000000..e02a26aff --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_141/buggy.java @@ -0,0 +1,458 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.util.ArrayList; +import java.util.List; + +import org.apache.maven.doxia.site.decoration.Banner; +import org.apache.maven.doxia.site.decoration.Body; +import org.apache.maven.doxia.site.decoration.DecorationModel; +import org.apache.maven.doxia.site.decoration.LinkItem; +import org.apache.maven.doxia.site.decoration.Logo; +import org.apache.maven.doxia.site.decoration.Menu; +import org.apache.maven.doxia.site.decoration.MenuItem; + +import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.util.xml.Xpp3Dom; + +/** + * Manage inheritance of the decoration model. + * + * @author Brett Porter + * @author Henning P. Schmiedehausen + */ +@Component( role = DecorationModelInheritanceAssembler.class ) +public class DefaultDecorationModelInheritanceAssembler + implements DecorationModelInheritanceAssembler +{ + /** {@inheritDoc} */ + public void assembleModelInheritance( String name, DecorationModel child, DecorationModel parent, + String childBaseUrl, String parentBaseUrl ) + { + if ( parent == null || !child.isMergeParent() ) + { + return; + } + + child.setCombineSelf( parent.getCombineSelf() ); + + URLRebaser urlContainer = new URLRebaser( parentBaseUrl, childBaseUrl ); + + if ( child.getBannerLeft() == null && parent.getBannerLeft() != null ) + { + child.setBannerLeft( parent.getBannerLeft().clone() ); + rebaseBannerPaths( child.getBannerLeft(), urlContainer ); + } + + if ( child.getBannerRight() == null && parent.getBannerRight() != null ) + { + child.setBannerRight( parent.getBannerRight().clone() ); + rebaseBannerPaths( child.getBannerRight(), urlContainer ); + } + + if ( child.isDefaultPublishDate() && parent.getPublishDate() != null ) + { + child.setPublishDate( parent.getPublishDate().clone() ); + } + + if ( child.isDefaultVersion() && parent.getVersion() != null ) + { + child.setVersion( parent.getVersion().clone() ); + } + + if ( child.getEdit() == null && parent.getEdit() != null ) + { + child.setEdit( parent.getEdit() ); + } + + if ( child.getSkin() == null && parent.getSkin() != null ) + { + child.setSkin( parent.getSkin().clone() ); + } + + child.setPoweredBy( mergePoweredByLists( child.getPoweredBy(), parent.getPoweredBy(), urlContainer ) ); + + if ( parent.getLastModified() > child.getLastModified() ) + { + child.setLastModified( parent.getLastModified() ); + } + + if ( child.getGoogleAdSenseClient() == null && parent.getGoogleAdSenseClient() != null ) + { + child.setGoogleAdSenseClient( parent.getGoogleAdSenseClient() ); + } + + if ( child.getGoogleAdSenseSlot() == null && parent.getGoogleAdSenseSlot() != null ) + { + child.setGoogleAdSenseSlot( parent.getGoogleAdSenseSlot() ); + } + + if ( child.getGoogleAnalyticsAccountId() == null && parent.getGoogleAnalyticsAccountId() != null ) + { + child.setGoogleAnalyticsAccountId( parent.getGoogleAnalyticsAccountId() ); + } + + assembleBodyInheritance( name, child, parent, urlContainer ); + + assembleCustomInheritance( child, parent ); + } + + /** {@inheritDoc} */ +/** + * {@inheritDoc } + */ +public void resolvePaths(final org.apache.maven.doxia.site.decoration.DecorationModel decoration, final java.lang.String baseUrl) { + if (baseUrl == null) { + return; + } + if (decoration.getBannerLeft() != null) { + relativizeBannerPaths(decoration.getBannerLeft(), baseUrl); + } + if (decoration.getBannerRight() != null) { + relativizeBannerPaths(decoration.getBannerRight(), baseUrl); + } + for (org.apache.maven.doxia.site.decoration.Logo logo : decoration.getPoweredBy()) { + relativizeLogoPaths(logo, baseUrl); + } + { + for (org.apache.maven.doxia.site.decoration.LinkItem linkItem : /* NPEX_NULL_EXP */ + decoration.getBody().getLinks()) { + relativizeLinkItemPaths(linkItem, baseUrl); + } + for (org.apache.maven.doxia.site.decoration.LinkItem linkItem : decoration.getBody().getBreadcrumbs()) { + relativizeLinkItemPaths(linkItem, baseUrl); + } + for (org.apache.maven.doxia.site.decoration.Menu menu : decoration.getBody().getMenus()) { + relativizeMenuPaths(menu.getItems(), baseUrl); + } + } +} + + /** + * Resolves all relative paths between the elements in a banner. The banner element might contain relative paths + * to the oldBaseUrl, these are changed to the newBannerUrl. + * + * @param banner + * @param baseUrl + */ + private void relativizeBannerPaths( final Banner banner, final String baseUrl ) + { + // banner has been checked to be not null, both href and src may be empty or null + banner.setHref( relativizeLink( banner.getHref(), baseUrl ) ); + banner.setSrc( relativizeLink( banner.getSrc(), baseUrl ) ); + } + + private void rebaseBannerPaths( final Banner banner, final URLRebaser urlContainer ) + { + if ( banner.getHref() != null ) // it may be empty + { + banner.setHref( urlContainer.rebaseLink( banner.getHref() ) ); + } + + if ( banner.getSrc() != null ) + { + banner.setSrc( urlContainer.rebaseLink( banner.getSrc() ) ); + } + } + + private void assembleCustomInheritance( final DecorationModel child, final DecorationModel parent ) + { + if ( child.getCustom() == null ) + { + child.setCustom( parent.getCustom() ); + } + else + { + child.setCustom( Xpp3Dom.mergeXpp3Dom( (Xpp3Dom) child.getCustom(), (Xpp3Dom) parent.getCustom() ) ); + } + } + + private void assembleBodyInheritance( final String name, final DecorationModel child, final DecorationModel parent, + final URLRebaser urlContainer ) + { + Body cBody = child.getBody(); + Body pBody = parent.getBody(); + + if ( cBody != null || pBody != null ) + { + if ( cBody == null ) + { + cBody = new Body(); + child.setBody( cBody ); + } + + if ( pBody == null ) + { + pBody = new Body(); + } + + if ( cBody.getHead() == null && pBody.getHead() != null ) + { + cBody.setHead( pBody.getHead() ); + } + + cBody.setLinks( mergeLinkItemLists( cBody.getLinks(), pBody.getLinks(), urlContainer, false ) ); + + if ( cBody.getBreadcrumbs().isEmpty() && !pBody.getBreadcrumbs().isEmpty() ) + { + LinkItem breadcrumb = new LinkItem(); + breadcrumb.setName( name ); + breadcrumb.setHref( "index.html" ); + cBody.getBreadcrumbs().add( breadcrumb ); + } + cBody.setBreadcrumbs( mergeLinkItemLists( cBody.getBreadcrumbs(), pBody.getBreadcrumbs(), urlContainer, + true ) ); + + cBody.setMenus( mergeMenus( cBody.getMenus(), pBody.getMenus(), urlContainer ) ); + + if ( cBody.getFooter() == null && pBody.getFooter() != null ) + { + cBody.setFooter( pBody.getFooter() ); + } + } + } + + private List mergeMenus( final List childMenus, final List parentMenus, + final URLRebaser urlContainer ) + { + List menus = new ArrayList( childMenus.size() + parentMenus.size() ); + + for ( Menu menu : childMenus ) + { + menus.add( menu ); + } + + int topCounter = 0; + for ( Menu menu : parentMenus ) + { + if ( "top".equals( menu.getInherit() ) ) + { + final Menu clone = menu.clone(); + + rebaseMenuPaths( clone.getItems(), urlContainer ); + + menus.add( topCounter, clone ); + topCounter++; + } + else if ( "bottom".equals( menu.getInherit() ) ) + { + final Menu clone = menu.clone(); + + rebaseMenuPaths( clone.getItems(), urlContainer ); + + menus.add( clone ); + } + } + + return menus; + } + + private void relativizeMenuPaths( final List items, final String baseUrl ) + { + for ( MenuItem item : items ) + { + relativizeLinkItemPaths( item, baseUrl ); + relativizeMenuPaths( item.getItems(), baseUrl ); + } + } + + private void rebaseMenuPaths( final List items, final URLRebaser urlContainer ) + { + for ( MenuItem item : items ) + { + rebaseLinkItemPaths( item, urlContainer ); + rebaseMenuPaths( item.getItems(), urlContainer ); + } + } + + private void relativizeLinkItemPaths( final LinkItem item, final String baseUrl ) + { + item.setHref( relativizeLink( item.getHref(), baseUrl ) ); + } + + private void rebaseLinkItemPaths( final LinkItem item, final URLRebaser urlContainer ) + { + item.setHref( urlContainer.rebaseLink( item.getHref() ) ); + } + + private void relativizeLogoPaths( final Logo logo, final String baseUrl ) + { + logo.setImg( relativizeLink( logo.getImg(), baseUrl ) ); + relativizeLinkItemPaths( logo, baseUrl ); + } + + private void rebaseLogoPaths( final Logo logo, final URLRebaser urlContainer ) + { + logo.setImg( urlContainer.rebaseLink( logo.getImg() ) ); + rebaseLinkItemPaths( logo, urlContainer ); + } + + private List mergeLinkItemLists( final List childList, final List parentList, + final URLRebaser urlContainer, boolean cutParentAfterDuplicate ) + { + List items = new ArrayList( childList.size() + parentList.size() ); + + for ( LinkItem item : parentList ) + { + if ( !items.contains( item ) && !childList.contains( item ) ) + { + final LinkItem clone = item.clone(); + + rebaseLinkItemPaths( clone, urlContainer ); + + items.add( clone ); + } + else if ( cutParentAfterDuplicate ) + { + // if a parent item is found in child, ignore next items (case for breadcrumbs) + // merge ( "B > E", "A > B > C > D" ) -> "A > B > E" (notice missing "C > D") + // see https://issues.apache.org/jira/browse/DOXIASITETOOLS-62 + break; + } + } + + for ( LinkItem item : childList ) + { + if ( !items.contains( item ) ) + { + items.add( item ); + } + } + + return items; + } + + private List mergePoweredByLists( final List childList, final List parentList, + final URLRebaser urlContainer ) + { + List logos = new ArrayList( 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/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_141/metadata.json b/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_141/metadata.json new file mode 100644 index 000000000..af50a957a --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_141/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_141", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "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", + "repo": "maven-doxia-sitetools", + "bug_id": "DefaultDecorationModelInheritanceAssembler_141" + } +} diff --git a/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_141/npe.json b/Java/maven-doxia-sitetools-DefaultDecorationModelInheritanceAssembler_141/npe.json new file mode 100644 index 000000000..19914fc51 --- /dev/null +++ b/Java/maven-doxia-sitetools-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/maven-doxia-sitetools-DefaultSiteRenderer_190/Dockerfile b/Java/maven-doxia-sitetools-DefaultSiteRenderer_190/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteRenderer_190/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-DefaultSiteRenderer_190/buggy.java b/Java/maven-doxia-sitetools-DefaultSiteRenderer_190/buggy.java new file mode 100644 index 000000000..2a14c2ead --- /dev/null +++ b/Java/maven-doxia-sitetools-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 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/maven-doxia-sitetools-DefaultSiteRenderer_190/metadata.json b/Java/maven-doxia-sitetools-DefaultSiteRenderer_190/metadata.json new file mode 100644 index 000000000..7d536e723 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteRenderer_190/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-DefaultSiteRenderer_190", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "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", + "repo": "maven-doxia-sitetools", + "bug_id": "DefaultSiteRenderer_190" + } +} diff --git a/Java/maven-doxia-sitetools-DefaultSiteRenderer_190/npe.json b/Java/maven-doxia-sitetools-DefaultSiteRenderer_190/npe.json new file mode 100644 index 000000000..d7a4ae3bd --- /dev/null +++ b/Java/maven-doxia-sitetools-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/maven-doxia-sitetools-DefaultSiteRenderer_525/Dockerfile b/Java/maven-doxia-sitetools-DefaultSiteRenderer_525/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteRenderer_525/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-DefaultSiteRenderer_525/buggy.java b/Java/maven-doxia-sitetools-DefaultSiteRenderer_525/buggy.java new file mode 100644 index 000000000..acfd6afc5 --- /dev/null +++ b/Java/maven-doxia-sitetools-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 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/maven-doxia-sitetools-DefaultSiteRenderer_525/metadata.json b/Java/maven-doxia-sitetools-DefaultSiteRenderer_525/metadata.json new file mode 100644 index 000000000..9435ffe54 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteRenderer_525/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-DefaultSiteRenderer_525", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "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", + "repo": "maven-doxia-sitetools", + "bug_id": "DefaultSiteRenderer_525" + } +} diff --git a/Java/maven-doxia-sitetools-DefaultSiteRenderer_525/npe.json b/Java/maven-doxia-sitetools-DefaultSiteRenderer_525/npe.json new file mode 100644 index 000000000..58e29ad93 --- /dev/null +++ b/Java/maven-doxia-sitetools-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/maven-doxia-sitetools-DefaultSiteRenderer_647/Dockerfile b/Java/maven-doxia-sitetools-DefaultSiteRenderer_647/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteRenderer_647/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-DefaultSiteRenderer_647/buggy.java b/Java/maven-doxia-sitetools-DefaultSiteRenderer_647/buggy.java new file mode 100644 index 000000000..5f9eeeebf --- /dev/null +++ b/Java/maven-doxia-sitetools-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 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/maven-doxia-sitetools-DefaultSiteRenderer_647/metadata.json b/Java/maven-doxia-sitetools-DefaultSiteRenderer_647/metadata.json new file mode 100644 index 000000000..d21ff62ce --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteRenderer_647/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-DefaultSiteRenderer_647", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "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", + "repo": "maven-doxia-sitetools", + "bug_id": "DefaultSiteRenderer_647" + } +} diff --git a/Java/maven-doxia-sitetools-DefaultSiteRenderer_647/npe.json b/Java/maven-doxia-sitetools-DefaultSiteRenderer_647/npe.json new file mode 100644 index 000000000..34b3d9d45 --- /dev/null +++ b/Java/maven-doxia-sitetools-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/maven-doxia-sitetools-DefaultSiteRenderer_736/Dockerfile b/Java/maven-doxia-sitetools-DefaultSiteRenderer_736/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteRenderer_736/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-DefaultSiteRenderer_736/buggy.java b/Java/maven-doxia-sitetools-DefaultSiteRenderer_736/buggy.java new file mode 100644 index 000000000..53c640474 --- /dev/null +++ b/Java/maven-doxia-sitetools-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 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/maven-doxia-sitetools-DefaultSiteRenderer_736/metadata.json b/Java/maven-doxia-sitetools-DefaultSiteRenderer_736/metadata.json new file mode 100644 index 000000000..f58adf22e --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteRenderer_736/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-DefaultSiteRenderer_736", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "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", + "repo": "maven-doxia-sitetools", + "bug_id": "DefaultSiteRenderer_736" + } +} diff --git a/Java/maven-doxia-sitetools-DefaultSiteRenderer_736/npe.json b/Java/maven-doxia-sitetools-DefaultSiteRenderer_736/npe.json new file mode 100644 index 000000000..02e49157f --- /dev/null +++ b/Java/maven-doxia-sitetools-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/maven-doxia-sitetools-DefaultSiteRenderer_738/Dockerfile b/Java/maven-doxia-sitetools-DefaultSiteRenderer_738/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteRenderer_738/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-DefaultSiteRenderer_738/buggy.java b/Java/maven-doxia-sitetools-DefaultSiteRenderer_738/buggy.java new file mode 100644 index 000000000..fdaf4cd5f --- /dev/null +++ b/Java/maven-doxia-sitetools-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 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/maven-doxia-sitetools-DefaultSiteRenderer_738/metadata.json b/Java/maven-doxia-sitetools-DefaultSiteRenderer_738/metadata.json new file mode 100644 index 000000000..75a844dd8 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteRenderer_738/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-DefaultSiteRenderer_738", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "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", + "repo": "maven-doxia-sitetools", + "bug_id": "DefaultSiteRenderer_738" + } +} diff --git a/Java/maven-doxia-sitetools-DefaultSiteRenderer_738/npe.json b/Java/maven-doxia-sitetools-DefaultSiteRenderer_738/npe.json new file mode 100644 index 000000000..c87c1ad1a --- /dev/null +++ b/Java/maven-doxia-sitetools-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/maven-doxia-sitetools-DefaultSiteRenderer_773/Dockerfile b/Java/maven-doxia-sitetools-DefaultSiteRenderer_773/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteRenderer_773/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-DefaultSiteRenderer_773/buggy.java b/Java/maven-doxia-sitetools-DefaultSiteRenderer_773/buggy.java new file mode 100644 index 000000000..0515ad48f --- /dev/null +++ b/Java/maven-doxia-sitetools-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 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/maven-doxia-sitetools-DefaultSiteRenderer_773/metadata.json b/Java/maven-doxia-sitetools-DefaultSiteRenderer_773/metadata.json new file mode 100644 index 000000000..1443bb4ca --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteRenderer_773/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-DefaultSiteRenderer_773", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "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", + "repo": "maven-doxia-sitetools", + "bug_id": "DefaultSiteRenderer_773" + } +} diff --git a/Java/maven-doxia-sitetools-DefaultSiteRenderer_773/npe.json b/Java/maven-doxia-sitetools-DefaultSiteRenderer_773/npe.json new file mode 100644 index 000000000..55be19965 --- /dev/null +++ b/Java/maven-doxia-sitetools-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/maven-doxia-sitetools-DefaultSiteRenderer_821/Dockerfile b/Java/maven-doxia-sitetools-DefaultSiteRenderer_821/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteRenderer_821/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-DefaultSiteRenderer_821/buggy.java b/Java/maven-doxia-sitetools-DefaultSiteRenderer_821/buggy.java new file mode 100644 index 000000000..c771b6653 --- /dev/null +++ b/Java/maven-doxia-sitetools-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 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/maven-doxia-sitetools-DefaultSiteRenderer_821/metadata.json b/Java/maven-doxia-sitetools-DefaultSiteRenderer_821/metadata.json new file mode 100644 index 000000000..41e6e2604 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteRenderer_821/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-DefaultSiteRenderer_821", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "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", + "repo": "maven-doxia-sitetools", + "bug_id": "DefaultSiteRenderer_821" + } +} diff --git a/Java/maven-doxia-sitetools-DefaultSiteRenderer_821/npe.json b/Java/maven-doxia-sitetools-DefaultSiteRenderer_821/npe.json new file mode 100644 index 000000000..76c2d1eb4 --- /dev/null +++ b/Java/maven-doxia-sitetools-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/maven-doxia-sitetools-DefaultSiteRenderer_828/Dockerfile b/Java/maven-doxia-sitetools-DefaultSiteRenderer_828/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteRenderer_828/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-DefaultSiteRenderer_828/buggy.java b/Java/maven-doxia-sitetools-DefaultSiteRenderer_828/buggy.java new file mode 100644 index 000000000..1add25b38 --- /dev/null +++ b/Java/maven-doxia-sitetools-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 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(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 = /* NPEX_NULL_EXP */ + 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 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/maven-doxia-sitetools-DefaultSiteRenderer_828/metadata.json b/Java/maven-doxia-sitetools-DefaultSiteRenderer_828/metadata.json new file mode 100644 index 000000000..e6a26d819 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteRenderer_828/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-DefaultSiteRenderer_828", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java", + "line": 817, + "npe_method": "createContextForSkin", + "deref_field": "getPrerequisites", + "npe_class": "DefaultSiteRenderer", + "repo": "maven-doxia-sitetools", + "bug_id": "DefaultSiteRenderer_828" + } +} diff --git a/Java/maven-doxia-sitetools-DefaultSiteRenderer_828/npe.json b/Java/maven-doxia-sitetools-DefaultSiteRenderer_828/npe.json new file mode 100644 index 000000000..0a6d2c958 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteRenderer_828/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java", + "line": 817, + "npe_method": "createContextForSkin", + "deref_field": "getPrerequisites", + "npe_class": "DefaultSiteRenderer" +} \ No newline at end of file diff --git a/Java/maven-doxia-sitetools-DefaultSiteRenderer_832/Dockerfile b/Java/maven-doxia-sitetools-DefaultSiteRenderer_832/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteRenderer_832/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-DefaultSiteRenderer_832/buggy.java b/Java/maven-doxia-sitetools-DefaultSiteRenderer_832/buggy.java new file mode 100644 index 000000000..e680c8ab9 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteRenderer_832/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(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 = /* NPEX_NULL_EXP */ + 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 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/maven-doxia-sitetools-DefaultSiteRenderer_832/metadata.json b/Java/maven-doxia-sitetools-DefaultSiteRenderer_832/metadata.json new file mode 100644 index 000000000..8e38d6a26 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteRenderer_832/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-DefaultSiteRenderer_832", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java", + "line": 819, + "npe_method": "createContextForSkin", + "deref_field": "p", + "npe_class": "DefaultSiteRenderer", + "repo": "maven-doxia-sitetools", + "bug_id": "DefaultSiteRenderer_832" + } +} diff --git a/Java/maven-doxia-sitetools-DefaultSiteRenderer_832/npe.json b/Java/maven-doxia-sitetools-DefaultSiteRenderer_832/npe.json new file mode 100644 index 000000000..3fb36cc22 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteRenderer_832/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java", + "line": 819, + "npe_method": "createContextForSkin", + "deref_field": "p", + "npe_class": "DefaultSiteRenderer" +} \ No newline at end of file diff --git a/Java/maven-doxia-sitetools-DefaultSiteTool_1036/Dockerfile b/Java/maven-doxia-sitetools-DefaultSiteTool_1036/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteTool_1036/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-DefaultSiteTool_1036/buggy.java b/Java/maven-doxia-sitetools-DefaultSiteTool_1036/buggy.java new file mode 100644 index 000000000..2f4af0b05 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteTool_1036/buggy.java @@ -0,0 +1,1539 @@ +package org.apache.maven.doxia.tools; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.StringTokenizer; + +import org.apache.commons.io.FilenameUtils; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.factory.ArtifactFactory; +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.artifact.resolver.ArtifactNotFoundException; +import org.apache.maven.artifact.resolver.ArtifactResolutionException; +import org.apache.maven.artifact.resolver.ArtifactResolver; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; +import org.apache.maven.artifact.versioning.VersionRange; +import org.apache.maven.doxia.site.decoration.Banner; +import org.apache.maven.doxia.site.decoration.DecorationModel; +import org.apache.maven.doxia.site.decoration.Menu; +import org.apache.maven.doxia.site.decoration.MenuItem; +import org.apache.maven.doxia.site.decoration.Skin; +import org.apache.maven.doxia.site.decoration.inheritance.DecorationModelInheritanceAssembler; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Reader; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Writer; +import org.apache.maven.model.DistributionManagement; +import org.apache.maven.model.Site; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.MavenProjectBuilder; +import org.apache.maven.project.ProjectBuildingException; +import org.apache.maven.reporting.MavenReport; +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.IOUtil; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.interpolation.EnvarBasedValueSource; +import org.codehaus.plexus.interpolation.InterpolationException; +import org.codehaus.plexus.interpolation.MapBasedValueSource; +import org.codehaus.plexus.interpolation.ObjectBasedValueSource; +import org.codehaus.plexus.interpolation.PrefixedObjectValueSource; +import org.codehaus.plexus.interpolation.PrefixedPropertiesValueSource; +import org.codehaus.plexus.interpolation.RegexBasedInterpolator; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +/** + * Default implementation of the site tool. + * + * @author Vincent Siveton + */ +@Component( role = SiteTool.class ) +public class DefaultSiteTool + extends AbstractLogEnabled + implements SiteTool +{ + // ---------------------------------------------------------------------- + // Components + // ---------------------------------------------------------------------- + + /** + * The component that is used to resolve additional artifacts required. + */ + @Requirement + private ArtifactResolver artifactResolver; + + /** + * The component used for creating artifact instances. + */ + @Requirement + private ArtifactFactory artifactFactory; + + /** + * Internationalization. + */ + @Requirement + protected I18N i18n; + + /** + * The component for assembling inheritance. + */ + @Requirement + protected DecorationModelInheritanceAssembler assembler; + + /** + * Project builder (deprecated in Maven 3: should use ProjectBuilder, which will avoid + * issues like DOXIASITETOOLS-166) + */ + @Requirement + protected MavenProjectBuilder mavenProjectBuilder; + + // ---------------------------------------------------------------------- + // Public methods + // ---------------------------------------------------------------------- + + public Artifact getSkinArtifactFromRepository( ArtifactRepository localRepository, + List remoteArtifactRepositories, + DecorationModel decoration ) + throws SiteToolException + { + checkNotNull( "localRepository", localRepository ); + checkNotNull( "remoteArtifactRepositories", remoteArtifactRepositories ); + checkNotNull( "decoration", decoration ); + + Skin skin = decoration.getSkin(); + + if ( skin == null ) + { + skin = Skin.getDefaultSkin(); + } + + String version = skin.getVersion(); + Artifact artifact; + try + { + if ( version == null ) + { + version = Artifact.RELEASE_VERSION; + } + VersionRange versionSpec = VersionRange.createFromVersionSpec( version ); + artifact = artifactFactory.createDependencyArtifact( skin.getGroupId(), skin.getArtifactId(), versionSpec, + "jar", null, null ); + + artifactResolver.resolve( artifact, remoteArtifactRepositories, localRepository ); + } + catch ( InvalidVersionSpecificationException e ) + { + throw new SiteToolException( "InvalidVersionSpecificationException: The skin version '" + version + + "' is not valid: " + e.getMessage(), e ); + } + catch ( ArtifactResolutionException e ) + { + throw new SiteToolException( "ArtifactResolutionException: Unable to find skin", e ); + } + catch ( ArtifactNotFoundException e ) + { + throw new SiteToolException( "ArtifactNotFoundException: The skin does not exist: " + e.getMessage(), e ); + } + + return artifact; + } + + public Artifact getDefaultSkinArtifact( ArtifactRepository localRepository, + List remoteArtifactRepositories ) + throws SiteToolException + { + return getSkinArtifactFromRepository( localRepository, remoteArtifactRepositories, new DecorationModel() ); + } + + /** + * This method is not implemented according to the URI specification and has many weird + * corner cases where it doesn't do the right thing. Please consider using a better + * implemented method from a different library such as org.apache.http.client.utils.URIUtils#resolve. + */ + @Deprecated + public String getRelativePath( String to, String from ) + { + checkNotNull( "to", to ); + checkNotNull( "from", from ); + + if ( to.contains( ":" ) && from.contains( ":" ) ) + { + String toScheme = to.substring( 0, to.lastIndexOf( ':' ) ); + String fromScheme = from.substring( 0, from.lastIndexOf( ':' ) ); + if ( !toScheme.equals( fromScheme ) ) + { + return to; + } + } + + URL toUrl = null; + URL fromUrl = null; + + String toPath = to; + String fromPath = from; + + try + { + toUrl = new URL( to ); + } + catch ( MalformedURLException e ) + { + try + { + toUrl = new File( getNormalizedPath( to ) ).toURI().toURL(); + } + catch ( MalformedURLException e1 ) + { + getLogger().warn( "Unable to load a URL for '" + to + "': " + e.getMessage() ); + return to; + } + } + + try + { + fromUrl = new URL( from ); + } + catch ( MalformedURLException e ) + { + try + { + fromUrl = new File( getNormalizedPath( from ) ).toURI().toURL(); + } + catch ( MalformedURLException e1 ) + { + getLogger().warn( "Unable to load a URL for '" + from + "': " + e.getMessage() ); + return to; + } + } + + if ( toUrl != null && fromUrl != null ) + { + // URLs, determine if they share protocol and domain info + + if ( ( toUrl.getProtocol().equalsIgnoreCase( fromUrl.getProtocol() ) ) + && ( toUrl.getHost().equalsIgnoreCase( fromUrl.getHost() ) ) + && ( toUrl.getPort() == fromUrl.getPort() ) ) + { + // shared URL domain details, use URI to determine relative path + + toPath = toUrl.getFile(); + fromPath = fromUrl.getFile(); + } + else + { + // don't share basic URL information, no relative available + + return to; + } + } + else if ( ( toUrl != null && fromUrl == null ) || ( toUrl == null && fromUrl != null ) ) + { + // one is a URL and the other isn't, no relative available. + + return to; + } + + // either the two locations are not URLs or if they are they + // share the common protocol and domain info and we are left + // with their URI information + + String relativePath = getRelativeFilePath( fromPath, toPath ); + + if ( relativePath == null ) + { + relativePath = to; + } + + if ( getLogger().isDebugEnabled() && !relativePath.toString().equals( to ) ) + { + getLogger().debug( "Mapped url: " + to + " to relative path: " + relativePath ); + } + + return relativePath; + } + + private static String getRelativeFilePath( final String oldPath, final String newPath ) + { + // normalize the path delimiters + + String fromPath = new File( oldPath ).getPath(); + String toPath = new File( newPath ).getPath(); + + // strip any leading slashes if its a windows path + if ( toPath.matches( "^\\[a-zA-Z]:" ) ) + { + toPath = toPath.substring( 1 ); + } + if ( fromPath.matches( "^\\[a-zA-Z]:" ) ) + { + fromPath = fromPath.substring( 1 ); + } + + // lowercase windows drive letters. + if ( fromPath.startsWith( ":", 1 ) ) + { + fromPath = Character.toLowerCase( fromPath.charAt( 0 ) ) + fromPath.substring( 1 ); + } + if ( toPath.startsWith( ":", 1 ) ) + { + toPath = Character.toLowerCase( toPath.charAt( 0 ) ) + toPath.substring( 1 ); + } + + // check for the presence of windows drives. No relative way of + // traversing from one to the other. + + if ( ( toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) + && ( !toPath.substring( 0, 1 ).equals( fromPath.substring( 0, 1 ) ) ) ) + { + // they both have drive path element but they don't match, no + // relative path + + return null; + } + + if ( ( toPath.startsWith( ":", 1 ) && !fromPath.startsWith( ":", 1 ) ) + || ( !toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) ) + { + + // one has a drive path element and the other doesn't, no relative + // path. + + return null; + + } + + final String relativePath = buildRelativePath( toPath, fromPath, File.separatorChar ); + + return relativePath.toString(); + } + + /** {@inheritDoc} */ + public File getSiteDescriptor( File siteDirectory, Locale locale ) + { + checkNotNull( "siteDirectory", siteDirectory ); + final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale; + + File siteDescriptor = new File( siteDirectory, "site_" + llocale.getLanguage() + ".xml" ); + + if ( !siteDescriptor.isFile() ) + { + siteDescriptor = new File( siteDirectory, "site.xml" ); + } + return siteDescriptor; + } + + /** + * Get a site descriptor from one of the repositories. + * + * @param project the Maven project, not null. + * @param localRepository the Maven local repository, not null. + * @param repositories the Maven remote repositories, not null. + * @param locale the locale wanted for the site descriptor. If not null, searching for + * site_localeLanguage.xml, otherwise searching for site.xml. + * @return the site descriptor into the local repository after download of it from repositories or null if not + * found in repositories. + * @throws SiteToolException if any + */ + File getSiteDescriptorFromRepository( MavenProject project, ArtifactRepository localRepository, + List repositories, Locale locale ) + throws SiteToolException + { + checkNotNull( "project", project ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "repositories", repositories ); + + final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale; + + try + { + return resolveSiteDescriptor( project, localRepository, repositories, llocale ); + } + catch ( ArtifactNotFoundException e ) + { + getLogger().debug( "ArtifactNotFoundException: Unable to locate site descriptor: " + e ); + return null; + } + catch ( ArtifactResolutionException e ) + { + throw new SiteToolException( "ArtifactResolutionException: Unable to locate site descriptor: " + + e.getMessage(), e ); + } + catch ( IOException e ) + { + throw new SiteToolException( "IOException: Unable to locate site descriptor: " + e.getMessage(), e ); + } + } + + /** + * Read site descriptor content from Reader, adding support for deprecated ${reports}, + * ${parentProject} and ${modules} tags. + * + * @param reader + * @return the input content interpolated with deprecated tags + * @throws IOException + */ + private String readSiteDescriptor( Reader reader, String projectId ) + throws IOException + { + String siteDescriptorContent = IOUtil.toString( reader ); + + // This is to support the deprecated ${reports}, ${parentProject} and ${modules} tags. + Properties props = new Properties(); + props.put( "reports", "" ); + props.put( "modules", "" ); + props.put( "parentProject", "" ); + + // warn if interpolation required + for ( Object prop : props.keySet() ) + { + if ( siteDescriptorContent.contains( "$" + prop ) ) + { + getLogger().warn( "Site descriptor for " + projectId + " contains $" + prop + + ": should be replaced with " + props.getProperty( (String) prop ) ); + } + if ( siteDescriptorContent.contains( "${" + prop + "}" ) ) + { + getLogger().warn( "Site descriptor for " + projectId + " contains ${" + prop + + "}: should be replaced with " + props.getProperty( (String) prop ) ); + } + } + + return StringUtils.interpolate( siteDescriptorContent, props ); + } + + /** {@inheritDoc} */ + public DecorationModel getDecorationModel( File siteDirectory, Locale locale, MavenProject project, + List reactorProjects, ArtifactRepository localRepository, + List repositories ) + throws SiteToolException + { + checkNotNull( "project", project ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "repositories", repositories ); + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + getLogger().debug( "Computing decoration model of " + project.getId() + " for locale " + llocale ); + + Map.Entry result = + getDecorationModel( 0, siteDirectory, llocale, project, reactorProjects, localRepository, repositories ); + DecorationModel decorationModel = result.getKey(); + MavenProject parentProject = result.getValue(); + + if ( decorationModel == null ) + { + getLogger().debug( "Using default site descriptor" ); + + String siteDescriptorContent; + + Reader reader = null; + try + { + // Note the default is not a super class - it is used when nothing else is found + reader = ReaderFactory.newXmlReader( getClass().getResourceAsStream( "/default-site.xml" ) ); + siteDescriptorContent = readSiteDescriptor( reader, "default-site.xml" ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading default site descriptor: " + e.getMessage(), e ); + } + finally + { + IOUtil.close( reader ); + } + + decorationModel = readDecorationModel( siteDescriptorContent ); + } + + // DecorationModel back to String to interpolate, then go back to DecorationModel + String siteDescriptorContent = decorationModelToString( decorationModel ); + + // "classical" late interpolation, after full inheritance + siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, false ); + + decorationModel = readDecorationModel( siteDescriptorContent ); + + if ( parentProject != null ) + { + populateParentMenu( decorationModel, llocale, project, parentProject, true ); + } + + try + { + populateModulesMenu( decorationModel, llocale, project, reactorProjects, localRepository, true ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error while populating modules menu: " + e.getMessage(), e ); + } + + if ( decorationModel.getBannerLeft() == null ) + { + // extra default to set + Banner banner = new Banner(); + banner.setName( project.getName() ); + decorationModel.setBannerLeft( banner ); + } + + return decorationModel; + } + + /** {@inheritDoc} */ + public String getInterpolatedSiteDescriptorContent( Map props, MavenProject aProject, + String siteDescriptorContent ) + throws SiteToolException + { + checkNotNull( "props", props ); + + // "classical" late interpolation + return getInterpolatedSiteDescriptorContent( aProject, siteDescriptorContent, false ); + } + + private String getInterpolatedSiteDescriptorContent( MavenProject aProject, + String siteDescriptorContent, boolean isEarly ) + throws SiteToolException + { + checkNotNull( "aProject", aProject ); + checkNotNull( "siteDescriptorContent", siteDescriptorContent ); + + RegexBasedInterpolator interpolator = new RegexBasedInterpolator(); + + if ( isEarly ) + { + interpolator.addValueSource( new PrefixedObjectValueSource( "this.", aProject ) ); + interpolator.addValueSource( new PrefixedPropertiesValueSource( "this.", aProject.getProperties() ) ); + } + else + { + interpolator.addValueSource( new ObjectBasedValueSource( aProject ) ); + interpolator.addValueSource( new MapBasedValueSource( aProject.getProperties() ) ); + + try + { + interpolator.addValueSource( new EnvarBasedValueSource() ); + } + catch ( IOException e ) + { + // Prefer logging? + throw new SiteToolException( "IOException: cannot interpolate environment properties: " + + e.getMessage(), e ); + } + } + + try + { + // FIXME: this does not escape xml entities, see MSITE-226, PLXCOMP-118 + return interpolator.interpolate( siteDescriptorContent, isEarly ? null : "project" ); + } + catch ( InterpolationException e ) + { + throw new SiteToolException( "Cannot interpolate site descriptor: " + e.getMessage(), e ); + } + } + + /** {@inheritDoc} */ + public MavenProject getParentProject( MavenProject aProject, List reactorProjects, + ArtifactRepository localRepository ) + { + checkNotNull( "aProject", aProject ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + + if ( isMaven3OrMore() ) + { + // no need to make voodoo with Maven 3: job already done + return aProject.getParent(); + } + + MavenProject parentProject = null; + + MavenProject origParent = aProject.getParent(); + if ( origParent != null ) + { + for ( MavenProject reactorProject : reactorProjects ) + { + if ( reactorProject.getGroupId().equals( origParent.getGroupId() ) + && reactorProject.getArtifactId().equals( origParent.getArtifactId() ) + && reactorProject.getVersion().equals( origParent.getVersion() ) ) + { + parentProject = reactorProject; + + getLogger().debug( "Parent project " + origParent.getId() + " picked from reactor" ); + break; + } + } + + if ( parentProject == null && aProject.getBasedir() != null + && StringUtils.isNotEmpty( aProject.getModel().getParent().getRelativePath() ) ) + { + try + { + String relativePath = aProject.getModel().getParent().getRelativePath(); + + File pomFile = new File( aProject.getBasedir(), relativePath ); + + if ( pomFile.isDirectory() ) + { + pomFile = new File( pomFile, "pom.xml" ); + } + pomFile = new File( getNormalizedPath( pomFile.getPath() ) ); + + if ( pomFile.isFile() ) + { + MavenProject mavenProject = mavenProjectBuilder.build( pomFile, localRepository, null ); + + if ( mavenProject.getGroupId().equals( origParent.getGroupId() ) + && mavenProject.getArtifactId().equals( origParent.getArtifactId() ) + && mavenProject.getVersion().equals( origParent.getVersion() ) ) + { + parentProject = mavenProject; + + getLogger().debug( "Parent project " + origParent.getId() + " loaded from a relative path: " + + relativePath ); + } + } + } + catch ( ProjectBuildingException e ) + { + getLogger().info( "Unable to load parent project " + origParent.getId() + " from a relative path: " + + e.getMessage() ); + } + } + + if ( parentProject == null ) + { + try + { + parentProject = mavenProjectBuilder.buildFromRepository( aProject.getParentArtifact(), aProject + .getRemoteArtifactRepositories(), localRepository ); + + getLogger().debug( "Parent project " + origParent.getId() + " loaded from repository" ); + } + catch ( ProjectBuildingException e ) + { + getLogger().warn( "Unable to load parent project " + origParent.getId() + " from repository: " + + e.getMessage() ); + } + } + + if ( parentProject == null ) + { + // fallback to original parent, which may contain uninterpolated value (still need a unit test) + + parentProject = origParent; + + getLogger().debug( "Parent project " + origParent.getId() + " picked from original value" ); + } + } + return parentProject; + } + + /** + * Populate the pre-defined parent menu of the decoration model, + * if used through <menu ref="parent"/>. + * + * @param decorationModel the Doxia Sitetools DecorationModel, not null. + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project a Maven project, not null. + * @param parentProject a Maven parent project, not null. + * @param keepInheritedRefs used for inherited references. + */ + private void populateParentMenu( DecorationModel decorationModel, Locale locale, MavenProject project, + MavenProject parentProject, boolean keepInheritedRefs ) + { + checkNotNull( "decorationModel", decorationModel ); + checkNotNull( "project", project ); + checkNotNull( "parentProject", parentProject ); + + Menu menu = decorationModel.getMenuRef( "parent" ); + + if ( menu == null ) + { + return; + } + + if ( keepInheritedRefs && menu.isInheritAsRef() ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + String parentUrl = getDistMgmntSiteUrl( parentProject ); + + if ( parentUrl != null ) + { + if ( parentUrl.endsWith( "/" ) ) + { + parentUrl += "index.html"; + } + else + { + parentUrl += "/index.html"; + } + + parentUrl = getRelativePath( parentUrl, getDistMgmntSiteUrl( project ) ); + } + else + { + // parent has no url, assume relative path is given by site structure + File parentBasedir = parentProject.getBasedir(); + // First make sure that the parent is available on the file system + if ( parentBasedir != null ) + { + // Try to find the relative path to the parent via the file system + String parentPath = parentBasedir.getAbsolutePath(); + String projectPath = project.getBasedir().getAbsolutePath(); + parentUrl = getRelativePath( parentPath, projectPath ) + "/index.html"; + } + } + + // Only add the parent menu if we were able to find a URL for it + if ( parentUrl == null ) + { + getLogger().warn( "Unable to find a URL to the parent project. The parent menu will NOT be added." ); + } + else + { + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.parentproject" ) ); + } + + MenuItem item = new MenuItem(); + item.setName( parentProject.getName() ); + item.setHref( parentUrl ); + menu.addItem( item ); + } + } + + /** + * Populate the pre-defined modules menu of the decoration model, + * if used through <menu ref="modules"/>. + * + * @param decorationModel the Doxia Sitetools DecorationModel, not null. + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project a Maven project, not null. + * @param reactorProjects the Maven reactor projects, not null. + * @param localRepository the Maven local repository, not null. + * @param keepInheritedRefs used for inherited references. + * @throws SiteToolException if any + * @throws IOException + */ + private void populateModulesMenu( DecorationModel decorationModel, Locale locale, MavenProject project, + List reactorProjects, ArtifactRepository localRepository, + boolean keepInheritedRefs ) + throws SiteToolException, IOException + { + checkNotNull( "project", project ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "decorationModel", decorationModel ); + + Menu menu = decorationModel.getMenuRef( "modules" ); + + if ( menu == null ) + { + return; + } + + if ( keepInheritedRefs && menu.isInheritAsRef() ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale ; + + // we require child modules and reactors to process module menu + if ( project.getModules().size() > 0 ) + { + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectmodules" ) ); + } + + for ( String module : (List) project.getModules() ) + { + MavenProject moduleProject = getModuleFromReactor( project, reactorProjects, module ); + + if ( moduleProject == null ) + { + getLogger().warn( "Module " + module + + " not found in reactor: loading locally" ); + + File f = new File( project.getBasedir(), module + "/pom.xml" ); + if ( f.exists() ) + { + try + { + moduleProject = mavenProjectBuilder.build( f, localRepository, null ); + } + catch ( ProjectBuildingException e ) + { + throw new SiteToolException( "Unable to read local module-POM", e ); + } + } + else + { + getLogger().warn( "No filesystem module-POM available" ); + + moduleProject = new MavenProject(); + moduleProject.setName( module ); + moduleProject.setDistributionManagement( new DistributionManagement() ); + moduleProject.getDistributionManagement().setSite( new Site() ); + moduleProject.getDistributionManagement().getSite().setUrl( module ); + } + } + + String siteUrl = getDistMgmntSiteUrl( moduleProject ); + String itemName = + ( moduleProject.getName() == null ) ? moduleProject.getArtifactId() : moduleProject.getName(); + + appendMenuItem( project, menu, itemName, siteUrl, moduleProject.getArtifactId() ); + } + } + else if ( decorationModel.getMenuRef( "modules" ).getInherit() == null ) + { + // only remove if project has no modules AND menu is not inherited, see MSHARED-174 + decorationModel.removeMenuRef( "modules" ); + } + } + + private static MavenProject getModuleFromReactor( MavenProject project, List reactorProjects, + String module ) + throws IOException + { + File moduleBasedir = new File( project.getBasedir(), module ).getCanonicalFile(); + + for ( MavenProject reactorProject : reactorProjects ) + { + if ( moduleBasedir.equals( reactorProject.getBasedir() ) ) + { + return reactorProject; + } + } + + // module not found in reactor + return null; + } + + /** {@inheritDoc} */ + public void populateReportsMenu( DecorationModel decorationModel, Locale locale, + Map> categories ) + { + checkNotNull( "decorationModel", decorationModel ); + checkNotNull( "categories", categories ); + + Menu menu = decorationModel.getMenuRef( "reports" ); + + if ( menu == null ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectdocumentation" ) ); + } + + boolean found = false; + if ( menu.getItems().isEmpty() ) + { + List categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_INFORMATION ); + if ( !isEmptyList( categoryReports ) ) + { + MenuItem item = createCategoryMenu( + i18n.getString( "site-tool", llocale, + "decorationModel.menu.projectinformation" ), + "/project-info.html", categoryReports, llocale ); + menu.getItems().add( item ); + found = true; + } + + categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_REPORTS ); + if ( !isEmptyList( categoryReports ) ) + { + MenuItem item = + createCategoryMenu( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectreports" ), + "/project-reports.html", categoryReports, llocale ); + menu.getItems().add( item ); + found = true; + } + } + if ( !found ) + { + decorationModel.removeMenuRef( "reports" ); + } + } + + /** {@inheritDoc} */ + public List getSiteLocales( String locales ) + { + if ( locales == null ) + { + return Collections.singletonList( DEFAULT_LOCALE ); + } + + String[] localesArray = StringUtils.split( locales, "," ); + List localesList = new ArrayList( localesArray.length ); + + for ( String localeString : localesArray ) + { + Locale locale = codeToLocale( localeString ); + + if ( locale == null ) + { + continue; + } + + if ( !Arrays.asList( Locale.getAvailableLocales() ).contains( locale ) ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "The locale defined by '" + locale + + "' is not available in this Java Virtual Machine (" + + System.getProperty( "java.version" ) + + " from " + System.getProperty( "java.vendor" ) + ") - IGNORING" ); + } + continue; + } + + // Default bundles are in English + if ( ( !locale.getLanguage().equals( DEFAULT_LOCALE.getLanguage() ) ) + && ( !i18n.getBundle( "site-tool", locale ).getLocale().getLanguage() + .equals( locale.getLanguage() ) ) ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "The locale '" + locale + "' (" + locale.getDisplayName( Locale.ENGLISH ) + + ") is not currently supported by Maven Site - IGNORING." + + "\nContributions are welcome and greatly appreciated!" + + "\nIf you want to contribute a new translation, please visit " + + "http://maven.apache.org/plugins/localization.html for detailed instructions." ); + } + + continue; + } + + localesList.add( locale ); + } + + if ( localesList.isEmpty() ) + { + localesList = Collections.singletonList( DEFAULT_LOCALE ); + } + + return localesList; + } + + /** + * Converts a locale code like "en", "en_US" or "en_US_win" to a java.util.Locale + * object. + *

    If localeCode = default, return the current value of the default locale for this instance + * of the Java Virtual Machine.

    + * + * @param localeCode the locale code string. + * @return a java.util.Locale object instanced or null if errors occurred + * @see java.util.Locale#getDefault() + */ + private Locale codeToLocale( String localeCode ) + { + if ( localeCode == null ) + { + return null; + } + + if ( "default".equalsIgnoreCase( localeCode ) ) + { + return Locale.getDefault(); + } + + String language = ""; + String country = ""; + String variant = ""; + + StringTokenizer tokenizer = new StringTokenizer( localeCode, "_" ); + final int maxTokens = 3; + if ( tokenizer.countTokens() > maxTokens ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "Invalid java.util.Locale format for '" + localeCode + "' entry - IGNORING" ); + } + return null; + } + + if ( tokenizer.hasMoreTokens() ) + { + language = tokenizer.nextToken(); + if ( tokenizer.hasMoreTokens() ) + { + country = tokenizer.nextToken(); + if ( tokenizer.hasMoreTokens() ) + { + variant = tokenizer.nextToken(); + } + } + } + + return new Locale( language, country, variant ); + } + + // ---------------------------------------------------------------------- + // Protected methods + // ---------------------------------------------------------------------- + + /** + * @param path could be null. + * @return the path normalized, i.e. by eliminating "/../" and "/./" in the path. + * @see FilenameUtils#normalize(String) + */ +// ---------------------------------------------------------------------- +// Protected methods +// ---------------------------------------------------------------------- +/** + * + * + * @param path + * could be null. + * @return the path normalized, i.e. by eliminating "/../" and "/./" in the path. + * @see FilenameUtils#normalize(String) + */ +protected static java.lang.String getNormalizedPath(java.lang.String path) { + java.lang.String normalized = org.apache.commons.io.FilenameUtils.normalize(path); + if (/* NPEX_NULL_EXP */ + normalized == null) { + normalized = path; + } + return normalized.replace('\\', '/'); +} + + // ---------------------------------------------------------------------- + // Private methods + // ---------------------------------------------------------------------- + + /** + * @param project not null + * @param localRepository not null + * @param repositories not null + * @param locale not null + * @return the resolved site descriptor + * @throws IOException if any + * @throws ArtifactResolutionException if any + * @throws ArtifactNotFoundException if any + */ + private File resolveSiteDescriptor( MavenProject project, ArtifactRepository localRepository, + List repositories, Locale locale ) + throws IOException, ArtifactResolutionException, ArtifactNotFoundException + { + File result; + + // TODO: this is a bit crude - proper type, or proper handling as metadata rather than an artifact in 2.1? + Artifact artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), + project.getArtifactId(), + project.getVersion(), "xml", + "site_" + locale.getLanguage() ); + + boolean found = false; + try + { + artifactResolver.resolve( artifact, repositories, localRepository ); + + result = artifact.getFile(); + + // we use zero length files to avoid re-resolution (see below) + if ( result.length() > 0 ) + { + found = true; + } + else + { + getLogger().debug( "No site descriptor found for " + project.getId() + " for locale " + + locale.getLanguage() + ", trying without locale..." ); + } + } + catch ( ArtifactNotFoundException e ) + { + getLogger().debug( "Unable to locate site descriptor for locale " + locale.getLanguage() + ": " + e ); + + // we can afford to write an empty descriptor here as we don't expect it to turn up later in the remote + // repository, because the parent was already released (and snapshots are updated automatically if changed) + result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); + result.getParentFile().mkdirs(); + result.createNewFile(); + } + + if ( !found ) + { + artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), project.getArtifactId(), + project.getVersion(), "xml", "site" ); + try + { + artifactResolver.resolve( artifact, repositories, localRepository ); + } + catch ( ArtifactNotFoundException e ) + { + // see above regarding this zero length file + result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); + result.getParentFile().mkdirs(); + result.createNewFile(); + + throw e; + } + + result = artifact.getFile(); + + // we use zero length files to avoid re-resolution (see below) + if ( result.length() == 0 ) + { + getLogger().debug( "No site descriptor found for " + project.getId() + " without locale." ); + result = null; + } + } + + return result; + } + + /** + * @param depth depth of project + * @param siteDirectory, can be null if project.basedir is null, ie POM from repository + * @param locale not null + * @param project not null + * @param reactorProjects not null + * @param localRepository not null + * @param repositories not null + * @param origProps not null + * @return the decoration model depending the locale and the parent project + * @throws SiteToolException if any + */ + private Map.Entry getDecorationModel( int depth, File siteDirectory, Locale locale, + MavenProject project, + List reactorProjects, + ArtifactRepository localRepository, + List repositories ) + throws SiteToolException + { + // 1. get site descriptor File + File siteDescriptor; + if ( project.getBasedir() == null ) + { + // POM is in the repository: look into the repository for site descriptor + try + { + siteDescriptor = getSiteDescriptorFromRepository( project, localRepository, repositories, locale ); + } + catch ( SiteToolException e ) + { + throw new SiteToolException( "The site descriptor cannot be resolved from the repository: " + + e.getMessage(), e ); + } + } + else + { + // POM is in build directory: look for site descriptor as local file + siteDescriptor = getSiteDescriptor( siteDirectory, locale ); + } + + // 2. read DecorationModel from site descriptor File and do early interpolation (${this.*}) + DecorationModel decoration = null; + Reader siteDescriptorReader = null; + try + { + if ( siteDescriptor != null && siteDescriptor.exists() ) + { + getLogger().debug( "Reading" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + + " site descriptor from " + siteDescriptor ); + + siteDescriptorReader = ReaderFactory.newXmlReader( siteDescriptor ); + + String siteDescriptorContent = readSiteDescriptor( siteDescriptorReader, project.getId() ); + + // interpolate ${this.*} = early interpolation + siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, true ); + + decoration = readDecorationModel( siteDescriptorContent ); + decoration.setLastModified( siteDescriptor.lastModified() ); + } + else + { + getLogger().debug( "No" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + " site descriptor." ); + } + } + catch ( IOException e ) + { + throw new SiteToolException( "The site descriptor for " + project.getId() + " cannot be read from " + + siteDescriptor, e ); + } + finally + { + IOUtil.close( siteDescriptorReader ); + } + + // 3. look for parent project + MavenProject parentProject = getParentProject( project, reactorProjects, localRepository ); + + // 4. merge with parent project DecorationModel + if ( parentProject != null && ( decoration == null || decoration.isMergeParent() ) ) + { + depth++; + getLogger().debug( "Looking for site descriptor of level " + depth + " parent project: " + + parentProject.getId() ); + + File parentSiteDirectory = null; + if ( parentProject.getBasedir() != null ) + { + // extrapolate parent project site directory + String siteRelativePath = getRelativeFilePath( project.getBasedir().getAbsolutePath(), + siteDescriptor.getParentFile().getAbsolutePath() ); + + parentSiteDirectory = new File( parentProject.getBasedir(), siteRelativePath ); + // notice: using same siteRelativePath for parent as current project; may be wrong if site plugin + // has different configuration. But this is a rare case (this only has impact if parent is from reactor) + } + + DecorationModel parentDecoration = + getDecorationModel( depth, parentSiteDirectory, locale, parentProject, reactorProjects, localRepository, + repositories ).getKey(); + + // MSHARED-116 requires an empty decoration model (instead of a null one) + // MSHARED-145 requires us to do this only if there is a parent to merge it with + if ( decoration == null && parentDecoration != null ) + { + // we have no site descriptor: merge the parent into an empty one + decoration = new DecorationModel(); + } + + String name = project.getName(); + if ( decoration != null && StringUtils.isNotEmpty( decoration.getName() ) ) + { + name = decoration.getName(); + } + + // Merge the parent and child DecorationModels + String projectDistMgmnt = getDistMgmntSiteUrl( project ); + String parentDistMgmnt = getDistMgmntSiteUrl( parentProject ); + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Site decoration model inheritance: assembling child with level " + depth + + " parent: distributionManagement.site.url child = " + projectDistMgmnt + " and parent = " + + parentDistMgmnt ); + } + assembler.assembleModelInheritance( name, decoration, parentDecoration, projectDistMgmnt, + parentDistMgmnt == null ? projectDistMgmnt : parentDistMgmnt ); + } + + return new AbstractMap.SimpleEntry( decoration, parentProject ); + } + + /** + * @param siteDescriptorContent not null + * @return the decoration model object + * @throws SiteToolException if any + */ + private DecorationModel readDecorationModel( String siteDescriptorContent ) + throws SiteToolException + { + try + { + return new DecorationXpp3Reader().read( new StringReader( siteDescriptorContent ) ); + } + catch ( XmlPullParserException e ) + { + throw new SiteToolException( "Error parsing site descriptor", e ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading site descriptor", e ); + } + } + + private String decorationModelToString( DecorationModel decoration ) + throws SiteToolException + { + StringWriter writer = new StringWriter(); + + try + { + new DecorationXpp3Writer().write( writer, decoration ); + return writer.toString(); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading site descriptor", e ); + } + finally + { + IOUtil.close( writer ); + } + } + + private static String buildRelativePath( final String toPath, final String fromPath, final char separatorChar ) + { + // use tokenizer to traverse paths and for lazy checking + StringTokenizer toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); + StringTokenizer fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); + + int count = 0; + + // walk along the to path looking for divergence from the from path + while ( toTokeniser.hasMoreTokens() && fromTokeniser.hasMoreTokens() ) + { + if ( separatorChar == '\\' ) + { + if ( !fromTokeniser.nextToken().equalsIgnoreCase( toTokeniser.nextToken() ) ) + { + break; + } + } + else + { + if ( !fromTokeniser.nextToken().equals( toTokeniser.nextToken() ) ) + { + break; + } + } + + count++; + } + + // reinitialize the tokenizers to count positions to retrieve the + // gobbled token + + toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); + fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); + + while ( count-- > 0 ) + { + fromTokeniser.nextToken(); + toTokeniser.nextToken(); + } + + StringBuilder relativePath = new StringBuilder(); + + // add back refs for the rest of from location. + while ( fromTokeniser.hasMoreTokens() ) + { + fromTokeniser.nextToken(); + + relativePath.append( ".." ); + + if ( fromTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + } + + if ( relativePath.length() != 0 && toTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + + // add fwd fills for whatever's left of to. + while ( toTokeniser.hasMoreTokens() ) + { + relativePath.append( toTokeniser.nextToken() ); + + if ( toTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + } + return relativePath.toString(); + } + + /** + * @param project not null + * @param menu not null + * @param name not null + * @param href could be null + * @param defaultHref not null + */ + private void appendMenuItem( MavenProject project, Menu menu, String name, String href, String defaultHref ) + { + String selectedHref = href; + + if ( selectedHref == null ) + { + selectedHref = defaultHref; + } + + MenuItem item = new MenuItem(); + item.setName( name ); + + String baseUrl = getDistMgmntSiteUrl( project ); + if ( baseUrl != null ) + { + selectedHref = getRelativePath( selectedHref, baseUrl ); + } + + if ( selectedHref.endsWith( "/" ) ) + { + item.setHref( selectedHref + "index.html" ); + } + else + { + item.setHref( selectedHref + "/index.html" ); + } + menu.addItem( item ); + } + + /** + * @param name not null + * @param href not null + * @param categoryReports not null + * @param locale not null + * @return the menu item object + */ + private MenuItem createCategoryMenu( String name, String href, List categoryReports, Locale locale ) + { + MenuItem item = new MenuItem(); + item.setName( name ); + item.setCollapse( true ); + item.setHref( href ); + + // MSHARED-172, allow reports to define their order in some other way? + //Collections.sort( categoryReports, new ReportComparator( locale ) ); + + for ( MavenReport report : categoryReports ) + { + MenuItem subitem = new MenuItem(); + subitem.setName( report.getName( locale ) ); + subitem.setHref( report.getOutputName() + ".html" ); + item.getItems().add( subitem ); + } + + return item; + } + + // ---------------------------------------------------------------------- + // static methods + // ---------------------------------------------------------------------- + + /** + * Convenience method. + * + * @param list could be null + * @return true if the list is null or empty + */ + private static boolean isEmptyList( List list ) + { + return list == null || list.isEmpty(); + } + + /** + * Return distributionManagement.site.url if defined, null otherwise. + * + * @param project not null + * @return could be null + */ + private static String getDistMgmntSiteUrl( MavenProject project ) + { + return getDistMgmntSiteUrl( project.getDistributionManagement() ); + } + + private static String getDistMgmntSiteUrl( DistributionManagement distMgmnt ) + { + if ( distMgmnt != null && distMgmnt.getSite() != null && distMgmnt.getSite().getUrl() != null ) + { + return urlEncode( distMgmnt.getSite().getUrl() ); + } + + return null; + } + + private static String urlEncode( final String url ) + { + if ( url == null ) + { + return null; + } + + try + { + return new File( url ).toURI().toURL().toExternalForm(); + } + catch ( MalformedURLException ex ) + { + return url; // this will then throw somewhere else + } + } + + private void checkNotNull( String name, Object value ) + { + if ( value == null ) + { + throw new IllegalArgumentException( "The parameter '" + name + "' cannot be null." ); + } + } + + /** + * Check the current Maven version to see if it's Maven 3.0 or newer. + */ + private static boolean isMaven3OrMore() + { + return new DefaultArtifactVersion( getMavenVersion() ).getMajorVersion() >= 3; + } + + private static String getMavenVersion() + { + // This relies on the fact that MavenProject is the in core classloader + // and that the core classloader is for the maven-core artifact + // and that should have a pom.properties file + // if this ever changes, we will have to revisit this code. + final Properties properties = new Properties(); + final String corePomProperties = "META-INF/maven/org.apache.maven/maven-core/pom.properties"; + final InputStream in = MavenProject.class.getClassLoader().getResourceAsStream( corePomProperties ); + try + { + properties.load( in ); + } + catch ( IOException ioe ) + { + return ""; + } + finally + { + IOUtil.close( in ); + } + + return properties.getProperty( "version" ).trim(); + } +} diff --git a/Java/maven-doxia-sitetools-DefaultSiteTool_1036/metadata.json b/Java/maven-doxia-sitetools-DefaultSiteTool_1036/metadata.json new file mode 100644 index 000000000..eefd14f4b --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteTool_1036/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-DefaultSiteTool_1036", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/DefaultSiteTool.java", + "line": 1043, + "npe_method": "getNormalizedPath", + "deref_field": "normalized", + "npe_class": "DefaultSiteTool", + "repo": "maven-doxia-sitetools", + "bug_id": "DefaultSiteTool_1036" + } +} diff --git a/Java/maven-doxia-sitetools-DefaultSiteTool_1036/npe.json b/Java/maven-doxia-sitetools-DefaultSiteTool_1036/npe.json new file mode 100644 index 000000000..6ccc98492 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteTool_1036/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/DefaultSiteTool.java", + "line": 1043, + "npe_method": "getNormalizedPath", + "deref_field": "normalized", + "npe_class": "DefaultSiteTool" +} \ No newline at end of file diff --git a/Java/maven-doxia-sitetools-DefaultSiteTool_349/Dockerfile b/Java/maven-doxia-sitetools-DefaultSiteTool_349/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteTool_349/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-DefaultSiteTool_349/buggy.java b/Java/maven-doxia-sitetools-DefaultSiteTool_349/buggy.java new file mode 100644 index 000000000..0088db730 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteTool_349/buggy.java @@ -0,0 +1,1529 @@ +package org.apache.maven.doxia.tools; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.StringTokenizer; + +import org.apache.commons.io.FilenameUtils; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.factory.ArtifactFactory; +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.artifact.resolver.ArtifactNotFoundException; +import org.apache.maven.artifact.resolver.ArtifactResolutionException; +import org.apache.maven.artifact.resolver.ArtifactResolver; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; +import org.apache.maven.artifact.versioning.VersionRange; +import org.apache.maven.doxia.site.decoration.Banner; +import org.apache.maven.doxia.site.decoration.DecorationModel; +import org.apache.maven.doxia.site.decoration.Menu; +import org.apache.maven.doxia.site.decoration.MenuItem; +import org.apache.maven.doxia.site.decoration.Skin; +import org.apache.maven.doxia.site.decoration.inheritance.DecorationModelInheritanceAssembler; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Reader; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Writer; +import org.apache.maven.model.DistributionManagement; +import org.apache.maven.model.Site; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.MavenProjectBuilder; +import org.apache.maven.project.ProjectBuildingException; +import org.apache.maven.reporting.MavenReport; +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.IOUtil; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.interpolation.EnvarBasedValueSource; +import org.codehaus.plexus.interpolation.InterpolationException; +import org.codehaus.plexus.interpolation.MapBasedValueSource; +import org.codehaus.plexus.interpolation.ObjectBasedValueSource; +import org.codehaus.plexus.interpolation.PrefixedObjectValueSource; +import org.codehaus.plexus.interpolation.PrefixedPropertiesValueSource; +import org.codehaus.plexus.interpolation.RegexBasedInterpolator; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +/** + * Default implementation of the site tool. + * + * @author Vincent Siveton + */ +@Component( role = SiteTool.class ) +public class DefaultSiteTool + extends AbstractLogEnabled + implements SiteTool +{ + // ---------------------------------------------------------------------- + // Components + // ---------------------------------------------------------------------- + + /** + * The component that is used to resolve additional artifacts required. + */ + @Requirement + private ArtifactResolver artifactResolver; + + /** + * The component used for creating artifact instances. + */ + @Requirement + private ArtifactFactory artifactFactory; + + /** + * Internationalization. + */ + @Requirement + protected I18N i18n; + + /** + * The component for assembling inheritance. + */ + @Requirement + protected DecorationModelInheritanceAssembler assembler; + + /** + * Project builder (deprecated in Maven 3: should use ProjectBuilder, which will avoid + * issues like DOXIASITETOOLS-166) + */ + @Requirement + protected MavenProjectBuilder mavenProjectBuilder; + + // ---------------------------------------------------------------------- + // Public methods + // ---------------------------------------------------------------------- + + public Artifact getSkinArtifactFromRepository( ArtifactRepository localRepository, + List remoteArtifactRepositories, + DecorationModel decoration ) + throws SiteToolException + { + checkNotNull( "localRepository", localRepository ); + checkNotNull( "remoteArtifactRepositories", remoteArtifactRepositories ); + checkNotNull( "decoration", decoration ); + + Skin skin = decoration.getSkin(); + + if ( skin == null ) + { + skin = Skin.getDefaultSkin(); + } + + String version = skin.getVersion(); + Artifact artifact; + try + { + if ( version == null ) + { + version = Artifact.RELEASE_VERSION; + } + VersionRange versionSpec = VersionRange.createFromVersionSpec( version ); + artifact = artifactFactory.createDependencyArtifact( skin.getGroupId(), skin.getArtifactId(), versionSpec, + "jar", null, null ); + + artifactResolver.resolve( artifact, remoteArtifactRepositories, localRepository ); + } + catch ( InvalidVersionSpecificationException e ) + { + throw new SiteToolException( "InvalidVersionSpecificationException: The skin version '" + version + + "' is not valid: " + e.getMessage(), e ); + } + catch ( ArtifactResolutionException e ) + { + throw new SiteToolException( "ArtifactResolutionException: Unable to find skin", e ); + } + catch ( ArtifactNotFoundException e ) + { + throw new SiteToolException( "ArtifactNotFoundException: The skin does not exist: " + e.getMessage(), e ); + } + + return artifact; + } + + public Artifact getDefaultSkinArtifact( ArtifactRepository localRepository, + List remoteArtifactRepositories ) + throws SiteToolException + { + return getSkinArtifactFromRepository( localRepository, remoteArtifactRepositories, new DecorationModel() ); + } + + /** + * This method is not implemented according to the URI specification and has many weird + * corner cases where it doesn't do the right thing. Please consider using a better + * implemented method from a different library such as org.apache.http.client.utils.URIUtils#resolve. + */ + @Deprecated + public String getRelativePath( String to, String from ) + { + checkNotNull( "to", to ); + checkNotNull( "from", from ); + + if ( to.contains( ":" ) && from.contains( ":" ) ) + { + String toScheme = to.substring( 0, to.lastIndexOf( ':' ) ); + String fromScheme = from.substring( 0, from.lastIndexOf( ':' ) ); + if ( !toScheme.equals( fromScheme ) ) + { + return to; + } + } + + URL toUrl = null; + URL fromUrl = null; + + String toPath = to; + String fromPath = from; + + try + { + toUrl = new URL( to ); + } + catch ( MalformedURLException e ) + { + try + { + toUrl = new File( getNormalizedPath( to ) ).toURI().toURL(); + } + catch ( MalformedURLException e1 ) + { + getLogger().warn( "Unable to load a URL for '" + to + "': " + e.getMessage() ); + return to; + } + } + + try + { + fromUrl = new URL( from ); + } + catch ( MalformedURLException e ) + { + try + { + fromUrl = new File( getNormalizedPath( from ) ).toURI().toURL(); + } + catch ( MalformedURLException e1 ) + { + getLogger().warn( "Unable to load a URL for '" + from + "': " + e.getMessage() ); + return to; + } + } + + if ( toUrl != null && fromUrl != null ) + { + // URLs, determine if they share protocol and domain info + + if ( ( toUrl.getProtocol().equalsIgnoreCase( fromUrl.getProtocol() ) ) + && ( toUrl.getHost().equalsIgnoreCase( fromUrl.getHost() ) ) + && ( toUrl.getPort() == fromUrl.getPort() ) ) + { + // shared URL domain details, use URI to determine relative path + + toPath = toUrl.getFile(); + fromPath = fromUrl.getFile(); + } + else + { + // don't share basic URL information, no relative available + + return to; + } + } + else if ( ( toUrl != null && fromUrl == null ) || ( toUrl == null && fromUrl != null ) ) + { + // one is a URL and the other isn't, no relative available. + + return to; + } + + // either the two locations are not URLs or if they are they + // share the common protocol and domain info and we are left + // with their URI information + + String relativePath = getRelativeFilePath( fromPath, toPath ); + + if ( relativePath == null ) + { + relativePath = to; + } + + if ( getLogger().isDebugEnabled() && !relativePath.toString().equals( to ) ) + { + getLogger().debug( "Mapped url: " + to + " to relative path: " + relativePath ); + } + + return relativePath; + } + + private static String getRelativeFilePath( final String oldPath, final String newPath ) + { + // normalize the path delimiters + + String fromPath = new File( oldPath ).getPath(); + String toPath = new File( newPath ).getPath(); + + // strip any leading slashes if its a windows path + if ( toPath.matches( "^\\[a-zA-Z]:" ) ) + { + toPath = toPath.substring( 1 ); + } + if ( fromPath.matches( "^\\[a-zA-Z]:" ) ) + { + fromPath = fromPath.substring( 1 ); + } + + // lowercase windows drive letters. + if ( fromPath.startsWith( ":", 1 ) ) + { + fromPath = Character.toLowerCase( fromPath.charAt( 0 ) ) + fromPath.substring( 1 ); + } + if ( toPath.startsWith( ":", 1 ) ) + { + toPath = Character.toLowerCase( toPath.charAt( 0 ) ) + toPath.substring( 1 ); + } + + // check for the presence of windows drives. No relative way of + // traversing from one to the other. + + if ( ( toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) + && ( !toPath.substring( 0, 1 ).equals( fromPath.substring( 0, 1 ) ) ) ) + { + // they both have drive path element but they don't match, no + // relative path + + return null; + } + + if ( ( toPath.startsWith( ":", 1 ) && !fromPath.startsWith( ":", 1 ) ) + || ( !toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) ) + { + + // one has a drive path element and the other doesn't, no relative + // path. + + return null; + + } + + final String relativePath = buildRelativePath( toPath, fromPath, File.separatorChar ); + + return relativePath.toString(); + } + + /** {@inheritDoc} */ +/** + * {@inheritDoc } + */ +public java.io.File getSiteDescriptor(java.io.File siteDirectory, java.util.Locale locale) { + checkNotNull("siteDirectory", siteDirectory); + final java.util.Locale llocale = /* NPEX_NULL_EXP */ + locale; + java.io.File siteDescriptor = new java.io.File(siteDirectory, ("site_" + llocale.getLanguage()) + ".xml"); + if (!siteDescriptor.isFile()) { + siteDescriptor = new java.io.File(siteDirectory, "site.xml"); + } + return siteDescriptor; +} + + /** + * Get a site descriptor from one of the repositories. + * + * @param project the Maven project, not null. + * @param localRepository the Maven local repository, not null. + * @param repositories the Maven remote repositories, not null. + * @param locale the locale wanted for the site descriptor. If not null, searching for + * site_localeLanguage.xml, otherwise searching for site.xml. + * @return the site descriptor into the local repository after download of it from repositories or null if not + * found in repositories. + * @throws SiteToolException if any + */ + File getSiteDescriptorFromRepository( MavenProject project, ArtifactRepository localRepository, + List repositories, Locale locale ) + throws SiteToolException + { + checkNotNull( "project", project ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "repositories", repositories ); + + final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale; + + try + { + return resolveSiteDescriptor( project, localRepository, repositories, llocale ); + } + catch ( ArtifactNotFoundException e ) + { + getLogger().debug( "ArtifactNotFoundException: Unable to locate site descriptor: " + e ); + return null; + } + catch ( ArtifactResolutionException e ) + { + throw new SiteToolException( "ArtifactResolutionException: Unable to locate site descriptor: " + + e.getMessage(), e ); + } + catch ( IOException e ) + { + throw new SiteToolException( "IOException: Unable to locate site descriptor: " + e.getMessage(), e ); + } + } + + /** + * Read site descriptor content from Reader, adding support for deprecated ${reports}, + * ${parentProject} and ${modules} tags. + * + * @param reader + * @return the input content interpolated with deprecated tags + * @throws IOException + */ + private String readSiteDescriptor( Reader reader, String projectId ) + throws IOException + { + String siteDescriptorContent = IOUtil.toString( reader ); + + // This is to support the deprecated ${reports}, ${parentProject} and ${modules} tags. + Properties props = new Properties(); + props.put( "reports", "" ); + props.put( "modules", "" ); + props.put( "parentProject", "" ); + + // warn if interpolation required + for ( Object prop : props.keySet() ) + { + if ( siteDescriptorContent.contains( "$" + prop ) ) + { + getLogger().warn( "Site descriptor for " + projectId + " contains $" + prop + + ": should be replaced with " + props.getProperty( (String) prop ) ); + } + if ( siteDescriptorContent.contains( "${" + prop + "}" ) ) + { + getLogger().warn( "Site descriptor for " + projectId + " contains ${" + prop + + "}: should be replaced with " + props.getProperty( (String) prop ) ); + } + } + + return StringUtils.interpolate( siteDescriptorContent, props ); + } + + /** {@inheritDoc} */ + public DecorationModel getDecorationModel( File siteDirectory, Locale locale, MavenProject project, + List reactorProjects, ArtifactRepository localRepository, + List repositories ) + throws SiteToolException + { + checkNotNull( "project", project ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "repositories", repositories ); + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + getLogger().debug( "Computing decoration model of " + project.getId() + " for locale " + llocale ); + + Map.Entry result = + getDecorationModel( 0, siteDirectory, llocale, project, reactorProjects, localRepository, repositories ); + DecorationModel decorationModel = result.getKey(); + MavenProject parentProject = result.getValue(); + + if ( decorationModel == null ) + { + getLogger().debug( "Using default site descriptor" ); + + String siteDescriptorContent; + + Reader reader = null; + try + { + // Note the default is not a super class - it is used when nothing else is found + reader = ReaderFactory.newXmlReader( getClass().getResourceAsStream( "/default-site.xml" ) ); + siteDescriptorContent = readSiteDescriptor( reader, "default-site.xml" ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading default site descriptor: " + e.getMessage(), e ); + } + finally + { + IOUtil.close( reader ); + } + + decorationModel = readDecorationModel( siteDescriptorContent ); + } + + // DecorationModel back to String to interpolate, then go back to DecorationModel + String siteDescriptorContent = decorationModelToString( decorationModel ); + + // "classical" late interpolation, after full inheritance + siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, false ); + + decorationModel = readDecorationModel( siteDescriptorContent ); + + if ( parentProject != null ) + { + populateParentMenu( decorationModel, llocale, project, parentProject, true ); + } + + try + { + populateModulesMenu( decorationModel, llocale, project, reactorProjects, localRepository, true ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error while populating modules menu: " + e.getMessage(), e ); + } + + if ( decorationModel.getBannerLeft() == null ) + { + // extra default to set + Banner banner = new Banner(); + banner.setName( project.getName() ); + decorationModel.setBannerLeft( banner ); + } + + return decorationModel; + } + + /** {@inheritDoc} */ + public String getInterpolatedSiteDescriptorContent( Map props, MavenProject aProject, + String siteDescriptorContent ) + throws SiteToolException + { + checkNotNull( "props", props ); + + // "classical" late interpolation + return getInterpolatedSiteDescriptorContent( aProject, siteDescriptorContent, false ); + } + + private String getInterpolatedSiteDescriptorContent( MavenProject aProject, + String siteDescriptorContent, boolean isEarly ) + throws SiteToolException + { + checkNotNull( "aProject", aProject ); + checkNotNull( "siteDescriptorContent", siteDescriptorContent ); + + RegexBasedInterpolator interpolator = new RegexBasedInterpolator(); + + if ( isEarly ) + { + interpolator.addValueSource( new PrefixedObjectValueSource( "this.", aProject ) ); + interpolator.addValueSource( new PrefixedPropertiesValueSource( "this.", aProject.getProperties() ) ); + } + else + { + interpolator.addValueSource( new ObjectBasedValueSource( aProject ) ); + interpolator.addValueSource( new MapBasedValueSource( aProject.getProperties() ) ); + + try + { + interpolator.addValueSource( new EnvarBasedValueSource() ); + } + catch ( IOException e ) + { + // Prefer logging? + throw new SiteToolException( "IOException: cannot interpolate environment properties: " + + e.getMessage(), e ); + } + } + + try + { + // FIXME: this does not escape xml entities, see MSITE-226, PLXCOMP-118 + return interpolator.interpolate( siteDescriptorContent, isEarly ? null : "project" ); + } + catch ( InterpolationException e ) + { + throw new SiteToolException( "Cannot interpolate site descriptor: " + e.getMessage(), e ); + } + } + + /** {@inheritDoc} */ + public MavenProject getParentProject( MavenProject aProject, List reactorProjects, + ArtifactRepository localRepository ) + { + checkNotNull( "aProject", aProject ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + + if ( isMaven3OrMore() ) + { + // no need to make voodoo with Maven 3: job already done + return aProject.getParent(); + } + + MavenProject parentProject = null; + + MavenProject origParent = aProject.getParent(); + if ( origParent != null ) + { + for ( MavenProject reactorProject : reactorProjects ) + { + if ( reactorProject.getGroupId().equals( origParent.getGroupId() ) + && reactorProject.getArtifactId().equals( origParent.getArtifactId() ) + && reactorProject.getVersion().equals( origParent.getVersion() ) ) + { + parentProject = reactorProject; + + getLogger().debug( "Parent project " + origParent.getId() + " picked from reactor" ); + break; + } + } + + if ( parentProject == null && aProject.getBasedir() != null + && StringUtils.isNotEmpty( aProject.getModel().getParent().getRelativePath() ) ) + { + try + { + String relativePath = aProject.getModel().getParent().getRelativePath(); + + File pomFile = new File( aProject.getBasedir(), relativePath ); + + if ( pomFile.isDirectory() ) + { + pomFile = new File( pomFile, "pom.xml" ); + } + pomFile = new File( getNormalizedPath( pomFile.getPath() ) ); + + if ( pomFile.isFile() ) + { + MavenProject mavenProject = mavenProjectBuilder.build( pomFile, localRepository, null ); + + if ( mavenProject.getGroupId().equals( origParent.getGroupId() ) + && mavenProject.getArtifactId().equals( origParent.getArtifactId() ) + && mavenProject.getVersion().equals( origParent.getVersion() ) ) + { + parentProject = mavenProject; + + getLogger().debug( "Parent project " + origParent.getId() + " loaded from a relative path: " + + relativePath ); + } + } + } + catch ( ProjectBuildingException e ) + { + getLogger().info( "Unable to load parent project " + origParent.getId() + " from a relative path: " + + e.getMessage() ); + } + } + + if ( parentProject == null ) + { + try + { + parentProject = mavenProjectBuilder.buildFromRepository( aProject.getParentArtifact(), aProject + .getRemoteArtifactRepositories(), localRepository ); + + getLogger().debug( "Parent project " + origParent.getId() + " loaded from repository" ); + } + catch ( ProjectBuildingException e ) + { + getLogger().warn( "Unable to load parent project " + origParent.getId() + " from repository: " + + e.getMessage() ); + } + } + + if ( parentProject == null ) + { + // fallback to original parent, which may contain uninterpolated value (still need a unit test) + + parentProject = origParent; + + getLogger().debug( "Parent project " + origParent.getId() + " picked from original value" ); + } + } + return parentProject; + } + + /** + * Populate the pre-defined parent menu of the decoration model, + * if used through <menu ref="parent"/>. + * + * @param decorationModel the Doxia Sitetools DecorationModel, not null. + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project a Maven project, not null. + * @param parentProject a Maven parent project, not null. + * @param keepInheritedRefs used for inherited references. + */ + private void populateParentMenu( DecorationModel decorationModel, Locale locale, MavenProject project, + MavenProject parentProject, boolean keepInheritedRefs ) + { + checkNotNull( "decorationModel", decorationModel ); + checkNotNull( "project", project ); + checkNotNull( "parentProject", parentProject ); + + Menu menu = decorationModel.getMenuRef( "parent" ); + + if ( menu == null ) + { + return; + } + + if ( keepInheritedRefs && menu.isInheritAsRef() ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + String parentUrl = getDistMgmntSiteUrl( parentProject ); + + if ( parentUrl != null ) + { + if ( parentUrl.endsWith( "/" ) ) + { + parentUrl += "index.html"; + } + else + { + parentUrl += "/index.html"; + } + + parentUrl = getRelativePath( parentUrl, getDistMgmntSiteUrl( project ) ); + } + else + { + // parent has no url, assume relative path is given by site structure + File parentBasedir = parentProject.getBasedir(); + // First make sure that the parent is available on the file system + if ( parentBasedir != null ) + { + // Try to find the relative path to the parent via the file system + String parentPath = parentBasedir.getAbsolutePath(); + String projectPath = project.getBasedir().getAbsolutePath(); + parentUrl = getRelativePath( parentPath, projectPath ) + "/index.html"; + } + } + + // Only add the parent menu if we were able to find a URL for it + if ( parentUrl == null ) + { + getLogger().warn( "Unable to find a URL to the parent project. The parent menu will NOT be added." ); + } + else + { + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.parentproject" ) ); + } + + MenuItem item = new MenuItem(); + item.setName( parentProject.getName() ); + item.setHref( parentUrl ); + menu.addItem( item ); + } + } + + /** + * Populate the pre-defined modules menu of the decoration model, + * if used through <menu ref="modules"/>. + * + * @param decorationModel the Doxia Sitetools DecorationModel, not null. + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project a Maven project, not null. + * @param reactorProjects the Maven reactor projects, not null. + * @param localRepository the Maven local repository, not null. + * @param keepInheritedRefs used for inherited references. + * @throws SiteToolException if any + * @throws IOException + */ + private void populateModulesMenu( DecorationModel decorationModel, Locale locale, MavenProject project, + List reactorProjects, ArtifactRepository localRepository, + boolean keepInheritedRefs ) + throws SiteToolException, IOException + { + checkNotNull( "project", project ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "decorationModel", decorationModel ); + + Menu menu = decorationModel.getMenuRef( "modules" ); + + if ( menu == null ) + { + return; + } + + if ( keepInheritedRefs && menu.isInheritAsRef() ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale ; + + // we require child modules and reactors to process module menu + if ( project.getModules().size() > 0 ) + { + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectmodules" ) ); + } + + for ( String module : (List) project.getModules() ) + { + MavenProject moduleProject = getModuleFromReactor( project, reactorProjects, module ); + + if ( moduleProject == null ) + { + getLogger().warn( "Module " + module + + " not found in reactor: loading locally" ); + + File f = new File( project.getBasedir(), module + "/pom.xml" ); + if ( f.exists() ) + { + try + { + moduleProject = mavenProjectBuilder.build( f, localRepository, null ); + } + catch ( ProjectBuildingException e ) + { + throw new SiteToolException( "Unable to read local module-POM", e ); + } + } + else + { + getLogger().warn( "No filesystem module-POM available" ); + + moduleProject = new MavenProject(); + moduleProject.setName( module ); + moduleProject.setDistributionManagement( new DistributionManagement() ); + moduleProject.getDistributionManagement().setSite( new Site() ); + moduleProject.getDistributionManagement().getSite().setUrl( module ); + } + } + + String siteUrl = getDistMgmntSiteUrl( moduleProject ); + String itemName = + ( moduleProject.getName() == null ) ? moduleProject.getArtifactId() : moduleProject.getName(); + + appendMenuItem( project, menu, itemName, siteUrl, moduleProject.getArtifactId() ); + } + } + else if ( decorationModel.getMenuRef( "modules" ).getInherit() == null ) + { + // only remove if project has no modules AND menu is not inherited, see MSHARED-174 + decorationModel.removeMenuRef( "modules" ); + } + } + + private static MavenProject getModuleFromReactor( MavenProject project, List reactorProjects, + String module ) + throws IOException + { + File moduleBasedir = new File( project.getBasedir(), module ).getCanonicalFile(); + + for ( MavenProject reactorProject : reactorProjects ) + { + if ( moduleBasedir.equals( reactorProject.getBasedir() ) ) + { + return reactorProject; + } + } + + // module not found in reactor + return null; + } + + /** {@inheritDoc} */ + public void populateReportsMenu( DecorationModel decorationModel, Locale locale, + Map> categories ) + { + checkNotNull( "decorationModel", decorationModel ); + checkNotNull( "categories", categories ); + + Menu menu = decorationModel.getMenuRef( "reports" ); + + if ( menu == null ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectdocumentation" ) ); + } + + boolean found = false; + if ( menu.getItems().isEmpty() ) + { + List categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_INFORMATION ); + if ( !isEmptyList( categoryReports ) ) + { + MenuItem item = createCategoryMenu( + i18n.getString( "site-tool", llocale, + "decorationModel.menu.projectinformation" ), + "/project-info.html", categoryReports, llocale ); + menu.getItems().add( item ); + found = true; + } + + categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_REPORTS ); + if ( !isEmptyList( categoryReports ) ) + { + MenuItem item = + createCategoryMenu( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectreports" ), + "/project-reports.html", categoryReports, llocale ); + menu.getItems().add( item ); + found = true; + } + } + if ( !found ) + { + decorationModel.removeMenuRef( "reports" ); + } + } + + /** {@inheritDoc} */ + public List getSiteLocales( String locales ) + { + if ( locales == null ) + { + return Collections.singletonList( DEFAULT_LOCALE ); + } + + String[] localesArray = StringUtils.split( locales, "," ); + List localesList = new ArrayList( localesArray.length ); + + for ( String localeString : localesArray ) + { + Locale locale = codeToLocale( localeString ); + + if ( locale == null ) + { + continue; + } + + if ( !Arrays.asList( Locale.getAvailableLocales() ).contains( locale ) ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "The locale defined by '" + locale + + "' is not available in this Java Virtual Machine (" + + System.getProperty( "java.version" ) + + " from " + System.getProperty( "java.vendor" ) + ") - IGNORING" ); + } + continue; + } + + // Default bundles are in English + if ( ( !locale.getLanguage().equals( DEFAULT_LOCALE.getLanguage() ) ) + && ( !i18n.getBundle( "site-tool", locale ).getLocale().getLanguage() + .equals( locale.getLanguage() ) ) ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "The locale '" + locale + "' (" + locale.getDisplayName( Locale.ENGLISH ) + + ") is not currently supported by Maven Site - IGNORING." + + "\nContributions are welcome and greatly appreciated!" + + "\nIf you want to contribute a new translation, please visit " + + "http://maven.apache.org/plugins/localization.html for detailed instructions." ); + } + + continue; + } + + localesList.add( locale ); + } + + if ( localesList.isEmpty() ) + { + localesList = Collections.singletonList( DEFAULT_LOCALE ); + } + + return localesList; + } + + /** + * Converts a locale code like "en", "en_US" or "en_US_win" to a java.util.Locale + * object. + *

    If localeCode = default, return the current value of the default locale for this instance + * of the Java Virtual Machine.

    + * + * @param localeCode the locale code string. + * @return a java.util.Locale object instanced or null if errors occurred + * @see java.util.Locale#getDefault() + */ + private Locale codeToLocale( String localeCode ) + { + if ( localeCode == null ) + { + return null; + } + + if ( "default".equalsIgnoreCase( localeCode ) ) + { + return Locale.getDefault(); + } + + String language = ""; + String country = ""; + String variant = ""; + + StringTokenizer tokenizer = new StringTokenizer( localeCode, "_" ); + final int maxTokens = 3; + if ( tokenizer.countTokens() > maxTokens ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "Invalid java.util.Locale format for '" + localeCode + "' entry - IGNORING" ); + } + return null; + } + + if ( tokenizer.hasMoreTokens() ) + { + language = tokenizer.nextToken(); + if ( tokenizer.hasMoreTokens() ) + { + country = tokenizer.nextToken(); + if ( tokenizer.hasMoreTokens() ) + { + variant = tokenizer.nextToken(); + } + } + } + + return new Locale( language, country, variant ); + } + + // ---------------------------------------------------------------------- + // Protected methods + // ---------------------------------------------------------------------- + + /** + * @param path could be null. + * @return the path normalized, i.e. by eliminating "/../" and "/./" in the path. + * @see FilenameUtils#normalize(String) + */ + protected static String getNormalizedPath( String path ) + { + String normalized = FilenameUtils.normalize( path ); + if ( normalized == null ) + { + normalized = path; + } + return ( normalized == null ) ? null : normalized.replace( '\\', '/' ); + } + + // ---------------------------------------------------------------------- + // Private methods + // ---------------------------------------------------------------------- + + /** + * @param project not null + * @param localRepository not null + * @param repositories not null + * @param locale not null + * @return the resolved site descriptor + * @throws IOException if any + * @throws ArtifactResolutionException if any + * @throws ArtifactNotFoundException if any + */ + private File resolveSiteDescriptor( MavenProject project, ArtifactRepository localRepository, + List repositories, Locale locale ) + throws IOException, ArtifactResolutionException, ArtifactNotFoundException + { + File result; + + // TODO: this is a bit crude - proper type, or proper handling as metadata rather than an artifact in 2.1? + Artifact artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), + project.getArtifactId(), + project.getVersion(), "xml", + "site_" + locale.getLanguage() ); + + boolean found = false; + try + { + artifactResolver.resolve( artifact, repositories, localRepository ); + + result = artifact.getFile(); + + // we use zero length files to avoid re-resolution (see below) + if ( result.length() > 0 ) + { + found = true; + } + else + { + getLogger().debug( "No site descriptor found for " + project.getId() + " for locale " + + locale.getLanguage() + ", trying without locale..." ); + } + } + catch ( ArtifactNotFoundException e ) + { + getLogger().debug( "Unable to locate site descriptor for locale " + locale.getLanguage() + ": " + e ); + + // we can afford to write an empty descriptor here as we don't expect it to turn up later in the remote + // repository, because the parent was already released (and snapshots are updated automatically if changed) + result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); + result.getParentFile().mkdirs(); + result.createNewFile(); + } + + if ( !found ) + { + artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), project.getArtifactId(), + project.getVersion(), "xml", "site" ); + try + { + artifactResolver.resolve( artifact, repositories, localRepository ); + } + catch ( ArtifactNotFoundException e ) + { + // see above regarding this zero length file + result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); + result.getParentFile().mkdirs(); + result.createNewFile(); + + throw e; + } + + result = artifact.getFile(); + + // we use zero length files to avoid re-resolution (see below) + if ( result.length() == 0 ) + { + getLogger().debug( "No site descriptor found for " + project.getId() + " without locale." ); + result = null; + } + } + + return result; + } + + /** + * @param depth depth of project + * @param siteDirectory, can be null if project.basedir is null, ie POM from repository + * @param locale not null + * @param project not null + * @param reactorProjects not null + * @param localRepository not null + * @param repositories not null + * @param origProps not null + * @return the decoration model depending the locale and the parent project + * @throws SiteToolException if any + */ + private Map.Entry getDecorationModel( int depth, File siteDirectory, Locale locale, + MavenProject project, + List reactorProjects, + ArtifactRepository localRepository, + List repositories ) + throws SiteToolException + { + // 1. get site descriptor File + File siteDescriptor; + if ( project.getBasedir() == null ) + { + // POM is in the repository: look into the repository for site descriptor + try + { + siteDescriptor = getSiteDescriptorFromRepository( project, localRepository, repositories, locale ); + } + catch ( SiteToolException e ) + { + throw new SiteToolException( "The site descriptor cannot be resolved from the repository: " + + e.getMessage(), e ); + } + } + else + { + // POM is in build directory: look for site descriptor as local file + siteDescriptor = getSiteDescriptor( siteDirectory, locale ); + } + + // 2. read DecorationModel from site descriptor File and do early interpolation (${this.*}) + DecorationModel decoration = null; + Reader siteDescriptorReader = null; + try + { + if ( siteDescriptor != null && siteDescriptor.exists() ) + { + getLogger().debug( "Reading" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + + " site descriptor from " + siteDescriptor ); + + siteDescriptorReader = ReaderFactory.newXmlReader( siteDescriptor ); + + String siteDescriptorContent = readSiteDescriptor( siteDescriptorReader, project.getId() ); + + // interpolate ${this.*} = early interpolation + siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, true ); + + decoration = readDecorationModel( siteDescriptorContent ); + decoration.setLastModified( siteDescriptor.lastModified() ); + } + else + { + getLogger().debug( "No" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + " site descriptor." ); + } + } + catch ( IOException e ) + { + throw new SiteToolException( "The site descriptor for " + project.getId() + " cannot be read from " + + siteDescriptor, e ); + } + finally + { + IOUtil.close( siteDescriptorReader ); + } + + // 3. look for parent project + MavenProject parentProject = getParentProject( project, reactorProjects, localRepository ); + + // 4. merge with parent project DecorationModel + if ( parentProject != null && ( decoration == null || decoration.isMergeParent() ) ) + { + depth++; + getLogger().debug( "Looking for site descriptor of level " + depth + " parent project: " + + parentProject.getId() ); + + File parentSiteDirectory = null; + if ( parentProject.getBasedir() != null ) + { + // extrapolate parent project site directory + String siteRelativePath = getRelativeFilePath( project.getBasedir().getAbsolutePath(), + siteDescriptor.getParentFile().getAbsolutePath() ); + + parentSiteDirectory = new File( parentProject.getBasedir(), siteRelativePath ); + // notice: using same siteRelativePath for parent as current project; may be wrong if site plugin + // has different configuration. But this is a rare case (this only has impact if parent is from reactor) + } + + DecorationModel parentDecoration = + getDecorationModel( depth, parentSiteDirectory, locale, parentProject, reactorProjects, localRepository, + repositories ).getKey(); + + // MSHARED-116 requires an empty decoration model (instead of a null one) + // MSHARED-145 requires us to do this only if there is a parent to merge it with + if ( decoration == null && parentDecoration != null ) + { + // we have no site descriptor: merge the parent into an empty one + decoration = new DecorationModel(); + } + + String name = project.getName(); + if ( decoration != null && StringUtils.isNotEmpty( decoration.getName() ) ) + { + name = decoration.getName(); + } + + // Merge the parent and child DecorationModels + String projectDistMgmnt = getDistMgmntSiteUrl( project ); + String parentDistMgmnt = getDistMgmntSiteUrl( parentProject ); + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Site decoration model inheritance: assembling child with level " + depth + + " parent: distributionManagement.site.url child = " + projectDistMgmnt + " and parent = " + + parentDistMgmnt ); + } + assembler.assembleModelInheritance( name, decoration, parentDecoration, projectDistMgmnt, + parentDistMgmnt == null ? projectDistMgmnt : parentDistMgmnt ); + } + + return new AbstractMap.SimpleEntry( decoration, parentProject ); + } + + /** + * @param siteDescriptorContent not null + * @return the decoration model object + * @throws SiteToolException if any + */ + private DecorationModel readDecorationModel( String siteDescriptorContent ) + throws SiteToolException + { + try + { + return new DecorationXpp3Reader().read( new StringReader( siteDescriptorContent ) ); + } + catch ( XmlPullParserException e ) + { + throw new SiteToolException( "Error parsing site descriptor", e ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading site descriptor", e ); + } + } + + private String decorationModelToString( DecorationModel decoration ) + throws SiteToolException + { + StringWriter writer = new StringWriter(); + + try + { + new DecorationXpp3Writer().write( writer, decoration ); + return writer.toString(); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading site descriptor", e ); + } + finally + { + IOUtil.close( writer ); + } + } + + private static String buildRelativePath( final String toPath, final String fromPath, final char separatorChar ) + { + // use tokenizer to traverse paths and for lazy checking + StringTokenizer toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); + StringTokenizer fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); + + int count = 0; + + // walk along the to path looking for divergence from the from path + while ( toTokeniser.hasMoreTokens() && fromTokeniser.hasMoreTokens() ) + { + if ( separatorChar == '\\' ) + { + if ( !fromTokeniser.nextToken().equalsIgnoreCase( toTokeniser.nextToken() ) ) + { + break; + } + } + else + { + if ( !fromTokeniser.nextToken().equals( toTokeniser.nextToken() ) ) + { + break; + } + } + + count++; + } + + // reinitialize the tokenizers to count positions to retrieve the + // gobbled token + + toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); + fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); + + while ( count-- > 0 ) + { + fromTokeniser.nextToken(); + toTokeniser.nextToken(); + } + + StringBuilder relativePath = new StringBuilder(); + + // add back refs for the rest of from location. + while ( fromTokeniser.hasMoreTokens() ) + { + fromTokeniser.nextToken(); + + relativePath.append( ".." ); + + if ( fromTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + } + + if ( relativePath.length() != 0 && toTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + + // add fwd fills for whatever's left of to. + while ( toTokeniser.hasMoreTokens() ) + { + relativePath.append( toTokeniser.nextToken() ); + + if ( toTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + } + return relativePath.toString(); + } + + /** + * @param project not null + * @param menu not null + * @param name not null + * @param href could be null + * @param defaultHref not null + */ + private void appendMenuItem( MavenProject project, Menu menu, String name, String href, String defaultHref ) + { + String selectedHref = href; + + if ( selectedHref == null ) + { + selectedHref = defaultHref; + } + + MenuItem item = new MenuItem(); + item.setName( name ); + + String baseUrl = getDistMgmntSiteUrl( project ); + if ( baseUrl != null ) + { + selectedHref = getRelativePath( selectedHref, baseUrl ); + } + + if ( selectedHref.endsWith( "/" ) ) + { + item.setHref( selectedHref + "index.html" ); + } + else + { + item.setHref( selectedHref + "/index.html" ); + } + menu.addItem( item ); + } + + /** + * @param name not null + * @param href not null + * @param categoryReports not null + * @param locale not null + * @return the menu item object + */ + private MenuItem createCategoryMenu( String name, String href, List categoryReports, Locale locale ) + { + MenuItem item = new MenuItem(); + item.setName( name ); + item.setCollapse( true ); + item.setHref( href ); + + // MSHARED-172, allow reports to define their order in some other way? + //Collections.sort( categoryReports, new ReportComparator( locale ) ); + + for ( MavenReport report : categoryReports ) + { + MenuItem subitem = new MenuItem(); + subitem.setName( report.getName( locale ) ); + subitem.setHref( report.getOutputName() + ".html" ); + item.getItems().add( subitem ); + } + + return item; + } + + // ---------------------------------------------------------------------- + // static methods + // ---------------------------------------------------------------------- + + /** + * Convenience method. + * + * @param list could be null + * @return true if the list is null or empty + */ + private static boolean isEmptyList( List list ) + { + return list == null || list.isEmpty(); + } + + /** + * Return distributionManagement.site.url if defined, null otherwise. + * + * @param project not null + * @return could be null + */ + private static String getDistMgmntSiteUrl( MavenProject project ) + { + return getDistMgmntSiteUrl( project.getDistributionManagement() ); + } + + private static String getDistMgmntSiteUrl( DistributionManagement distMgmnt ) + { + if ( distMgmnt != null && distMgmnt.getSite() != null && distMgmnt.getSite().getUrl() != null ) + { + return urlEncode( distMgmnt.getSite().getUrl() ); + } + + return null; + } + + private static String urlEncode( final String url ) + { + if ( url == null ) + { + return null; + } + + try + { + return new File( url ).toURI().toURL().toExternalForm(); + } + catch ( MalformedURLException ex ) + { + return url; // this will then throw somewhere else + } + } + + private void checkNotNull( String name, Object value ) + { + if ( value == null ) + { + throw new IllegalArgumentException( "The parameter '" + name + "' cannot be null." ); + } + } + + /** + * Check the current Maven version to see if it's Maven 3.0 or newer. + */ + private static boolean isMaven3OrMore() + { + return new DefaultArtifactVersion( getMavenVersion() ).getMajorVersion() >= 3; + } + + private static String getMavenVersion() + { + // This relies on the fact that MavenProject is the in core classloader + // and that the core classloader is for the maven-core artifact + // and that should have a pom.properties file + // if this ever changes, we will have to revisit this code. + final Properties properties = new Properties(); + final String corePomProperties = "META-INF/maven/org.apache.maven/maven-core/pom.properties"; + final InputStream in = MavenProject.class.getClassLoader().getResourceAsStream( corePomProperties ); + try + { + properties.load( in ); + } + catch ( IOException ioe ) + { + return ""; + } + finally + { + IOUtil.close( in ); + } + + return properties.getProperty( "version" ).trim(); + } +} diff --git a/Java/maven-doxia-sitetools-DefaultSiteTool_349/metadata.json b/Java/maven-doxia-sitetools-DefaultSiteTool_349/metadata.json new file mode 100644 index 000000000..21b5f0e18 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteTool_349/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-DefaultSiteTool_349", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/DefaultSiteTool.java", + "line": 352, + "npe_method": "getSiteDescriptor", + "deref_field": "locale", + "npe_class": "DefaultSiteTool", + "repo": "maven-doxia-sitetools", + "bug_id": "DefaultSiteTool_349" + } +} diff --git a/Java/maven-doxia-sitetools-DefaultSiteTool_349/npe.json b/Java/maven-doxia-sitetools-DefaultSiteTool_349/npe.json new file mode 100644 index 000000000..a61ad53a0 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteTool_349/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/DefaultSiteTool.java", + "line": 352, + "npe_method": "getSiteDescriptor", + "deref_field": "locale", + "npe_class": "DefaultSiteTool" +} \ No newline at end of file diff --git a/Java/maven-doxia-sitetools-DefaultSiteTool_492/Dockerfile b/Java/maven-doxia-sitetools-DefaultSiteTool_492/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteTool_492/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-DefaultSiteTool_492/buggy.java b/Java/maven-doxia-sitetools-DefaultSiteTool_492/buggy.java new file mode 100644 index 000000000..26e43d495 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteTool_492/buggy.java @@ -0,0 +1,1503 @@ +package org.apache.maven.doxia.tools; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.StringTokenizer; + +import org.apache.commons.io.FilenameUtils; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.factory.ArtifactFactory; +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.artifact.resolver.ArtifactNotFoundException; +import org.apache.maven.artifact.resolver.ArtifactResolutionException; +import org.apache.maven.artifact.resolver.ArtifactResolver; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; +import org.apache.maven.artifact.versioning.VersionRange; +import org.apache.maven.doxia.site.decoration.Banner; +import org.apache.maven.doxia.site.decoration.DecorationModel; +import org.apache.maven.doxia.site.decoration.Menu; +import org.apache.maven.doxia.site.decoration.MenuItem; +import org.apache.maven.doxia.site.decoration.Skin; +import org.apache.maven.doxia.site.decoration.inheritance.DecorationModelInheritanceAssembler; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Reader; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Writer; +import org.apache.maven.model.DistributionManagement; +import org.apache.maven.model.Site; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.MavenProjectBuilder; +import org.apache.maven.project.ProjectBuildingException; +import org.apache.maven.reporting.MavenReport; +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.IOUtil; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.interpolation.EnvarBasedValueSource; +import org.codehaus.plexus.interpolation.InterpolationException; +import org.codehaus.plexus.interpolation.MapBasedValueSource; +import org.codehaus.plexus.interpolation.ObjectBasedValueSource; +import org.codehaus.plexus.interpolation.PrefixedObjectValueSource; +import org.codehaus.plexus.interpolation.PrefixedPropertiesValueSource; +import org.codehaus.plexus.interpolation.RegexBasedInterpolator; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +/** + * Default implementation of the site tool. + * + * @author Vincent Siveton + */ +@Component( role = SiteTool.class ) +public class DefaultSiteTool + extends AbstractLogEnabled + implements SiteTool +{ + // ---------------------------------------------------------------------- + // Components + // ---------------------------------------------------------------------- + + /** + * The component that is used to resolve additional artifacts required. + */ + @Requirement + private ArtifactResolver artifactResolver; + + /** + * The component used for creating artifact instances. + */ + @Requirement + private ArtifactFactory artifactFactory; + + /** + * Internationalization. + */ + @Requirement + protected I18N i18n; + + /** + * The component for assembling inheritance. + */ + @Requirement + protected DecorationModelInheritanceAssembler assembler; + + /** + * Project builder (deprecated in Maven 3: should use ProjectBuilder, which will avoid + * issues like DOXIASITETOOLS-166) + */ + @Requirement + protected MavenProjectBuilder mavenProjectBuilder; + + // ---------------------------------------------------------------------- + // Public methods + // ---------------------------------------------------------------------- + + public Artifact getSkinArtifactFromRepository( ArtifactRepository localRepository, + List remoteArtifactRepositories, + DecorationModel decoration ) + throws SiteToolException + { + checkNotNull( "localRepository", localRepository ); + checkNotNull( "remoteArtifactRepositories", remoteArtifactRepositories ); + checkNotNull( "decoration", decoration ); + + Skin skin = decoration.getSkin(); + + if ( skin == null ) + { + skin = Skin.getDefaultSkin(); + } + + String version = skin.getVersion(); + Artifact artifact; + try + { + if ( version == null ) + { + version = Artifact.RELEASE_VERSION; + } + VersionRange versionSpec = VersionRange.createFromVersionSpec( version ); + artifact = artifactFactory.createDependencyArtifact( skin.getGroupId(), skin.getArtifactId(), versionSpec, + "jar", null, null ); + + artifactResolver.resolve( artifact, remoteArtifactRepositories, localRepository ); + } + catch ( InvalidVersionSpecificationException e ) + { + throw new SiteToolException( "InvalidVersionSpecificationException: The skin version '" + version + + "' is not valid: " + e.getMessage(), e ); + } + catch ( ArtifactResolutionException e ) + { + throw new SiteToolException( "ArtifactResolutionException: Unable to find skin", e ); + } + catch ( ArtifactNotFoundException e ) + { + throw new SiteToolException( "ArtifactNotFoundException: The skin does not exist: " + e.getMessage(), e ); + } + + return artifact; + } + + public Artifact getDefaultSkinArtifact( ArtifactRepository localRepository, + List remoteArtifactRepositories ) + throws SiteToolException + { + return getSkinArtifactFromRepository( localRepository, remoteArtifactRepositories, new DecorationModel() ); + } + + /** + * This method is not implemented according to the URI specification and has many weird + * corner cases where it doesn't do the right thing. Please consider using a better + * implemented method from a different library such as org.apache.http.client.utils.URIUtils#resolve. + */ + @Deprecated + public String getRelativePath( String to, String from ) + { + checkNotNull( "to", to ); + checkNotNull( "from", from ); + + if ( to.contains( ":" ) && from.contains( ":" ) ) + { + String toScheme = to.substring( 0, to.lastIndexOf( ':' ) ); + String fromScheme = from.substring( 0, from.lastIndexOf( ':' ) ); + if ( !toScheme.equals( fromScheme ) ) + { + return to; + } + } + + URL toUrl = null; + URL fromUrl = null; + + String toPath = to; + String fromPath = from; + + try + { + toUrl = new URL( to ); + } + catch ( MalformedURLException e ) + { + try + { + toUrl = new File( getNormalizedPath( to ) ).toURI().toURL(); + } + catch ( MalformedURLException e1 ) + { + getLogger().warn( "Unable to load a URL for '" + to + "': " + e.getMessage() ); + return to; + } + } + + try + { + fromUrl = new URL( from ); + } + catch ( MalformedURLException e ) + { + try + { + fromUrl = new File( getNormalizedPath( from ) ).toURI().toURL(); + } + catch ( MalformedURLException e1 ) + { + getLogger().warn( "Unable to load a URL for '" + from + "': " + e.getMessage() ); + return to; + } + } + + if ( toUrl != null && fromUrl != null ) + { + // URLs, determine if they share protocol and domain info + + if ( ( toUrl.getProtocol().equalsIgnoreCase( fromUrl.getProtocol() ) ) + && ( toUrl.getHost().equalsIgnoreCase( fromUrl.getHost() ) ) + && ( toUrl.getPort() == fromUrl.getPort() ) ) + { + // shared URL domain details, use URI to determine relative path + + toPath = toUrl.getFile(); + fromPath = fromUrl.getFile(); + } + else + { + // don't share basic URL information, no relative available + + return to; + } + } + else if ( ( toUrl != null && fromUrl == null ) || ( toUrl == null && fromUrl != null ) ) + { + // one is a URL and the other isn't, no relative available. + + return to; + } + + // either the two locations are not URLs or if they are they + // share the common protocol and domain info and we are left + // with their URI information + + String relativePath = getRelativeFilePath( fromPath, toPath ); + + if ( relativePath == null ) + { + relativePath = to; + } + + if ( getLogger().isDebugEnabled() && !relativePath.toString().equals( to ) ) + { + getLogger().debug( "Mapped url: " + to + " to relative path: " + relativePath ); + } + + return relativePath; + } + + private static String getRelativeFilePath( final String oldPath, final String newPath ) + { + // normalize the path delimiters + + String fromPath = new File( oldPath ).getPath(); + String toPath = new File( newPath ).getPath(); + + // strip any leading slashes if its a windows path + if ( toPath.matches( "^\\[a-zA-Z]:" ) ) + { + toPath = toPath.substring( 1 ); + } + if ( fromPath.matches( "^\\[a-zA-Z]:" ) ) + { + fromPath = fromPath.substring( 1 ); + } + + // lowercase windows drive letters. + if ( fromPath.startsWith( ":", 1 ) ) + { + fromPath = Character.toLowerCase( fromPath.charAt( 0 ) ) + fromPath.substring( 1 ); + } + if ( toPath.startsWith( ":", 1 ) ) + { + toPath = Character.toLowerCase( toPath.charAt( 0 ) ) + toPath.substring( 1 ); + } + + // check for the presence of windows drives. No relative way of + // traversing from one to the other. + + if ( ( toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) + && ( !toPath.substring( 0, 1 ).equals( fromPath.substring( 0, 1 ) ) ) ) + { + // they both have drive path element but they don't match, no + // relative path + + return null; + } + + if ( ( toPath.startsWith( ":", 1 ) && !fromPath.startsWith( ":", 1 ) ) + || ( !toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) ) + { + + // one has a drive path element and the other doesn't, no relative + // path. + + return null; + + } + + final String relativePath = buildRelativePath( toPath, fromPath, File.separatorChar ); + + return relativePath.toString(); + } + + /** {@inheritDoc} */ + public File getSiteDescriptor( File siteDirectory, Locale locale ) + { + checkNotNull( "siteDirectory", siteDirectory ); + final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale; + + File siteDescriptor = new File( siteDirectory, "site_" + llocale.getLanguage() + ".xml" ); + + if ( !siteDescriptor.isFile() ) + { + siteDescriptor = new File( siteDirectory, "site.xml" ); + } + return siteDescriptor; + } + + /** + * Get a site descriptor from one of the repositories. + * + * @param project the Maven project, not null. + * @param localRepository the Maven local repository, not null. + * @param repositories the Maven remote repositories, not null. + * @param locale the locale wanted for the site descriptor. If not null, searching for + * site_localeLanguage.xml, otherwise searching for site.xml. + * @return the site descriptor into the local repository after download of it from repositories or null if not + * found in repositories. + * @throws SiteToolException if any + */ + File getSiteDescriptorFromRepository( MavenProject project, ArtifactRepository localRepository, + List repositories, Locale locale ) + throws SiteToolException + { + checkNotNull( "project", project ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "repositories", repositories ); + + final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale; + + try + { + return resolveSiteDescriptor( project, localRepository, repositories, llocale ); + } + catch ( ArtifactNotFoundException e ) + { + getLogger().debug( "ArtifactNotFoundException: Unable to locate site descriptor: " + e ); + return null; + } + catch ( ArtifactResolutionException e ) + { + throw new SiteToolException( "ArtifactResolutionException: Unable to locate site descriptor: " + + e.getMessage(), e ); + } + catch ( IOException e ) + { + throw new SiteToolException( "IOException: Unable to locate site descriptor: " + e.getMessage(), e ); + } + } + + /** + * Read site descriptor content from Reader, adding support for deprecated ${reports}, + * ${parentProject} and ${modules} tags. + * + * @param reader + * @return the input content interpolated with deprecated tags + * @throws IOException + */ + private String readSiteDescriptor( Reader reader, String projectId ) + throws IOException + { + String siteDescriptorContent = IOUtil.toString( reader ); + + // This is to support the deprecated ${reports}, ${parentProject} and ${modules} tags. + Properties props = new Properties(); + props.put( "reports", "" ); + props.put( "modules", "" ); + props.put( "parentProject", "" ); + + // warn if interpolation required + for ( Object prop : props.keySet() ) + { + if ( siteDescriptorContent.contains( "$" + prop ) ) + { + getLogger().warn( "Site descriptor for " + projectId + " contains $" + prop + + ": should be replaced with " + props.getProperty( (String) prop ) ); + } + if ( siteDescriptorContent.contains( "${" + prop + "}" ) ) + { + getLogger().warn( "Site descriptor for " + projectId + " contains ${" + prop + + "}: should be replaced with " + props.getProperty( (String) prop ) ); + } + } + + return StringUtils.interpolate( siteDescriptorContent, props ); + } + + /** {@inheritDoc} */ +/** + * {@inheritDoc } + */ +public org.apache.maven.doxia.site.decoration.DecorationModel getDecorationModel(java.io.File siteDirectory, java.util.Locale locale, org.apache.maven.project.MavenProject project, java.util.List reactorProjects, org.apache.maven.artifact.repository.ArtifactRepository localRepository, java.util.List repositories) throws org.apache.maven.doxia.tools.SiteToolException { + checkNotNull("project", project); + checkNotNull("reactorProjects", reactorProjects); + checkNotNull("localRepository", localRepository); + checkNotNull("repositories", repositories); + final java.util.Locale llocale = (locale == null) ? java.util.Locale.getDefault() : locale; + getLogger().debug((("Computing decoration model of " + project.getId()) + " for locale ") + llocale); + java.util.Map.Entry result = getDecorationModel(0, siteDirectory, llocale, project, reactorProjects, localRepository, repositories); + org.apache.maven.doxia.site.decoration.DecorationModel decorationModel = result.getKey(); + org.apache.maven.project.MavenProject parentProject = result.getValue(); + if (decorationModel == null) { + getLogger().debug("Using default site descriptor"); + java.lang.String siteDescriptorContent; + java.io.Reader reader = null; + try { + // Note the default is not a super class - it is used when nothing else is found + reader = org.codehaus.plexus.util.ReaderFactory.newXmlReader(getClass().getResourceAsStream("/default-site.xml")); + siteDescriptorContent = readSiteDescriptor(reader, "default-site.xml"); + } catch (java.io.IOException e) { + throw new org.apache.maven.doxia.tools.SiteToolException("Error reading default site descriptor: " + e.getMessage(), e); + } finally { + org.codehaus.plexus.util.IOUtil.close(reader); + } + decorationModel = readDecorationModel(siteDescriptorContent); + } + // DecorationModel back to String to interpolate, then go back to DecorationModel + java.lang.String siteDescriptorContent = decorationModelToString(decorationModel); + // "classical" late interpolation, after full inheritance + siteDescriptorContent = getInterpolatedSiteDescriptorContent(project, siteDescriptorContent, false); + decorationModel = readDecorationModel(siteDescriptorContent); + { + populateParentMenu(decorationModel, llocale, project, /* NPEX_NULL_EXP */ + parentProject, true); + } + try { + populateModulesMenu(decorationModel, llocale, project, reactorProjects, localRepository, true); + } catch (java.io.IOException e) { + throw new org.apache.maven.doxia.tools.SiteToolException("Error while populating modules menu: " + e.getMessage(), e); + } + if (decorationModel.getBannerLeft() == null) { + // extra default to set + org.apache.maven.doxia.site.decoration.Banner banner = new org.apache.maven.doxia.site.decoration.Banner(); + banner.setName(project.getName()); + decorationModel.setBannerLeft(banner); + } + return decorationModel; +} + + /** {@inheritDoc} */ + public String getInterpolatedSiteDescriptorContent( Map props, MavenProject aProject, + String siteDescriptorContent ) + throws SiteToolException + { + checkNotNull( "props", props ); + + // "classical" late interpolation + return getInterpolatedSiteDescriptorContent( aProject, siteDescriptorContent, false ); + } + + private String getInterpolatedSiteDescriptorContent( MavenProject aProject, + String siteDescriptorContent, boolean isEarly ) + throws SiteToolException + { + checkNotNull( "aProject", aProject ); + checkNotNull( "siteDescriptorContent", siteDescriptorContent ); + + RegexBasedInterpolator interpolator = new RegexBasedInterpolator(); + + if ( isEarly ) + { + interpolator.addValueSource( new PrefixedObjectValueSource( "this.", aProject ) ); + interpolator.addValueSource( new PrefixedPropertiesValueSource( "this.", aProject.getProperties() ) ); + } + else + { + interpolator.addValueSource( new ObjectBasedValueSource( aProject ) ); + interpolator.addValueSource( new MapBasedValueSource( aProject.getProperties() ) ); + + try + { + interpolator.addValueSource( new EnvarBasedValueSource() ); + } + catch ( IOException e ) + { + // Prefer logging? + throw new SiteToolException( "IOException: cannot interpolate environment properties: " + + e.getMessage(), e ); + } + } + + try + { + // FIXME: this does not escape xml entities, see MSITE-226, PLXCOMP-118 + return interpolator.interpolate( siteDescriptorContent, isEarly ? null : "project" ); + } + catch ( InterpolationException e ) + { + throw new SiteToolException( "Cannot interpolate site descriptor: " + e.getMessage(), e ); + } + } + + /** {@inheritDoc} */ + public MavenProject getParentProject( MavenProject aProject, List reactorProjects, + ArtifactRepository localRepository ) + { + checkNotNull( "aProject", aProject ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + + if ( isMaven3OrMore() ) + { + // no need to make voodoo with Maven 3: job already done + return aProject.getParent(); + } + + MavenProject parentProject = null; + + MavenProject origParent = aProject.getParent(); + if ( origParent != null ) + { + for ( MavenProject reactorProject : reactorProjects ) + { + if ( reactorProject.getGroupId().equals( origParent.getGroupId() ) + && reactorProject.getArtifactId().equals( origParent.getArtifactId() ) + && reactorProject.getVersion().equals( origParent.getVersion() ) ) + { + parentProject = reactorProject; + + getLogger().debug( "Parent project " + origParent.getId() + " picked from reactor" ); + break; + } + } + + if ( parentProject == null && aProject.getBasedir() != null + && StringUtils.isNotEmpty( aProject.getModel().getParent().getRelativePath() ) ) + { + try + { + String relativePath = aProject.getModel().getParent().getRelativePath(); + + File pomFile = new File( aProject.getBasedir(), relativePath ); + + if ( pomFile.isDirectory() ) + { + pomFile = new File( pomFile, "pom.xml" ); + } + pomFile = new File( getNormalizedPath( pomFile.getPath() ) ); + + if ( pomFile.isFile() ) + { + MavenProject mavenProject = mavenProjectBuilder.build( pomFile, localRepository, null ); + + if ( mavenProject.getGroupId().equals( origParent.getGroupId() ) + && mavenProject.getArtifactId().equals( origParent.getArtifactId() ) + && mavenProject.getVersion().equals( origParent.getVersion() ) ) + { + parentProject = mavenProject; + + getLogger().debug( "Parent project " + origParent.getId() + " loaded from a relative path: " + + relativePath ); + } + } + } + catch ( ProjectBuildingException e ) + { + getLogger().info( "Unable to load parent project " + origParent.getId() + " from a relative path: " + + e.getMessage() ); + } + } + + if ( parentProject == null ) + { + try + { + parentProject = mavenProjectBuilder.buildFromRepository( aProject.getParentArtifact(), aProject + .getRemoteArtifactRepositories(), localRepository ); + + getLogger().debug( "Parent project " + origParent.getId() + " loaded from repository" ); + } + catch ( ProjectBuildingException e ) + { + getLogger().warn( "Unable to load parent project " + origParent.getId() + " from repository: " + + e.getMessage() ); + } + } + + if ( parentProject == null ) + { + // fallback to original parent, which may contain uninterpolated value (still need a unit test) + + parentProject = origParent; + + getLogger().debug( "Parent project " + origParent.getId() + " picked from original value" ); + } + } + return parentProject; + } + + /** + * Populate the pre-defined parent menu of the decoration model, + * if used through <menu ref="parent"/>. + * + * @param decorationModel the Doxia Sitetools DecorationModel, not null. + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project a Maven project, not null. + * @param parentProject a Maven parent project, not null. + * @param keepInheritedRefs used for inherited references. + */ + private void populateParentMenu( DecorationModel decorationModel, Locale locale, MavenProject project, + MavenProject parentProject, boolean keepInheritedRefs ) + { + checkNotNull( "decorationModel", decorationModel ); + checkNotNull( "project", project ); + checkNotNull( "parentProject", parentProject ); + + Menu menu = decorationModel.getMenuRef( "parent" ); + + if ( menu == null ) + { + return; + } + + if ( keepInheritedRefs && menu.isInheritAsRef() ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + String parentUrl = getDistMgmntSiteUrl( parentProject ); + + if ( parentUrl != null ) + { + if ( parentUrl.endsWith( "/" ) ) + { + parentUrl += "index.html"; + } + else + { + parentUrl += "/index.html"; + } + + parentUrl = getRelativePath( parentUrl, getDistMgmntSiteUrl( project ) ); + } + else + { + // parent has no url, assume relative path is given by site structure + File parentBasedir = parentProject.getBasedir(); + // First make sure that the parent is available on the file system + if ( parentBasedir != null ) + { + // Try to find the relative path to the parent via the file system + String parentPath = parentBasedir.getAbsolutePath(); + String projectPath = project.getBasedir().getAbsolutePath(); + parentUrl = getRelativePath( parentPath, projectPath ) + "/index.html"; + } + } + + // Only add the parent menu if we were able to find a URL for it + if ( parentUrl == null ) + { + getLogger().warn( "Unable to find a URL to the parent project. The parent menu will NOT be added." ); + } + else + { + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.parentproject" ) ); + } + + MenuItem item = new MenuItem(); + item.setName( parentProject.getName() ); + item.setHref( parentUrl ); + menu.addItem( item ); + } + } + + /** + * Populate the pre-defined modules menu of the decoration model, + * if used through <menu ref="modules"/>. + * + * @param decorationModel the Doxia Sitetools DecorationModel, not null. + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project a Maven project, not null. + * @param reactorProjects the Maven reactor projects, not null. + * @param localRepository the Maven local repository, not null. + * @param keepInheritedRefs used for inherited references. + * @throws SiteToolException if any + * @throws IOException + */ + private void populateModulesMenu( DecorationModel decorationModel, Locale locale, MavenProject project, + List reactorProjects, ArtifactRepository localRepository, + boolean keepInheritedRefs ) + throws SiteToolException, IOException + { + checkNotNull( "project", project ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "decorationModel", decorationModel ); + + Menu menu = decorationModel.getMenuRef( "modules" ); + + if ( menu == null ) + { + return; + } + + if ( keepInheritedRefs && menu.isInheritAsRef() ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale ; + + // we require child modules and reactors to process module menu + if ( project.getModules().size() > 0 ) + { + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectmodules" ) ); + } + + for ( String module : (List) project.getModules() ) + { + MavenProject moduleProject = getModuleFromReactor( project, reactorProjects, module ); + + if ( moduleProject == null ) + { + getLogger().warn( "Module " + module + + " not found in reactor: loading locally" ); + + File f = new File( project.getBasedir(), module + "/pom.xml" ); + if ( f.exists() ) + { + try + { + moduleProject = mavenProjectBuilder.build( f, localRepository, null ); + } + catch ( ProjectBuildingException e ) + { + throw new SiteToolException( "Unable to read local module-POM", e ); + } + } + else + { + getLogger().warn( "No filesystem module-POM available" ); + + moduleProject = new MavenProject(); + moduleProject.setName( module ); + moduleProject.setDistributionManagement( new DistributionManagement() ); + moduleProject.getDistributionManagement().setSite( new Site() ); + moduleProject.getDistributionManagement().getSite().setUrl( module ); + } + } + + String siteUrl = getDistMgmntSiteUrl( moduleProject ); + String itemName = + ( moduleProject.getName() == null ) ? moduleProject.getArtifactId() : moduleProject.getName(); + + appendMenuItem( project, menu, itemName, siteUrl, moduleProject.getArtifactId() ); + } + } + else if ( decorationModel.getMenuRef( "modules" ).getInherit() == null ) + { + // only remove if project has no modules AND menu is not inherited, see MSHARED-174 + decorationModel.removeMenuRef( "modules" ); + } + } + + private static MavenProject getModuleFromReactor( MavenProject project, List reactorProjects, + String module ) + throws IOException + { + File moduleBasedir = new File( project.getBasedir(), module ).getCanonicalFile(); + + for ( MavenProject reactorProject : reactorProjects ) + { + if ( moduleBasedir.equals( reactorProject.getBasedir() ) ) + { + return reactorProject; + } + } + + // module not found in reactor + return null; + } + + /** {@inheritDoc} */ + public void populateReportsMenu( DecorationModel decorationModel, Locale locale, + Map> categories ) + { + checkNotNull( "decorationModel", decorationModel ); + checkNotNull( "categories", categories ); + + Menu menu = decorationModel.getMenuRef( "reports" ); + + if ( menu == null ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectdocumentation" ) ); + } + + boolean found = false; + if ( menu.getItems().isEmpty() ) + { + List categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_INFORMATION ); + if ( !isEmptyList( categoryReports ) ) + { + MenuItem item = createCategoryMenu( + i18n.getString( "site-tool", llocale, + "decorationModel.menu.projectinformation" ), + "/project-info.html", categoryReports, llocale ); + menu.getItems().add( item ); + found = true; + } + + categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_REPORTS ); + if ( !isEmptyList( categoryReports ) ) + { + MenuItem item = + createCategoryMenu( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectreports" ), + "/project-reports.html", categoryReports, llocale ); + menu.getItems().add( item ); + found = true; + } + } + if ( !found ) + { + decorationModel.removeMenuRef( "reports" ); + } + } + + /** {@inheritDoc} */ + public List getSiteLocales( String locales ) + { + if ( locales == null ) + { + return Collections.singletonList( DEFAULT_LOCALE ); + } + + String[] localesArray = StringUtils.split( locales, "," ); + List localesList = new ArrayList( localesArray.length ); + + for ( String localeString : localesArray ) + { + Locale locale = codeToLocale( localeString ); + + if ( locale == null ) + { + continue; + } + + if ( !Arrays.asList( Locale.getAvailableLocales() ).contains( locale ) ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "The locale defined by '" + locale + + "' is not available in this Java Virtual Machine (" + + System.getProperty( "java.version" ) + + " from " + System.getProperty( "java.vendor" ) + ") - IGNORING" ); + } + continue; + } + + // Default bundles are in English + if ( ( !locale.getLanguage().equals( DEFAULT_LOCALE.getLanguage() ) ) + && ( !i18n.getBundle( "site-tool", locale ).getLocale().getLanguage() + .equals( locale.getLanguage() ) ) ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "The locale '" + locale + "' (" + locale.getDisplayName( Locale.ENGLISH ) + + ") is not currently supported by Maven Site - IGNORING." + + "\nContributions are welcome and greatly appreciated!" + + "\nIf you want to contribute a new translation, please visit " + + "http://maven.apache.org/plugins/localization.html for detailed instructions." ); + } + + continue; + } + + localesList.add( locale ); + } + + if ( localesList.isEmpty() ) + { + localesList = Collections.singletonList( DEFAULT_LOCALE ); + } + + return localesList; + } + + /** + * Converts a locale code like "en", "en_US" or "en_US_win" to a java.util.Locale + * object. + *

    If localeCode = default, return the current value of the default locale for this instance + * of the Java Virtual Machine.

    + * + * @param localeCode the locale code string. + * @return a java.util.Locale object instanced or null if errors occurred + * @see java.util.Locale#getDefault() + */ + private Locale codeToLocale( String localeCode ) + { + if ( localeCode == null ) + { + return null; + } + + if ( "default".equalsIgnoreCase( localeCode ) ) + { + return Locale.getDefault(); + } + + String language = ""; + String country = ""; + String variant = ""; + + StringTokenizer tokenizer = new StringTokenizer( localeCode, "_" ); + final int maxTokens = 3; + if ( tokenizer.countTokens() > maxTokens ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "Invalid java.util.Locale format for '" + localeCode + "' entry - IGNORING" ); + } + return null; + } + + if ( tokenizer.hasMoreTokens() ) + { + language = tokenizer.nextToken(); + if ( tokenizer.hasMoreTokens() ) + { + country = tokenizer.nextToken(); + if ( tokenizer.hasMoreTokens() ) + { + variant = tokenizer.nextToken(); + } + } + } + + return new Locale( language, country, variant ); + } + + // ---------------------------------------------------------------------- + // Protected methods + // ---------------------------------------------------------------------- + + /** + * @param path could be null. + * @return the path normalized, i.e. by eliminating "/../" and "/./" in the path. + * @see FilenameUtils#normalize(String) + */ + protected static String getNormalizedPath( String path ) + { + String normalized = FilenameUtils.normalize( path ); + if ( normalized == null ) + { + normalized = path; + } + return ( normalized == null ) ? null : normalized.replace( '\\', '/' ); + } + + // ---------------------------------------------------------------------- + // Private methods + // ---------------------------------------------------------------------- + + /** + * @param project not null + * @param localRepository not null + * @param repositories not null + * @param locale not null + * @return the resolved site descriptor + * @throws IOException if any + * @throws ArtifactResolutionException if any + * @throws ArtifactNotFoundException if any + */ + private File resolveSiteDescriptor( MavenProject project, ArtifactRepository localRepository, + List repositories, Locale locale ) + throws IOException, ArtifactResolutionException, ArtifactNotFoundException + { + File result; + + // TODO: this is a bit crude - proper type, or proper handling as metadata rather than an artifact in 2.1? + Artifact artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), + project.getArtifactId(), + project.getVersion(), "xml", + "site_" + locale.getLanguage() ); + + boolean found = false; + try + { + artifactResolver.resolve( artifact, repositories, localRepository ); + + result = artifact.getFile(); + + // we use zero length files to avoid re-resolution (see below) + if ( result.length() > 0 ) + { + found = true; + } + else + { + getLogger().debug( "No site descriptor found for " + project.getId() + " for locale " + + locale.getLanguage() + ", trying without locale..." ); + } + } + catch ( ArtifactNotFoundException e ) + { + getLogger().debug( "Unable to locate site descriptor for locale " + locale.getLanguage() + ": " + e ); + + // we can afford to write an empty descriptor here as we don't expect it to turn up later in the remote + // repository, because the parent was already released (and snapshots are updated automatically if changed) + result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); + result.getParentFile().mkdirs(); + result.createNewFile(); + } + + if ( !found ) + { + artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), project.getArtifactId(), + project.getVersion(), "xml", "site" ); + try + { + artifactResolver.resolve( artifact, repositories, localRepository ); + } + catch ( ArtifactNotFoundException e ) + { + // see above regarding this zero length file + result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); + result.getParentFile().mkdirs(); + result.createNewFile(); + + throw e; + } + + result = artifact.getFile(); + + // we use zero length files to avoid re-resolution (see below) + if ( result.length() == 0 ) + { + getLogger().debug( "No site descriptor found for " + project.getId() + " without locale." ); + result = null; + } + } + + return result; + } + + /** + * @param depth depth of project + * @param siteDirectory, can be null if project.basedir is null, ie POM from repository + * @param locale not null + * @param project not null + * @param reactorProjects not null + * @param localRepository not null + * @param repositories not null + * @param origProps not null + * @return the decoration model depending the locale and the parent project + * @throws SiteToolException if any + */ + private Map.Entry getDecorationModel( int depth, File siteDirectory, Locale locale, + MavenProject project, + List reactorProjects, + ArtifactRepository localRepository, + List repositories ) + throws SiteToolException + { + // 1. get site descriptor File + File siteDescriptor; + if ( project.getBasedir() == null ) + { + // POM is in the repository: look into the repository for site descriptor + try + { + siteDescriptor = getSiteDescriptorFromRepository( project, localRepository, repositories, locale ); + } + catch ( SiteToolException e ) + { + throw new SiteToolException( "The site descriptor cannot be resolved from the repository: " + + e.getMessage(), e ); + } + } + else + { + // POM is in build directory: look for site descriptor as local file + siteDescriptor = getSiteDescriptor( siteDirectory, locale ); + } + + // 2. read DecorationModel from site descriptor File and do early interpolation (${this.*}) + DecorationModel decoration = null; + Reader siteDescriptorReader = null; + try + { + if ( siteDescriptor != null && siteDescriptor.exists() ) + { + getLogger().debug( "Reading" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + + " site descriptor from " + siteDescriptor ); + + siteDescriptorReader = ReaderFactory.newXmlReader( siteDescriptor ); + + String siteDescriptorContent = readSiteDescriptor( siteDescriptorReader, project.getId() ); + + // interpolate ${this.*} = early interpolation + siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, true ); + + decoration = readDecorationModel( siteDescriptorContent ); + decoration.setLastModified( siteDescriptor.lastModified() ); + } + else + { + getLogger().debug( "No" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + " site descriptor." ); + } + } + catch ( IOException e ) + { + throw new SiteToolException( "The site descriptor for " + project.getId() + " cannot be read from " + + siteDescriptor, e ); + } + finally + { + IOUtil.close( siteDescriptorReader ); + } + + // 3. look for parent project + MavenProject parentProject = getParentProject( project, reactorProjects, localRepository ); + + // 4. merge with parent project DecorationModel + if ( parentProject != null && ( decoration == null || decoration.isMergeParent() ) ) + { + depth++; + getLogger().debug( "Looking for site descriptor of level " + depth + " parent project: " + + parentProject.getId() ); + + File parentSiteDirectory = null; + if ( parentProject.getBasedir() != null ) + { + // extrapolate parent project site directory + String siteRelativePath = getRelativeFilePath( project.getBasedir().getAbsolutePath(), + siteDescriptor.getParentFile().getAbsolutePath() ); + + parentSiteDirectory = new File( parentProject.getBasedir(), siteRelativePath ); + // notice: using same siteRelativePath for parent as current project; may be wrong if site plugin + // has different configuration. But this is a rare case (this only has impact if parent is from reactor) + } + + DecorationModel parentDecoration = + getDecorationModel( depth, parentSiteDirectory, locale, parentProject, reactorProjects, localRepository, + repositories ).getKey(); + + // MSHARED-116 requires an empty decoration model (instead of a null one) + // MSHARED-145 requires us to do this only if there is a parent to merge it with + if ( decoration == null && parentDecoration != null ) + { + // we have no site descriptor: merge the parent into an empty one + decoration = new DecorationModel(); + } + + String name = project.getName(); + if ( decoration != null && StringUtils.isNotEmpty( decoration.getName() ) ) + { + name = decoration.getName(); + } + + // Merge the parent and child DecorationModels + String projectDistMgmnt = getDistMgmntSiteUrl( project ); + String parentDistMgmnt = getDistMgmntSiteUrl( parentProject ); + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Site decoration model inheritance: assembling child with level " + depth + + " parent: distributionManagement.site.url child = " + projectDistMgmnt + " and parent = " + + parentDistMgmnt ); + } + assembler.assembleModelInheritance( name, decoration, parentDecoration, projectDistMgmnt, + parentDistMgmnt == null ? projectDistMgmnt : parentDistMgmnt ); + } + + return new AbstractMap.SimpleEntry( decoration, parentProject ); + } + + /** + * @param siteDescriptorContent not null + * @return the decoration model object + * @throws SiteToolException if any + */ + private DecorationModel readDecorationModel( String siteDescriptorContent ) + throws SiteToolException + { + try + { + return new DecorationXpp3Reader().read( new StringReader( siteDescriptorContent ) ); + } + catch ( XmlPullParserException e ) + { + throw new SiteToolException( "Error parsing site descriptor", e ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading site descriptor", e ); + } + } + + private String decorationModelToString( DecorationModel decoration ) + throws SiteToolException + { + StringWriter writer = new StringWriter(); + + try + { + new DecorationXpp3Writer().write( writer, decoration ); + return writer.toString(); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading site descriptor", e ); + } + finally + { + IOUtil.close( writer ); + } + } + + private static String buildRelativePath( final String toPath, final String fromPath, final char separatorChar ) + { + // use tokenizer to traverse paths and for lazy checking + StringTokenizer toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); + StringTokenizer fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); + + int count = 0; + + // walk along the to path looking for divergence from the from path + while ( toTokeniser.hasMoreTokens() && fromTokeniser.hasMoreTokens() ) + { + if ( separatorChar == '\\' ) + { + if ( !fromTokeniser.nextToken().equalsIgnoreCase( toTokeniser.nextToken() ) ) + { + break; + } + } + else + { + if ( !fromTokeniser.nextToken().equals( toTokeniser.nextToken() ) ) + { + break; + } + } + + count++; + } + + // reinitialize the tokenizers to count positions to retrieve the + // gobbled token + + toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); + fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); + + while ( count-- > 0 ) + { + fromTokeniser.nextToken(); + toTokeniser.nextToken(); + } + + StringBuilder relativePath = new StringBuilder(); + + // add back refs for the rest of from location. + while ( fromTokeniser.hasMoreTokens() ) + { + fromTokeniser.nextToken(); + + relativePath.append( ".." ); + + if ( fromTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + } + + if ( relativePath.length() != 0 && toTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + + // add fwd fills for whatever's left of to. + while ( toTokeniser.hasMoreTokens() ) + { + relativePath.append( toTokeniser.nextToken() ); + + if ( toTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + } + return relativePath.toString(); + } + + /** + * @param project not null + * @param menu not null + * @param name not null + * @param href could be null + * @param defaultHref not null + */ + private void appendMenuItem( MavenProject project, Menu menu, String name, String href, String defaultHref ) + { + String selectedHref = href; + + if ( selectedHref == null ) + { + selectedHref = defaultHref; + } + + MenuItem item = new MenuItem(); + item.setName( name ); + + String baseUrl = getDistMgmntSiteUrl( project ); + if ( baseUrl != null ) + { + selectedHref = getRelativePath( selectedHref, baseUrl ); + } + + if ( selectedHref.endsWith( "/" ) ) + { + item.setHref( selectedHref + "index.html" ); + } + else + { + item.setHref( selectedHref + "/index.html" ); + } + menu.addItem( item ); + } + + /** + * @param name not null + * @param href not null + * @param categoryReports not null + * @param locale not null + * @return the menu item object + */ + private MenuItem createCategoryMenu( String name, String href, List categoryReports, Locale locale ) + { + MenuItem item = new MenuItem(); + item.setName( name ); + item.setCollapse( true ); + item.setHref( href ); + + // MSHARED-172, allow reports to define their order in some other way? + //Collections.sort( categoryReports, new ReportComparator( locale ) ); + + for ( MavenReport report : categoryReports ) + { + MenuItem subitem = new MenuItem(); + subitem.setName( report.getName( locale ) ); + subitem.setHref( report.getOutputName() + ".html" ); + item.getItems().add( subitem ); + } + + return item; + } + + // ---------------------------------------------------------------------- + // static methods + // ---------------------------------------------------------------------- + + /** + * Convenience method. + * + * @param list could be null + * @return true if the list is null or empty + */ + private static boolean isEmptyList( List list ) + { + return list == null || list.isEmpty(); + } + + /** + * Return distributionManagement.site.url if defined, null otherwise. + * + * @param project not null + * @return could be null + */ + private static String getDistMgmntSiteUrl( MavenProject project ) + { + return getDistMgmntSiteUrl( project.getDistributionManagement() ); + } + + private static String getDistMgmntSiteUrl( DistributionManagement distMgmnt ) + { + if ( distMgmnt != null && distMgmnt.getSite() != null && distMgmnt.getSite().getUrl() != null ) + { + return urlEncode( distMgmnt.getSite().getUrl() ); + } + + return null; + } + + private static String urlEncode( final String url ) + { + if ( url == null ) + { + return null; + } + + try + { + return new File( url ).toURI().toURL().toExternalForm(); + } + catch ( MalformedURLException ex ) + { + return url; // this will then throw somewhere else + } + } + + private void checkNotNull( String name, Object value ) + { + if ( value == null ) + { + throw new IllegalArgumentException( "The parameter '" + name + "' cannot be null." ); + } + } + + /** + * Check the current Maven version to see if it's Maven 3.0 or newer. + */ + private static boolean isMaven3OrMore() + { + return new DefaultArtifactVersion( getMavenVersion() ).getMajorVersion() >= 3; + } + + private static String getMavenVersion() + { + // This relies on the fact that MavenProject is the in core classloader + // and that the core classloader is for the maven-core artifact + // and that should have a pom.properties file + // if this ever changes, we will have to revisit this code. + final Properties properties = new Properties(); + final String corePomProperties = "META-INF/maven/org.apache.maven/maven-core/pom.properties"; + final InputStream in = MavenProject.class.getClassLoader().getResourceAsStream( corePomProperties ); + try + { + properties.load( in ); + } + catch ( IOException ioe ) + { + return ""; + } + finally + { + IOUtil.close( in ); + } + + return properties.getProperty( "version" ).trim(); + } +} diff --git a/Java/maven-doxia-sitetools-DefaultSiteTool_492/metadata.json b/Java/maven-doxia-sitetools-DefaultSiteTool_492/metadata.json new file mode 100644 index 000000000..2663ac168 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteTool_492/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-DefaultSiteTool_492", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/DefaultSiteTool.java", + "line": 475, + "npe_method": "getDecorationModel", + "deref_field": "parentProject", + "npe_class": "DefaultSiteTool", + "repo": "maven-doxia-sitetools", + "bug_id": "DefaultSiteTool_492" + } +} diff --git a/Java/maven-doxia-sitetools-DefaultSiteTool_492/npe.json b/Java/maven-doxia-sitetools-DefaultSiteTool_492/npe.json new file mode 100644 index 000000000..119293c86 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteTool_492/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/DefaultSiteTool.java", + "line": 475, + "npe_method": "getDecorationModel", + "deref_field": "parentProject", + "npe_class": "DefaultSiteTool" +} \ No newline at end of file diff --git a/Java/maven-doxia-sitetools-DefaultSiteTool_587/Dockerfile b/Java/maven-doxia-sitetools-DefaultSiteTool_587/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteTool_587/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-DefaultSiteTool_587/buggy.java b/Java/maven-doxia-sitetools-DefaultSiteTool_587/buggy.java new file mode 100644 index 000000000..d79a3fb55 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteTool_587/buggy.java @@ -0,0 +1,1491 @@ +package org.apache.maven.doxia.tools; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.StringTokenizer; + +import org.apache.commons.io.FilenameUtils; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.factory.ArtifactFactory; +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.artifact.resolver.ArtifactNotFoundException; +import org.apache.maven.artifact.resolver.ArtifactResolutionException; +import org.apache.maven.artifact.resolver.ArtifactResolver; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; +import org.apache.maven.artifact.versioning.VersionRange; +import org.apache.maven.doxia.site.decoration.Banner; +import org.apache.maven.doxia.site.decoration.DecorationModel; +import org.apache.maven.doxia.site.decoration.Menu; +import org.apache.maven.doxia.site.decoration.MenuItem; +import org.apache.maven.doxia.site.decoration.Skin; +import org.apache.maven.doxia.site.decoration.inheritance.DecorationModelInheritanceAssembler; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Reader; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Writer; +import org.apache.maven.model.DistributionManagement; +import org.apache.maven.model.Site; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.MavenProjectBuilder; +import org.apache.maven.project.ProjectBuildingException; +import org.apache.maven.reporting.MavenReport; +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.IOUtil; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.interpolation.EnvarBasedValueSource; +import org.codehaus.plexus.interpolation.InterpolationException; +import org.codehaus.plexus.interpolation.MapBasedValueSource; +import org.codehaus.plexus.interpolation.ObjectBasedValueSource; +import org.codehaus.plexus.interpolation.PrefixedObjectValueSource; +import org.codehaus.plexus.interpolation.PrefixedPropertiesValueSource; +import org.codehaus.plexus.interpolation.RegexBasedInterpolator; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +/** + * Default implementation of the site tool. + * + * @author Vincent Siveton + */ +@Component( role = SiteTool.class ) +public class DefaultSiteTool + extends AbstractLogEnabled + implements SiteTool +{ + // ---------------------------------------------------------------------- + // Components + // ---------------------------------------------------------------------- + + /** + * The component that is used to resolve additional artifacts required. + */ + @Requirement + private ArtifactResolver artifactResolver; + + /** + * The component used for creating artifact instances. + */ + @Requirement + private ArtifactFactory artifactFactory; + + /** + * Internationalization. + */ + @Requirement + protected I18N i18n; + + /** + * The component for assembling inheritance. + */ + @Requirement + protected DecorationModelInheritanceAssembler assembler; + + /** + * Project builder (deprecated in Maven 3: should use ProjectBuilder, which will avoid + * issues like DOXIASITETOOLS-166) + */ + @Requirement + protected MavenProjectBuilder mavenProjectBuilder; + + // ---------------------------------------------------------------------- + // Public methods + // ---------------------------------------------------------------------- + + public Artifact getSkinArtifactFromRepository( ArtifactRepository localRepository, + List remoteArtifactRepositories, + DecorationModel decoration ) + throws SiteToolException + { + checkNotNull( "localRepository", localRepository ); + checkNotNull( "remoteArtifactRepositories", remoteArtifactRepositories ); + checkNotNull( "decoration", decoration ); + + Skin skin = decoration.getSkin(); + + if ( skin == null ) + { + skin = Skin.getDefaultSkin(); + } + + String version = skin.getVersion(); + Artifact artifact; + try + { + if ( version == null ) + { + version = Artifact.RELEASE_VERSION; + } + VersionRange versionSpec = VersionRange.createFromVersionSpec( version ); + artifact = artifactFactory.createDependencyArtifact( skin.getGroupId(), skin.getArtifactId(), versionSpec, + "jar", null, null ); + + artifactResolver.resolve( artifact, remoteArtifactRepositories, localRepository ); + } + catch ( InvalidVersionSpecificationException e ) + { + throw new SiteToolException( "InvalidVersionSpecificationException: The skin version '" + version + + "' is not valid: " + e.getMessage(), e ); + } + catch ( ArtifactResolutionException e ) + { + throw new SiteToolException( "ArtifactResolutionException: Unable to find skin", e ); + } + catch ( ArtifactNotFoundException e ) + { + throw new SiteToolException( "ArtifactNotFoundException: The skin does not exist: " + e.getMessage(), e ); + } + + return artifact; + } + + public Artifact getDefaultSkinArtifact( ArtifactRepository localRepository, + List remoteArtifactRepositories ) + throws SiteToolException + { + return getSkinArtifactFromRepository( localRepository, remoteArtifactRepositories, new DecorationModel() ); + } + + /** + * This method is not implemented according to the URI specification and has many weird + * corner cases where it doesn't do the right thing. Please consider using a better + * implemented method from a different library such as org.apache.http.client.utils.URIUtils#resolve. + */ + @Deprecated + public String getRelativePath( String to, String from ) + { + checkNotNull( "to", to ); + checkNotNull( "from", from ); + + if ( to.contains( ":" ) && from.contains( ":" ) ) + { + String toScheme = to.substring( 0, to.lastIndexOf( ':' ) ); + String fromScheme = from.substring( 0, from.lastIndexOf( ':' ) ); + if ( !toScheme.equals( fromScheme ) ) + { + return to; + } + } + + URL toUrl = null; + URL fromUrl = null; + + String toPath = to; + String fromPath = from; + + try + { + toUrl = new URL( to ); + } + catch ( MalformedURLException e ) + { + try + { + toUrl = new File( getNormalizedPath( to ) ).toURI().toURL(); + } + catch ( MalformedURLException e1 ) + { + getLogger().warn( "Unable to load a URL for '" + to + "': " + e.getMessage() ); + return to; + } + } + + try + { + fromUrl = new URL( from ); + } + catch ( MalformedURLException e ) + { + try + { + fromUrl = new File( getNormalizedPath( from ) ).toURI().toURL(); + } + catch ( MalformedURLException e1 ) + { + getLogger().warn( "Unable to load a URL for '" + from + "': " + e.getMessage() ); + return to; + } + } + + if ( toUrl != null && fromUrl != null ) + { + // URLs, determine if they share protocol and domain info + + if ( ( toUrl.getProtocol().equalsIgnoreCase( fromUrl.getProtocol() ) ) + && ( toUrl.getHost().equalsIgnoreCase( fromUrl.getHost() ) ) + && ( toUrl.getPort() == fromUrl.getPort() ) ) + { + // shared URL domain details, use URI to determine relative path + + toPath = toUrl.getFile(); + fromPath = fromUrl.getFile(); + } + else + { + // don't share basic URL information, no relative available + + return to; + } + } + else if ( ( toUrl != null && fromUrl == null ) || ( toUrl == null && fromUrl != null ) ) + { + // one is a URL and the other isn't, no relative available. + + return to; + } + + // either the two locations are not URLs or if they are they + // share the common protocol and domain info and we are left + // with their URI information + + String relativePath = getRelativeFilePath( fromPath, toPath ); + + if ( relativePath == null ) + { + relativePath = to; + } + + if ( getLogger().isDebugEnabled() && !relativePath.toString().equals( to ) ) + { + getLogger().debug( "Mapped url: " + to + " to relative path: " + relativePath ); + } + + return relativePath; + } + + private static String getRelativeFilePath( final String oldPath, final String newPath ) + { + // normalize the path delimiters + + String fromPath = new File( oldPath ).getPath(); + String toPath = new File( newPath ).getPath(); + + // strip any leading slashes if its a windows path + if ( toPath.matches( "^\\[a-zA-Z]:" ) ) + { + toPath = toPath.substring( 1 ); + } + if ( fromPath.matches( "^\\[a-zA-Z]:" ) ) + { + fromPath = fromPath.substring( 1 ); + } + + // lowercase windows drive letters. + if ( fromPath.startsWith( ":", 1 ) ) + { + fromPath = Character.toLowerCase( fromPath.charAt( 0 ) ) + fromPath.substring( 1 ); + } + if ( toPath.startsWith( ":", 1 ) ) + { + toPath = Character.toLowerCase( toPath.charAt( 0 ) ) + toPath.substring( 1 ); + } + + // check for the presence of windows drives. No relative way of + // traversing from one to the other. + + if ( ( toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) + && ( !toPath.substring( 0, 1 ).equals( fromPath.substring( 0, 1 ) ) ) ) + { + // they both have drive path element but they don't match, no + // relative path + + return null; + } + + if ( ( toPath.startsWith( ":", 1 ) && !fromPath.startsWith( ":", 1 ) ) + || ( !toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) ) + { + + // one has a drive path element and the other doesn't, no relative + // path. + + return null; + + } + + final String relativePath = buildRelativePath( toPath, fromPath, File.separatorChar ); + + return relativePath.toString(); + } + + /** {@inheritDoc} */ + public File getSiteDescriptor( File siteDirectory, Locale locale ) + { + checkNotNull( "siteDirectory", siteDirectory ); + final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale; + + File siteDescriptor = new File( siteDirectory, "site_" + llocale.getLanguage() + ".xml" ); + + if ( !siteDescriptor.isFile() ) + { + siteDescriptor = new File( siteDirectory, "site.xml" ); + } + return siteDescriptor; + } + + /** + * Get a site descriptor from one of the repositories. + * + * @param project the Maven project, not null. + * @param localRepository the Maven local repository, not null. + * @param repositories the Maven remote repositories, not null. + * @param locale the locale wanted for the site descriptor. If not null, searching for + * site_localeLanguage.xml, otherwise searching for site.xml. + * @return the site descriptor into the local repository after download of it from repositories or null if not + * found in repositories. + * @throws SiteToolException if any + */ + File getSiteDescriptorFromRepository( MavenProject project, ArtifactRepository localRepository, + List repositories, Locale locale ) + throws SiteToolException + { + checkNotNull( "project", project ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "repositories", repositories ); + + final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale; + + try + { + return resolveSiteDescriptor( project, localRepository, repositories, llocale ); + } + catch ( ArtifactNotFoundException e ) + { + getLogger().debug( "ArtifactNotFoundException: Unable to locate site descriptor: " + e ); + return null; + } + catch ( ArtifactResolutionException e ) + { + throw new SiteToolException( "ArtifactResolutionException: Unable to locate site descriptor: " + + e.getMessage(), e ); + } + catch ( IOException e ) + { + throw new SiteToolException( "IOException: Unable to locate site descriptor: " + e.getMessage(), e ); + } + } + + /** + * Read site descriptor content from Reader, adding support for deprecated ${reports}, + * ${parentProject} and ${modules} tags. + * + * @param reader + * @return the input content interpolated with deprecated tags + * @throws IOException + */ + private String readSiteDescriptor( Reader reader, String projectId ) + throws IOException + { + String siteDescriptorContent = IOUtil.toString( reader ); + + // This is to support the deprecated ${reports}, ${parentProject} and ${modules} tags. + Properties props = new Properties(); + props.put( "reports", "" ); + props.put( "modules", "" ); + props.put( "parentProject", "" ); + + // warn if interpolation required + for ( Object prop : props.keySet() ) + { + if ( siteDescriptorContent.contains( "$" + prop ) ) + { + getLogger().warn( "Site descriptor for " + projectId + " contains $" + prop + + ": should be replaced with " + props.getProperty( (String) prop ) ); + } + if ( siteDescriptorContent.contains( "${" + prop + "}" ) ) + { + getLogger().warn( "Site descriptor for " + projectId + " contains ${" + prop + + "}: should be replaced with " + props.getProperty( (String) prop ) ); + } + } + + return StringUtils.interpolate( siteDescriptorContent, props ); + } + + /** {@inheritDoc} */ + public DecorationModel getDecorationModel( File siteDirectory, Locale locale, MavenProject project, + List reactorProjects, ArtifactRepository localRepository, + List repositories ) + throws SiteToolException + { + checkNotNull( "project", project ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "repositories", repositories ); + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + getLogger().debug( "Computing decoration model of " + project.getId() + " for locale " + llocale ); + + Map.Entry result = + getDecorationModel( 0, siteDirectory, llocale, project, reactorProjects, localRepository, repositories ); + DecorationModel decorationModel = result.getKey(); + MavenProject parentProject = result.getValue(); + + if ( decorationModel == null ) + { + getLogger().debug( "Using default site descriptor" ); + + String siteDescriptorContent; + + Reader reader = null; + try + { + // Note the default is not a super class - it is used when nothing else is found + reader = ReaderFactory.newXmlReader( getClass().getResourceAsStream( "/default-site.xml" ) ); + siteDescriptorContent = readSiteDescriptor( reader, "default-site.xml" ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading default site descriptor: " + e.getMessage(), e ); + } + finally + { + IOUtil.close( reader ); + } + + decorationModel = readDecorationModel( siteDescriptorContent ); + } + + // DecorationModel back to String to interpolate, then go back to DecorationModel + String siteDescriptorContent = decorationModelToString( decorationModel ); + + // "classical" late interpolation, after full inheritance + siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, false ); + + decorationModel = readDecorationModel( siteDescriptorContent ); + + if ( parentProject != null ) + { + populateParentMenu( decorationModel, llocale, project, parentProject, true ); + } + + try + { + populateModulesMenu( decorationModel, llocale, project, reactorProjects, localRepository, true ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error while populating modules menu: " + e.getMessage(), e ); + } + + if ( decorationModel.getBannerLeft() == null ) + { + // extra default to set + Banner banner = new Banner(); + banner.setName( project.getName() ); + decorationModel.setBannerLeft( banner ); + } + + return decorationModel; + } + + /** {@inheritDoc} */ + public String getInterpolatedSiteDescriptorContent( Map props, MavenProject aProject, + String siteDescriptorContent ) + throws SiteToolException + { + checkNotNull( "props", props ); + + // "classical" late interpolation + return getInterpolatedSiteDescriptorContent( aProject, siteDescriptorContent, false ); + } + + private String getInterpolatedSiteDescriptorContent( MavenProject aProject, + String siteDescriptorContent, boolean isEarly ) + throws SiteToolException + { + checkNotNull( "aProject", aProject ); + checkNotNull( "siteDescriptorContent", siteDescriptorContent ); + + RegexBasedInterpolator interpolator = new RegexBasedInterpolator(); + + if ( isEarly ) + { + interpolator.addValueSource( new PrefixedObjectValueSource( "this.", aProject ) ); + interpolator.addValueSource( new PrefixedPropertiesValueSource( "this.", aProject.getProperties() ) ); + } + else + { + interpolator.addValueSource( new ObjectBasedValueSource( aProject ) ); + interpolator.addValueSource( new MapBasedValueSource( aProject.getProperties() ) ); + + try + { + interpolator.addValueSource( new EnvarBasedValueSource() ); + } + catch ( IOException e ) + { + // Prefer logging? + throw new SiteToolException( "IOException: cannot interpolate environment properties: " + + e.getMessage(), e ); + } + } + + try + { + // FIXME: this does not escape xml entities, see MSITE-226, PLXCOMP-118 + return interpolator.interpolate( siteDescriptorContent, isEarly ? null : "project" ); + } + catch ( InterpolationException e ) + { + throw new SiteToolException( "Cannot interpolate site descriptor: " + e.getMessage(), e ); + } + } + + /** {@inheritDoc} */ +/** + * {@inheritDoc } + */ +public org.apache.maven.project.MavenProject getParentProject(org.apache.maven.project.MavenProject aProject, java.util.List reactorProjects, org.apache.maven.artifact.repository.ArtifactRepository localRepository) { + checkNotNull("aProject", aProject); + checkNotNull("reactorProjects", reactorProjects); + checkNotNull("localRepository", localRepository); + if (org.apache.maven.doxia.tools.DefaultSiteTool.isMaven3OrMore()) { + // no need to make voodoo with Maven 3: job already done + return aProject.getParent(); + } + org.apache.maven.project.MavenProject parentProject = null; + org.apache.maven.project.MavenProject origParent = aProject.getParent(); + { + for (org.apache.maven.project.MavenProject reactorProject : reactorProjects) { + if ((reactorProject.getGroupId().equals(/* NPEX_NULL_EXP */ + origParent.getGroupId()) && reactorProject.getArtifactId().equals(origParent.getArtifactId())) && reactorProject.getVersion().equals(origParent.getVersion())) { + parentProject = reactorProject; + getLogger().debug(("Parent project " + origParent.getId()) + " picked from reactor"); + break; + } + } + if (((parentProject == null) && (aProject.getBasedir() != null)) && org.codehaus.plexus.util.StringUtils.isNotEmpty(aProject.getModel().getParent().getRelativePath())) { + try { + java.lang.String relativePath = aProject.getModel().getParent().getRelativePath(); + java.io.File pomFile = new java.io.File(aProject.getBasedir(), relativePath); + if (pomFile.isDirectory()) { + pomFile = new java.io.File(pomFile, "pom.xml"); + } + pomFile = new java.io.File(org.apache.maven.doxia.tools.DefaultSiteTool.getNormalizedPath(pomFile.getPath())); + if (pomFile.isFile()) { + org.apache.maven.project.MavenProject mavenProject = mavenProjectBuilder.build(pomFile, localRepository, null); + if ((mavenProject.getGroupId().equals(origParent.getGroupId()) && mavenProject.getArtifactId().equals(origParent.getArtifactId())) && mavenProject.getVersion().equals(origParent.getVersion())) { + parentProject = mavenProject; + getLogger().debug((("Parent project " + origParent.getId()) + " loaded from a relative path: ") + relativePath); + } + } + } catch (org.apache.maven.project.ProjectBuildingException e) { + getLogger().info((("Unable to load parent project " + origParent.getId()) + " from a relative path: ") + e.getMessage()); + } + } + if (parentProject == null) { + try { + parentProject = mavenProjectBuilder.buildFromRepository(aProject.getParentArtifact(), aProject.getRemoteArtifactRepositories(), localRepository); + getLogger().debug(("Parent project " + origParent.getId()) + " loaded from repository"); + } catch (org.apache.maven.project.ProjectBuildingException e) { + getLogger().warn((("Unable to load parent project " + origParent.getId()) + " from repository: ") + e.getMessage()); + } + } + if (parentProject == null) { + // fallback to original parent, which may contain uninterpolated value (still need a unit test) + parentProject = origParent; + getLogger().debug(("Parent project " + origParent.getId()) + " picked from original value"); + } + } + return parentProject; +} + + /** + * Populate the pre-defined parent menu of the decoration model, + * if used through <menu ref="parent"/>. + * + * @param decorationModel the Doxia Sitetools DecorationModel, not null. + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project a Maven project, not null. + * @param parentProject a Maven parent project, not null. + * @param keepInheritedRefs used for inherited references. + */ + private void populateParentMenu( DecorationModel decorationModel, Locale locale, MavenProject project, + MavenProject parentProject, boolean keepInheritedRefs ) + { + checkNotNull( "decorationModel", decorationModel ); + checkNotNull( "project", project ); + checkNotNull( "parentProject", parentProject ); + + Menu menu = decorationModel.getMenuRef( "parent" ); + + if ( menu == null ) + { + return; + } + + if ( keepInheritedRefs && menu.isInheritAsRef() ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + String parentUrl = getDistMgmntSiteUrl( parentProject ); + + if ( parentUrl != null ) + { + if ( parentUrl.endsWith( "/" ) ) + { + parentUrl += "index.html"; + } + else + { + parentUrl += "/index.html"; + } + + parentUrl = getRelativePath( parentUrl, getDistMgmntSiteUrl( project ) ); + } + else + { + // parent has no url, assume relative path is given by site structure + File parentBasedir = parentProject.getBasedir(); + // First make sure that the parent is available on the file system + if ( parentBasedir != null ) + { + // Try to find the relative path to the parent via the file system + String parentPath = parentBasedir.getAbsolutePath(); + String projectPath = project.getBasedir().getAbsolutePath(); + parentUrl = getRelativePath( parentPath, projectPath ) + "/index.html"; + } + } + + // Only add the parent menu if we were able to find a URL for it + if ( parentUrl == null ) + { + getLogger().warn( "Unable to find a URL to the parent project. The parent menu will NOT be added." ); + } + else + { + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.parentproject" ) ); + } + + MenuItem item = new MenuItem(); + item.setName( parentProject.getName() ); + item.setHref( parentUrl ); + menu.addItem( item ); + } + } + + /** + * Populate the pre-defined modules menu of the decoration model, + * if used through <menu ref="modules"/>. + * + * @param decorationModel the Doxia Sitetools DecorationModel, not null. + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project a Maven project, not null. + * @param reactorProjects the Maven reactor projects, not null. + * @param localRepository the Maven local repository, not null. + * @param keepInheritedRefs used for inherited references. + * @throws SiteToolException if any + * @throws IOException + */ + private void populateModulesMenu( DecorationModel decorationModel, Locale locale, MavenProject project, + List reactorProjects, ArtifactRepository localRepository, + boolean keepInheritedRefs ) + throws SiteToolException, IOException + { + checkNotNull( "project", project ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "decorationModel", decorationModel ); + + Menu menu = decorationModel.getMenuRef( "modules" ); + + if ( menu == null ) + { + return; + } + + if ( keepInheritedRefs && menu.isInheritAsRef() ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale ; + + // we require child modules and reactors to process module menu + if ( project.getModules().size() > 0 ) + { + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectmodules" ) ); + } + + for ( String module : (List) project.getModules() ) + { + MavenProject moduleProject = getModuleFromReactor( project, reactorProjects, module ); + + if ( moduleProject == null ) + { + getLogger().warn( "Module " + module + + " not found in reactor: loading locally" ); + + File f = new File( project.getBasedir(), module + "/pom.xml" ); + if ( f.exists() ) + { + try + { + moduleProject = mavenProjectBuilder.build( f, localRepository, null ); + } + catch ( ProjectBuildingException e ) + { + throw new SiteToolException( "Unable to read local module-POM", e ); + } + } + else + { + getLogger().warn( "No filesystem module-POM available" ); + + moduleProject = new MavenProject(); + moduleProject.setName( module ); + moduleProject.setDistributionManagement( new DistributionManagement() ); + moduleProject.getDistributionManagement().setSite( new Site() ); + moduleProject.getDistributionManagement().getSite().setUrl( module ); + } + } + + String siteUrl = getDistMgmntSiteUrl( moduleProject ); + String itemName = + ( moduleProject.getName() == null ) ? moduleProject.getArtifactId() : moduleProject.getName(); + + appendMenuItem( project, menu, itemName, siteUrl, moduleProject.getArtifactId() ); + } + } + else if ( decorationModel.getMenuRef( "modules" ).getInherit() == null ) + { + // only remove if project has no modules AND menu is not inherited, see MSHARED-174 + decorationModel.removeMenuRef( "modules" ); + } + } + + private static MavenProject getModuleFromReactor( MavenProject project, List reactorProjects, + String module ) + throws IOException + { + File moduleBasedir = new File( project.getBasedir(), module ).getCanonicalFile(); + + for ( MavenProject reactorProject : reactorProjects ) + { + if ( moduleBasedir.equals( reactorProject.getBasedir() ) ) + { + return reactorProject; + } + } + + // module not found in reactor + return null; + } + + /** {@inheritDoc} */ + public void populateReportsMenu( DecorationModel decorationModel, Locale locale, + Map> categories ) + { + checkNotNull( "decorationModel", decorationModel ); + checkNotNull( "categories", categories ); + + Menu menu = decorationModel.getMenuRef( "reports" ); + + if ( menu == null ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectdocumentation" ) ); + } + + boolean found = false; + if ( menu.getItems().isEmpty() ) + { + List categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_INFORMATION ); + if ( !isEmptyList( categoryReports ) ) + { + MenuItem item = createCategoryMenu( + i18n.getString( "site-tool", llocale, + "decorationModel.menu.projectinformation" ), + "/project-info.html", categoryReports, llocale ); + menu.getItems().add( item ); + found = true; + } + + categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_REPORTS ); + if ( !isEmptyList( categoryReports ) ) + { + MenuItem item = + createCategoryMenu( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectreports" ), + "/project-reports.html", categoryReports, llocale ); + menu.getItems().add( item ); + found = true; + } + } + if ( !found ) + { + decorationModel.removeMenuRef( "reports" ); + } + } + + /** {@inheritDoc} */ + public List getSiteLocales( String locales ) + { + if ( locales == null ) + { + return Collections.singletonList( DEFAULT_LOCALE ); + } + + String[] localesArray = StringUtils.split( locales, "," ); + List localesList = new ArrayList( localesArray.length ); + + for ( String localeString : localesArray ) + { + Locale locale = codeToLocale( localeString ); + + if ( locale == null ) + { + continue; + } + + if ( !Arrays.asList( Locale.getAvailableLocales() ).contains( locale ) ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "The locale defined by '" + locale + + "' is not available in this Java Virtual Machine (" + + System.getProperty( "java.version" ) + + " from " + System.getProperty( "java.vendor" ) + ") - IGNORING" ); + } + continue; + } + + // Default bundles are in English + if ( ( !locale.getLanguage().equals( DEFAULT_LOCALE.getLanguage() ) ) + && ( !i18n.getBundle( "site-tool", locale ).getLocale().getLanguage() + .equals( locale.getLanguage() ) ) ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "The locale '" + locale + "' (" + locale.getDisplayName( Locale.ENGLISH ) + + ") is not currently supported by Maven Site - IGNORING." + + "\nContributions are welcome and greatly appreciated!" + + "\nIf you want to contribute a new translation, please visit " + + "http://maven.apache.org/plugins/localization.html for detailed instructions." ); + } + + continue; + } + + localesList.add( locale ); + } + + if ( localesList.isEmpty() ) + { + localesList = Collections.singletonList( DEFAULT_LOCALE ); + } + + return localesList; + } + + /** + * Converts a locale code like "en", "en_US" or "en_US_win" to a java.util.Locale + * object. + *

    If localeCode = default, return the current value of the default locale for this instance + * of the Java Virtual Machine.

    + * + * @param localeCode the locale code string. + * @return a java.util.Locale object instanced or null if errors occurred + * @see java.util.Locale#getDefault() + */ + private Locale codeToLocale( String localeCode ) + { + if ( localeCode == null ) + { + return null; + } + + if ( "default".equalsIgnoreCase( localeCode ) ) + { + return Locale.getDefault(); + } + + String language = ""; + String country = ""; + String variant = ""; + + StringTokenizer tokenizer = new StringTokenizer( localeCode, "_" ); + final int maxTokens = 3; + if ( tokenizer.countTokens() > maxTokens ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "Invalid java.util.Locale format for '" + localeCode + "' entry - IGNORING" ); + } + return null; + } + + if ( tokenizer.hasMoreTokens() ) + { + language = tokenizer.nextToken(); + if ( tokenizer.hasMoreTokens() ) + { + country = tokenizer.nextToken(); + if ( tokenizer.hasMoreTokens() ) + { + variant = tokenizer.nextToken(); + } + } + } + + return new Locale( language, country, variant ); + } + + // ---------------------------------------------------------------------- + // Protected methods + // ---------------------------------------------------------------------- + + /** + * @param path could be null. + * @return the path normalized, i.e. by eliminating "/../" and "/./" in the path. + * @see FilenameUtils#normalize(String) + */ + protected static String getNormalizedPath( String path ) + { + String normalized = FilenameUtils.normalize( path ); + if ( normalized == null ) + { + normalized = path; + } + return ( normalized == null ) ? null : normalized.replace( '\\', '/' ); + } + + // ---------------------------------------------------------------------- + // Private methods + // ---------------------------------------------------------------------- + + /** + * @param project not null + * @param localRepository not null + * @param repositories not null + * @param locale not null + * @return the resolved site descriptor + * @throws IOException if any + * @throws ArtifactResolutionException if any + * @throws ArtifactNotFoundException if any + */ + private File resolveSiteDescriptor( MavenProject project, ArtifactRepository localRepository, + List repositories, Locale locale ) + throws IOException, ArtifactResolutionException, ArtifactNotFoundException + { + File result; + + // TODO: this is a bit crude - proper type, or proper handling as metadata rather than an artifact in 2.1? + Artifact artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), + project.getArtifactId(), + project.getVersion(), "xml", + "site_" + locale.getLanguage() ); + + boolean found = false; + try + { + artifactResolver.resolve( artifact, repositories, localRepository ); + + result = artifact.getFile(); + + // we use zero length files to avoid re-resolution (see below) + if ( result.length() > 0 ) + { + found = true; + } + else + { + getLogger().debug( "No site descriptor found for " + project.getId() + " for locale " + + locale.getLanguage() + ", trying without locale..." ); + } + } + catch ( ArtifactNotFoundException e ) + { + getLogger().debug( "Unable to locate site descriptor for locale " + locale.getLanguage() + ": " + e ); + + // we can afford to write an empty descriptor here as we don't expect it to turn up later in the remote + // repository, because the parent was already released (and snapshots are updated automatically if changed) + result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); + result.getParentFile().mkdirs(); + result.createNewFile(); + } + + if ( !found ) + { + artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), project.getArtifactId(), + project.getVersion(), "xml", "site" ); + try + { + artifactResolver.resolve( artifact, repositories, localRepository ); + } + catch ( ArtifactNotFoundException e ) + { + // see above regarding this zero length file + result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); + result.getParentFile().mkdirs(); + result.createNewFile(); + + throw e; + } + + result = artifact.getFile(); + + // we use zero length files to avoid re-resolution (see below) + if ( result.length() == 0 ) + { + getLogger().debug( "No site descriptor found for " + project.getId() + " without locale." ); + result = null; + } + } + + return result; + } + + /** + * @param depth depth of project + * @param siteDirectory, can be null if project.basedir is null, ie POM from repository + * @param locale not null + * @param project not null + * @param reactorProjects not null + * @param localRepository not null + * @param repositories not null + * @param origProps not null + * @return the decoration model depending the locale and the parent project + * @throws SiteToolException if any + */ + private Map.Entry getDecorationModel( int depth, File siteDirectory, Locale locale, + MavenProject project, + List reactorProjects, + ArtifactRepository localRepository, + List repositories ) + throws SiteToolException + { + // 1. get site descriptor File + File siteDescriptor; + if ( project.getBasedir() == null ) + { + // POM is in the repository: look into the repository for site descriptor + try + { + siteDescriptor = getSiteDescriptorFromRepository( project, localRepository, repositories, locale ); + } + catch ( SiteToolException e ) + { + throw new SiteToolException( "The site descriptor cannot be resolved from the repository: " + + e.getMessage(), e ); + } + } + else + { + // POM is in build directory: look for site descriptor as local file + siteDescriptor = getSiteDescriptor( siteDirectory, locale ); + } + + // 2. read DecorationModel from site descriptor File and do early interpolation (${this.*}) + DecorationModel decoration = null; + Reader siteDescriptorReader = null; + try + { + if ( siteDescriptor != null && siteDescriptor.exists() ) + { + getLogger().debug( "Reading" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + + " site descriptor from " + siteDescriptor ); + + siteDescriptorReader = ReaderFactory.newXmlReader( siteDescriptor ); + + String siteDescriptorContent = readSiteDescriptor( siteDescriptorReader, project.getId() ); + + // interpolate ${this.*} = early interpolation + siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, true ); + + decoration = readDecorationModel( siteDescriptorContent ); + decoration.setLastModified( siteDescriptor.lastModified() ); + } + else + { + getLogger().debug( "No" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + " site descriptor." ); + } + } + catch ( IOException e ) + { + throw new SiteToolException( "The site descriptor for " + project.getId() + " cannot be read from " + + siteDescriptor, e ); + } + finally + { + IOUtil.close( siteDescriptorReader ); + } + + // 3. look for parent project + MavenProject parentProject = getParentProject( project, reactorProjects, localRepository ); + + // 4. merge with parent project DecorationModel + if ( parentProject != null && ( decoration == null || decoration.isMergeParent() ) ) + { + depth++; + getLogger().debug( "Looking for site descriptor of level " + depth + " parent project: " + + parentProject.getId() ); + + File parentSiteDirectory = null; + if ( parentProject.getBasedir() != null ) + { + // extrapolate parent project site directory + String siteRelativePath = getRelativeFilePath( project.getBasedir().getAbsolutePath(), + siteDescriptor.getParentFile().getAbsolutePath() ); + + parentSiteDirectory = new File( parentProject.getBasedir(), siteRelativePath ); + // notice: using same siteRelativePath for parent as current project; may be wrong if site plugin + // has different configuration. But this is a rare case (this only has impact if parent is from reactor) + } + + DecorationModel parentDecoration = + getDecorationModel( depth, parentSiteDirectory, locale, parentProject, reactorProjects, localRepository, + repositories ).getKey(); + + // MSHARED-116 requires an empty decoration model (instead of a null one) + // MSHARED-145 requires us to do this only if there is a parent to merge it with + if ( decoration == null && parentDecoration != null ) + { + // we have no site descriptor: merge the parent into an empty one + decoration = new DecorationModel(); + } + + String name = project.getName(); + if ( decoration != null && StringUtils.isNotEmpty( decoration.getName() ) ) + { + name = decoration.getName(); + } + + // Merge the parent and child DecorationModels + String projectDistMgmnt = getDistMgmntSiteUrl( project ); + String parentDistMgmnt = getDistMgmntSiteUrl( parentProject ); + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Site decoration model inheritance: assembling child with level " + depth + + " parent: distributionManagement.site.url child = " + projectDistMgmnt + " and parent = " + + parentDistMgmnt ); + } + assembler.assembleModelInheritance( name, decoration, parentDecoration, projectDistMgmnt, + parentDistMgmnt == null ? projectDistMgmnt : parentDistMgmnt ); + } + + return new AbstractMap.SimpleEntry( decoration, parentProject ); + } + + /** + * @param siteDescriptorContent not null + * @return the decoration model object + * @throws SiteToolException if any + */ + private DecorationModel readDecorationModel( String siteDescriptorContent ) + throws SiteToolException + { + try + { + return new DecorationXpp3Reader().read( new StringReader( siteDescriptorContent ) ); + } + catch ( XmlPullParserException e ) + { + throw new SiteToolException( "Error parsing site descriptor", e ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading site descriptor", e ); + } + } + + private String decorationModelToString( DecorationModel decoration ) + throws SiteToolException + { + StringWriter writer = new StringWriter(); + + try + { + new DecorationXpp3Writer().write( writer, decoration ); + return writer.toString(); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading site descriptor", e ); + } + finally + { + IOUtil.close( writer ); + } + } + + private static String buildRelativePath( final String toPath, final String fromPath, final char separatorChar ) + { + // use tokenizer to traverse paths and for lazy checking + StringTokenizer toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); + StringTokenizer fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); + + int count = 0; + + // walk along the to path looking for divergence from the from path + while ( toTokeniser.hasMoreTokens() && fromTokeniser.hasMoreTokens() ) + { + if ( separatorChar == '\\' ) + { + if ( !fromTokeniser.nextToken().equalsIgnoreCase( toTokeniser.nextToken() ) ) + { + break; + } + } + else + { + if ( !fromTokeniser.nextToken().equals( toTokeniser.nextToken() ) ) + { + break; + } + } + + count++; + } + + // reinitialize the tokenizers to count positions to retrieve the + // gobbled token + + toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); + fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); + + while ( count-- > 0 ) + { + fromTokeniser.nextToken(); + toTokeniser.nextToken(); + } + + StringBuilder relativePath = new StringBuilder(); + + // add back refs for the rest of from location. + while ( fromTokeniser.hasMoreTokens() ) + { + fromTokeniser.nextToken(); + + relativePath.append( ".." ); + + if ( fromTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + } + + if ( relativePath.length() != 0 && toTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + + // add fwd fills for whatever's left of to. + while ( toTokeniser.hasMoreTokens() ) + { + relativePath.append( toTokeniser.nextToken() ); + + if ( toTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + } + return relativePath.toString(); + } + + /** + * @param project not null + * @param menu not null + * @param name not null + * @param href could be null + * @param defaultHref not null + */ + private void appendMenuItem( MavenProject project, Menu menu, String name, String href, String defaultHref ) + { + String selectedHref = href; + + if ( selectedHref == null ) + { + selectedHref = defaultHref; + } + + MenuItem item = new MenuItem(); + item.setName( name ); + + String baseUrl = getDistMgmntSiteUrl( project ); + if ( baseUrl != null ) + { + selectedHref = getRelativePath( selectedHref, baseUrl ); + } + + if ( selectedHref.endsWith( "/" ) ) + { + item.setHref( selectedHref + "index.html" ); + } + else + { + item.setHref( selectedHref + "/index.html" ); + } + menu.addItem( item ); + } + + /** + * @param name not null + * @param href not null + * @param categoryReports not null + * @param locale not null + * @return the menu item object + */ + private MenuItem createCategoryMenu( String name, String href, List categoryReports, Locale locale ) + { + MenuItem item = new MenuItem(); + item.setName( name ); + item.setCollapse( true ); + item.setHref( href ); + + // MSHARED-172, allow reports to define their order in some other way? + //Collections.sort( categoryReports, new ReportComparator( locale ) ); + + for ( MavenReport report : categoryReports ) + { + MenuItem subitem = new MenuItem(); + subitem.setName( report.getName( locale ) ); + subitem.setHref( report.getOutputName() + ".html" ); + item.getItems().add( subitem ); + } + + return item; + } + + // ---------------------------------------------------------------------- + // static methods + // ---------------------------------------------------------------------- + + /** + * Convenience method. + * + * @param list could be null + * @return true if the list is null or empty + */ + private static boolean isEmptyList( List list ) + { + return list == null || list.isEmpty(); + } + + /** + * Return distributionManagement.site.url if defined, null otherwise. + * + * @param project not null + * @return could be null + */ + private static String getDistMgmntSiteUrl( MavenProject project ) + { + return getDistMgmntSiteUrl( project.getDistributionManagement() ); + } + + private static String getDistMgmntSiteUrl( DistributionManagement distMgmnt ) + { + if ( distMgmnt != null && distMgmnt.getSite() != null && distMgmnt.getSite().getUrl() != null ) + { + return urlEncode( distMgmnt.getSite().getUrl() ); + } + + return null; + } + + private static String urlEncode( final String url ) + { + if ( url == null ) + { + return null; + } + + try + { + return new File( url ).toURI().toURL().toExternalForm(); + } + catch ( MalformedURLException ex ) + { + return url; // this will then throw somewhere else + } + } + + private void checkNotNull( String name, Object value ) + { + if ( value == null ) + { + throw new IllegalArgumentException( "The parameter '" + name + "' cannot be null." ); + } + } + + /** + * Check the current Maven version to see if it's Maven 3.0 or newer. + */ + private static boolean isMaven3OrMore() + { + return new DefaultArtifactVersion( getMavenVersion() ).getMajorVersion() >= 3; + } + + private static String getMavenVersion() + { + // This relies on the fact that MavenProject is the in core classloader + // and that the core classloader is for the maven-core artifact + // and that should have a pom.properties file + // if this ever changes, we will have to revisit this code. + final Properties properties = new Properties(); + final String corePomProperties = "META-INF/maven/org.apache.maven/maven-core/pom.properties"; + final InputStream in = MavenProject.class.getClassLoader().getResourceAsStream( corePomProperties ); + try + { + properties.load( in ); + } + catch ( IOException ioe ) + { + return ""; + } + finally + { + IOUtil.close( in ); + } + + return properties.getProperty( "version" ).trim(); + } +} diff --git a/Java/maven-doxia-sitetools-DefaultSiteTool_587/metadata.json b/Java/maven-doxia-sitetools-DefaultSiteTool_587/metadata.json new file mode 100644 index 000000000..eccedf3c9 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteTool_587/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-DefaultSiteTool_587", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/DefaultSiteTool.java", + "line": 587, + "npe_method": "getParentProject", + "deref_field": "origParent", + "npe_class": "DefaultSiteTool", + "repo": "maven-doxia-sitetools", + "bug_id": "DefaultSiteTool_587" + } +} diff --git a/Java/maven-doxia-sitetools-DefaultSiteTool_587/npe.json b/Java/maven-doxia-sitetools-DefaultSiteTool_587/npe.json new file mode 100644 index 000000000..07e55beb0 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteTool_587/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/DefaultSiteTool.java", + "line": 587, + "npe_method": "getParentProject", + "deref_field": "origParent", + "npe_class": "DefaultSiteTool" +} \ No newline at end of file diff --git a/Java/maven-doxia-sitetools-DefaultSiteTool_686/Dockerfile b/Java/maven-doxia-sitetools-DefaultSiteTool_686/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteTool_686/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-DefaultSiteTool_686/buggy.java b/Java/maven-doxia-sitetools-DefaultSiteTool_686/buggy.java new file mode 100644 index 000000000..fbcecbbd7 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteTool_686/buggy.java @@ -0,0 +1,1520 @@ +package org.apache.maven.doxia.tools; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.StringTokenizer; + +import org.apache.commons.io.FilenameUtils; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.factory.ArtifactFactory; +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.artifact.resolver.ArtifactNotFoundException; +import org.apache.maven.artifact.resolver.ArtifactResolutionException; +import org.apache.maven.artifact.resolver.ArtifactResolver; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; +import org.apache.maven.artifact.versioning.VersionRange; +import org.apache.maven.doxia.site.decoration.Banner; +import org.apache.maven.doxia.site.decoration.DecorationModel; +import org.apache.maven.doxia.site.decoration.Menu; +import org.apache.maven.doxia.site.decoration.MenuItem; +import org.apache.maven.doxia.site.decoration.Skin; +import org.apache.maven.doxia.site.decoration.inheritance.DecorationModelInheritanceAssembler; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Reader; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Writer; +import org.apache.maven.model.DistributionManagement; +import org.apache.maven.model.Site; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.MavenProjectBuilder; +import org.apache.maven.project.ProjectBuildingException; +import org.apache.maven.reporting.MavenReport; +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.IOUtil; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.interpolation.EnvarBasedValueSource; +import org.codehaus.plexus.interpolation.InterpolationException; +import org.codehaus.plexus.interpolation.MapBasedValueSource; +import org.codehaus.plexus.interpolation.ObjectBasedValueSource; +import org.codehaus.plexus.interpolation.PrefixedObjectValueSource; +import org.codehaus.plexus.interpolation.PrefixedPropertiesValueSource; +import org.codehaus.plexus.interpolation.RegexBasedInterpolator; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +/** + * Default implementation of the site tool. + * + * @author Vincent Siveton + */ +@Component( role = SiteTool.class ) +public class DefaultSiteTool + extends AbstractLogEnabled + implements SiteTool +{ + // ---------------------------------------------------------------------- + // Components + // ---------------------------------------------------------------------- + + /** + * The component that is used to resolve additional artifacts required. + */ + @Requirement + private ArtifactResolver artifactResolver; + + /** + * The component used for creating artifact instances. + */ + @Requirement + private ArtifactFactory artifactFactory; + + /** + * Internationalization. + */ + @Requirement + protected I18N i18n; + + /** + * The component for assembling inheritance. + */ + @Requirement + protected DecorationModelInheritanceAssembler assembler; + + /** + * Project builder (deprecated in Maven 3: should use ProjectBuilder, which will avoid + * issues like DOXIASITETOOLS-166) + */ + @Requirement + protected MavenProjectBuilder mavenProjectBuilder; + + // ---------------------------------------------------------------------- + // Public methods + // ---------------------------------------------------------------------- + + public Artifact getSkinArtifactFromRepository( ArtifactRepository localRepository, + List remoteArtifactRepositories, + DecorationModel decoration ) + throws SiteToolException + { + checkNotNull( "localRepository", localRepository ); + checkNotNull( "remoteArtifactRepositories", remoteArtifactRepositories ); + checkNotNull( "decoration", decoration ); + + Skin skin = decoration.getSkin(); + + if ( skin == null ) + { + skin = Skin.getDefaultSkin(); + } + + String version = skin.getVersion(); + Artifact artifact; + try + { + if ( version == null ) + { + version = Artifact.RELEASE_VERSION; + } + VersionRange versionSpec = VersionRange.createFromVersionSpec( version ); + artifact = artifactFactory.createDependencyArtifact( skin.getGroupId(), skin.getArtifactId(), versionSpec, + "jar", null, null ); + + artifactResolver.resolve( artifact, remoteArtifactRepositories, localRepository ); + } + catch ( InvalidVersionSpecificationException e ) + { + throw new SiteToolException( "InvalidVersionSpecificationException: The skin version '" + version + + "' is not valid: " + e.getMessage(), e ); + } + catch ( ArtifactResolutionException e ) + { + throw new SiteToolException( "ArtifactResolutionException: Unable to find skin", e ); + } + catch ( ArtifactNotFoundException e ) + { + throw new SiteToolException( "ArtifactNotFoundException: The skin does not exist: " + e.getMessage(), e ); + } + + return artifact; + } + + public Artifact getDefaultSkinArtifact( ArtifactRepository localRepository, + List remoteArtifactRepositories ) + throws SiteToolException + { + return getSkinArtifactFromRepository( localRepository, remoteArtifactRepositories, new DecorationModel() ); + } + + /** + * This method is not implemented according to the URI specification and has many weird + * corner cases where it doesn't do the right thing. Please consider using a better + * implemented method from a different library such as org.apache.http.client.utils.URIUtils#resolve. + */ + @Deprecated + public String getRelativePath( String to, String from ) + { + checkNotNull( "to", to ); + checkNotNull( "from", from ); + + if ( to.contains( ":" ) && from.contains( ":" ) ) + { + String toScheme = to.substring( 0, to.lastIndexOf( ':' ) ); + String fromScheme = from.substring( 0, from.lastIndexOf( ':' ) ); + if ( !toScheme.equals( fromScheme ) ) + { + return to; + } + } + + URL toUrl = null; + URL fromUrl = null; + + String toPath = to; + String fromPath = from; + + try + { + toUrl = new URL( to ); + } + catch ( MalformedURLException e ) + { + try + { + toUrl = new File( getNormalizedPath( to ) ).toURI().toURL(); + } + catch ( MalformedURLException e1 ) + { + getLogger().warn( "Unable to load a URL for '" + to + "': " + e.getMessage() ); + return to; + } + } + + try + { + fromUrl = new URL( from ); + } + catch ( MalformedURLException e ) + { + try + { + fromUrl = new File( getNormalizedPath( from ) ).toURI().toURL(); + } + catch ( MalformedURLException e1 ) + { + getLogger().warn( "Unable to load a URL for '" + from + "': " + e.getMessage() ); + return to; + } + } + + if ( toUrl != null && fromUrl != null ) + { + // URLs, determine if they share protocol and domain info + + if ( ( toUrl.getProtocol().equalsIgnoreCase( fromUrl.getProtocol() ) ) + && ( toUrl.getHost().equalsIgnoreCase( fromUrl.getHost() ) ) + && ( toUrl.getPort() == fromUrl.getPort() ) ) + { + // shared URL domain details, use URI to determine relative path + + toPath = toUrl.getFile(); + fromPath = fromUrl.getFile(); + } + else + { + // don't share basic URL information, no relative available + + return to; + } + } + else if ( ( toUrl != null && fromUrl == null ) || ( toUrl == null && fromUrl != null ) ) + { + // one is a URL and the other isn't, no relative available. + + return to; + } + + // either the two locations are not URLs or if they are they + // share the common protocol and domain info and we are left + // with their URI information + + String relativePath = getRelativeFilePath( fromPath, toPath ); + + if ( relativePath == null ) + { + relativePath = to; + } + + if ( getLogger().isDebugEnabled() && !relativePath.toString().equals( to ) ) + { + getLogger().debug( "Mapped url: " + to + " to relative path: " + relativePath ); + } + + return relativePath; + } + + private static String getRelativeFilePath( final String oldPath, final String newPath ) + { + // normalize the path delimiters + + String fromPath = new File( oldPath ).getPath(); + String toPath = new File( newPath ).getPath(); + + // strip any leading slashes if its a windows path + if ( toPath.matches( "^\\[a-zA-Z]:" ) ) + { + toPath = toPath.substring( 1 ); + } + if ( fromPath.matches( "^\\[a-zA-Z]:" ) ) + { + fromPath = fromPath.substring( 1 ); + } + + // lowercase windows drive letters. + if ( fromPath.startsWith( ":", 1 ) ) + { + fromPath = Character.toLowerCase( fromPath.charAt( 0 ) ) + fromPath.substring( 1 ); + } + if ( toPath.startsWith( ":", 1 ) ) + { + toPath = Character.toLowerCase( toPath.charAt( 0 ) ) + toPath.substring( 1 ); + } + + // check for the presence of windows drives. No relative way of + // traversing from one to the other. + + if ( ( toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) + && ( !toPath.substring( 0, 1 ).equals( fromPath.substring( 0, 1 ) ) ) ) + { + // they both have drive path element but they don't match, no + // relative path + + return null; + } + + if ( ( toPath.startsWith( ":", 1 ) && !fromPath.startsWith( ":", 1 ) ) + || ( !toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) ) + { + + // one has a drive path element and the other doesn't, no relative + // path. + + return null; + + } + + final String relativePath = buildRelativePath( toPath, fromPath, File.separatorChar ); + + return relativePath.toString(); + } + + /** {@inheritDoc} */ + public File getSiteDescriptor( File siteDirectory, Locale locale ) + { + checkNotNull( "siteDirectory", siteDirectory ); + final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale; + + File siteDescriptor = new File( siteDirectory, "site_" + llocale.getLanguage() + ".xml" ); + + if ( !siteDescriptor.isFile() ) + { + siteDescriptor = new File( siteDirectory, "site.xml" ); + } + return siteDescriptor; + } + + /** + * Get a site descriptor from one of the repositories. + * + * @param project the Maven project, not null. + * @param localRepository the Maven local repository, not null. + * @param repositories the Maven remote repositories, not null. + * @param locale the locale wanted for the site descriptor. If not null, searching for + * site_localeLanguage.xml, otherwise searching for site.xml. + * @return the site descriptor into the local repository after download of it from repositories or null if not + * found in repositories. + * @throws SiteToolException if any + */ + File getSiteDescriptorFromRepository( MavenProject project, ArtifactRepository localRepository, + List repositories, Locale locale ) + throws SiteToolException + { + checkNotNull( "project", project ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "repositories", repositories ); + + final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale; + + try + { + return resolveSiteDescriptor( project, localRepository, repositories, llocale ); + } + catch ( ArtifactNotFoundException e ) + { + getLogger().debug( "ArtifactNotFoundException: Unable to locate site descriptor: " + e ); + return null; + } + catch ( ArtifactResolutionException e ) + { + throw new SiteToolException( "ArtifactResolutionException: Unable to locate site descriptor: " + + e.getMessage(), e ); + } + catch ( IOException e ) + { + throw new SiteToolException( "IOException: Unable to locate site descriptor: " + e.getMessage(), e ); + } + } + + /** + * Read site descriptor content from Reader, adding support for deprecated ${reports}, + * ${parentProject} and ${modules} tags. + * + * @param reader + * @return the input content interpolated with deprecated tags + * @throws IOException + */ + private String readSiteDescriptor( Reader reader, String projectId ) + throws IOException + { + String siteDescriptorContent = IOUtil.toString( reader ); + + // This is to support the deprecated ${reports}, ${parentProject} and ${modules} tags. + Properties props = new Properties(); + props.put( "reports", "" ); + props.put( "modules", "" ); + props.put( "parentProject", "" ); + + // warn if interpolation required + for ( Object prop : props.keySet() ) + { + if ( siteDescriptorContent.contains( "$" + prop ) ) + { + getLogger().warn( "Site descriptor for " + projectId + " contains $" + prop + + ": should be replaced with " + props.getProperty( (String) prop ) ); + } + if ( siteDescriptorContent.contains( "${" + prop + "}" ) ) + { + getLogger().warn( "Site descriptor for " + projectId + " contains ${" + prop + + "}: should be replaced with " + props.getProperty( (String) prop ) ); + } + } + + return StringUtils.interpolate( siteDescriptorContent, props ); + } + + /** {@inheritDoc} */ + public DecorationModel getDecorationModel( File siteDirectory, Locale locale, MavenProject project, + List reactorProjects, ArtifactRepository localRepository, + List repositories ) + throws SiteToolException + { + checkNotNull( "project", project ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "repositories", repositories ); + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + getLogger().debug( "Computing decoration model of " + project.getId() + " for locale " + llocale ); + + Map.Entry result = + getDecorationModel( 0, siteDirectory, llocale, project, reactorProjects, localRepository, repositories ); + DecorationModel decorationModel = result.getKey(); + MavenProject parentProject = result.getValue(); + + if ( decorationModel == null ) + { + getLogger().debug( "Using default site descriptor" ); + + String siteDescriptorContent; + + Reader reader = null; + try + { + // Note the default is not a super class - it is used when nothing else is found + reader = ReaderFactory.newXmlReader( getClass().getResourceAsStream( "/default-site.xml" ) ); + siteDescriptorContent = readSiteDescriptor( reader, "default-site.xml" ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading default site descriptor: " + e.getMessage(), e ); + } + finally + { + IOUtil.close( reader ); + } + + decorationModel = readDecorationModel( siteDescriptorContent ); + } + + // DecorationModel back to String to interpolate, then go back to DecorationModel + String siteDescriptorContent = decorationModelToString( decorationModel ); + + // "classical" late interpolation, after full inheritance + siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, false ); + + decorationModel = readDecorationModel( siteDescriptorContent ); + + if ( parentProject != null ) + { + populateParentMenu( decorationModel, llocale, project, parentProject, true ); + } + + try + { + populateModulesMenu( decorationModel, llocale, project, reactorProjects, localRepository, true ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error while populating modules menu: " + e.getMessage(), e ); + } + + if ( decorationModel.getBannerLeft() == null ) + { + // extra default to set + Banner banner = new Banner(); + banner.setName( project.getName() ); + decorationModel.setBannerLeft( banner ); + } + + return decorationModel; + } + + /** {@inheritDoc} */ + public String getInterpolatedSiteDescriptorContent( Map props, MavenProject aProject, + String siteDescriptorContent ) + throws SiteToolException + { + checkNotNull( "props", props ); + + // "classical" late interpolation + return getInterpolatedSiteDescriptorContent( aProject, siteDescriptorContent, false ); + } + + private String getInterpolatedSiteDescriptorContent( MavenProject aProject, + String siteDescriptorContent, boolean isEarly ) + throws SiteToolException + { + checkNotNull( "aProject", aProject ); + checkNotNull( "siteDescriptorContent", siteDescriptorContent ); + + RegexBasedInterpolator interpolator = new RegexBasedInterpolator(); + + if ( isEarly ) + { + interpolator.addValueSource( new PrefixedObjectValueSource( "this.", aProject ) ); + interpolator.addValueSource( new PrefixedPropertiesValueSource( "this.", aProject.getProperties() ) ); + } + else + { + interpolator.addValueSource( new ObjectBasedValueSource( aProject ) ); + interpolator.addValueSource( new MapBasedValueSource( aProject.getProperties() ) ); + + try + { + interpolator.addValueSource( new EnvarBasedValueSource() ); + } + catch ( IOException e ) + { + // Prefer logging? + throw new SiteToolException( "IOException: cannot interpolate environment properties: " + + e.getMessage(), e ); + } + } + + try + { + // FIXME: this does not escape xml entities, see MSITE-226, PLXCOMP-118 + return interpolator.interpolate( siteDescriptorContent, isEarly ? null : "project" ); + } + catch ( InterpolationException e ) + { + throw new SiteToolException( "Cannot interpolate site descriptor: " + e.getMessage(), e ); + } + } + + /** {@inheritDoc} */ + public MavenProject getParentProject( MavenProject aProject, List reactorProjects, + ArtifactRepository localRepository ) + { + checkNotNull( "aProject", aProject ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + + if ( isMaven3OrMore() ) + { + // no need to make voodoo with Maven 3: job already done + return aProject.getParent(); + } + + MavenProject parentProject = null; + + MavenProject origParent = aProject.getParent(); + if ( origParent != null ) + { + for ( MavenProject reactorProject : reactorProjects ) + { + if ( reactorProject.getGroupId().equals( origParent.getGroupId() ) + && reactorProject.getArtifactId().equals( origParent.getArtifactId() ) + && reactorProject.getVersion().equals( origParent.getVersion() ) ) + { + parentProject = reactorProject; + + getLogger().debug( "Parent project " + origParent.getId() + " picked from reactor" ); + break; + } + } + + if ( parentProject == null && aProject.getBasedir() != null + && StringUtils.isNotEmpty( aProject.getModel().getParent().getRelativePath() ) ) + { + try + { + String relativePath = aProject.getModel().getParent().getRelativePath(); + + File pomFile = new File( aProject.getBasedir(), relativePath ); + + if ( pomFile.isDirectory() ) + { + pomFile = new File( pomFile, "pom.xml" ); + } + pomFile = new File( getNormalizedPath( pomFile.getPath() ) ); + + if ( pomFile.isFile() ) + { + MavenProject mavenProject = mavenProjectBuilder.build( pomFile, localRepository, null ); + + if ( mavenProject.getGroupId().equals( origParent.getGroupId() ) + && mavenProject.getArtifactId().equals( origParent.getArtifactId() ) + && mavenProject.getVersion().equals( origParent.getVersion() ) ) + { + parentProject = mavenProject; + + getLogger().debug( "Parent project " + origParent.getId() + " loaded from a relative path: " + + relativePath ); + } + } + } + catch ( ProjectBuildingException e ) + { + getLogger().info( "Unable to load parent project " + origParent.getId() + " from a relative path: " + + e.getMessage() ); + } + } + + if ( parentProject == null ) + { + try + { + parentProject = mavenProjectBuilder.buildFromRepository( aProject.getParentArtifact(), aProject + .getRemoteArtifactRepositories(), localRepository ); + + getLogger().debug( "Parent project " + origParent.getId() + " loaded from repository" ); + } + catch ( ProjectBuildingException e ) + { + getLogger().warn( "Unable to load parent project " + origParent.getId() + " from repository: " + + e.getMessage() ); + } + } + + if ( parentProject == null ) + { + // fallback to original parent, which may contain uninterpolated value (still need a unit test) + + parentProject = origParent; + + getLogger().debug( "Parent project " + origParent.getId() + " picked from original value" ); + } + } + return parentProject; + } + + /** + * Populate the pre-defined parent menu of the decoration model, + * if used through <menu ref="parent"/>. + * + * @param decorationModel the Doxia Sitetools DecorationModel, not null. + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project a Maven project, not null. + * @param parentProject a Maven parent project, not null. + * @param keepInheritedRefs used for inherited references. + */ +/** + * Populate the pre-defined parent menu of the decoration model, + * if used through <menu ref="parent"/>. + * + * @param decorationModel + * the Doxia Sitetools DecorationModel, not null. + * @param locale + * the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project + * a Maven project, not null. + * @param parentProject + * a Maven parent project, not null. + * @param keepInheritedRefs + * used for inherited references. + */ +private void populateParentMenu(org.apache.maven.doxia.site.decoration.DecorationModel decorationModel, java.util.Locale locale, org.apache.maven.project.MavenProject project, org.apache.maven.project.MavenProject parentProject, boolean keepInheritedRefs) { + checkNotNull("decorationModel", decorationModel); + checkNotNull("project", project); + checkNotNull("parentProject", parentProject); + org.apache.maven.doxia.site.decoration.Menu menu = decorationModel.getMenuRef("parent"); + { + if (keepInheritedRefs && /* NPEX_NULL_EXP */ + menu.isInheritAsRef()) { + return; + } + final java.util.Locale llocale = (locale == null) ? java.util.Locale.getDefault() : locale; + java.lang.String parentUrl = org.apache.maven.doxia.tools.DefaultSiteTool.getDistMgmntSiteUrl(parentProject); + if (parentUrl != null) { + if (parentUrl.endsWith("/")) { + parentUrl += "index.html"; + } else { + parentUrl += "/index.html"; + } + parentUrl = getRelativePath(parentUrl, org.apache.maven.doxia.tools.DefaultSiteTool.getDistMgmntSiteUrl(project)); + } else { + // parent has no url, assume relative path is given by site structure + java.io.File parentBasedir = parentProject.getBasedir(); + // First make sure that the parent is available on the file system + if (parentBasedir != null) { + // Try to find the relative path to the parent via the file system + java.lang.String parentPath = parentBasedir.getAbsolutePath(); + java.lang.String projectPath = project.getBasedir().getAbsolutePath(); + parentUrl = getRelativePath(parentPath, projectPath) + "/index.html"; + } + } + // Only add the parent menu if we were able to find a URL for it + if (parentUrl == null) { + getLogger().warn("Unable to find a URL to the parent project. The parent menu will NOT be added."); + } else { + if (menu.getName() == null) { + menu.setName(i18n.getString("site-tool", llocale, "decorationModel.menu.parentproject")); + } + org.apache.maven.doxia.site.decoration.MenuItem item = new org.apache.maven.doxia.site.decoration.MenuItem(); + item.setName(parentProject.getName()); + item.setHref(parentUrl); + menu.addItem(item); + } + } +} + + /** + * Populate the pre-defined modules menu of the decoration model, + * if used through <menu ref="modules"/>. + * + * @param decorationModel the Doxia Sitetools DecorationModel, not null. + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project a Maven project, not null. + * @param reactorProjects the Maven reactor projects, not null. + * @param localRepository the Maven local repository, not null. + * @param keepInheritedRefs used for inherited references. + * @throws SiteToolException if any + * @throws IOException + */ + private void populateModulesMenu( DecorationModel decorationModel, Locale locale, MavenProject project, + List reactorProjects, ArtifactRepository localRepository, + boolean keepInheritedRefs ) + throws SiteToolException, IOException + { + checkNotNull( "project", project ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "decorationModel", decorationModel ); + + Menu menu = decorationModel.getMenuRef( "modules" ); + + if ( menu == null ) + { + return; + } + + if ( keepInheritedRefs && menu.isInheritAsRef() ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale ; + + // we require child modules and reactors to process module menu + if ( project.getModules().size() > 0 ) + { + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectmodules" ) ); + } + + for ( String module : (List) project.getModules() ) + { + MavenProject moduleProject = getModuleFromReactor( project, reactorProjects, module ); + + if ( moduleProject == null ) + { + getLogger().warn( "Module " + module + + " not found in reactor: loading locally" ); + + File f = new File( project.getBasedir(), module + "/pom.xml" ); + if ( f.exists() ) + { + try + { + moduleProject = mavenProjectBuilder.build( f, localRepository, null ); + } + catch ( ProjectBuildingException e ) + { + throw new SiteToolException( "Unable to read local module-POM", e ); + } + } + else + { + getLogger().warn( "No filesystem module-POM available" ); + + moduleProject = new MavenProject(); + moduleProject.setName( module ); + moduleProject.setDistributionManagement( new DistributionManagement() ); + moduleProject.getDistributionManagement().setSite( new Site() ); + moduleProject.getDistributionManagement().getSite().setUrl( module ); + } + } + + String siteUrl = getDistMgmntSiteUrl( moduleProject ); + String itemName = + ( moduleProject.getName() == null ) ? moduleProject.getArtifactId() : moduleProject.getName(); + + appendMenuItem( project, menu, itemName, siteUrl, moduleProject.getArtifactId() ); + } + } + else if ( decorationModel.getMenuRef( "modules" ).getInherit() == null ) + { + // only remove if project has no modules AND menu is not inherited, see MSHARED-174 + decorationModel.removeMenuRef( "modules" ); + } + } + + private static MavenProject getModuleFromReactor( MavenProject project, List reactorProjects, + String module ) + throws IOException + { + File moduleBasedir = new File( project.getBasedir(), module ).getCanonicalFile(); + + for ( MavenProject reactorProject : reactorProjects ) + { + if ( moduleBasedir.equals( reactorProject.getBasedir() ) ) + { + return reactorProject; + } + } + + // module not found in reactor + return null; + } + + /** {@inheritDoc} */ + public void populateReportsMenu( DecorationModel decorationModel, Locale locale, + Map> categories ) + { + checkNotNull( "decorationModel", decorationModel ); + checkNotNull( "categories", categories ); + + Menu menu = decorationModel.getMenuRef( "reports" ); + + if ( menu == null ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectdocumentation" ) ); + } + + boolean found = false; + if ( menu.getItems().isEmpty() ) + { + List categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_INFORMATION ); + if ( !isEmptyList( categoryReports ) ) + { + MenuItem item = createCategoryMenu( + i18n.getString( "site-tool", llocale, + "decorationModel.menu.projectinformation" ), + "/project-info.html", categoryReports, llocale ); + menu.getItems().add( item ); + found = true; + } + + categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_REPORTS ); + if ( !isEmptyList( categoryReports ) ) + { + MenuItem item = + createCategoryMenu( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectreports" ), + "/project-reports.html", categoryReports, llocale ); + menu.getItems().add( item ); + found = true; + } + } + if ( !found ) + { + decorationModel.removeMenuRef( "reports" ); + } + } + + /** {@inheritDoc} */ + public List getSiteLocales( String locales ) + { + if ( locales == null ) + { + return Collections.singletonList( DEFAULT_LOCALE ); + } + + String[] localesArray = StringUtils.split( locales, "," ); + List localesList = new ArrayList( localesArray.length ); + + for ( String localeString : localesArray ) + { + Locale locale = codeToLocale( localeString ); + + if ( locale == null ) + { + continue; + } + + if ( !Arrays.asList( Locale.getAvailableLocales() ).contains( locale ) ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "The locale defined by '" + locale + + "' is not available in this Java Virtual Machine (" + + System.getProperty( "java.version" ) + + " from " + System.getProperty( "java.vendor" ) + ") - IGNORING" ); + } + continue; + } + + // Default bundles are in English + if ( ( !locale.getLanguage().equals( DEFAULT_LOCALE.getLanguage() ) ) + && ( !i18n.getBundle( "site-tool", locale ).getLocale().getLanguage() + .equals( locale.getLanguage() ) ) ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "The locale '" + locale + "' (" + locale.getDisplayName( Locale.ENGLISH ) + + ") is not currently supported by Maven Site - IGNORING." + + "\nContributions are welcome and greatly appreciated!" + + "\nIf you want to contribute a new translation, please visit " + + "http://maven.apache.org/plugins/localization.html for detailed instructions." ); + } + + continue; + } + + localesList.add( locale ); + } + + if ( localesList.isEmpty() ) + { + localesList = Collections.singletonList( DEFAULT_LOCALE ); + } + + return localesList; + } + + /** + * Converts a locale code like "en", "en_US" or "en_US_win" to a java.util.Locale + * object. + *

    If localeCode = default, return the current value of the default locale for this instance + * of the Java Virtual Machine.

    + * + * @param localeCode the locale code string. + * @return a java.util.Locale object instanced or null if errors occurred + * @see java.util.Locale#getDefault() + */ + private Locale codeToLocale( String localeCode ) + { + if ( localeCode == null ) + { + return null; + } + + if ( "default".equalsIgnoreCase( localeCode ) ) + { + return Locale.getDefault(); + } + + String language = ""; + String country = ""; + String variant = ""; + + StringTokenizer tokenizer = new StringTokenizer( localeCode, "_" ); + final int maxTokens = 3; + if ( tokenizer.countTokens() > maxTokens ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "Invalid java.util.Locale format for '" + localeCode + "' entry - IGNORING" ); + } + return null; + } + + if ( tokenizer.hasMoreTokens() ) + { + language = tokenizer.nextToken(); + if ( tokenizer.hasMoreTokens() ) + { + country = tokenizer.nextToken(); + if ( tokenizer.hasMoreTokens() ) + { + variant = tokenizer.nextToken(); + } + } + } + + return new Locale( language, country, variant ); + } + + // ---------------------------------------------------------------------- + // Protected methods + // ---------------------------------------------------------------------- + + /** + * @param path could be null. + * @return the path normalized, i.e. by eliminating "/../" and "/./" in the path. + * @see FilenameUtils#normalize(String) + */ + protected static String getNormalizedPath( String path ) + { + String normalized = FilenameUtils.normalize( path ); + if ( normalized == null ) + { + normalized = path; + } + return ( normalized == null ) ? null : normalized.replace( '\\', '/' ); + } + + // ---------------------------------------------------------------------- + // Private methods + // ---------------------------------------------------------------------- + + /** + * @param project not null + * @param localRepository not null + * @param repositories not null + * @param locale not null + * @return the resolved site descriptor + * @throws IOException if any + * @throws ArtifactResolutionException if any + * @throws ArtifactNotFoundException if any + */ + private File resolveSiteDescriptor( MavenProject project, ArtifactRepository localRepository, + List repositories, Locale locale ) + throws IOException, ArtifactResolutionException, ArtifactNotFoundException + { + File result; + + // TODO: this is a bit crude - proper type, or proper handling as metadata rather than an artifact in 2.1? + Artifact artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), + project.getArtifactId(), + project.getVersion(), "xml", + "site_" + locale.getLanguage() ); + + boolean found = false; + try + { + artifactResolver.resolve( artifact, repositories, localRepository ); + + result = artifact.getFile(); + + // we use zero length files to avoid re-resolution (see below) + if ( result.length() > 0 ) + { + found = true; + } + else + { + getLogger().debug( "No site descriptor found for " + project.getId() + " for locale " + + locale.getLanguage() + ", trying without locale..." ); + } + } + catch ( ArtifactNotFoundException e ) + { + getLogger().debug( "Unable to locate site descriptor for locale " + locale.getLanguage() + ": " + e ); + + // we can afford to write an empty descriptor here as we don't expect it to turn up later in the remote + // repository, because the parent was already released (and snapshots are updated automatically if changed) + result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); + result.getParentFile().mkdirs(); + result.createNewFile(); + } + + if ( !found ) + { + artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), project.getArtifactId(), + project.getVersion(), "xml", "site" ); + try + { + artifactResolver.resolve( artifact, repositories, localRepository ); + } + catch ( ArtifactNotFoundException e ) + { + // see above regarding this zero length file + result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); + result.getParentFile().mkdirs(); + result.createNewFile(); + + throw e; + } + + result = artifact.getFile(); + + // we use zero length files to avoid re-resolution (see below) + if ( result.length() == 0 ) + { + getLogger().debug( "No site descriptor found for " + project.getId() + " without locale." ); + result = null; + } + } + + return result; + } + + /** + * @param depth depth of project + * @param siteDirectory, can be null if project.basedir is null, ie POM from repository + * @param locale not null + * @param project not null + * @param reactorProjects not null + * @param localRepository not null + * @param repositories not null + * @param origProps not null + * @return the decoration model depending the locale and the parent project + * @throws SiteToolException if any + */ + private Map.Entry getDecorationModel( int depth, File siteDirectory, Locale locale, + MavenProject project, + List reactorProjects, + ArtifactRepository localRepository, + List repositories ) + throws SiteToolException + { + // 1. get site descriptor File + File siteDescriptor; + if ( project.getBasedir() == null ) + { + // POM is in the repository: look into the repository for site descriptor + try + { + siteDescriptor = getSiteDescriptorFromRepository( project, localRepository, repositories, locale ); + } + catch ( SiteToolException e ) + { + throw new SiteToolException( "The site descriptor cannot be resolved from the repository: " + + e.getMessage(), e ); + } + } + else + { + // POM is in build directory: look for site descriptor as local file + siteDescriptor = getSiteDescriptor( siteDirectory, locale ); + } + + // 2. read DecorationModel from site descriptor File and do early interpolation (${this.*}) + DecorationModel decoration = null; + Reader siteDescriptorReader = null; + try + { + if ( siteDescriptor != null && siteDescriptor.exists() ) + { + getLogger().debug( "Reading" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + + " site descriptor from " + siteDescriptor ); + + siteDescriptorReader = ReaderFactory.newXmlReader( siteDescriptor ); + + String siteDescriptorContent = readSiteDescriptor( siteDescriptorReader, project.getId() ); + + // interpolate ${this.*} = early interpolation + siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, true ); + + decoration = readDecorationModel( siteDescriptorContent ); + decoration.setLastModified( siteDescriptor.lastModified() ); + } + else + { + getLogger().debug( "No" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + " site descriptor." ); + } + } + catch ( IOException e ) + { + throw new SiteToolException( "The site descriptor for " + project.getId() + " cannot be read from " + + siteDescriptor, e ); + } + finally + { + IOUtil.close( siteDescriptorReader ); + } + + // 3. look for parent project + MavenProject parentProject = getParentProject( project, reactorProjects, localRepository ); + + // 4. merge with parent project DecorationModel + if ( parentProject != null && ( decoration == null || decoration.isMergeParent() ) ) + { + depth++; + getLogger().debug( "Looking for site descriptor of level " + depth + " parent project: " + + parentProject.getId() ); + + File parentSiteDirectory = null; + if ( parentProject.getBasedir() != null ) + { + // extrapolate parent project site directory + String siteRelativePath = getRelativeFilePath( project.getBasedir().getAbsolutePath(), + siteDescriptor.getParentFile().getAbsolutePath() ); + + parentSiteDirectory = new File( parentProject.getBasedir(), siteRelativePath ); + // notice: using same siteRelativePath for parent as current project; may be wrong if site plugin + // has different configuration. But this is a rare case (this only has impact if parent is from reactor) + } + + DecorationModel parentDecoration = + getDecorationModel( depth, parentSiteDirectory, locale, parentProject, reactorProjects, localRepository, + repositories ).getKey(); + + // MSHARED-116 requires an empty decoration model (instead of a null one) + // MSHARED-145 requires us to do this only if there is a parent to merge it with + if ( decoration == null && parentDecoration != null ) + { + // we have no site descriptor: merge the parent into an empty one + decoration = new DecorationModel(); + } + + String name = project.getName(); + if ( decoration != null && StringUtils.isNotEmpty( decoration.getName() ) ) + { + name = decoration.getName(); + } + + // Merge the parent and child DecorationModels + String projectDistMgmnt = getDistMgmntSiteUrl( project ); + String parentDistMgmnt = getDistMgmntSiteUrl( parentProject ); + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Site decoration model inheritance: assembling child with level " + depth + + " parent: distributionManagement.site.url child = " + projectDistMgmnt + " and parent = " + + parentDistMgmnt ); + } + assembler.assembleModelInheritance( name, decoration, parentDecoration, projectDistMgmnt, + parentDistMgmnt == null ? projectDistMgmnt : parentDistMgmnt ); + } + + return new AbstractMap.SimpleEntry( decoration, parentProject ); + } + + /** + * @param siteDescriptorContent not null + * @return the decoration model object + * @throws SiteToolException if any + */ + private DecorationModel readDecorationModel( String siteDescriptorContent ) + throws SiteToolException + { + try + { + return new DecorationXpp3Reader().read( new StringReader( siteDescriptorContent ) ); + } + catch ( XmlPullParserException e ) + { + throw new SiteToolException( "Error parsing site descriptor", e ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading site descriptor", e ); + } + } + + private String decorationModelToString( DecorationModel decoration ) + throws SiteToolException + { + StringWriter writer = new StringWriter(); + + try + { + new DecorationXpp3Writer().write( writer, decoration ); + return writer.toString(); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading site descriptor", e ); + } + finally + { + IOUtil.close( writer ); + } + } + + private static String buildRelativePath( final String toPath, final String fromPath, final char separatorChar ) + { + // use tokenizer to traverse paths and for lazy checking + StringTokenizer toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); + StringTokenizer fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); + + int count = 0; + + // walk along the to path looking for divergence from the from path + while ( toTokeniser.hasMoreTokens() && fromTokeniser.hasMoreTokens() ) + { + if ( separatorChar == '\\' ) + { + if ( !fromTokeniser.nextToken().equalsIgnoreCase( toTokeniser.nextToken() ) ) + { + break; + } + } + else + { + if ( !fromTokeniser.nextToken().equals( toTokeniser.nextToken() ) ) + { + break; + } + } + + count++; + } + + // reinitialize the tokenizers to count positions to retrieve the + // gobbled token + + toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); + fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); + + while ( count-- > 0 ) + { + fromTokeniser.nextToken(); + toTokeniser.nextToken(); + } + + StringBuilder relativePath = new StringBuilder(); + + // add back refs for the rest of from location. + while ( fromTokeniser.hasMoreTokens() ) + { + fromTokeniser.nextToken(); + + relativePath.append( ".." ); + + if ( fromTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + } + + if ( relativePath.length() != 0 && toTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + + // add fwd fills for whatever's left of to. + while ( toTokeniser.hasMoreTokens() ) + { + relativePath.append( toTokeniser.nextToken() ); + + if ( toTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + } + return relativePath.toString(); + } + + /** + * @param project not null + * @param menu not null + * @param name not null + * @param href could be null + * @param defaultHref not null + */ + private void appendMenuItem( MavenProject project, Menu menu, String name, String href, String defaultHref ) + { + String selectedHref = href; + + if ( selectedHref == null ) + { + selectedHref = defaultHref; + } + + MenuItem item = new MenuItem(); + item.setName( name ); + + String baseUrl = getDistMgmntSiteUrl( project ); + if ( baseUrl != null ) + { + selectedHref = getRelativePath( selectedHref, baseUrl ); + } + + if ( selectedHref.endsWith( "/" ) ) + { + item.setHref( selectedHref + "index.html" ); + } + else + { + item.setHref( selectedHref + "/index.html" ); + } + menu.addItem( item ); + } + + /** + * @param name not null + * @param href not null + * @param categoryReports not null + * @param locale not null + * @return the menu item object + */ + private MenuItem createCategoryMenu( String name, String href, List categoryReports, Locale locale ) + { + MenuItem item = new MenuItem(); + item.setName( name ); + item.setCollapse( true ); + item.setHref( href ); + + // MSHARED-172, allow reports to define their order in some other way? + //Collections.sort( categoryReports, new ReportComparator( locale ) ); + + for ( MavenReport report : categoryReports ) + { + MenuItem subitem = new MenuItem(); + subitem.setName( report.getName( locale ) ); + subitem.setHref( report.getOutputName() + ".html" ); + item.getItems().add( subitem ); + } + + return item; + } + + // ---------------------------------------------------------------------- + // static methods + // ---------------------------------------------------------------------- + + /** + * Convenience method. + * + * @param list could be null + * @return true if the list is null or empty + */ + private static boolean isEmptyList( List list ) + { + return list == null || list.isEmpty(); + } + + /** + * Return distributionManagement.site.url if defined, null otherwise. + * + * @param project not null + * @return could be null + */ + private static String getDistMgmntSiteUrl( MavenProject project ) + { + return getDistMgmntSiteUrl( project.getDistributionManagement() ); + } + + private static String getDistMgmntSiteUrl( DistributionManagement distMgmnt ) + { + if ( distMgmnt != null && distMgmnt.getSite() != null && distMgmnt.getSite().getUrl() != null ) + { + return urlEncode( distMgmnt.getSite().getUrl() ); + } + + return null; + } + + private static String urlEncode( final String url ) + { + if ( url == null ) + { + return null; + } + + try + { + return new File( url ).toURI().toURL().toExternalForm(); + } + catch ( MalformedURLException ex ) + { + return url; // this will then throw somewhere else + } + } + + private void checkNotNull( String name, Object value ) + { + if ( value == null ) + { + throw new IllegalArgumentException( "The parameter '" + name + "' cannot be null." ); + } + } + + /** + * Check the current Maven version to see if it's Maven 3.0 or newer. + */ + private static boolean isMaven3OrMore() + { + return new DefaultArtifactVersion( getMavenVersion() ).getMajorVersion() >= 3; + } + + private static String getMavenVersion() + { + // This relies on the fact that MavenProject is the in core classloader + // and that the core classloader is for the maven-core artifact + // and that should have a pom.properties file + // if this ever changes, we will have to revisit this code. + final Properties properties = new Properties(); + final String corePomProperties = "META-INF/maven/org.apache.maven/maven-core/pom.properties"; + final InputStream in = MavenProject.class.getClassLoader().getResourceAsStream( corePomProperties ); + try + { + properties.load( in ); + } + catch ( IOException ioe ) + { + return ""; + } + finally + { + IOUtil.close( in ); + } + + return properties.getProperty( "version" ).trim(); + } +} diff --git a/Java/maven-doxia-sitetools-DefaultSiteTool_686/metadata.json b/Java/maven-doxia-sitetools-DefaultSiteTool_686/metadata.json new file mode 100644 index 000000000..a104b7850 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteTool_686/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-DefaultSiteTool_686", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/DefaultSiteTool.java", + "line": 699, + "npe_method": "populateParentMenu", + "deref_field": "menu", + "npe_class": "DefaultSiteTool", + "repo": "maven-doxia-sitetools", + "bug_id": "DefaultSiteTool_686" + } +} diff --git a/Java/maven-doxia-sitetools-DefaultSiteTool_686/npe.json b/Java/maven-doxia-sitetools-DefaultSiteTool_686/npe.json new file mode 100644 index 000000000..62b60039b --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteTool_686/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/DefaultSiteTool.java", + "line": 699, + "npe_method": "populateParentMenu", + "deref_field": "menu", + "npe_class": "DefaultSiteTool" +} \ No newline at end of file diff --git a/Java/maven-doxia-sitetools-DefaultSiteTool_771/Dockerfile b/Java/maven-doxia-sitetools-DefaultSiteTool_771/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteTool_771/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-DefaultSiteTool_771/buggy.java b/Java/maven-doxia-sitetools-DefaultSiteTool_771/buggy.java new file mode 100644 index 000000000..f98f1f211 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteTool_771/buggy.java @@ -0,0 +1,1519 @@ +package org.apache.maven.doxia.tools; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.StringTokenizer; + +import org.apache.commons.io.FilenameUtils; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.factory.ArtifactFactory; +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.artifact.resolver.ArtifactNotFoundException; +import org.apache.maven.artifact.resolver.ArtifactResolutionException; +import org.apache.maven.artifact.resolver.ArtifactResolver; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; +import org.apache.maven.artifact.versioning.VersionRange; +import org.apache.maven.doxia.site.decoration.Banner; +import org.apache.maven.doxia.site.decoration.DecorationModel; +import org.apache.maven.doxia.site.decoration.Menu; +import org.apache.maven.doxia.site.decoration.MenuItem; +import org.apache.maven.doxia.site.decoration.Skin; +import org.apache.maven.doxia.site.decoration.inheritance.DecorationModelInheritanceAssembler; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Reader; +import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Writer; +import org.apache.maven.model.DistributionManagement; +import org.apache.maven.model.Site; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.MavenProjectBuilder; +import org.apache.maven.project.ProjectBuildingException; +import org.apache.maven.reporting.MavenReport; +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.IOUtil; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.interpolation.EnvarBasedValueSource; +import org.codehaus.plexus.interpolation.InterpolationException; +import org.codehaus.plexus.interpolation.MapBasedValueSource; +import org.codehaus.plexus.interpolation.ObjectBasedValueSource; +import org.codehaus.plexus.interpolation.PrefixedObjectValueSource; +import org.codehaus.plexus.interpolation.PrefixedPropertiesValueSource; +import org.codehaus.plexus.interpolation.RegexBasedInterpolator; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +/** + * Default implementation of the site tool. + * + * @author Vincent Siveton + */ +@Component( role = SiteTool.class ) +public class DefaultSiteTool + extends AbstractLogEnabled + implements SiteTool +{ + // ---------------------------------------------------------------------- + // Components + // ---------------------------------------------------------------------- + + /** + * The component that is used to resolve additional artifacts required. + */ + @Requirement + private ArtifactResolver artifactResolver; + + /** + * The component used for creating artifact instances. + */ + @Requirement + private ArtifactFactory artifactFactory; + + /** + * Internationalization. + */ + @Requirement + protected I18N i18n; + + /** + * The component for assembling inheritance. + */ + @Requirement + protected DecorationModelInheritanceAssembler assembler; + + /** + * Project builder (deprecated in Maven 3: should use ProjectBuilder, which will avoid + * issues like DOXIASITETOOLS-166) + */ + @Requirement + protected MavenProjectBuilder mavenProjectBuilder; + + // ---------------------------------------------------------------------- + // Public methods + // ---------------------------------------------------------------------- + + public Artifact getSkinArtifactFromRepository( ArtifactRepository localRepository, + List remoteArtifactRepositories, + DecorationModel decoration ) + throws SiteToolException + { + checkNotNull( "localRepository", localRepository ); + checkNotNull( "remoteArtifactRepositories", remoteArtifactRepositories ); + checkNotNull( "decoration", decoration ); + + Skin skin = decoration.getSkin(); + + if ( skin == null ) + { + skin = Skin.getDefaultSkin(); + } + + String version = skin.getVersion(); + Artifact artifact; + try + { + if ( version == null ) + { + version = Artifact.RELEASE_VERSION; + } + VersionRange versionSpec = VersionRange.createFromVersionSpec( version ); + artifact = artifactFactory.createDependencyArtifact( skin.getGroupId(), skin.getArtifactId(), versionSpec, + "jar", null, null ); + + artifactResolver.resolve( artifact, remoteArtifactRepositories, localRepository ); + } + catch ( InvalidVersionSpecificationException e ) + { + throw new SiteToolException( "InvalidVersionSpecificationException: The skin version '" + version + + "' is not valid: " + e.getMessage(), e ); + } + catch ( ArtifactResolutionException e ) + { + throw new SiteToolException( "ArtifactResolutionException: Unable to find skin", e ); + } + catch ( ArtifactNotFoundException e ) + { + throw new SiteToolException( "ArtifactNotFoundException: The skin does not exist: " + e.getMessage(), e ); + } + + return artifact; + } + + public Artifact getDefaultSkinArtifact( ArtifactRepository localRepository, + List remoteArtifactRepositories ) + throws SiteToolException + { + return getSkinArtifactFromRepository( localRepository, remoteArtifactRepositories, new DecorationModel() ); + } + + /** + * This method is not implemented according to the URI specification and has many weird + * corner cases where it doesn't do the right thing. Please consider using a better + * implemented method from a different library such as org.apache.http.client.utils.URIUtils#resolve. + */ + @Deprecated + public String getRelativePath( String to, String from ) + { + checkNotNull( "to", to ); + checkNotNull( "from", from ); + + if ( to.contains( ":" ) && from.contains( ":" ) ) + { + String toScheme = to.substring( 0, to.lastIndexOf( ':' ) ); + String fromScheme = from.substring( 0, from.lastIndexOf( ':' ) ); + if ( !toScheme.equals( fromScheme ) ) + { + return to; + } + } + + URL toUrl = null; + URL fromUrl = null; + + String toPath = to; + String fromPath = from; + + try + { + toUrl = new URL( to ); + } + catch ( MalformedURLException e ) + { + try + { + toUrl = new File( getNormalizedPath( to ) ).toURI().toURL(); + } + catch ( MalformedURLException e1 ) + { + getLogger().warn( "Unable to load a URL for '" + to + "': " + e.getMessage() ); + return to; + } + } + + try + { + fromUrl = new URL( from ); + } + catch ( MalformedURLException e ) + { + try + { + fromUrl = new File( getNormalizedPath( from ) ).toURI().toURL(); + } + catch ( MalformedURLException e1 ) + { + getLogger().warn( "Unable to load a URL for '" + from + "': " + e.getMessage() ); + return to; + } + } + + if ( toUrl != null && fromUrl != null ) + { + // URLs, determine if they share protocol and domain info + + if ( ( toUrl.getProtocol().equalsIgnoreCase( fromUrl.getProtocol() ) ) + && ( toUrl.getHost().equalsIgnoreCase( fromUrl.getHost() ) ) + && ( toUrl.getPort() == fromUrl.getPort() ) ) + { + // shared URL domain details, use URI to determine relative path + + toPath = toUrl.getFile(); + fromPath = fromUrl.getFile(); + } + else + { + // don't share basic URL information, no relative available + + return to; + } + } + else if ( ( toUrl != null && fromUrl == null ) || ( toUrl == null && fromUrl != null ) ) + { + // one is a URL and the other isn't, no relative available. + + return to; + } + + // either the two locations are not URLs or if they are they + // share the common protocol and domain info and we are left + // with their URI information + + String relativePath = getRelativeFilePath( fromPath, toPath ); + + if ( relativePath == null ) + { + relativePath = to; + } + + if ( getLogger().isDebugEnabled() && !relativePath.toString().equals( to ) ) + { + getLogger().debug( "Mapped url: " + to + " to relative path: " + relativePath ); + } + + return relativePath; + } + + private static String getRelativeFilePath( final String oldPath, final String newPath ) + { + // normalize the path delimiters + + String fromPath = new File( oldPath ).getPath(); + String toPath = new File( newPath ).getPath(); + + // strip any leading slashes if its a windows path + if ( toPath.matches( "^\\[a-zA-Z]:" ) ) + { + toPath = toPath.substring( 1 ); + } + if ( fromPath.matches( "^\\[a-zA-Z]:" ) ) + { + fromPath = fromPath.substring( 1 ); + } + + // lowercase windows drive letters. + if ( fromPath.startsWith( ":", 1 ) ) + { + fromPath = Character.toLowerCase( fromPath.charAt( 0 ) ) + fromPath.substring( 1 ); + } + if ( toPath.startsWith( ":", 1 ) ) + { + toPath = Character.toLowerCase( toPath.charAt( 0 ) ) + toPath.substring( 1 ); + } + + // check for the presence of windows drives. No relative way of + // traversing from one to the other. + + if ( ( toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) + && ( !toPath.substring( 0, 1 ).equals( fromPath.substring( 0, 1 ) ) ) ) + { + // they both have drive path element but they don't match, no + // relative path + + return null; + } + + if ( ( toPath.startsWith( ":", 1 ) && !fromPath.startsWith( ":", 1 ) ) + || ( !toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) ) + { + + // one has a drive path element and the other doesn't, no relative + // path. + + return null; + + } + + final String relativePath = buildRelativePath( toPath, fromPath, File.separatorChar ); + + return relativePath.toString(); + } + + /** {@inheritDoc} */ + public File getSiteDescriptor( File siteDirectory, Locale locale ) + { + checkNotNull( "siteDirectory", siteDirectory ); + final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale; + + File siteDescriptor = new File( siteDirectory, "site_" + llocale.getLanguage() + ".xml" ); + + if ( !siteDescriptor.isFile() ) + { + siteDescriptor = new File( siteDirectory, "site.xml" ); + } + return siteDescriptor; + } + + /** + * Get a site descriptor from one of the repositories. + * + * @param project the Maven project, not null. + * @param localRepository the Maven local repository, not null. + * @param repositories the Maven remote repositories, not null. + * @param locale the locale wanted for the site descriptor. If not null, searching for + * site_localeLanguage.xml, otherwise searching for site.xml. + * @return the site descriptor into the local repository after download of it from repositories or null if not + * found in repositories. + * @throws SiteToolException if any + */ + File getSiteDescriptorFromRepository( MavenProject project, ArtifactRepository localRepository, + List repositories, Locale locale ) + throws SiteToolException + { + checkNotNull( "project", project ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "repositories", repositories ); + + final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale; + + try + { + return resolveSiteDescriptor( project, localRepository, repositories, llocale ); + } + catch ( ArtifactNotFoundException e ) + { + getLogger().debug( "ArtifactNotFoundException: Unable to locate site descriptor: " + e ); + return null; + } + catch ( ArtifactResolutionException e ) + { + throw new SiteToolException( "ArtifactResolutionException: Unable to locate site descriptor: " + + e.getMessage(), e ); + } + catch ( IOException e ) + { + throw new SiteToolException( "IOException: Unable to locate site descriptor: " + e.getMessage(), e ); + } + } + + /** + * Read site descriptor content from Reader, adding support for deprecated ${reports}, + * ${parentProject} and ${modules} tags. + * + * @param reader + * @return the input content interpolated with deprecated tags + * @throws IOException + */ + private String readSiteDescriptor( Reader reader, String projectId ) + throws IOException + { + String siteDescriptorContent = IOUtil.toString( reader ); + + // This is to support the deprecated ${reports}, ${parentProject} and ${modules} tags. + Properties props = new Properties(); + props.put( "reports", "" ); + props.put( "modules", "" ); + props.put( "parentProject", "" ); + + // warn if interpolation required + for ( Object prop : props.keySet() ) + { + if ( siteDescriptorContent.contains( "$" + prop ) ) + { + getLogger().warn( "Site descriptor for " + projectId + " contains $" + prop + + ": should be replaced with " + props.getProperty( (String) prop ) ); + } + if ( siteDescriptorContent.contains( "${" + prop + "}" ) ) + { + getLogger().warn( "Site descriptor for " + projectId + " contains ${" + prop + + "}: should be replaced with " + props.getProperty( (String) prop ) ); + } + } + + return StringUtils.interpolate( siteDescriptorContent, props ); + } + + /** {@inheritDoc} */ + public DecorationModel getDecorationModel( File siteDirectory, Locale locale, MavenProject project, + List reactorProjects, ArtifactRepository localRepository, + List repositories ) + throws SiteToolException + { + checkNotNull( "project", project ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + checkNotNull( "repositories", repositories ); + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + getLogger().debug( "Computing decoration model of " + project.getId() + " for locale " + llocale ); + + Map.Entry result = + getDecorationModel( 0, siteDirectory, llocale, project, reactorProjects, localRepository, repositories ); + DecorationModel decorationModel = result.getKey(); + MavenProject parentProject = result.getValue(); + + if ( decorationModel == null ) + { + getLogger().debug( "Using default site descriptor" ); + + String siteDescriptorContent; + + Reader reader = null; + try + { + // Note the default is not a super class - it is used when nothing else is found + reader = ReaderFactory.newXmlReader( getClass().getResourceAsStream( "/default-site.xml" ) ); + siteDescriptorContent = readSiteDescriptor( reader, "default-site.xml" ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading default site descriptor: " + e.getMessage(), e ); + } + finally + { + IOUtil.close( reader ); + } + + decorationModel = readDecorationModel( siteDescriptorContent ); + } + + // DecorationModel back to String to interpolate, then go back to DecorationModel + String siteDescriptorContent = decorationModelToString( decorationModel ); + + // "classical" late interpolation, after full inheritance + siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, false ); + + decorationModel = readDecorationModel( siteDescriptorContent ); + + if ( parentProject != null ) + { + populateParentMenu( decorationModel, llocale, project, parentProject, true ); + } + + try + { + populateModulesMenu( decorationModel, llocale, project, reactorProjects, localRepository, true ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error while populating modules menu: " + e.getMessage(), e ); + } + + if ( decorationModel.getBannerLeft() == null ) + { + // extra default to set + Banner banner = new Banner(); + banner.setName( project.getName() ); + decorationModel.setBannerLeft( banner ); + } + + return decorationModel; + } + + /** {@inheritDoc} */ + public String getInterpolatedSiteDescriptorContent( Map props, MavenProject aProject, + String siteDescriptorContent ) + throws SiteToolException + { + checkNotNull( "props", props ); + + // "classical" late interpolation + return getInterpolatedSiteDescriptorContent( aProject, siteDescriptorContent, false ); + } + + private String getInterpolatedSiteDescriptorContent( MavenProject aProject, + String siteDescriptorContent, boolean isEarly ) + throws SiteToolException + { + checkNotNull( "aProject", aProject ); + checkNotNull( "siteDescriptorContent", siteDescriptorContent ); + + RegexBasedInterpolator interpolator = new RegexBasedInterpolator(); + + if ( isEarly ) + { + interpolator.addValueSource( new PrefixedObjectValueSource( "this.", aProject ) ); + interpolator.addValueSource( new PrefixedPropertiesValueSource( "this.", aProject.getProperties() ) ); + } + else + { + interpolator.addValueSource( new ObjectBasedValueSource( aProject ) ); + interpolator.addValueSource( new MapBasedValueSource( aProject.getProperties() ) ); + + try + { + interpolator.addValueSource( new EnvarBasedValueSource() ); + } + catch ( IOException e ) + { + // Prefer logging? + throw new SiteToolException( "IOException: cannot interpolate environment properties: " + + e.getMessage(), e ); + } + } + + try + { + // FIXME: this does not escape xml entities, see MSITE-226, PLXCOMP-118 + return interpolator.interpolate( siteDescriptorContent, isEarly ? null : "project" ); + } + catch ( InterpolationException e ) + { + throw new SiteToolException( "Cannot interpolate site descriptor: " + e.getMessage(), e ); + } + } + + /** {@inheritDoc} */ + public MavenProject getParentProject( MavenProject aProject, List reactorProjects, + ArtifactRepository localRepository ) + { + checkNotNull( "aProject", aProject ); + checkNotNull( "reactorProjects", reactorProjects ); + checkNotNull( "localRepository", localRepository ); + + if ( isMaven3OrMore() ) + { + // no need to make voodoo with Maven 3: job already done + return aProject.getParent(); + } + + MavenProject parentProject = null; + + MavenProject origParent = aProject.getParent(); + if ( origParent != null ) + { + for ( MavenProject reactorProject : reactorProjects ) + { + if ( reactorProject.getGroupId().equals( origParent.getGroupId() ) + && reactorProject.getArtifactId().equals( origParent.getArtifactId() ) + && reactorProject.getVersion().equals( origParent.getVersion() ) ) + { + parentProject = reactorProject; + + getLogger().debug( "Parent project " + origParent.getId() + " picked from reactor" ); + break; + } + } + + if ( parentProject == null && aProject.getBasedir() != null + && StringUtils.isNotEmpty( aProject.getModel().getParent().getRelativePath() ) ) + { + try + { + String relativePath = aProject.getModel().getParent().getRelativePath(); + + File pomFile = new File( aProject.getBasedir(), relativePath ); + + if ( pomFile.isDirectory() ) + { + pomFile = new File( pomFile, "pom.xml" ); + } + pomFile = new File( getNormalizedPath( pomFile.getPath() ) ); + + if ( pomFile.isFile() ) + { + MavenProject mavenProject = mavenProjectBuilder.build( pomFile, localRepository, null ); + + if ( mavenProject.getGroupId().equals( origParent.getGroupId() ) + && mavenProject.getArtifactId().equals( origParent.getArtifactId() ) + && mavenProject.getVersion().equals( origParent.getVersion() ) ) + { + parentProject = mavenProject; + + getLogger().debug( "Parent project " + origParent.getId() + " loaded from a relative path: " + + relativePath ); + } + } + } + catch ( ProjectBuildingException e ) + { + getLogger().info( "Unable to load parent project " + origParent.getId() + " from a relative path: " + + e.getMessage() ); + } + } + + if ( parentProject == null ) + { + try + { + parentProject = mavenProjectBuilder.buildFromRepository( aProject.getParentArtifact(), aProject + .getRemoteArtifactRepositories(), localRepository ); + + getLogger().debug( "Parent project " + origParent.getId() + " loaded from repository" ); + } + catch ( ProjectBuildingException e ) + { + getLogger().warn( "Unable to load parent project " + origParent.getId() + " from repository: " + + e.getMessage() ); + } + } + + if ( parentProject == null ) + { + // fallback to original parent, which may contain uninterpolated value (still need a unit test) + + parentProject = origParent; + + getLogger().debug( "Parent project " + origParent.getId() + " picked from original value" ); + } + } + return parentProject; + } + + /** + * Populate the pre-defined parent menu of the decoration model, + * if used through <menu ref="parent"/>. + * + * @param decorationModel the Doxia Sitetools DecorationModel, not null. + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project a Maven project, not null. + * @param parentProject a Maven parent project, not null. + * @param keepInheritedRefs used for inherited references. + */ + private void populateParentMenu( DecorationModel decorationModel, Locale locale, MavenProject project, + MavenProject parentProject, boolean keepInheritedRefs ) + { + checkNotNull( "decorationModel", decorationModel ); + checkNotNull( "project", project ); + checkNotNull( "parentProject", parentProject ); + + Menu menu = decorationModel.getMenuRef( "parent" ); + + if ( menu == null ) + { + return; + } + + if ( keepInheritedRefs && menu.isInheritAsRef() ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + String parentUrl = getDistMgmntSiteUrl( parentProject ); + + if ( parentUrl != null ) + { + if ( parentUrl.endsWith( "/" ) ) + { + parentUrl += "index.html"; + } + else + { + parentUrl += "/index.html"; + } + + parentUrl = getRelativePath( parentUrl, getDistMgmntSiteUrl( project ) ); + } + else + { + // parent has no url, assume relative path is given by site structure + File parentBasedir = parentProject.getBasedir(); + // First make sure that the parent is available on the file system + if ( parentBasedir != null ) + { + // Try to find the relative path to the parent via the file system + String parentPath = parentBasedir.getAbsolutePath(); + String projectPath = project.getBasedir().getAbsolutePath(); + parentUrl = getRelativePath( parentPath, projectPath ) + "/index.html"; + } + } + + // Only add the parent menu if we were able to find a URL for it + if ( parentUrl == null ) + { + getLogger().warn( "Unable to find a URL to the parent project. The parent menu will NOT be added." ); + } + else + { + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.parentproject" ) ); + } + + MenuItem item = new MenuItem(); + item.setName( parentProject.getName() ); + item.setHref( parentUrl ); + menu.addItem( item ); + } + } + + /** + * Populate the pre-defined modules menu of the decoration model, + * if used through <menu ref="modules"/>. + * + * @param decorationModel the Doxia Sitetools DecorationModel, not null. + * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project a Maven project, not null. + * @param reactorProjects the Maven reactor projects, not null. + * @param localRepository the Maven local repository, not null. + * @param keepInheritedRefs used for inherited references. + * @throws SiteToolException if any + * @throws IOException + */ +/** + * Populate the pre-defined modules menu of the decoration model, + * if used through <menu ref="modules"/>. + * + * @param decorationModel + * the Doxia Sitetools DecorationModel, not null. + * @param locale + * the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. + * @param project + * a Maven project, not null. + * @param reactorProjects + * the Maven reactor projects, not null. + * @param localRepository + * the Maven local repository, not null. + * @param keepInheritedRefs + * used for inherited references. + * @throws SiteToolException + * if any + * @throws IOException + * + */ +private void populateModulesMenu(org.apache.maven.doxia.site.decoration.DecorationModel decorationModel, java.util.Locale locale, org.apache.maven.project.MavenProject project, java.util.List reactorProjects, org.apache.maven.artifact.repository.ArtifactRepository localRepository, boolean keepInheritedRefs) throws org.apache.maven.doxia.tools.SiteToolException, java.io.IOException { + checkNotNull("project", project); + checkNotNull("reactorProjects", reactorProjects); + checkNotNull("localRepository", localRepository); + checkNotNull("decorationModel", decorationModel); + org.apache.maven.doxia.site.decoration.Menu menu = decorationModel.getMenuRef("modules"); + { + if (keepInheritedRefs && /* NPEX_NULL_EXP */ + menu.isInheritAsRef()) { + return; + } + final java.util.Locale llocale = (locale == null) ? java.util.Locale.getDefault() : locale; + // we require child modules and reactors to process module menu + if (project.getModules().size() > 0) { + if (menu.getName() == null) { + menu.setName(i18n.getString("site-tool", llocale, "decorationModel.menu.projectmodules")); + } + for (java.lang.String module : ((java.util.List) (project.getModules()))) { + org.apache.maven.project.MavenProject moduleProject = org.apache.maven.doxia.tools.DefaultSiteTool.getModuleFromReactor(project, reactorProjects, module); + if (moduleProject == null) { + getLogger().warn(("Module " + module) + " not found in reactor: loading locally"); + java.io.File f = new java.io.File(project.getBasedir(), module + "/pom.xml"); + if (f.exists()) { + try { + moduleProject = mavenProjectBuilder.build(f, localRepository, null); + } catch (org.apache.maven.project.ProjectBuildingException e) { + throw new org.apache.maven.doxia.tools.SiteToolException("Unable to read local module-POM", e); + } + } else { + getLogger().warn("No filesystem module-POM available"); + moduleProject = new org.apache.maven.project.MavenProject(); + moduleProject.setName(module); + moduleProject.setDistributionManagement(new org.apache.maven.model.DistributionManagement()); + moduleProject.getDistributionManagement().setSite(new org.apache.maven.model.Site()); + moduleProject.getDistributionManagement().getSite().setUrl(module); + } + } + java.lang.String siteUrl = org.apache.maven.doxia.tools.DefaultSiteTool.getDistMgmntSiteUrl(moduleProject); + java.lang.String itemName = (moduleProject.getName() == null) ? moduleProject.getArtifactId() : moduleProject.getName(); + appendMenuItem(project, menu, itemName, siteUrl, moduleProject.getArtifactId()); + } + } else if (decorationModel.getMenuRef("modules").getInherit() == null) { + // only remove if project has no modules AND menu is not inherited, see MSHARED-174 + decorationModel.removeMenuRef("modules"); + } + } +} + + private static MavenProject getModuleFromReactor( MavenProject project, List reactorProjects, + String module ) + throws IOException + { + File moduleBasedir = new File( project.getBasedir(), module ).getCanonicalFile(); + + for ( MavenProject reactorProject : reactorProjects ) + { + if ( moduleBasedir.equals( reactorProject.getBasedir() ) ) + { + return reactorProject; + } + } + + // module not found in reactor + return null; + } + + /** {@inheritDoc} */ + public void populateReportsMenu( DecorationModel decorationModel, Locale locale, + Map> categories ) + { + checkNotNull( "decorationModel", decorationModel ); + checkNotNull( "categories", categories ); + + Menu menu = decorationModel.getMenuRef( "reports" ); + + if ( menu == null ) + { + return; + } + + final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale; + + if ( menu.getName() == null ) + { + menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectdocumentation" ) ); + } + + boolean found = false; + if ( menu.getItems().isEmpty() ) + { + List categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_INFORMATION ); + if ( !isEmptyList( categoryReports ) ) + { + MenuItem item = createCategoryMenu( + i18n.getString( "site-tool", llocale, + "decorationModel.menu.projectinformation" ), + "/project-info.html", categoryReports, llocale ); + menu.getItems().add( item ); + found = true; + } + + categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_REPORTS ); + if ( !isEmptyList( categoryReports ) ) + { + MenuItem item = + createCategoryMenu( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectreports" ), + "/project-reports.html", categoryReports, llocale ); + menu.getItems().add( item ); + found = true; + } + } + if ( !found ) + { + decorationModel.removeMenuRef( "reports" ); + } + } + + /** {@inheritDoc} */ + public List getSiteLocales( String locales ) + { + if ( locales == null ) + { + return Collections.singletonList( DEFAULT_LOCALE ); + } + + String[] localesArray = StringUtils.split( locales, "," ); + List localesList = new ArrayList( localesArray.length ); + + for ( String localeString : localesArray ) + { + Locale locale = codeToLocale( localeString ); + + if ( locale == null ) + { + continue; + } + + if ( !Arrays.asList( Locale.getAvailableLocales() ).contains( locale ) ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "The locale defined by '" + locale + + "' is not available in this Java Virtual Machine (" + + System.getProperty( "java.version" ) + + " from " + System.getProperty( "java.vendor" ) + ") - IGNORING" ); + } + continue; + } + + // Default bundles are in English + if ( ( !locale.getLanguage().equals( DEFAULT_LOCALE.getLanguage() ) ) + && ( !i18n.getBundle( "site-tool", locale ).getLocale().getLanguage() + .equals( locale.getLanguage() ) ) ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "The locale '" + locale + "' (" + locale.getDisplayName( Locale.ENGLISH ) + + ") is not currently supported by Maven Site - IGNORING." + + "\nContributions are welcome and greatly appreciated!" + + "\nIf you want to contribute a new translation, please visit " + + "http://maven.apache.org/plugins/localization.html for detailed instructions." ); + } + + continue; + } + + localesList.add( locale ); + } + + if ( localesList.isEmpty() ) + { + localesList = Collections.singletonList( DEFAULT_LOCALE ); + } + + return localesList; + } + + /** + * Converts a locale code like "en", "en_US" or "en_US_win" to a java.util.Locale + * object. + *

    If localeCode = default, return the current value of the default locale for this instance + * of the Java Virtual Machine.

    + * + * @param localeCode the locale code string. + * @return a java.util.Locale object instanced or null if errors occurred + * @see java.util.Locale#getDefault() + */ + private Locale codeToLocale( String localeCode ) + { + if ( localeCode == null ) + { + return null; + } + + if ( "default".equalsIgnoreCase( localeCode ) ) + { + return Locale.getDefault(); + } + + String language = ""; + String country = ""; + String variant = ""; + + StringTokenizer tokenizer = new StringTokenizer( localeCode, "_" ); + final int maxTokens = 3; + if ( tokenizer.countTokens() > maxTokens ) + { + if ( getLogger().isWarnEnabled() ) + { + getLogger().warn( "Invalid java.util.Locale format for '" + localeCode + "' entry - IGNORING" ); + } + return null; + } + + if ( tokenizer.hasMoreTokens() ) + { + language = tokenizer.nextToken(); + if ( tokenizer.hasMoreTokens() ) + { + country = tokenizer.nextToken(); + if ( tokenizer.hasMoreTokens() ) + { + variant = tokenizer.nextToken(); + } + } + } + + return new Locale( language, country, variant ); + } + + // ---------------------------------------------------------------------- + // Protected methods + // ---------------------------------------------------------------------- + + /** + * @param path could be null. + * @return the path normalized, i.e. by eliminating "/../" and "/./" in the path. + * @see FilenameUtils#normalize(String) + */ + protected static String getNormalizedPath( String path ) + { + String normalized = FilenameUtils.normalize( path ); + if ( normalized == null ) + { + normalized = path; + } + return ( normalized == null ) ? null : normalized.replace( '\\', '/' ); + } + + // ---------------------------------------------------------------------- + // Private methods + // ---------------------------------------------------------------------- + + /** + * @param project not null + * @param localRepository not null + * @param repositories not null + * @param locale not null + * @return the resolved site descriptor + * @throws IOException if any + * @throws ArtifactResolutionException if any + * @throws ArtifactNotFoundException if any + */ + private File resolveSiteDescriptor( MavenProject project, ArtifactRepository localRepository, + List repositories, Locale locale ) + throws IOException, ArtifactResolutionException, ArtifactNotFoundException + { + File result; + + // TODO: this is a bit crude - proper type, or proper handling as metadata rather than an artifact in 2.1? + Artifact artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), + project.getArtifactId(), + project.getVersion(), "xml", + "site_" + locale.getLanguage() ); + + boolean found = false; + try + { + artifactResolver.resolve( artifact, repositories, localRepository ); + + result = artifact.getFile(); + + // we use zero length files to avoid re-resolution (see below) + if ( result.length() > 0 ) + { + found = true; + } + else + { + getLogger().debug( "No site descriptor found for " + project.getId() + " for locale " + + locale.getLanguage() + ", trying without locale..." ); + } + } + catch ( ArtifactNotFoundException e ) + { + getLogger().debug( "Unable to locate site descriptor for locale " + locale.getLanguage() + ": " + e ); + + // we can afford to write an empty descriptor here as we don't expect it to turn up later in the remote + // repository, because the parent was already released (and snapshots are updated automatically if changed) + result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); + result.getParentFile().mkdirs(); + result.createNewFile(); + } + + if ( !found ) + { + artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), project.getArtifactId(), + project.getVersion(), "xml", "site" ); + try + { + artifactResolver.resolve( artifact, repositories, localRepository ); + } + catch ( ArtifactNotFoundException e ) + { + // see above regarding this zero length file + result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); + result.getParentFile().mkdirs(); + result.createNewFile(); + + throw e; + } + + result = artifact.getFile(); + + // we use zero length files to avoid re-resolution (see below) + if ( result.length() == 0 ) + { + getLogger().debug( "No site descriptor found for " + project.getId() + " without locale." ); + result = null; + } + } + + return result; + } + + /** + * @param depth depth of project + * @param siteDirectory, can be null if project.basedir is null, ie POM from repository + * @param locale not null + * @param project not null + * @param reactorProjects not null + * @param localRepository not null + * @param repositories not null + * @param origProps not null + * @return the decoration model depending the locale and the parent project + * @throws SiteToolException if any + */ + private Map.Entry getDecorationModel( int depth, File siteDirectory, Locale locale, + MavenProject project, + List reactorProjects, + ArtifactRepository localRepository, + List repositories ) + throws SiteToolException + { + // 1. get site descriptor File + File siteDescriptor; + if ( project.getBasedir() == null ) + { + // POM is in the repository: look into the repository for site descriptor + try + { + siteDescriptor = getSiteDescriptorFromRepository( project, localRepository, repositories, locale ); + } + catch ( SiteToolException e ) + { + throw new SiteToolException( "The site descriptor cannot be resolved from the repository: " + + e.getMessage(), e ); + } + } + else + { + // POM is in build directory: look for site descriptor as local file + siteDescriptor = getSiteDescriptor( siteDirectory, locale ); + } + + // 2. read DecorationModel from site descriptor File and do early interpolation (${this.*}) + DecorationModel decoration = null; + Reader siteDescriptorReader = null; + try + { + if ( siteDescriptor != null && siteDescriptor.exists() ) + { + getLogger().debug( "Reading" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + + " site descriptor from " + siteDescriptor ); + + siteDescriptorReader = ReaderFactory.newXmlReader( siteDescriptor ); + + String siteDescriptorContent = readSiteDescriptor( siteDescriptorReader, project.getId() ); + + // interpolate ${this.*} = early interpolation + siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, true ); + + decoration = readDecorationModel( siteDescriptorContent ); + decoration.setLastModified( siteDescriptor.lastModified() ); + } + else + { + getLogger().debug( "No" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + " site descriptor." ); + } + } + catch ( IOException e ) + { + throw new SiteToolException( "The site descriptor for " + project.getId() + " cannot be read from " + + siteDescriptor, e ); + } + finally + { + IOUtil.close( siteDescriptorReader ); + } + + // 3. look for parent project + MavenProject parentProject = getParentProject( project, reactorProjects, localRepository ); + + // 4. merge with parent project DecorationModel + if ( parentProject != null && ( decoration == null || decoration.isMergeParent() ) ) + { + depth++; + getLogger().debug( "Looking for site descriptor of level " + depth + " parent project: " + + parentProject.getId() ); + + File parentSiteDirectory = null; + if ( parentProject.getBasedir() != null ) + { + // extrapolate parent project site directory + String siteRelativePath = getRelativeFilePath( project.getBasedir().getAbsolutePath(), + siteDescriptor.getParentFile().getAbsolutePath() ); + + parentSiteDirectory = new File( parentProject.getBasedir(), siteRelativePath ); + // notice: using same siteRelativePath for parent as current project; may be wrong if site plugin + // has different configuration. But this is a rare case (this only has impact if parent is from reactor) + } + + DecorationModel parentDecoration = + getDecorationModel( depth, parentSiteDirectory, locale, parentProject, reactorProjects, localRepository, + repositories ).getKey(); + + // MSHARED-116 requires an empty decoration model (instead of a null one) + // MSHARED-145 requires us to do this only if there is a parent to merge it with + if ( decoration == null && parentDecoration != null ) + { + // we have no site descriptor: merge the parent into an empty one + decoration = new DecorationModel(); + } + + String name = project.getName(); + if ( decoration != null && StringUtils.isNotEmpty( decoration.getName() ) ) + { + name = decoration.getName(); + } + + // Merge the parent and child DecorationModels + String projectDistMgmnt = getDistMgmntSiteUrl( project ); + String parentDistMgmnt = getDistMgmntSiteUrl( parentProject ); + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Site decoration model inheritance: assembling child with level " + depth + + " parent: distributionManagement.site.url child = " + projectDistMgmnt + " and parent = " + + parentDistMgmnt ); + } + assembler.assembleModelInheritance( name, decoration, parentDecoration, projectDistMgmnt, + parentDistMgmnt == null ? projectDistMgmnt : parentDistMgmnt ); + } + + return new AbstractMap.SimpleEntry( decoration, parentProject ); + } + + /** + * @param siteDescriptorContent not null + * @return the decoration model object + * @throws SiteToolException if any + */ + private DecorationModel readDecorationModel( String siteDescriptorContent ) + throws SiteToolException + { + try + { + return new DecorationXpp3Reader().read( new StringReader( siteDescriptorContent ) ); + } + catch ( XmlPullParserException e ) + { + throw new SiteToolException( "Error parsing site descriptor", e ); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading site descriptor", e ); + } + } + + private String decorationModelToString( DecorationModel decoration ) + throws SiteToolException + { + StringWriter writer = new StringWriter(); + + try + { + new DecorationXpp3Writer().write( writer, decoration ); + return writer.toString(); + } + catch ( IOException e ) + { + throw new SiteToolException( "Error reading site descriptor", e ); + } + finally + { + IOUtil.close( writer ); + } + } + + private static String buildRelativePath( final String toPath, final String fromPath, final char separatorChar ) + { + // use tokenizer to traverse paths and for lazy checking + StringTokenizer toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); + StringTokenizer fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); + + int count = 0; + + // walk along the to path looking for divergence from the from path + while ( toTokeniser.hasMoreTokens() && fromTokeniser.hasMoreTokens() ) + { + if ( separatorChar == '\\' ) + { + if ( !fromTokeniser.nextToken().equalsIgnoreCase( toTokeniser.nextToken() ) ) + { + break; + } + } + else + { + if ( !fromTokeniser.nextToken().equals( toTokeniser.nextToken() ) ) + { + break; + } + } + + count++; + } + + // reinitialize the tokenizers to count positions to retrieve the + // gobbled token + + toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); + fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); + + while ( count-- > 0 ) + { + fromTokeniser.nextToken(); + toTokeniser.nextToken(); + } + + StringBuilder relativePath = new StringBuilder(); + + // add back refs for the rest of from location. + while ( fromTokeniser.hasMoreTokens() ) + { + fromTokeniser.nextToken(); + + relativePath.append( ".." ); + + if ( fromTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + } + + if ( relativePath.length() != 0 && toTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + + // add fwd fills for whatever's left of to. + while ( toTokeniser.hasMoreTokens() ) + { + relativePath.append( toTokeniser.nextToken() ); + + if ( toTokeniser.hasMoreTokens() ) + { + relativePath.append( separatorChar ); + } + } + return relativePath.toString(); + } + + /** + * @param project not null + * @param menu not null + * @param name not null + * @param href could be null + * @param defaultHref not null + */ + private void appendMenuItem( MavenProject project, Menu menu, String name, String href, String defaultHref ) + { + String selectedHref = href; + + if ( selectedHref == null ) + { + selectedHref = defaultHref; + } + + MenuItem item = new MenuItem(); + item.setName( name ); + + String baseUrl = getDistMgmntSiteUrl( project ); + if ( baseUrl != null ) + { + selectedHref = getRelativePath( selectedHref, baseUrl ); + } + + if ( selectedHref.endsWith( "/" ) ) + { + item.setHref( selectedHref + "index.html" ); + } + else + { + item.setHref( selectedHref + "/index.html" ); + } + menu.addItem( item ); + } + + /** + * @param name not null + * @param href not null + * @param categoryReports not null + * @param locale not null + * @return the menu item object + */ + private MenuItem createCategoryMenu( String name, String href, List categoryReports, Locale locale ) + { + MenuItem item = new MenuItem(); + item.setName( name ); + item.setCollapse( true ); + item.setHref( href ); + + // MSHARED-172, allow reports to define their order in some other way? + //Collections.sort( categoryReports, new ReportComparator( locale ) ); + + for ( MavenReport report : categoryReports ) + { + MenuItem subitem = new MenuItem(); + subitem.setName( report.getName( locale ) ); + subitem.setHref( report.getOutputName() + ".html" ); + item.getItems().add( subitem ); + } + + return item; + } + + // ---------------------------------------------------------------------- + // static methods + // ---------------------------------------------------------------------- + + /** + * Convenience method. + * + * @param list could be null + * @return true if the list is null or empty + */ + private static boolean isEmptyList( List list ) + { + return list == null || list.isEmpty(); + } + + /** + * Return distributionManagement.site.url if defined, null otherwise. + * + * @param project not null + * @return could be null + */ + private static String getDistMgmntSiteUrl( MavenProject project ) + { + return getDistMgmntSiteUrl( project.getDistributionManagement() ); + } + + private static String getDistMgmntSiteUrl( DistributionManagement distMgmnt ) + { + if ( distMgmnt != null && distMgmnt.getSite() != null && distMgmnt.getSite().getUrl() != null ) + { + return urlEncode( distMgmnt.getSite().getUrl() ); + } + + return null; + } + + private static String urlEncode( final String url ) + { + if ( url == null ) + { + return null; + } + + try + { + return new File( url ).toURI().toURL().toExternalForm(); + } + catch ( MalformedURLException ex ) + { + return url; // this will then throw somewhere else + } + } + + private void checkNotNull( String name, Object value ) + { + if ( value == null ) + { + throw new IllegalArgumentException( "The parameter '" + name + "' cannot be null." ); + } + } + + /** + * Check the current Maven version to see if it's Maven 3.0 or newer. + */ + private static boolean isMaven3OrMore() + { + return new DefaultArtifactVersion( getMavenVersion() ).getMajorVersion() >= 3; + } + + private static String getMavenVersion() + { + // This relies on the fact that MavenProject is the in core classloader + // and that the core classloader is for the maven-core artifact + // and that should have a pom.properties file + // if this ever changes, we will have to revisit this code. + final Properties properties = new Properties(); + final String corePomProperties = "META-INF/maven/org.apache.maven/maven-core/pom.properties"; + final InputStream in = MavenProject.class.getClassLoader().getResourceAsStream( corePomProperties ); + try + { + properties.load( in ); + } + catch ( IOException ioe ) + { + return ""; + } + finally + { + IOUtil.close( in ); + } + + return properties.getProperty( "version" ).trim(); + } +} diff --git a/Java/maven-doxia-sitetools-DefaultSiteTool_771/metadata.json b/Java/maven-doxia-sitetools-DefaultSiteTool_771/metadata.json new file mode 100644 index 000000000..a5d1ac35f --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteTool_771/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-DefaultSiteTool_771", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/DefaultSiteTool.java", + "line": 788, + "npe_method": "populateModulesMenu", + "deref_field": "menu", + "npe_class": "DefaultSiteTool", + "repo": "maven-doxia-sitetools", + "bug_id": "DefaultSiteTool_771" + } +} diff --git a/Java/maven-doxia-sitetools-DefaultSiteTool_771/npe.json b/Java/maven-doxia-sitetools-DefaultSiteTool_771/npe.json new file mode 100644 index 000000000..4a81867d6 --- /dev/null +++ b/Java/maven-doxia-sitetools-DefaultSiteTool_771/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-integration-tools/src/main/java/org/apache/maven/doxia/tools/DefaultSiteTool.java", + "line": 788, + "npe_method": "populateModulesMenu", + "deref_field": "menu", + "npe_class": "DefaultSiteTool" +} \ No newline at end of file diff --git a/Java/maven-doxia-sitetools-FoPdfRenderer_90/Dockerfile b/Java/maven-doxia-sitetools-FoPdfRenderer_90/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-FoPdfRenderer_90/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-FoPdfRenderer_90/buggy.java b/Java/maven-doxia-sitetools-FoPdfRenderer_90/buggy.java new file mode 100644 index 000000000..3b4d030c8 --- /dev/null +++ b/Java/maven-doxia-sitetools-FoPdfRenderer_90/buggy.java @@ -0,0 +1,325 @@ +package org.apache.maven.doxia.docrenderer.pdf.fo; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.IOException; +import java.io.Writer; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import javax.xml.transform.TransformerException; + +import org.apache.maven.doxia.docrenderer.DocumentRendererContext; +import org.apache.maven.doxia.docrenderer.DocumentRendererException; +import org.apache.maven.doxia.docrenderer.pdf.AbstractPdfRenderer; +import org.apache.maven.doxia.docrenderer.pdf.PdfRenderer; +import org.apache.maven.doxia.document.DocumentModel; +import org.apache.maven.doxia.document.DocumentTOC; +import org.apache.maven.doxia.document.DocumentTOCItem; +import org.apache.maven.doxia.module.fo.FoAggregateSink; +import org.apache.maven.doxia.module.fo.FoSink; +import org.apache.maven.doxia.module.fo.FoSinkFactory; +import org.apache.maven.doxia.module.fo.FoUtils; +import org.apache.maven.doxia.parser.module.ParserModule; + +import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.util.IOUtil; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.util.WriterFactory; + +import org.xml.sax.SAXParseException; + +/** + * PDF renderer that uses Doxia's FO module. + * + * @author ltheussl + * @since 1.1 + */ +@Component( role = PdfRenderer.class, hint = "fo" ) +public class FoPdfRenderer + extends AbstractPdfRenderer +{ + /** + * {@inheritDoc} + * @see org.apache.maven.doxia.module.fo.FoUtils#convertFO2PDF(File, File, String) + */ + public void generatePdf( File inputFile, File pdfFile ) + throws DocumentRendererException + { + // Should take care of the document model for the metadata... + generatePdf( inputFile, pdfFile, null ); + } + + /** {@inheritDoc} */ + @Override + public void render( Map filesToProcess, File outputDirectory, DocumentModel documentModel ) + throws DocumentRendererException, IOException + { + render( filesToProcess, outputDirectory, documentModel, null ); + } + + /** {@inheritDoc} */ + @Override +/** + * {@inheritDoc } + */ +public void render(java.util.Map filesToProcess, java.io.File outputDirectory, org.apache.maven.doxia.document.DocumentModel documentModel, org.apache.maven.doxia.docrenderer.DocumentRendererContext context) throws org.apache.maven.doxia.docrenderer.DocumentRendererException, java.io.IOException { + // copy resources, images, etc. + copyResources(outputDirectory); + { + java.lang.String outputName = getOutputName(/* NPEX_NULL_EXP */ + documentModel); + java.io.File outputFOFile = new java.io.File(outputDirectory, outputName + ".fo"); + if (!outputFOFile.getParentFile().exists()) { + outputFOFile.getParentFile().mkdirs(); + } + java.io.File pdfOutputFile = new java.io.File(outputDirectory, outputName + ".pdf"); + if (!pdfOutputFile.getParentFile().exists()) { + pdfOutputFile.getParentFile().mkdirs(); + } + java.io.Writer writer = null; + try { + writer = org.codehaus.plexus.util.WriterFactory.newXmlWriter(outputFOFile); + org.apache.maven.doxia.module.fo.FoAggregateSink sink = new org.apache.maven.doxia.module.fo.FoAggregateSink(writer); + java.io.File fOConfigFile = new java.io.File(outputDirectory, "pdf-config.xml"); + if (fOConfigFile.exists()) { + sink.load(fOConfigFile); + getLogger().debug("Loaded pdf config file: " + fOConfigFile.getAbsolutePath()); + } + java.lang.String generateTOC = ((context != null) && (context.get("generateTOC") != null)) ? context.get("generateTOC").toString().trim() : "start"; + int tocPosition = 0; + if ("start".equalsIgnoreCase(generateTOC)) { + tocPosition = org.apache.maven.doxia.module.fo.FoAggregateSink.TOC_START; + } else if ("end".equalsIgnoreCase(generateTOC)) { + tocPosition = org.apache.maven.doxia.module.fo.FoAggregateSink.TOC_END; + } else { + tocPosition = org.apache.maven.doxia.module.fo.FoAggregateSink.TOC_NONE; + } + sink.setDocumentModel(documentModel, tocPosition); + sink.beginDocument(); + sink.coverPage(); + if (tocPosition == org.apache.maven.doxia.module.fo.FoAggregateSink.TOC_START) { + sink.toc(); + } + if ((documentModel.getToc() == null) || (documentModel.getToc().getItems() == null)) { + getLogger().info("No TOC is defined in the document descriptor. Merging all documents."); + mergeAllSources(filesToProcess, sink, context); + } else { + getLogger().debug("Using TOC defined in the document descriptor."); + mergeSourcesFromTOC(documentModel.getToc(), sink, context); + } + if (tocPosition == org.apache.maven.doxia.module.fo.FoAggregateSink.TOC_END) { + sink.toc(); + } + sink.endDocument(); + } finally { + org.codehaus.plexus.util.IOUtil.close(writer); + } + generatePdf(outputFOFile, pdfOutputFile, documentModel); + } +} + + /** {@inheritDoc} */ + @Override + public void renderIndividual( Map filesToProcess, File outputDirectory ) + throws DocumentRendererException, IOException + { + renderIndividual( filesToProcess, outputDirectory, null ); + } + + /** {@inheritDoc} */ + @Override + public void renderIndividual( Map filesToProcess, File outputDirectory, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + for ( Map.Entry entry : filesToProcess.entrySet() ) + { + String key = entry.getKey(); + ParserModule module = entry.getValue(); + + File fullDoc = new File( getBaseDir(), module.getSourceDirectory() + File.separator + key ); + + String output = key; + for ( String extension : module.getExtensions() ) + { + String lowerCaseExtension = extension.toLowerCase( Locale.ENGLISH ); + if ( output.toLowerCase( Locale.ENGLISH ).indexOf( "." + lowerCaseExtension ) != -1 ) + { + output = + output.substring( 0, output.toLowerCase( Locale.ENGLISH ).indexOf( "." + lowerCaseExtension ) ); + } + } + + File outputFOFile = new File( outputDirectory, output + ".fo" ); + if ( !outputFOFile.getParentFile().exists() ) + { + outputFOFile.getParentFile().mkdirs(); + } + + File pdfOutputFile = new File( outputDirectory, output + ".pdf" ); + if ( !pdfOutputFile.getParentFile().exists() ) + { + pdfOutputFile.getParentFile().mkdirs(); + } + + FoSink sink = + (FoSink) new FoSinkFactory().createSink( outputFOFile.getParentFile(), outputFOFile.getName() ); + sink.beginDocument(); + parse( fullDoc.getAbsolutePath(), module.getParserId(), sink, context ); + sink.endDocument(); + + generatePdf( outputFOFile, pdfOutputFile, null ); + } + } + + private void mergeAllSources( Map filesToProcess, FoAggregateSink sink, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + for ( Map.Entry entry : filesToProcess.entrySet() ) + { + String key = entry.getKey(); + ParserModule module = entry.getValue(); + sink.setDocumentName( key ); + File fullDoc = new File( getBaseDir(), module.getSourceDirectory() + File.separator + key ); + + parse( fullDoc.getAbsolutePath(), module.getParserId(), sink, context ); + } + } + + private void mergeSourcesFromTOC( DocumentTOC toc, FoAggregateSink sink, DocumentRendererContext context ) + throws IOException, DocumentRendererException + { + parseTocItems( toc.getItems(), sink, context ); + } + + private void parseTocItems( List items, FoAggregateSink sink, DocumentRendererContext context ) + throws IOException, DocumentRendererException + { + for ( DocumentTOCItem tocItem : items ) + { + if ( tocItem.getRef() == null ) + { + if ( getLogger().isInfoEnabled() ) + { + getLogger().info( "No ref defined for tocItem " + tocItem.getName() ); + } + + continue; + } + + String href = StringUtils.replace( tocItem.getRef(), "\\", "/" ); + if ( href.lastIndexOf( '.' ) != -1 ) + { + href = href.substring( 0, href.lastIndexOf( '.' ) ); + } + + renderModules( href, sink, tocItem, context ); + + if ( tocItem.getItems() != null ) + { + parseTocItems( tocItem.getItems(), sink, context ); + } + } + } + + private void renderModules( String href, FoAggregateSink sink, DocumentTOCItem tocItem, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + Collection modules = parserModuleManager.getParserModules(); + for ( ParserModule module : modules ) + { + File moduleBasedir = new File( getBaseDir(), module.getSourceDirectory() ); + + if ( moduleBasedir.exists() ) + { + for ( String extension : module.getExtensions() ) + { + String doc = href + "." + extension; + File source = new File( moduleBasedir, doc ); + + // Velocity file? + if ( !source.exists() ) + { + if ( href.indexOf( "." + extension ) != -1 ) + { + doc = href + ".vm"; + } + else + { + doc = href + "." + extension + ".vm"; + } + source = new File( moduleBasedir, doc ); + } + + if ( source.exists() ) + { + sink.setDocumentName( doc ); + sink.setDocumentTitle( tocItem.getName() ); + + parse( source.getPath(), module.getParserId(), sink, context ); + } + } + } + } + } + + /** + * @param inputFile + * @param pdfFile + * @param documentModel could be null + * @throws DocumentRendererException if any + * @since 1.1.1 + */ + private void generatePdf( File inputFile, File pdfFile, DocumentModel documentModel ) + throws DocumentRendererException + { + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Generating: " + pdfFile ); + } + + try + { + FoUtils.convertFO2PDF( inputFile, pdfFile, null, documentModel ); + } + catch ( TransformerException e ) + { + if ( ( e.getCause() != null ) && ( e.getCause() instanceof SAXParseException ) ) + { + SAXParseException sax = (SAXParseException) e.getCause(); + + StringBuilder sb = new StringBuilder(); + sb.append( "Error creating PDF from " ).append( inputFile.getAbsolutePath() ).append( ":" ) + .append( sax.getLineNumber() ).append( ":" ).append( sax.getColumnNumber() ).append( "\n" ); + sb.append( e.getMessage() ); + + throw new DocumentRendererException( sb.toString() ); + } + + throw new DocumentRendererException( "Error creating PDF from " + inputFile + ": " + e.getMessage() ); + } + } +} diff --git a/Java/maven-doxia-sitetools-FoPdfRenderer_90/metadata.json b/Java/maven-doxia-sitetools-FoPdfRenderer_90/metadata.json new file mode 100644 index 000000000..4956e4b3b --- /dev/null +++ b/Java/maven-doxia-sitetools-FoPdfRenderer_90/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-FoPdfRenderer_90", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/pdf/fo/FoPdfRenderer.java", + "line": 91, + "npe_method": "render", + "deref_field": "documentModel", + "npe_class": "FoPdfRenderer", + "repo": "maven-doxia-sitetools", + "bug_id": "FoPdfRenderer_90" + } +} diff --git a/Java/maven-doxia-sitetools-FoPdfRenderer_90/npe.json b/Java/maven-doxia-sitetools-FoPdfRenderer_90/npe.json new file mode 100644 index 000000000..45abac0f4 --- /dev/null +++ b/Java/maven-doxia-sitetools-FoPdfRenderer_90/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/pdf/fo/FoPdfRenderer.java", + "line": 91, + "npe_method": "render", + "deref_field": "documentModel", + "npe_class": "FoPdfRenderer" +} \ No newline at end of file diff --git a/Java/maven-doxia-sitetools-ITextPdfRenderer_152/Dockerfile b/Java/maven-doxia-sitetools-ITextPdfRenderer_152/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-ITextPdfRenderer_152/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-ITextPdfRenderer_152/buggy.java b/Java/maven-doxia-sitetools-ITextPdfRenderer_152/buggy.java new file mode 100644 index 000000000..af95bb63a --- /dev/null +++ b/Java/maven-doxia-sitetools-ITextPdfRenderer_152/buggy.java @@ -0,0 +1,669 @@ +package org.apache.maven.doxia.docrenderer.pdf.itext; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.Writer; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.text.SimpleDateFormat; +import java.util.Collection; +import java.util.Date; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.apache.maven.doxia.docrenderer.DocumentRendererContext; +import org.apache.maven.doxia.docrenderer.DocumentRendererException; +import org.apache.maven.doxia.docrenderer.pdf.AbstractPdfRenderer; +import org.apache.maven.doxia.docrenderer.pdf.PdfRenderer; +import org.apache.maven.doxia.document.DocumentCover; +import org.apache.maven.doxia.document.DocumentMeta; +import org.apache.maven.doxia.document.DocumentModel; +import org.apache.maven.doxia.document.DocumentTOCItem; +import org.apache.maven.doxia.module.itext.ITextSink; +import org.apache.maven.doxia.module.itext.ITextSinkFactory; +import org.apache.maven.doxia.module.itext.ITextUtil; +import org.apache.maven.doxia.parser.module.ParserModule; +import org.apache.xml.utils.DefaultErrorHandler; +import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.util.IOUtil; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.util.WriterFactory; +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.xml.sax.SAXException; + +import com.lowagie.text.ElementTags; + +/** + * Abstract document render with the iText framework + * + * @author Vincent Siveton + * @author ltheussl + * @since 1.1 + */ +@Component( role = PdfRenderer.class, hint = "itext" ) +public class ITextPdfRenderer + extends AbstractPdfRenderer +{ + /** The xslt style sheet used to transform a Document to an iText file. */ + private static final String XSLT_RESOURCE = "TOC.xslt"; + + /** The TransformerFactory. */ + private static final TransformerFactory TRANSFORMER_FACTORY = TransformerFactory.newInstance(); + + /** The DocumentBuilderFactory. */ + private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance(); + + /** The DocumentBuilder. */ + private static final DocumentBuilder DOCUMENT_BUILDER; + + static + { + TRANSFORMER_FACTORY.setErrorListener( new DefaultErrorHandler() ); + + try + { + DOCUMENT_BUILDER = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder(); + } + catch ( ParserConfigurationException e ) + { + throw new RuntimeException( "Error building document :" + e.getMessage() ); + } + } + + /** {@inheritDoc} */ + public void generatePdf( File inputFile, File pdfFile ) + throws DocumentRendererException + { + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Generating : " + pdfFile ); + } + + try + { + ITextUtil.writePdf( new FileInputStream( inputFile ), new FileOutputStream( pdfFile ) ); + } + catch ( IOException e ) + { + throw new DocumentRendererException( "Cannot create PDF from " + inputFile + ": " + e.getMessage(), e ); + } + catch ( RuntimeException e ) + { + throw new DocumentRendererException( "Error creating PDF from " + inputFile + ": " + e.getMessage(), e ); + } + } + + /** {@inheritDoc} */ + @Override + public void render( Map filesToProcess, File outputDirectory, DocumentModel documentModel ) + throws DocumentRendererException, IOException + { + render( filesToProcess, outputDirectory, documentModel, null ); + } + + /** {@inheritDoc} */ + @Override +/** + * {@inheritDoc } + */ +public void render(java.util.Map filesToProcess, java.io.File outputDirectory, org.apache.maven.doxia.document.DocumentModel documentModel, org.apache.maven.doxia.docrenderer.DocumentRendererContext context) throws org.apache.maven.doxia.docrenderer.DocumentRendererException, java.io.IOException { + // copy resources, images, etc. + copyResources(outputDirectory); + { + java.lang.String outputName = getOutputName(/* NPEX_NULL_EXP */ + documentModel); + java.io.File outputITextFile = new java.io.File(outputDirectory, outputName + ".xml"); + if (!outputITextFile.getParentFile().exists()) { + outputITextFile.getParentFile().mkdirs(); + } + java.io.File pdfOutputFile = new java.io.File(outputDirectory, outputName + ".pdf"); + if (!pdfOutputFile.getParentFile().exists()) { + pdfOutputFile.getParentFile().mkdirs(); + } + java.util.List iTextFiles; + if ((documentModel.getToc() == null) || (documentModel.getToc().getItems() == null)) { + getLogger().info("No TOC is defined in the document descriptor. Merging all documents."); + iTextFiles = parseAllFiles(filesToProcess, outputDirectory, context); + } else { + getLogger().debug("Using TOC defined in the document descriptor."); + iTextFiles = parseTOCFiles(outputDirectory, documentModel, context); + } + java.lang.String generateTOC = ((context != null) && (context.get("generateTOC") != null)) ? context.get("generateTOC").toString() : "start"; + java.io.File iTextFile = new java.io.File(outputDirectory, outputName + ".xml"); + java.io.File iTextOutput = new java.io.File(outputDirectory, (outputName + ".") + getOutputExtension()); + org.w3c.dom.Document document = generateDocument(iTextFiles); + transform(documentModel, document, iTextFile, generateTOC); + generatePdf(iTextFile, iTextOutput); + } +} + + /** {@inheritDoc} */ + @Override + public void renderIndividual( Map filesToProcess, File outputDirectory ) + throws DocumentRendererException, IOException + { + renderIndividual( filesToProcess, outputDirectory, null ); + } + + /** {@inheritDoc} */ + @Override + public void renderIndividual( Map filesToProcess, File outputDirectory, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + for ( Map.Entry entry : filesToProcess.entrySet() ) + { + String key = entry.getKey(); + ParserModule module = entry.getValue(); + File fullDoc = new File( getBaseDir(), module.getSourceDirectory() + File.separator + key ); + + String output = key; + for ( String extension : module.getExtensions() ) + { + String lowerCaseExtension = extension.toLowerCase( Locale.ENGLISH ); + if ( output.toLowerCase( Locale.ENGLISH ).indexOf( "." + lowerCaseExtension ) != -1 ) + { + output = + output.substring( 0, output.toLowerCase( Locale.ENGLISH ).indexOf( "." + lowerCaseExtension ) ); + } + } + + File outputITextFile = new File( outputDirectory, output + ".xml" ); + if ( !outputITextFile.getParentFile().exists() ) + { + outputITextFile.getParentFile().mkdirs(); + } + + File pdfOutputFile = new File( outputDirectory, output + ".pdf" ); + if ( !pdfOutputFile.getParentFile().exists() ) + { + pdfOutputFile.getParentFile().mkdirs(); + } + + parse( fullDoc, module, outputITextFile, context ); + + generatePdf( outputITextFile, pdfOutputFile ); + } + } + + //-------------------------------------------- + // + //-------------------------------------------- + + + /** + * Parse a source document and emit results into a sink. + * + * @param fullDocPath file to the source document. + * @param module the site module associated with the source document (determines the parser to use). + * @param iTextFile the resulting iText xml file. + * @throws DocumentRendererException in case of a parsing problem. + * @throws IOException if the source and/or target document cannot be opened. + */ + private void parse( File fullDoc, ParserModule module, File iTextFile, DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + if ( getLogger().isDebugEnabled() ) + { + getLogger().debug( "Parsing file " + fullDoc.getAbsolutePath() ); + } + + System.setProperty( "itext.basedir", iTextFile.getParentFile().getAbsolutePath() ); + + Writer writer = null; + ITextSink sink = null; + try + { + writer = WriterFactory.newXmlWriter( iTextFile ); + sink = (ITextSink) new ITextSinkFactory().createSink( writer ); + + sink.setClassLoader( new URLClassLoader( new URL[] { iTextFile.getParentFile().toURI().toURL() } ) ); + + parse( fullDoc.getAbsolutePath(), module.getParserId(), sink, context ); + } + finally + { + if ( sink != null ) + { + sink.flush(); + sink.close(); + } + IOUtil.close( writer ); + System.getProperties().remove( "itext.basedir" ); + } + } + + /** + * Merge all iTextFiles to a single one. + * + * @param iTextFiles list of iText xml files. + * @return Document. + * @throws DocumentRendererException if any. + * @throws IOException if any. + */ + private Document generateDocument( List iTextFiles ) + throws DocumentRendererException, IOException + { + Document document = DOCUMENT_BUILDER.newDocument(); + document.appendChild( document.createElement( ElementTags.ITEXT ) ); // Used only to set a root + + for ( File iTextFile : iTextFiles ) + { + Document iTextDocument; + + try + { + iTextDocument = DOCUMENT_BUILDER.parse( iTextFile ); + } + catch ( SAXException e ) + { + throw new DocumentRendererException( "SAX Error : " + e.getMessage() ); + } + + // Only one chapter per doc + Node chapter = iTextDocument.getElementsByTagName( ElementTags.CHAPTER ).item( 0 ); + + try + { + document.getDocumentElement().appendChild( document.importNode( chapter, true ) ); + } + catch ( DOMException e ) + { + throw new DocumentRendererException( "Error appending chapter for " + + iTextFile + " : " + e.getMessage() ); + } + } + + return document; + } + + /** + * Initialize the transformer object. + * + * @return an instance of a transformer object. + * @throws DocumentRendererException if any. + */ + private Transformer initTransformer() + throws DocumentRendererException + { + try + { + Transformer transformer = TRANSFORMER_FACTORY.newTransformer( new StreamSource( ITextPdfRenderer.class + .getResourceAsStream( XSLT_RESOURCE ) ) ); + + transformer.setErrorListener( TRANSFORMER_FACTORY.getErrorListener() ); + + transformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "false" ); + + transformer.setOutputProperty( OutputKeys.INDENT, "yes" ); + + transformer.setOutputProperty( OutputKeys.METHOD, "xml" ); + + transformer.setOutputProperty( OutputKeys.ENCODING, "UTF-8" ); + + // No doctype since itext doctype is not up to date! + + return transformer; + } + catch ( TransformerConfigurationException e ) + { + throw new DocumentRendererException( "Error configuring Transformer for " + XSLT_RESOURCE + ": " + + e.getMessage() ); + } + catch ( IllegalArgumentException e ) + { + throw new DocumentRendererException( "Error configuring Transformer for " + XSLT_RESOURCE + ": " + + e.getMessage() ); + } + } + + /** + * Add transformer parameters from a DocumentModel. + * + * @param transformer the Transformer to set the parameters. + * @param documentModel the DocumentModel to take the parameters from, could be null. + * @param iTextFile the iTextFile not null for the relative paths. + * @param generateTOC not null, possible values are: 'none', 'start' and 'end'. + */ + private void addTransformerParameters( Transformer transformer, DocumentModel documentModel, File iTextFile, + String generateTOC ) + { + if ( documentModel == null ) + { + return; + } + + // TOC + addTransformerParameter( transformer, "toc.position", generateTOC ); + + // Meta parameters + boolean hasNullMeta = false; + if ( documentModel.getMeta() == null ) + { + hasNullMeta = true; + documentModel.setMeta( new DocumentMeta() ); + } + addTransformerParameter( transformer, "meta.author", documentModel.getMeta().getAllAuthorNames(), + System.getProperty( "user.name", "null" ) ); + addTransformerParameter( transformer, "meta.creator", documentModel.getMeta().getCreator(), + System.getProperty( "user.name", "null" ) ); + // see com.lowagie.text.Document#addCreationDate() + SimpleDateFormat sdf = new SimpleDateFormat( "EEE MMM dd HH:mm:ss zzz yyyy" ); + addTransformerParameter( transformer, "meta.creationdate", documentModel.getMeta().getCreationdate(), + sdf.format( new Date() ) ); + addTransformerParameter( transformer, "meta.keywords", documentModel.getMeta().getAllKeyWords() ); + addTransformerParameter( transformer, "meta.pagesize", documentModel.getMeta().getPageSize(), + ITextUtil.getPageSize( ITextUtil.getDefaultPageSize() ) ); + addTransformerParameter( transformer, "meta.producer", documentModel.getMeta().getGenerator(), + "Apache Doxia iText" ); + addTransformerParameter( transformer, "meta.subject", documentModel.getMeta().getSubject(), + ( documentModel.getMeta().getTitle() != null ? documentModel.getMeta().getTitle() + : "" ) ); + addTransformerParameter( transformer, "meta.title", documentModel.getMeta().getTitle() ); + if ( hasNullMeta ) + { + documentModel.setMeta( null ); + } + + // cover parameter + boolean hasNullCover = false; + if ( documentModel.getCover() == null ) + { + hasNullCover = true; + documentModel.setCover( new DocumentCover() ); + } + addTransformerParameter( transformer, "cover.author", documentModel.getCover().getAllAuthorNames(), + System.getProperty( "user.name", "null" ) ); + String companyLogo = getLogoURL( documentModel.getCover().getCompanyLogo(), iTextFile.getParentFile() ); + addTransformerParameter( transformer, "cover.companyLogo", companyLogo ); + addTransformerParameter( transformer, "cover.companyName", documentModel.getCover().getCompanyName() ); + if ( documentModel.getCover().getCoverdate() == null ) + { + documentModel.getCover().setCoverDate( new Date() ); + addTransformerParameter( transformer, "cover.date", documentModel.getCover().getCoverdate() ); + documentModel.getCover().setCoverDate( null ); + } + else + { + addTransformerParameter( transformer, "cover.date", documentModel.getCover().getCoverdate() ); + } + addTransformerParameter( transformer, "cover.subtitle", documentModel.getCover().getCoverSubTitle() ); + addTransformerParameter( transformer, "cover.title", documentModel.getCover().getCoverTitle() ); + addTransformerParameter( transformer, "cover.type", documentModel.getCover().getCoverType() ); + addTransformerParameter( transformer, "cover.version", documentModel.getCover().getCoverVersion() ); + String projectLogo = getLogoURL( documentModel.getCover().getProjectLogo(), iTextFile.getParentFile() ); + addTransformerParameter( transformer, "cover.projectLogo", projectLogo ); + addTransformerParameter( transformer, "cover.projectName", documentModel.getCover().getProjectName() ); + if ( hasNullCover ) + { + documentModel.setCover( null ); + } + } + + /** + * @param transformer not null + * @param name not null + * @param value could be empty + * @param defaultValue could be empty + * @since 1.1.1 + */ + private void addTransformerParameter( Transformer transformer, String name, String value, String defaultValue ) + { + if ( StringUtils.isEmpty( value ) ) + { + addTransformerParameter( transformer, name, defaultValue ); + } + else + { + addTransformerParameter( transformer, name, value ); + } + } + + /** + * @param transformer not null + * @param name not null + * @param value could be empty + * @since 1.1.1 + */ + private void addTransformerParameter( Transformer transformer, String name, String value ) + { + if ( StringUtils.isEmpty( value ) ) + { + return; + } + + transformer.setParameter( name, value ); + } + + /** + * Transform a document to an iTextFile. + * + * @param documentModel the DocumentModel to take the parameters from, could be null. + * @param document the Document to transform. + * @param iTextFile the resulting iText xml file. + * @param generateTOC not null, possible values are: 'none', 'start' and 'end'. + * @throws DocumentRendererException in case of a transformation error. + */ + private void transform( DocumentModel documentModel, Document document, File iTextFile, String generateTOC ) + throws DocumentRendererException + { + Transformer transformer = initTransformer(); + + addTransformerParameters( transformer, documentModel, iTextFile, generateTOC ); + + // need a writer for StreamResult to prevent FileNotFoundException when iTextFile contains spaces + Writer writer = null; + try + { + writer = WriterFactory.newXmlWriter( iTextFile ); + transformer.transform( new DOMSource( document ), new StreamResult( writer ) ); + } + catch ( TransformerException e ) + { + throw new DocumentRendererException( + "Error transforming Document " + document + ": " + e.getMessage(), + e ); + } + catch ( IOException e ) + { + throw new DocumentRendererException( + "Error transforming Document " + document + ": " + e.getMessage(), + e ); + } + finally + { + IOUtil.close( writer ); + } + } + + /** + * @param filesToProcess not null + * @param outputDirectory not null + * @return a list of all parsed files. + * @throws DocumentRendererException if any + * @throws IOException if any + * @since 1.1.1 + */ + private List parseAllFiles( Map filesToProcess, File outputDirectory, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + List iTextFiles = new LinkedList(); + for ( Map.Entry entry : filesToProcess.entrySet() ) + { + String key = entry.getKey(); + ParserModule module = entry.getValue(); + File fullDoc = new File( getBaseDir(), module.getSourceDirectory() + File.separator + key ); + + String outputITextName = key.substring( 0, key.lastIndexOf( '.' ) + 1 ) + "xml"; + File outputITextFileTmp = new File( outputDirectory, outputITextName ); + outputITextFileTmp.deleteOnExit(); + if ( !outputITextFileTmp.getParentFile().exists() ) + { + outputITextFileTmp.getParentFile().mkdirs(); + } + + iTextFiles.add( outputITextFileTmp ); + parse( fullDoc, module, outputITextFileTmp, context ); + } + + return iTextFiles; + } + + /** + * @param filesToProcess not null + * @param outputDirectory not null + * @return a list of all parsed files. + * @throws DocumentRendererException if any + * @throws IOException if any + * @since 1.1.1 + */ + private List parseTOCFiles( File outputDirectory, DocumentModel documentModel, + DocumentRendererContext context ) + throws DocumentRendererException, IOException + { + List iTextFiles = new LinkedList(); + for ( Iterator it = documentModel.getToc().getItems().iterator(); it.hasNext(); ) + { + DocumentTOCItem tocItem = it.next(); + + if ( tocItem.getRef() == null ) + { + getLogger().debug( + "No ref defined for the tocItem '" + tocItem.getName() + + "' in the document descriptor. IGNORING" ); + continue; + } + + String href = StringUtils.replace( tocItem.getRef(), "\\", "/" ); + if ( href.lastIndexOf( '.' ) != -1 ) + { + href = href.substring( 0, href.lastIndexOf( '.' ) ); + } + + Collection modules = parserModuleManager.getParserModules(); + for ( ParserModule module : modules ) + { + File moduleBasedir = new File( getBaseDir(), module.getSourceDirectory() ); + + if ( moduleBasedir.exists() ) + { + for ( String extension : module.getExtensions() ) + { + String doc = href + "." + extension; + File source = new File( moduleBasedir, doc ); + + // Velocity file? + if ( !source.exists() ) + { + if ( href.indexOf( "." + extension ) != -1 ) + { + doc = href + ".vm"; + } + else + { + doc = href + "." + extension + ".vm"; + } + source = new File( moduleBasedir, doc ); + } + + if ( source.exists() ) + { + String outputITextName = doc.substring( 0, doc.lastIndexOf( '.' ) + 1 ) + "xml"; + File outputITextFileTmp = new File( outputDirectory, outputITextName ); + outputITextFileTmp.deleteOnExit(); + if ( !outputITextFileTmp.getParentFile().exists() ) + { + outputITextFileTmp.getParentFile().mkdirs(); + } + + iTextFiles.add( outputITextFileTmp ); + parse( source, module, outputITextFileTmp, context ); + } + } + } + } + } + + return iTextFiles; + } + + /** + * @param logo + * @param parentFile + * @return the logo url or null if unable to create it. + * @since 1.1.1 + */ + private String getLogoURL( String logo, File parentFile ) + { + if ( logo == null ) + { + return null; + } + + try + { + return new URL( logo ).toString(); + } + catch ( MalformedURLException e ) + { + try + { + File f = new File( parentFile, logo ); + if ( !f.exists() ) + { + getLogger().warn( "The logo " + f.getAbsolutePath() + " doesnt exist. IGNORING" ); + } + else + { + return f.toURI().toURL().toString(); + } + } + catch ( MalformedURLException e1 ) + { + getLogger().debug( "Failed to convert to URL: " + logo, e1 ); + } + } + + return null; + } +} diff --git a/Java/maven-doxia-sitetools-ITextPdfRenderer_152/metadata.json b/Java/maven-doxia-sitetools-ITextPdfRenderer_152/metadata.json new file mode 100644 index 000000000..a4d867489 --- /dev/null +++ b/Java/maven-doxia-sitetools-ITextPdfRenderer_152/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-ITextPdfRenderer_152", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/pdf/itext/ITextPdfRenderer.java", + "line": 153, + "npe_method": "render", + "deref_field": "documentModel", + "npe_class": "ITextPdfRenderer", + "repo": "maven-doxia-sitetools", + "bug_id": "ITextPdfRenderer_152" + } +} diff --git a/Java/maven-doxia-sitetools-ITextPdfRenderer_152/npe.json b/Java/maven-doxia-sitetools-ITextPdfRenderer_152/npe.json new file mode 100644 index 000000000..438f06b81 --- /dev/null +++ b/Java/maven-doxia-sitetools-ITextPdfRenderer_152/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-doc-renderer/src/main/java/org/apache/maven/doxia/docrenderer/pdf/itext/ITextPdfRenderer.java", + "line": 153, + "npe_method": "render", + "deref_field": "documentModel", + "npe_class": "ITextPdfRenderer" +} \ No newline at end of file diff --git a/Java/maven-doxia-sitetools-PathDescriptor_118/Dockerfile b/Java/maven-doxia-sitetools-PathDescriptor_118/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-PathDescriptor_118/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-PathDescriptor_118/buggy.java b/Java/maven-doxia-sitetools-PathDescriptor_118/buggy.java new file mode 100644 index 000000000..81e127a37 --- /dev/null +++ b/Java/maven-doxia-sitetools-PathDescriptor_118/buggy.java @@ -0,0 +1,248 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; + +import org.codehaus.plexus.util.StringUtils; + +/** + * This class holds an instance of a maven path. This consists of a relative path (e.g. images/maven-logo.png) and a + * base reference which can also be a relative path (e.g. '.' or '../doxia') or an URL that is used for an absolute + * anchor. + * + * @author Henning P. Schmiedehausen + * @deprecated use {@link URIPathDescriptor} instead. + */ + +public class PathDescriptor +{ + private final URL baseUrl; + + private final URL pathUrl; + + private final String relativePath; + + /** + * Construct a PathDescriptor from a path. + * + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final String path ) + throws MalformedURLException + { + this( (URL) null, path ); + } + + /** + * Construct a PathDescriptor from a path and a base. + * + * @param base a base reference. + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final String base, final String path ) + throws MalformedURLException + { + this( PathDescriptor.buildBaseUrl( base ), path ); + } + + /** + * Construct a PathDescriptor from a path and a base. + * + * @param baseUrl a base reference. + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final URL baseUrl, final String path ) + throws MalformedURLException + { + this.baseUrl = baseUrl; + + URL pathURL = null; + String relPath = null; + + try + { + pathURL = new URL( path ); + } + catch ( MalformedURLException e ) + { + try + { + pathURL = buildUrl( baseUrl, path ); + } + catch ( MalformedURLException e2 ) + { + // If we got an absolute path passed in and end here, then the path + // is converted to relative because we have no reference URL anyway + // to which it has been anchored. + if ( path != null && path.startsWith( "/" ) ) + { + relPath = path.substring( 1 ); + } + else + { + relPath = path; + } + } + } + + this.pathUrl = pathURL; + this.relativePath = relPath; + } + +private static java.net.URL buildBaseUrl(final java.lang.String base) throws java.net.MalformedURLException { + { + try { + return new java.net.URL(/* NPEX_NULL_EXP */ + base); + } catch (java.net.MalformedURLException e) { + return new java.io.File(base).toURI().toURL(); + } + } +} + + private static URL buildUrl( final URL baseUrl, final String path ) + throws MalformedURLException + { + if ( baseUrl == null ) + { + throw new MalformedURLException( "Base is null!" ); + } + + if ( path == null ) + { + return baseUrl; + } + + if ( baseUrl.getProtocol().equals( "file" ) ) + { + return new File( baseUrl.getFile(), path ).toURI().toURL(); + } + + if ( path.startsWith( "/" ) && baseUrl.getPath().endsWith( "/" ) ) + { + return new URL( baseUrl, path.substring( 1 ) ); + } + + return new URL( baseUrl, path ); + } + + /** + * Check if this PathDescriptor describes a file. + * + * @return true for file, false otherwise. + */ + public boolean isFile() + { + return isRelative() || pathUrl.getProtocol().equals( "file" ); + } + + /** + * Check if this PathDescriptor describes a relative path. + * + * @return true if {@link #getPathUrl()} returns null. + */ + public boolean isRelative() + { + return pathUrl == null; + } + + /** + * Get the base URL. + * + * @return the base URL. + */ + public URL getBaseUrl() + { + return baseUrl; + } + + /** + * Get the path as a URL. + * + * @return the path as a URL. + */ + public URL getPathUrl() + { + return pathUrl; + } + + /** + * Get the path. + * + * @return the path. + */ + public String getPath() + { + if ( getPathUrl() != null ) + { + if ( isFile() ) + { + return StringUtils.stripEnd( getPathUrl().getPath(), "/" ); + } + else + { + return getPathUrl().getPath(); + } + } + else + { + return relativePath; + } + } + + /** + * Get the location for files. + * + * @return the location. + */ + public String getLocation() + { + if ( isFile() ) + { + if ( getPathUrl() != null ) + { + return StringUtils.stripEnd( getPathUrl().getFile(), "/" ); + } + else + { + return relativePath; + } + } + else + { + return getPathUrl().toExternalForm(); + } + } + + /** {@inheritDoc} */ + public String toString() + { + StringBuilder res = + new StringBuilder( ( StringUtils.isNotEmpty( relativePath ) ) ? relativePath : String.valueOf( pathUrl ) ); + res.append( " (Base: " ).append( baseUrl ).append( ") Location: " ).append( getLocation() ); + return res.toString(); + } +} diff --git a/Java/maven-doxia-sitetools-PathDescriptor_118/metadata.json b/Java/maven-doxia-sitetools-PathDescriptor_118/metadata.json new file mode 100644 index 000000000..658060697 --- /dev/null +++ b/Java/maven-doxia-sitetools-PathDescriptor_118/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-PathDescriptor_118", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/PathDescriptor.java", + "line": 119, + "npe_method": "buildBaseUrl", + "deref_field": "base", + "npe_class": "PathDescriptor", + "repo": "maven-doxia-sitetools", + "bug_id": "PathDescriptor_118" + } +} diff --git a/Java/maven-doxia-sitetools-PathDescriptor_118/npe.json b/Java/maven-doxia-sitetools-PathDescriptor_118/npe.json new file mode 100644 index 000000000..949de8328 --- /dev/null +++ b/Java/maven-doxia-sitetools-PathDescriptor_118/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/PathDescriptor.java", + "line": 119, + "npe_method": "buildBaseUrl", + "deref_field": "base", + "npe_class": "PathDescriptor" +} \ No newline at end of file diff --git a/Java/maven-doxia-sitetools-PathDescriptor_136/Dockerfile b/Java/maven-doxia-sitetools-PathDescriptor_136/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-PathDescriptor_136/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-PathDescriptor_136/buggy.java b/Java/maven-doxia-sitetools-PathDescriptor_136/buggy.java new file mode 100644 index 000000000..6b42bf732 --- /dev/null +++ b/Java/maven-doxia-sitetools-PathDescriptor_136/buggy.java @@ -0,0 +1,245 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; + +import org.codehaus.plexus.util.StringUtils; + +/** + * This class holds an instance of a maven path. This consists of a relative path (e.g. images/maven-logo.png) and a + * base reference which can also be a relative path (e.g. '.' or '../doxia') or an URL that is used for an absolute + * anchor. + * + * @author Henning P. Schmiedehausen + * @deprecated use {@link URIPathDescriptor} instead. + */ + +public class PathDescriptor +{ + private final URL baseUrl; + + private final URL pathUrl; + + private final String relativePath; + + /** + * Construct a PathDescriptor from a path. + * + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final String path ) + throws MalformedURLException + { + this( (URL) null, path ); + } + + /** + * Construct a PathDescriptor from a path and a base. + * + * @param base a base reference. + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final String base, final String path ) + throws MalformedURLException + { + this( PathDescriptor.buildBaseUrl( base ), path ); + } + + /** + * Construct a PathDescriptor from a path and a base. + * + * @param baseUrl a base reference. + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final URL baseUrl, final String path ) + throws MalformedURLException + { + this.baseUrl = baseUrl; + + URL pathURL = null; + String relPath = null; + + try + { + pathURL = new URL( path ); + } + catch ( MalformedURLException e ) + { + try + { + pathURL = buildUrl( baseUrl, path ); + } + catch ( MalformedURLException e2 ) + { + // If we got an absolute path passed in and end here, then the path + // is converted to relative because we have no reference URL anyway + // to which it has been anchored. + if ( path != null && path.startsWith( "/" ) ) + { + relPath = path.substring( 1 ); + } + else + { + relPath = path; + } + } + } + + this.pathUrl = pathURL; + this.relativePath = relPath; + } + + private static URL buildBaseUrl( final String base ) + throws MalformedURLException + { + if ( base == null ) + { + return null; + } + + try + { + return new URL( base ); + } + catch ( MalformedURLException e ) + { + return new File( base ).toURI().toURL(); + } + } + +private static java.net.URL buildUrl(final java.net.URL baseUrl, final java.lang.String path) throws java.net.MalformedURLException { + { + if (path == null) { + return /* NPEX_NULL_EXP */ + baseUrl; + } + if (baseUrl.getProtocol().equals("file")) { + return new java.io.File(baseUrl.getFile(), path).toURI().toURL(); + } + if (path.startsWith("/") && baseUrl.getPath().endsWith("/")) { + return new java.net.URL(baseUrl, path.substring(1)); + } + return new java.net.URL(baseUrl, path); + } +} + + /** + * Check if this PathDescriptor describes a file. + * + * @return true for file, false otherwise. + */ + public boolean isFile() + { + return isRelative() || pathUrl.getProtocol().equals( "file" ); + } + + /** + * Check if this PathDescriptor describes a relative path. + * + * @return true if {@link #getPathUrl()} returns null. + */ + public boolean isRelative() + { + return pathUrl == null; + } + + /** + * Get the base URL. + * + * @return the base URL. + */ + public URL getBaseUrl() + { + return baseUrl; + } + + /** + * Get the path as a URL. + * + * @return the path as a URL. + */ + public URL getPathUrl() + { + return pathUrl; + } + + /** + * Get the path. + * + * @return the path. + */ + public String getPath() + { + if ( getPathUrl() != null ) + { + if ( isFile() ) + { + return StringUtils.stripEnd( getPathUrl().getPath(), "/" ); + } + else + { + return getPathUrl().getPath(); + } + } + else + { + return relativePath; + } + } + + /** + * Get the location for files. + * + * @return the location. + */ + public String getLocation() + { + if ( isFile() ) + { + if ( getPathUrl() != null ) + { + return StringUtils.stripEnd( getPathUrl().getFile(), "/" ); + } + else + { + return relativePath; + } + } + else + { + return getPathUrl().toExternalForm(); + } + } + + /** {@inheritDoc} */ + public String toString() + { + StringBuilder res = + new StringBuilder( ( StringUtils.isNotEmpty( relativePath ) ) ? relativePath : String.valueOf( pathUrl ) ); + res.append( " (Base: " ).append( baseUrl ).append( ") Location: " ).append( getLocation() ); + return res.toString(); + } +} diff --git a/Java/maven-doxia-sitetools-PathDescriptor_136/metadata.json b/Java/maven-doxia-sitetools-PathDescriptor_136/metadata.json new file mode 100644 index 000000000..25bb0700f --- /dev/null +++ b/Java/maven-doxia-sitetools-PathDescriptor_136/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-PathDescriptor_136", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/PathDescriptor.java", + "line": 137, + "npe_method": "buildUrl", + "deref_field": "baseUrl", + "npe_class": "PathDescriptor", + "repo": "maven-doxia-sitetools", + "bug_id": "PathDescriptor_136" + } +} diff --git a/Java/maven-doxia-sitetools-PathDescriptor_136/npe.json b/Java/maven-doxia-sitetools-PathDescriptor_136/npe.json new file mode 100644 index 000000000..03eb52e74 --- /dev/null +++ b/Java/maven-doxia-sitetools-PathDescriptor_136/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/PathDescriptor.java", + "line": 137, + "npe_method": "buildUrl", + "deref_field": "baseUrl", + "npe_class": "PathDescriptor" +} \ No newline at end of file diff --git a/Java/maven-doxia-sitetools-PathDescriptor_141/Dockerfile b/Java/maven-doxia-sitetools-PathDescriptor_141/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-PathDescriptor_141/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-PathDescriptor_141/buggy.java b/Java/maven-doxia-sitetools-PathDescriptor_141/buggy.java new file mode 100644 index 000000000..0d15abb59 --- /dev/null +++ b/Java/maven-doxia-sitetools-PathDescriptor_141/buggy.java @@ -0,0 +1,245 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; + +import org.codehaus.plexus.util.StringUtils; + +/** + * This class holds an instance of a maven path. This consists of a relative path (e.g. images/maven-logo.png) and a + * base reference which can also be a relative path (e.g. '.' or '../doxia') or an URL that is used for an absolute + * anchor. + * + * @author Henning P. Schmiedehausen + * @deprecated use {@link URIPathDescriptor} instead. + */ + +public class PathDescriptor +{ + private final URL baseUrl; + + private final URL pathUrl; + + private final String relativePath; + + /** + * Construct a PathDescriptor from a path. + * + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final String path ) + throws MalformedURLException + { + this( (URL) null, path ); + } + + /** + * Construct a PathDescriptor from a path and a base. + * + * @param base a base reference. + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final String base, final String path ) + throws MalformedURLException + { + this( PathDescriptor.buildBaseUrl( base ), path ); + } + + /** + * Construct a PathDescriptor from a path and a base. + * + * @param baseUrl a base reference. + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final URL baseUrl, final String path ) + throws MalformedURLException + { + this.baseUrl = baseUrl; + + URL pathURL = null; + String relPath = null; + + try + { + pathURL = new URL( path ); + } + catch ( MalformedURLException e ) + { + try + { + pathURL = buildUrl( baseUrl, path ); + } + catch ( MalformedURLException e2 ) + { + // If we got an absolute path passed in and end here, then the path + // is converted to relative because we have no reference URL anyway + // to which it has been anchored. + if ( path != null && path.startsWith( "/" ) ) + { + relPath = path.substring( 1 ); + } + else + { + relPath = path; + } + } + } + + this.pathUrl = pathURL; + this.relativePath = relPath; + } + + private static URL buildBaseUrl( final String base ) + throws MalformedURLException + { + if ( base == null ) + { + return null; + } + + try + { + return new URL( base ); + } + catch ( MalformedURLException e ) + { + return new File( base ).toURI().toURL(); + } + } + +private static java.net.URL buildUrl(final java.net.URL baseUrl, final java.lang.String path) throws java.net.MalformedURLException { + if (baseUrl == null) { + throw new java.net.MalformedURLException("Base is null!"); + } + { + if (baseUrl.getProtocol().equals("file")) { + return new java.io.File(baseUrl.getFile(), /* NPEX_NULL_EXP */ + path).toURI().toURL(); + } + if (path.startsWith("/") && baseUrl.getPath().endsWith("/")) { + return new java.net.URL(baseUrl, path.substring(1)); + } + return new java.net.URL(baseUrl, path); + } +} + + /** + * Check if this PathDescriptor describes a file. + * + * @return true for file, false otherwise. + */ + public boolean isFile() + { + return isRelative() || pathUrl.getProtocol().equals( "file" ); + } + + /** + * Check if this PathDescriptor describes a relative path. + * + * @return true if {@link #getPathUrl()} returns null. + */ + public boolean isRelative() + { + return pathUrl == null; + } + + /** + * Get the base URL. + * + * @return the base URL. + */ + public URL getBaseUrl() + { + return baseUrl; + } + + /** + * Get the path as a URL. + * + * @return the path as a URL. + */ + public URL getPathUrl() + { + return pathUrl; + } + + /** + * Get the path. + * + * @return the path. + */ + public String getPath() + { + if ( getPathUrl() != null ) + { + if ( isFile() ) + { + return StringUtils.stripEnd( getPathUrl().getPath(), "/" ); + } + else + { + return getPathUrl().getPath(); + } + } + else + { + return relativePath; + } + } + + /** + * Get the location for files. + * + * @return the location. + */ + public String getLocation() + { + if ( isFile() ) + { + if ( getPathUrl() != null ) + { + return StringUtils.stripEnd( getPathUrl().getFile(), "/" ); + } + else + { + return relativePath; + } + } + else + { + return getPathUrl().toExternalForm(); + } + } + + /** {@inheritDoc} */ + public String toString() + { + StringBuilder res = + new StringBuilder( ( StringUtils.isNotEmpty( relativePath ) ) ? relativePath : String.valueOf( pathUrl ) ); + res.append( " (Base: " ).append( baseUrl ).append( ") Location: " ).append( getLocation() ); + return res.toString(); + } +} diff --git a/Java/maven-doxia-sitetools-PathDescriptor_141/metadata.json b/Java/maven-doxia-sitetools-PathDescriptor_141/metadata.json new file mode 100644 index 000000000..274271d6f --- /dev/null +++ b/Java/maven-doxia-sitetools-PathDescriptor_141/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-PathDescriptor_141", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/PathDescriptor.java", + "line": 140, + "npe_method": "buildUrl", + "deref_field": "path", + "npe_class": "PathDescriptor", + "repo": "maven-doxia-sitetools", + "bug_id": "PathDescriptor_141" + } +} diff --git a/Java/maven-doxia-sitetools-PathDescriptor_141/npe.json b/Java/maven-doxia-sitetools-PathDescriptor_141/npe.json new file mode 100644 index 000000000..5026743d7 --- /dev/null +++ b/Java/maven-doxia-sitetools-PathDescriptor_141/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/PathDescriptor.java", + "line": 140, + "npe_method": "buildUrl", + "deref_field": "path", + "npe_class": "PathDescriptor" +} \ No newline at end of file diff --git a/Java/maven-doxia-sitetools-PathDescriptor_206/Dockerfile b/Java/maven-doxia-sitetools-PathDescriptor_206/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-PathDescriptor_206/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-PathDescriptor_206/buggy.java b/Java/maven-doxia-sitetools-PathDescriptor_206/buggy.java new file mode 100644 index 000000000..dc33280df --- /dev/null +++ b/Java/maven-doxia-sitetools-PathDescriptor_206/buggy.java @@ -0,0 +1,252 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; + +import org.codehaus.plexus.util.StringUtils; + +/** + * This class holds an instance of a maven path. This consists of a relative path (e.g. images/maven-logo.png) and a + * base reference which can also be a relative path (e.g. '.' or '../doxia') or an URL that is used for an absolute + * anchor. + * + * @author Henning P. Schmiedehausen + * @deprecated use {@link URIPathDescriptor} instead. + */ + +public class PathDescriptor +{ + private final URL baseUrl; + + private final URL pathUrl; + + private final String relativePath; + + /** + * Construct a PathDescriptor from a path. + * + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final String path ) + throws MalformedURLException + { + this( (URL) null, path ); + } + + /** + * Construct a PathDescriptor from a path and a base. + * + * @param base a base reference. + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final String base, final String path ) + throws MalformedURLException + { + this( PathDescriptor.buildBaseUrl( base ), path ); + } + + /** + * Construct a PathDescriptor from a path and a base. + * + * @param baseUrl a base reference. + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final URL baseUrl, final String path ) + throws MalformedURLException + { + this.baseUrl = baseUrl; + + URL pathURL = null; + String relPath = null; + + try + { + pathURL = new URL( path ); + } + catch ( MalformedURLException e ) + { + try + { + pathURL = buildUrl( baseUrl, path ); + } + catch ( MalformedURLException e2 ) + { + // If we got an absolute path passed in and end here, then the path + // is converted to relative because we have no reference URL anyway + // to which it has been anchored. + if ( path != null && path.startsWith( "/" ) ) + { + relPath = path.substring( 1 ); + } + else + { + relPath = path; + } + } + } + + this.pathUrl = pathURL; + this.relativePath = relPath; + } + + private static URL buildBaseUrl( final String base ) + throws MalformedURLException + { + if ( base == null ) + { + return null; + } + + try + { + return new URL( base ); + } + catch ( MalformedURLException e ) + { + return new File( base ).toURI().toURL(); + } + } + + private static URL buildUrl( final URL baseUrl, final String path ) + throws MalformedURLException + { + if ( baseUrl == null ) + { + throw new MalformedURLException( "Base is null!" ); + } + + if ( path == null ) + { + return baseUrl; + } + + if ( baseUrl.getProtocol().equals( "file" ) ) + { + return new File( baseUrl.getFile(), path ).toURI().toURL(); + } + + if ( path.startsWith( "/" ) && baseUrl.getPath().endsWith( "/" ) ) + { + return new URL( baseUrl, path.substring( 1 ) ); + } + + return new URL( baseUrl, path ); + } + + /** + * Check if this PathDescriptor describes a file. + * + * @return true for file, false otherwise. + */ + public boolean isFile() + { + return isRelative() || pathUrl.getProtocol().equals( "file" ); + } + + /** + * Check if this PathDescriptor describes a relative path. + * + * @return true if {@link #getPathUrl()} returns null. + */ + public boolean isRelative() + { + return pathUrl == null; + } + + /** + * Get the base URL. + * + * @return the base URL. + */ + public URL getBaseUrl() + { + return baseUrl; + } + + /** + * Get the path as a URL. + * + * @return the path as a URL. + */ + public URL getPathUrl() + { + return pathUrl; + } + + /** + * Get the path. + * + * @return the path. + */ +/** + * Get the path. + * + * @return the path. + */ +public java.lang.String getPath() { + { + if (isFile()) { + return org.codehaus.plexus.util.StringUtils.stripEnd(/* NPEX_NULL_EXP */ + getPathUrl().getPath(), "/"); + } else { + return getPathUrl().getPath(); + } + } +} + + /** + * Get the location for files. + * + * @return the location. + */ + public String getLocation() + { + if ( isFile() ) + { + if ( getPathUrl() != null ) + { + return StringUtils.stripEnd( getPathUrl().getFile(), "/" ); + } + else + { + return relativePath; + } + } + else + { + return getPathUrl().toExternalForm(); + } + } + + /** {@inheritDoc} */ + public String toString() + { + StringBuilder res = + new StringBuilder( ( StringUtils.isNotEmpty( relativePath ) ) ? relativePath : String.valueOf( pathUrl ) ); + res.append( " (Base: " ).append( baseUrl ).append( ") Location: " ).append( getLocation() ); + return res.toString(); + } +} diff --git a/Java/maven-doxia-sitetools-PathDescriptor_206/metadata.json b/Java/maven-doxia-sitetools-PathDescriptor_206/metadata.json new file mode 100644 index 000000000..b27182e58 --- /dev/null +++ b/Java/maven-doxia-sitetools-PathDescriptor_206/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-PathDescriptor_206", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/PathDescriptor.java", + "line": 213, + "npe_method": "getPath", + "deref_field": "getPathUrl", + "npe_class": "PathDescriptor", + "repo": "maven-doxia-sitetools", + "bug_id": "PathDescriptor_206" + } +} diff --git a/Java/maven-doxia-sitetools-PathDescriptor_206/npe.json b/Java/maven-doxia-sitetools-PathDescriptor_206/npe.json new file mode 100644 index 000000000..44d7ca90e --- /dev/null +++ b/Java/maven-doxia-sitetools-PathDescriptor_206/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/PathDescriptor.java", + "line": 213, + "npe_method": "getPath", + "deref_field": "getPathUrl", + "npe_class": "PathDescriptor" +} \ No newline at end of file diff --git a/Java/maven-doxia-sitetools-PathDescriptor_232/Dockerfile b/Java/maven-doxia-sitetools-PathDescriptor_232/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-PathDescriptor_232/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-PathDescriptor_232/buggy.java b/Java/maven-doxia-sitetools-PathDescriptor_232/buggy.java new file mode 100644 index 000000000..b941fdd6d --- /dev/null +++ b/Java/maven-doxia-sitetools-PathDescriptor_232/buggy.java @@ -0,0 +1,252 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; + +import org.codehaus.plexus.util.StringUtils; + +/** + * This class holds an instance of a maven path. This consists of a relative path (e.g. images/maven-logo.png) and a + * base reference which can also be a relative path (e.g. '.' or '../doxia') or an URL that is used for an absolute + * anchor. + * + * @author Henning P. Schmiedehausen + * @deprecated use {@link URIPathDescriptor} instead. + */ + +public class PathDescriptor +{ + private final URL baseUrl; + + private final URL pathUrl; + + private final String relativePath; + + /** + * Construct a PathDescriptor from a path. + * + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final String path ) + throws MalformedURLException + { + this( (URL) null, path ); + } + + /** + * Construct a PathDescriptor from a path and a base. + * + * @param base a base reference. + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final String base, final String path ) + throws MalformedURLException + { + this( PathDescriptor.buildBaseUrl( base ), path ); + } + + /** + * Construct a PathDescriptor from a path and a base. + * + * @param baseUrl a base reference. + * @param path the path. + * @throws java.net.MalformedURLException if a URL cannot be formed from the path. + */ + public PathDescriptor( final URL baseUrl, final String path ) + throws MalformedURLException + { + this.baseUrl = baseUrl; + + URL pathURL = null; + String relPath = null; + + try + { + pathURL = new URL( path ); + } + catch ( MalformedURLException e ) + { + try + { + pathURL = buildUrl( baseUrl, path ); + } + catch ( MalformedURLException e2 ) + { + // If we got an absolute path passed in and end here, then the path + // is converted to relative because we have no reference URL anyway + // to which it has been anchored. + if ( path != null && path.startsWith( "/" ) ) + { + relPath = path.substring( 1 ); + } + else + { + relPath = path; + } + } + } + + this.pathUrl = pathURL; + this.relativePath = relPath; + } + + private static URL buildBaseUrl( final String base ) + throws MalformedURLException + { + if ( base == null ) + { + return null; + } + + try + { + return new URL( base ); + } + catch ( MalformedURLException e ) + { + return new File( base ).toURI().toURL(); + } + } + + private static URL buildUrl( final URL baseUrl, final String path ) + throws MalformedURLException + { + if ( baseUrl == null ) + { + throw new MalformedURLException( "Base is null!" ); + } + + if ( path == null ) + { + return baseUrl; + } + + if ( baseUrl.getProtocol().equals( "file" ) ) + { + return new File( baseUrl.getFile(), path ).toURI().toURL(); + } + + if ( path.startsWith( "/" ) && baseUrl.getPath().endsWith( "/" ) ) + { + return new URL( baseUrl, path.substring( 1 ) ); + } + + return new URL( baseUrl, path ); + } + + /** + * Check if this PathDescriptor describes a file. + * + * @return true for file, false otherwise. + */ + public boolean isFile() + { + return isRelative() || pathUrl.getProtocol().equals( "file" ); + } + + /** + * Check if this PathDescriptor describes a relative path. + * + * @return true if {@link #getPathUrl()} returns null. + */ + public boolean isRelative() + { + return pathUrl == null; + } + + /** + * Get the base URL. + * + * @return the base URL. + */ + public URL getBaseUrl() + { + return baseUrl; + } + + /** + * Get the path as a URL. + * + * @return the path as a URL. + */ + public URL getPathUrl() + { + return pathUrl; + } + + /** + * Get the path. + * + * @return the path. + */ + public String getPath() + { + if ( getPathUrl() != null ) + { + if ( isFile() ) + { + return StringUtils.stripEnd( getPathUrl().getPath(), "/" ); + } + else + { + return getPathUrl().getPath(); + } + } + else + { + return relativePath; + } + } + + /** + * Get the location for files. + * + * @return the location. + */ +/** + * Get the location for files. + * + * @return the location. + */ +public java.lang.String getLocation() { + if (isFile()) { + { + return org.codehaus.plexus.util.StringUtils.stripEnd(/* NPEX_NULL_EXP */ + getPathUrl().getFile(), "/"); + } + } else { + return getPathUrl().toExternalForm(); + } +} + + /** {@inheritDoc} */ + public String toString() + { + StringBuilder res = + new StringBuilder( ( StringUtils.isNotEmpty( relativePath ) ) ? relativePath : String.valueOf( pathUrl ) ); + res.append( " (Base: " ).append( baseUrl ).append( ") Location: " ).append( getLocation() ); + return res.toString(); + } +} diff --git a/Java/maven-doxia-sitetools-PathDescriptor_232/metadata.json b/Java/maven-doxia-sitetools-PathDescriptor_232/metadata.json new file mode 100644 index 000000000..5339e74d5 --- /dev/null +++ b/Java/maven-doxia-sitetools-PathDescriptor_232/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-PathDescriptor_232", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/PathDescriptor.java", + "line": 237, + "npe_method": "getLocation", + "deref_field": "getPathUrl", + "npe_class": "PathDescriptor", + "repo": "maven-doxia-sitetools", + "bug_id": "PathDescriptor_232" + } +} diff --git a/Java/maven-doxia-sitetools-PathDescriptor_232/npe.json b/Java/maven-doxia-sitetools-PathDescriptor_232/npe.json new file mode 100644 index 000000000..79a9f5cc1 --- /dev/null +++ b/Java/maven-doxia-sitetools-PathDescriptor_232/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/PathDescriptor.java", + "line": 237, + "npe_method": "getLocation", + "deref_field": "getPathUrl", + "npe_class": "PathDescriptor" +} \ No newline at end of file diff --git a/Java/maven-doxia-sitetools-PathUtils_57/Dockerfile b/Java/maven-doxia-sitetools-PathUtils_57/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-PathUtils_57/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-PathUtils_57/buggy.java b/Java/maven-doxia-sitetools-PathUtils_57/buggy.java new file mode 100644 index 000000000..718484ff7 --- /dev/null +++ b/Java/maven-doxia-sitetools-PathUtils_57/buggy.java @@ -0,0 +1,151 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * 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.net.MalformedURLException; +import java.net.URL; + +import org.codehaus.plexus.util.PathTool; + +/** + * Utilities that allow conversion of old and new pathes and URLs relative to each other. + * + * @author Brett Porter + * @author Henning P. Schmiedehausen + * @deprecated this only operates on deprecated classes, it is not used anymore. + */ +public abstract class PathUtils +{ + /** + * Private constructor. + */ + private PathUtils() + { + // do not instantiate + } + + /** + *

    convertPath.

    + * + * @param oldPath not null + * @param newPath not null + * @return a PathDescriptor converted by the new path + * @throws java.net.MalformedURLException if any + */ +/** + *

    convertPath.

    + * + * @param oldPath + * not null + * @param newPath + * not null + * @return a PathDescriptor converted by the new path + * @throws java.net.MalformedURLException + * if any + */ +public static final org.apache.maven.doxia.site.decoration.inheritance.PathDescriptor convertPath(final org.apache.maven.doxia.site.decoration.inheritance.PathDescriptor oldPath, final org.apache.maven.doxia.site.decoration.inheritance.PathDescriptor newPath) throws java.net.MalformedURLException { + java.lang.String relative = org.apache.maven.doxia.site.decoration.inheritance.PathUtils.getRelativePath(oldPath, newPath); + { + return new org.apache.maven.doxia.site.decoration.inheritance.PathDescriptor(/* NPEX_NULL_EXP */ + relative); + } +} + + /** + *

    getRelativePath.

    + * + * @param oldPathDescriptor not null + * @param newPathDescriptor not null + * @return a relative path depending if PathDescriptor is a file or a web url. + * @see PathTool#getRelativeFilePath(String, String) + * @see PathTool#getRelativeWebPath(String, String) + */ + public static final String getRelativePath( final PathDescriptor oldPathDescriptor, + final PathDescriptor newPathDescriptor ) + { + // Cannot convert from URL to file. + if ( oldPathDescriptor.isFile() ) + { + if ( !newPathDescriptor.isFile() ) + { + // We want to convert from a file to an URL. This is normally not possible... + if ( oldPathDescriptor.isRelative() ) + { + // unless the old path is a relative path. Then we might convert an existing + // site into a new URL using resolvePaths()... + return oldPathDescriptor.getPath(); + } + + // The old path is not relative. Bail out. + return null; + } + else + { + // both are files, if either of them is relative, bail out + // see DOXIASITETOOLS-29, MSITE-404, PLXUTILS-116 + if ( oldPathDescriptor.isRelative() || newPathDescriptor.isRelative() ) + { + return null; + } + } + } + + // Don't optimize to else. This might also be old.isFile && new.isFile ... + if ( !oldPathDescriptor.isFile() ) + { + // URLs, determine if they share protocol and domain info + URL oldUrl = oldPathDescriptor.getPathUrl(); + URL newUrl = newPathDescriptor.getPathUrl(); + + if ( oldUrl == null || newUrl == null ) + { + // One of the sites has a strange URL. no relative path possible, bail out. + return null; + } + + if ( ( newUrl.getProtocol().equalsIgnoreCase( oldUrl.getProtocol() ) ) + && ( newUrl.getHost().equalsIgnoreCase( oldUrl.getHost() ) ) + && ( newUrl.getPort() == oldUrl.getPort() ) ) + { + // Both paths point to the same site. So we can use relative paths. + + String oldPath = oldPathDescriptor.getPath(); + String newPath = newPathDescriptor.getPath(); + + return PathTool.getRelativeWebPath( newPath, oldPath ); + } + + // Different sites. No relative Path possible. + return null; + } + + // Both Descriptors point to an absolute path. We can build a relative path. + String oldPath = oldPathDescriptor.getPath(); + String newPath = newPathDescriptor.getPath(); + + if ( oldPath == null || newPath == null ) + { + // One of the sites has a strange URL. no relative path possible, bail out. + return null; + } + + return PathTool.getRelativeFilePath( oldPath, newPath ); + } +} diff --git a/Java/maven-doxia-sitetools-PathUtils_57/metadata.json b/Java/maven-doxia-sitetools-PathUtils_57/metadata.json new file mode 100644 index 000000000..aece6b77f --- /dev/null +++ b/Java/maven-doxia-sitetools-PathUtils_57/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-PathUtils_57", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/PathUtils.java", + "line": 67, + "npe_method": "convertPath", + "deref_field": "relative", + "npe_class": "PathUtils", + "repo": "maven-doxia-sitetools", + "bug_id": "PathUtils_57" + } +} diff --git a/Java/maven-doxia-sitetools-PathUtils_57/npe.json b/Java/maven-doxia-sitetools-PathUtils_57/npe.json new file mode 100644 index 000000000..20dc3fb9b --- /dev/null +++ b/Java/maven-doxia-sitetools-PathUtils_57/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/PathUtils.java", + "line": 67, + "npe_method": "convertPath", + "deref_field": "relative", + "npe_class": "PathUtils" +} \ No newline at end of file diff --git a/Java/maven-doxia-sitetools-SiteRendererSink_269/Dockerfile b/Java/maven-doxia-sitetools-SiteRendererSink_269/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-SiteRendererSink_269/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-SiteRendererSink_269/buggy.java b/Java/maven-doxia-sitetools-SiteRendererSink_269/buggy.java new file mode 100644 index 000000000..e3fb4826b --- /dev/null +++ b/Java/maven-doxia-sitetools-SiteRendererSink_269/buggy.java @@ -0,0 +1,354 @@ +package org.apache.maven.doxia.siterenderer.sink; + +/* + * 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.StringWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.swing.text.html.HTML.Attribute; + +import org.apache.maven.doxia.markup.HtmlMarkup; +import org.apache.maven.doxia.module.xhtml5.Xhtml5Sink; +import org.apache.maven.doxia.sink.Sink; +import org.apache.maven.doxia.sink.SinkEventAttributes; +import org.apache.maven.doxia.siterenderer.DocumentContent; +import org.apache.maven.doxia.siterenderer.RenderingContext; +import org.apache.maven.doxia.util.HtmlTools; +import org.codehaus.plexus.util.StringUtils; + +/** + * Sink for site rendering of a document, to allow later merge document's output with a template. + * During raw Doxia rendering, content is stored in multiple fields for later use when incorporating + * into skin or template: title, date, authors, head, body + * + * @author Emmanuel Venisse + */ +@SuppressWarnings( "checkstyle:methodname" ) +public class SiteRendererSink + extends Xhtml5Sink + implements Sink, org.codehaus.doxia.sink.Sink, DocumentContent +{ + private String date = ""; + + private String title = ""; + + private List authors = new ArrayList(); + + private final StringWriter headWriter; + + private StringBuilder sectionTitleBuffer; + + private StringBuilder sectionTitleWriteBuffer; + + private boolean sectionHasID; + + private boolean isSectionTitle; + + private Set anchorsInSectionTitle; + + private final Writer writer; + + private RenderingContext renderingContext; + + /** + * Construct a new SiteRendererSink for a document. + * + * @param renderingContext the document's RenderingContext. + */ + public SiteRendererSink( RenderingContext renderingContext ) + { + this( new StringWriter(), renderingContext ); + } + + /** + * Construct a new SiteRendererSink for a document. + * + * @param writer the writer for the sink. + * @param renderingContext the document's RenderingContext. + */ + private SiteRendererSink( StringWriter writer, RenderingContext renderingContext ) + { + super( writer ); + + this.writer = writer; + this.headWriter = new StringWriter(); + this.renderingContext = renderingContext; + + /* the template is expected to have used the main tag, which can be used only once */ + super.contentStack.push( HtmlMarkup.MAIN ); + } + + /** {@inheritDoc} */ + @Override + public void title_() + { + if ( getTextBuffer().length() > 0 ) + { + title = getTextBuffer().toString(); + } + + resetTextBuffer(); + } + + /** + * {@inheritDoc} + * + * Reset text buffer, since text content before title mustn't be in title. + * @see org.apache.maven.doxia.module.xhtml5.Xhtml5Sink#title() + */ + @Override + public void title() + { + resetTextBuffer(); + } + + /** {@inheritDoc} */ + @Override + public void author() + { + resetTextBuffer(); + } + + /** {@inheritDoc} */ + @Override + public void author_() + { + if ( getTextBuffer().length() > 0 ) + { + String text = HtmlTools.escapeHTML( getTextBuffer().toString() ); + text = StringUtils.replace( text, "&#", "&#" ); + authors.add( text.trim() ); + } + + resetTextBuffer(); + } + + /** {@inheritDoc} */ + @Override + public void date() + { + resetTextBuffer(); + } + + /** {@inheritDoc} */ + @Override + public void date_() + { + if ( getTextBuffer().length() > 0 ) + { + date = getTextBuffer().toString().trim(); + } + + resetTextBuffer(); + } + + /** + * {@inheritDoc} + * + * Do nothing. + * @see org.apache.maven.doxia.module.xhtml5.Xhtml5Sink#body_() + */ + @Override + public void body_() + { + // nop + } + + /** + * {@inheritDoc} + * + * Do nothing. + * @see org.apache.maven.doxia.module.xhtml5.Xhtml5Sink#body() + */ + @Override + public void body() + { + // nop + } + + /** {@inheritDoc} */ + @Override + public void head_() + { + setHeadFlag( false ); + } + + /** {@inheritDoc} */ + @Override + public void head() + { + setHeadFlag( true ); + } + + /** {@inheritDoc} */ + @Override + public void anchor( String name, SinkEventAttributes attributes ) + { + super.anchor( name, attributes ); + if ( isSectionTitle ) + { + if ( anchorsInSectionTitle == null ) + { + anchorsInSectionTitle = new HashSet(); + } + anchorsInSectionTitle.add( name ); + } + } + + /** {@inheritDoc} */ + @Override + protected void onSectionTitle( int depth, SinkEventAttributes attributes ) + { + sectionHasID = ( attributes != null && attributes.isDefined ( Attribute.ID.toString() ) ); + isSectionTitle = true; + + super.onSectionTitle( depth, attributes ); + + this.sectionTitleBuffer = new StringBuilder(); + this.sectionTitleWriteBuffer = new StringBuilder(); + } + + /** {@inheritDoc} */ + @Override + protected void onSectionTitle_( int depth ) + { + String sectionTitle = sectionTitleBuffer.toString(); + this.sectionTitleBuffer = null; + String sectionWriteTitle = sectionTitleWriteBuffer.toString(); + this.sectionTitleWriteBuffer = null; + + if ( !StringUtils.isEmpty( sectionTitle ) ) + { + if ( sectionHasID ) + { + sectionHasID = false; + } + else + { + String id = HtmlTools.encodeId( sectionTitle ); + if ( ( anchorsInSectionTitle == null ) || ( !anchorsInSectionTitle.contains( id ) ) ) + { + anchor( id ); + anchor_(); + } + } + } + + super.write( sectionWriteTitle ); + + this.isSectionTitle = false; + anchorsInSectionTitle = null; + super.onSectionTitle_( depth ); + } + + /** {@inheritDoc} */ + @Override +/** + * {@inheritDoc } + */ +public void text(java.lang.String text) { + { + // this implies we're inside a section title, collect text events for anchor generation + /* NPEX_NULL_EXP */ + sectionTitleBuffer.append(text); + } + super.text(text); +} + + /** {@inheritDoc} */ + @Override + protected void write( String text ) + { + String txt = text; + + if ( isHeadFlag() ) + { + headWriter.write( unifyEOLs( txt ) ); + + return; + } + + if ( renderingContext != null ) + { + String relativePathToBasedir = renderingContext.getRelativePath(); + + if ( relativePathToBasedir == null ) + { + txt = StringUtils.replace( txt, "$relativePath", "." ); + } + else + { + txt = StringUtils.replace( txt, "$relativePath", relativePathToBasedir ); + } + } + + if ( sectionTitleWriteBuffer != null ) + { + // this implies we're inside a section title, collect text events for anchor generation + sectionTitleWriteBuffer.append( txt ); + } + else + { + super.write( txt ); + } + } + + // DocumentContent interface + + /** {@inheritDoc} */ + public String getTitle() + { + return title; + } + + /** {@inheritDoc} */ + public List getAuthors() + { + return authors; + } + + /** {@inheritDoc} */ + public String getDate() + { + return date; + } + + /** {@inheritDoc} */ + public String getBody() + { + return writer.toString(); + } + + /** {@inheritDoc} */ + public String getHead() + { + return headWriter.toString(); + } + + /** {@inheritDoc} */ + public RenderingContext getRenderingContext() + { + return renderingContext; + } +} diff --git a/Java/maven-doxia-sitetools-SiteRendererSink_269/metadata.json b/Java/maven-doxia-sitetools-SiteRendererSink_269/metadata.json new file mode 100644 index 000000000..b00b72ca0 --- /dev/null +++ b/Java/maven-doxia-sitetools-SiteRendererSink_269/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-SiteRendererSink_269", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/sink/SiteRendererSink.java", + "line": 274, + "npe_method": "text", + "deref_field": "sectionTitleBuffer", + "npe_class": "SiteRendererSink", + "repo": "maven-doxia-sitetools", + "bug_id": "SiteRendererSink_269" + } +} diff --git a/Java/maven-doxia-sitetools-SiteRendererSink_269/npe.json b/Java/maven-doxia-sitetools-SiteRendererSink_269/npe.json new file mode 100644 index 000000000..a97d8b44b --- /dev/null +++ b/Java/maven-doxia-sitetools-SiteRendererSink_269/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/sink/SiteRendererSink.java", + "line": 274, + "npe_method": "text", + "deref_field": "sectionTitleBuffer", + "npe_class": "SiteRendererSink" +} \ No newline at end of file diff --git a/Java/maven-doxia-sitetools-SiteRendererSink_305/Dockerfile b/Java/maven-doxia-sitetools-SiteRendererSink_305/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-SiteRendererSink_305/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-SiteRendererSink_305/buggy.java b/Java/maven-doxia-sitetools-SiteRendererSink_305/buggy.java new file mode 100644 index 000000000..0cdc44736 --- /dev/null +++ b/Java/maven-doxia-sitetools-SiteRendererSink_305/buggy.java @@ -0,0 +1,341 @@ +package org.apache.maven.doxia.siterenderer.sink; + +/* + * 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.StringWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.swing.text.html.HTML.Attribute; + +import org.apache.maven.doxia.markup.HtmlMarkup; +import org.apache.maven.doxia.module.xhtml5.Xhtml5Sink; +import org.apache.maven.doxia.sink.Sink; +import org.apache.maven.doxia.sink.SinkEventAttributes; +import org.apache.maven.doxia.siterenderer.DocumentContent; +import org.apache.maven.doxia.siterenderer.RenderingContext; +import org.apache.maven.doxia.util.HtmlTools; +import org.codehaus.plexus.util.StringUtils; + +/** + * Sink for site rendering of a document, to allow later merge document's output with a template. + * During raw Doxia rendering, content is stored in multiple fields for later use when incorporating + * into skin or template: title, date, authors, head, body + * + * @author Emmanuel Venisse + */ +@SuppressWarnings( "checkstyle:methodname" ) +public class SiteRendererSink + extends Xhtml5Sink + implements Sink, org.codehaus.doxia.sink.Sink, DocumentContent +{ + private String date = ""; + + private String title = ""; + + private List authors = new ArrayList(); + + private final StringWriter headWriter; + + private StringBuilder sectionTitleBuffer; + + private StringBuilder sectionTitleWriteBuffer; + + private boolean sectionHasID; + + private boolean isSectionTitle; + + private Set anchorsInSectionTitle; + + private final Writer writer; + + private RenderingContext renderingContext; + + /** + * Construct a new SiteRendererSink for a document. + * + * @param renderingContext the document's RenderingContext. + */ + public SiteRendererSink( RenderingContext renderingContext ) + { + this( new StringWriter(), renderingContext ); + } + + /** + * Construct a new SiteRendererSink for a document. + * + * @param writer the writer for the sink. + * @param renderingContext the document's RenderingContext. + */ + private SiteRendererSink( StringWriter writer, RenderingContext renderingContext ) + { + super( writer ); + + this.writer = writer; + this.headWriter = new StringWriter(); + this.renderingContext = renderingContext; + + /* the template is expected to have used the main tag, which can be used only once */ + super.contentStack.push( HtmlMarkup.MAIN ); + } + + /** {@inheritDoc} */ + @Override + public void title_() + { + if ( getTextBuffer().length() > 0 ) + { + title = getTextBuffer().toString(); + } + + resetTextBuffer(); + } + + /** + * {@inheritDoc} + * + * Reset text buffer, since text content before title mustn't be in title. + * @see org.apache.maven.doxia.module.xhtml5.Xhtml5Sink#title() + */ + @Override + public void title() + { + resetTextBuffer(); + } + + /** {@inheritDoc} */ + @Override + public void author() + { + resetTextBuffer(); + } + + /** {@inheritDoc} */ + @Override + public void author_() + { + if ( getTextBuffer().length() > 0 ) + { + String text = HtmlTools.escapeHTML( getTextBuffer().toString() ); + text = StringUtils.replace( text, "&#", "&#" ); + authors.add( text.trim() ); + } + + resetTextBuffer(); + } + + /** {@inheritDoc} */ + @Override + public void date() + { + resetTextBuffer(); + } + + /** {@inheritDoc} */ + @Override + public void date_() + { + if ( getTextBuffer().length() > 0 ) + { + date = getTextBuffer().toString().trim(); + } + + resetTextBuffer(); + } + + /** + * {@inheritDoc} + * + * Do nothing. + * @see org.apache.maven.doxia.module.xhtml5.Xhtml5Sink#body_() + */ + @Override + public void body_() + { + // nop + } + + /** + * {@inheritDoc} + * + * Do nothing. + * @see org.apache.maven.doxia.module.xhtml5.Xhtml5Sink#body() + */ + @Override + public void body() + { + // nop + } + + /** {@inheritDoc} */ + @Override + public void head_() + { + setHeadFlag( false ); + } + + /** {@inheritDoc} */ + @Override + public void head() + { + setHeadFlag( true ); + } + + /** {@inheritDoc} */ + @Override + public void anchor( String name, SinkEventAttributes attributes ) + { + super.anchor( name, attributes ); + if ( isSectionTitle ) + { + if ( anchorsInSectionTitle == null ) + { + anchorsInSectionTitle = new HashSet(); + } + anchorsInSectionTitle.add( name ); + } + } + + /** {@inheritDoc} */ + @Override + protected void onSectionTitle( int depth, SinkEventAttributes attributes ) + { + sectionHasID = ( attributes != null && attributes.isDefined ( Attribute.ID.toString() ) ); + isSectionTitle = true; + + super.onSectionTitle( depth, attributes ); + + this.sectionTitleBuffer = new StringBuilder(); + this.sectionTitleWriteBuffer = new StringBuilder(); + } + + /** {@inheritDoc} */ + @Override + protected void onSectionTitle_( int depth ) + { + String sectionTitle = sectionTitleBuffer.toString(); + this.sectionTitleBuffer = null; + String sectionWriteTitle = sectionTitleWriteBuffer.toString(); + this.sectionTitleWriteBuffer = null; + + if ( !StringUtils.isEmpty( sectionTitle ) ) + { + if ( sectionHasID ) + { + sectionHasID = false; + } + else + { + String id = HtmlTools.encodeId( sectionTitle ); + if ( ( anchorsInSectionTitle == null ) || ( !anchorsInSectionTitle.contains( id ) ) ) + { + anchor( id ); + anchor_(); + } + } + } + + super.write( sectionWriteTitle ); + + this.isSectionTitle = false; + anchorsInSectionTitle = null; + super.onSectionTitle_( depth ); + } + + /** {@inheritDoc} */ + @Override + public void text( String text ) + { + if ( sectionTitleBuffer != null ) + { + // this implies we're inside a section title, collect text events for anchor generation + sectionTitleBuffer.append( text ); + } + + super.text( text ); + } + + /** {@inheritDoc} */ + @Override +/** + * {@inheritDoc } + */ +protected void write(java.lang.String text) { + java.lang.String txt = text; + if (isHeadFlag()) { + headWriter.write(org.apache.maven.doxia.sink.impl.AbstractSink.unifyEOLs(txt)); + return; + } + if (renderingContext != null) { + java.lang.String relativePathToBasedir = renderingContext.getRelativePath(); + if (relativePathToBasedir == null) { + txt = org.codehaus.plexus.util.StringUtils.replace(txt, "$relativePath", "."); + } else { + txt = org.codehaus.plexus.util.StringUtils.replace(txt, "$relativePath", relativePathToBasedir); + } + } + { + // this implies we're inside a section title, collect text events for anchor generation + /* NPEX_NULL_EXP */ + sectionTitleWriteBuffer.append(txt); + } +} + + // DocumentContent interface + + /** {@inheritDoc} */ + public String getTitle() + { + return title; + } + + /** {@inheritDoc} */ + public List getAuthors() + { + return authors; + } + + /** {@inheritDoc} */ + public String getDate() + { + return date; + } + + /** {@inheritDoc} */ + public String getBody() + { + return writer.toString(); + } + + /** {@inheritDoc} */ + public String getHead() + { + return headWriter.toString(); + } + + /** {@inheritDoc} */ + public RenderingContext getRenderingContext() + { + return renderingContext; + } +} diff --git a/Java/maven-doxia-sitetools-SiteRendererSink_305/metadata.json b/Java/maven-doxia-sitetools-SiteRendererSink_305/metadata.json new file mode 100644 index 000000000..6401ef749 --- /dev/null +++ b/Java/maven-doxia-sitetools-SiteRendererSink_305/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-SiteRendererSink_305", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/sink/SiteRendererSink.java", + "line": 300, + "npe_method": "write", + "deref_field": "sectionTitleWriteBuffer", + "npe_class": "SiteRendererSink", + "repo": "maven-doxia-sitetools", + "bug_id": "SiteRendererSink_305" + } +} diff --git a/Java/maven-doxia-sitetools-SiteRendererSink_305/npe.json b/Java/maven-doxia-sitetools-SiteRendererSink_305/npe.json new file mode 100644 index 000000000..67b09b048 --- /dev/null +++ b/Java/maven-doxia-sitetools-SiteRendererSink_305/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/sink/SiteRendererSink.java", + "line": 300, + "npe_method": "write", + "deref_field": "sectionTitleWriteBuffer", + "npe_class": "SiteRendererSink" +} \ No newline at end of file diff --git a/Java/maven-doxia-sitetools-URIPathDescriptor_166/Dockerfile b/Java/maven-doxia-sitetools-URIPathDescriptor_166/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-URIPathDescriptor_166/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-URIPathDescriptor_166/buggy.java b/Java/maven-doxia-sitetools-URIPathDescriptor_166/buggy.java new file mode 100644 index 000000000..bfbad3cf4 --- /dev/null +++ b/Java/maven-doxia-sitetools-URIPathDescriptor_166/buggy.java @@ -0,0 +1,267 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * 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.net.URI; +import java.net.URISyntaxException; + +import org.codehaus.plexus.util.PathTool; + +/** + * Describes a link that may be absolute or relative, and that is anchored to an absolute URI. + * + * @author ltheussl + * + * @since 1.2 + */ +public class URIPathDescriptor +{ + private final URI baseURI; + private final URI link; + + /** + * A URIPathDescriptor consists of a base URI and a link. + * Both arguments to this constructor have to be parsable to URIs. + * The baseURI parameter has to be absolute in the sense of {@link URI#isAbsolute()}. + * + * Before being parsed to {@link URI}s, the arguments are modified to catch + * some common bad practices: first all Windows-style backslashes '\' are replaced by + * forward slashes '/'. + * If the baseURI does not end with '/', a slash is appended. + * If the link starts with a '/', the first character is stripped. + * + * @param baseURI The base URI. Has to be a valid absolute URI. + * In addition, the path of the URI should not have any file part, + * ie http://maven.apache.org/ is valid, + * http://maven.apache.org/index.html is not. + * Even though the latter form is accepted without warning, + * the methods in this class will not return what is probably expected, + * because a slash is appended during construction, as noted above. + * @param link the link. This may be a relative link or an absolute link. + * Note that URIs that start with a "/", ie don't specify a scheme, are considered relative. + * + * @throws IllegalArgumentException if either argument is not parsable as a URI, + * or if baseURI is not absolute. + */ + public URIPathDescriptor( final String baseURI, final String link ) + { + final String llink = sanitizeLink( link ); + final String bbase = sanitizeBase( baseURI ); + + this.baseURI = URI.create( bbase ).normalize(); + this.link = URI.create( llink ).normalize(); + + if ( !this.baseURI.isAbsolute() ) + { + throw new IllegalArgumentException( "Base URI is not absolute: " + baseURI ); + } + } + + /** + * Return the base of this URIPathDescriptor as a URI. + * This is always {@link URI#normalize() normalized}. + * + * @return the normalized base URI. + */ + public URI getBaseURI() + { + return baseURI; + } + + /** + * Return the link of this URIPathDescriptor as a URI. + * This is always {@link URI#normalize() normalized}. + * + * @return the normalized link URI. + */ + public URI getLink() + { + return link; + } + + /** + * Resolve the link to the base. + * This always returns an absolute URI. If link is absolute, link is returned. + * + * @return the resolved link. This is equivalent to calling + * {@link #getBaseURI()}.{@link URI#resolve(java.net.URI) resolve}( {@link #getLink()} ). + */ + public URI resolveLink() + { + return baseURI.resolve( link ); + } + + /** + * Calculate the relative link with respect to the base. + * The original link is returned if either + * link is relative; + * or link and base do not share the {@link #sameSite(java.net.URI) same site}. + * + * @return the link as a relative URI. + */ + public URI relativizeLink() + { + return relativizeLink( baseURI.toString(), link ); + } + + // NOTE: URI.relativize does not work as expected, see + // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6226081 + private static URI relativizeLink( final String base, final URI link ) + { + if ( !link.isAbsolute() ) + { + return link; + } + + final URI newBaseURI = URI.create( base ); + + if ( !sameSite( link, newBaseURI ) ) + { + return link; + } + + final String relativePath = PathTool.getRelativeWebPath( newBaseURI.toString(), link.toString() ); + + return URI.create( correctRelativePath( relativePath ) ); + } + + /** + * Calculate the link as viewed from a different base. + * This returns the original link if link is absolute. + * This returns {@link #resolveLink()} if either + * newBase == null, + * or newBase is not parsable as a URI, + * or newBase and this {@link #getBaseURI()} do not share the + * {@link #sameSite(java.net.URI) same site}. + * + * @param newBase the new base URI. Has to be parsable as a URI. + *. + * @return a new relative link or the original link {@link #resolveLink() resolved}, + * i.e. as an absolute link, if the link cannot be re-based. + */ +/** + * Calculate the link as viewed from a different base. + * This returns the original link if link is absolute. + * This returns {@link #resolveLink()} if either + * newBase == null, + * or newBase is not parsable as a URI, + * or newBase and this {@link #getBaseURI()} do not share the + * {@link #sameSite(java.net.URI) same site}. + * + * @param newBase + * the new base URI. Has to be parsable as a URI. + * . + * @return a new relative link or the original link {@link #resolveLink() resolved}, +i.e. as an absolute link, if the link cannot be re-based. + */ +public java.net.URI rebaseLink(final java.lang.String newBase) { + if (link.isAbsolute()) { + return link; + } + { + final java.net.URI newBaseURI; + try { + newBaseURI = new java.net.URI(/* NPEX_NULL_EXP */ + newBase); + } catch (java.net.URISyntaxException ex) { + return resolveLink(); + } + if (!sameSite(newBaseURI)) { + return resolveLink(); + } + final java.lang.String relativeBasePath = org.codehaus.plexus.util.PathTool.getRelativeWebPath(newBaseURI.getPath(), baseURI.getPath()); + return java.net.URI.create(org.apache.maven.doxia.site.decoration.inheritance.URIPathDescriptor.correctRelativePath(relativeBasePath)).resolve(link); + } +} + + private static String correctRelativePath( final String relativePath ) + { + if ( "".equals( relativePath ) || "/".equals( relativePath ) ) + { + return "./"; + } + else + { + return relativePath; + } + } + + /** + * Check if this URIPathDescriptor lives on the same site as the given URI. + * + * @param uri a URI to compare with. + * May be null, in which case false is returned. + * + * @return true if {@link #getBaseURI()} shares the same scheme, host and port with the given URI + * where null values are allowed. + */ + public boolean sameSite( final URI uri ) + { + return ( uri != null ) && sameSite( this.baseURI, uri ); + } + + private static boolean sameSite( final URI baseURI, final URI newBaseURI ) + { + final boolean sameScheme = + ( newBaseURI.getScheme() == null ? false : baseURI.getScheme().equalsIgnoreCase( newBaseURI.getScheme() ) ); + final boolean sameHost = + ( baseURI.getHost() == null ? newBaseURI.getHost() == null + : baseURI.getHost().equalsIgnoreCase( newBaseURI.getHost() ) ); + final boolean samePort = ( baseURI.getPort() == newBaseURI.getPort() ); + + return ( sameScheme && samePort && sameHost ); + } + + /** + * Construct a string representation of this URIPathDescriptor. + * This is equivalent to calling {@link #resolveLink()}.toString(). + * + * @return this URIPathDescriptor as a String. + */ + @Override + public String toString() + { + return resolveLink().toString(); + } + + private static String sanitizeBase( final String base ) + { + String sane = base.replace( '\\', '/' ); + + if ( !sane.endsWith( "/" ) ) + { + sane += "/"; + } + + return sane; + } + + private static String sanitizeLink( final String link ) + { + String sane = link.replace( '\\', '/' ); + + if ( sane.startsWith( "/" ) ) + { + sane = sane.substring( 1 ); + } + + return sane; + } +} diff --git a/Java/maven-doxia-sitetools-URIPathDescriptor_166/metadata.json b/Java/maven-doxia-sitetools-URIPathDescriptor_166/metadata.json new file mode 100644 index 000000000..fe8852710 --- /dev/null +++ b/Java/maven-doxia-sitetools-URIPathDescriptor_166/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-URIPathDescriptor_166", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/URIPathDescriptor.java", + "line": 182, + "npe_method": "rebaseLink", + "deref_field": "newBase", + "npe_class": "URIPathDescriptor", + "repo": "maven-doxia-sitetools", + "bug_id": "URIPathDescriptor_166" + } +} diff --git a/Java/maven-doxia-sitetools-URIPathDescriptor_166/npe.json b/Java/maven-doxia-sitetools-URIPathDescriptor_166/npe.json new file mode 100644 index 000000000..ef09d5e34 --- /dev/null +++ b/Java/maven-doxia-sitetools-URIPathDescriptor_166/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/URIPathDescriptor.java", + "line": 182, + "npe_method": "rebaseLink", + "deref_field": "newBase", + "npe_class": "URIPathDescriptor" +} \ No newline at end of file diff --git a/Java/maven-doxia-sitetools-URIPathDescriptor_222/Dockerfile b/Java/maven-doxia-sitetools-URIPathDescriptor_222/Dockerfile new file mode 100644 index 000000000..36567fd64 --- /dev/null +++ b/Java/maven-doxia-sitetools-URIPathDescriptor_222/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-doxia-sitetools + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/maven-doxia-sitetools-URIPathDescriptor_222/buggy.java b/Java/maven-doxia-sitetools-URIPathDescriptor_222/buggy.java new file mode 100644 index 000000000..63e69fb2c --- /dev/null +++ b/Java/maven-doxia-sitetools-URIPathDescriptor_222/buggy.java @@ -0,0 +1,261 @@ +package org.apache.maven.doxia.site.decoration.inheritance; + +/* + * 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.net.URI; +import java.net.URISyntaxException; + +import org.codehaus.plexus.util.PathTool; + +/** + * Describes a link that may be absolute or relative, and that is anchored to an absolute URI. + * + * @author ltheussl + * + * @since 1.2 + */ +public class URIPathDescriptor +{ + private final URI baseURI; + private final URI link; + + /** + * A URIPathDescriptor consists of a base URI and a link. + * Both arguments to this constructor have to be parsable to URIs. + * The baseURI parameter has to be absolute in the sense of {@link URI#isAbsolute()}. + * + * Before being parsed to {@link URI}s, the arguments are modified to catch + * some common bad practices: first all Windows-style backslashes '\' are replaced by + * forward slashes '/'. + * If the baseURI does not end with '/', a slash is appended. + * If the link starts with a '/', the first character is stripped. + * + * @param baseURI The base URI. Has to be a valid absolute URI. + * In addition, the path of the URI should not have any file part, + * ie http://maven.apache.org/ is valid, + * http://maven.apache.org/index.html is not. + * Even though the latter form is accepted without warning, + * the methods in this class will not return what is probably expected, + * because a slash is appended during construction, as noted above. + * @param link the link. This may be a relative link or an absolute link. + * Note that URIs that start with a "/", ie don't specify a scheme, are considered relative. + * + * @throws IllegalArgumentException if either argument is not parsable as a URI, + * or if baseURI is not absolute. + */ + public URIPathDescriptor( final String baseURI, final String link ) + { + final String llink = sanitizeLink( link ); + final String bbase = sanitizeBase( baseURI ); + + this.baseURI = URI.create( bbase ).normalize(); + this.link = URI.create( llink ).normalize(); + + if ( !this.baseURI.isAbsolute() ) + { + throw new IllegalArgumentException( "Base URI is not absolute: " + baseURI ); + } + } + + /** + * Return the base of this URIPathDescriptor as a URI. + * This is always {@link URI#normalize() normalized}. + * + * @return the normalized base URI. + */ + public URI getBaseURI() + { + return baseURI; + } + + /** + * Return the link of this URIPathDescriptor as a URI. + * This is always {@link URI#normalize() normalized}. + * + * @return the normalized link URI. + */ + public URI getLink() + { + return link; + } + + /** + * Resolve the link to the base. + * This always returns an absolute URI. If link is absolute, link is returned. + * + * @return the resolved link. This is equivalent to calling + * {@link #getBaseURI()}.{@link URI#resolve(java.net.URI) resolve}( {@link #getLink()} ). + */ + public URI resolveLink() + { + return baseURI.resolve( link ); + } + + /** + * Calculate the relative link with respect to the base. + * The original link is returned if either + * link is relative; + * or link and base do not share the {@link #sameSite(java.net.URI) same site}. + * + * @return the link as a relative URI. + */ + public URI relativizeLink() + { + return relativizeLink( baseURI.toString(), link ); + } + + // NOTE: URI.relativize does not work as expected, see + // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6226081 + private static URI relativizeLink( final String base, final URI link ) + { + if ( !link.isAbsolute() ) + { + return link; + } + + final URI newBaseURI = URI.create( base ); + + if ( !sameSite( link, newBaseURI ) ) + { + return link; + } + + final String relativePath = PathTool.getRelativeWebPath( newBaseURI.toString(), link.toString() ); + + return URI.create( correctRelativePath( relativePath ) ); + } + + /** + * Calculate the link as viewed from a different base. + * This returns the original link if link is absolute. + * This returns {@link #resolveLink()} if either + * newBase == null, + * or newBase is not parsable as a URI, + * or newBase and this {@link #getBaseURI()} do not share the + * {@link #sameSite(java.net.URI) same site}. + * + * @param newBase the new base URI. Has to be parsable as a URI. + *. + * @return a new relative link or the original link {@link #resolveLink() resolved}, + * i.e. as an absolute link, if the link cannot be re-based. + */ + public URI rebaseLink( final String newBase ) + { + if ( link.isAbsolute() ) + { + return link; + } + + if ( newBase == null ) + { + return resolveLink(); + } + + final URI newBaseURI; + + try + { + newBaseURI = new URI( newBase ); + } + catch ( URISyntaxException ex ) + { + return resolveLink(); + } + + if ( !sameSite( newBaseURI ) ) + { + return resolveLink(); + } + + final String relativeBasePath = PathTool.getRelativeWebPath( newBaseURI.getPath(), baseURI.getPath() ); + + return URI.create( correctRelativePath( relativeBasePath ) ).resolve( link ); + } + + private static String correctRelativePath( final String relativePath ) + { + if ( "".equals( relativePath ) || "/".equals( relativePath ) ) + { + return "./"; + } + else + { + return relativePath; + } + } + + /** + * Check if this URIPathDescriptor lives on the same site as the given URI. + * + * @param uri a URI to compare with. + * May be null, in which case false is returned. + * + * @return true if {@link #getBaseURI()} shares the same scheme, host and port with the given URI + * where null values are allowed. + */ + public boolean sameSite( final URI uri ) + { + return ( uri != null ) && sameSite( this.baseURI, uri ); + } + +private static boolean sameSite(final java.net.URI baseURI, final java.net.URI newBaseURI) { + final boolean sameScheme = (newBaseURI.getScheme() == null) ? false : baseURI.getScheme().equalsIgnoreCase(newBaseURI.getScheme()); + final boolean sameHost = /* NPEX_NULL_EXP */ + baseURI.getHost().equalsIgnoreCase(newBaseURI.getHost()); + final boolean samePort = baseURI.getPort() == newBaseURI.getPort(); + return (sameScheme && samePort) && sameHost; +} + + /** + * Construct a string representation of this URIPathDescriptor. + * This is equivalent to calling {@link #resolveLink()}.toString(). + * + * @return this URIPathDescriptor as a String. + */ + @Override + public String toString() + { + return resolveLink().toString(); + } + + private static String sanitizeBase( final String base ) + { + String sane = base.replace( '\\', '/' ); + + if ( !sane.endsWith( "/" ) ) + { + sane += "/"; + } + + return sane; + } + + private static String sanitizeLink( final String link ) + { + String sane = link.replace( '\\', '/' ); + + if ( sane.startsWith( "/" ) ) + { + sane = sane.substring( 1 ); + } + + return sane; + } +} diff --git a/Java/maven-doxia-sitetools-URIPathDescriptor_222/metadata.json b/Java/maven-doxia-sitetools-URIPathDescriptor_222/metadata.json new file mode 100644 index 000000000..76e8213d4 --- /dev/null +++ b/Java/maven-doxia-sitetools-URIPathDescriptor_222/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-doxia-sitetools-URIPathDescriptor_222", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/URIPathDescriptor.java", + "line": 221, + "npe_method": "sameSite", + "deref_field": "getHost", + "npe_class": "URIPathDescriptor", + "repo": "maven-doxia-sitetools", + "bug_id": "URIPathDescriptor_222" + } +} diff --git a/Java/maven-doxia-sitetools-URIPathDescriptor_222/npe.json b/Java/maven-doxia-sitetools-URIPathDescriptor_222/npe.json new file mode 100644 index 000000000..8db35eb63 --- /dev/null +++ b/Java/maven-doxia-sitetools-URIPathDescriptor_222/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/URIPathDescriptor.java", + "line": 221, + "npe_method": "sameSite", + "deref_field": "getHost", + "npe_class": "URIPathDescriptor" +} \ No newline at end of file