From 922cfc837a8b77d75b1657466c4a3155a704fc1f Mon Sep 17 00:00:00 2001 From: Henry Lee Date: Thu, 30 Nov 2023 19:57:05 +0900 Subject: [PATCH] refactor(java): maven-changelog-plugin --- .../Dockerfile | 18 + .../buggy.java | 230 ++ .../metadata.json | 21 + .../npe.json | 7 + .../Dockerfile | 18 + .../buggy.java | 230 ++ .../metadata.json | 21 + .../npe.json | 7 + .../Dockerfile | 18 + .../buggy.java | 1904 ++++++++++++++++ .../metadata.json | 21 + .../npe.json | 7 + .../Dockerfile | 18 + .../buggy.java | 1917 +++++++++++++++++ .../metadata.json | 21 + .../npe.json | 7 + .../Dockerfile | 18 + .../buggy.java | 1899 ++++++++++++++++ .../metadata.json | 21 + .../npe.json | 7 + .../Dockerfile | 18 + .../buggy.java | 1899 ++++++++++++++++ .../metadata.json | 21 + .../npe.json | 7 + .../Dockerfile | 18 + .../buggy.java | 1916 ++++++++++++++++ .../metadata.json | 21 + .../npe.json | 7 + 28 files changed, 10317 insertions(+) create mode 100644 Java/maven-changelog-plugin-ChangeLogHandler_114/Dockerfile create mode 100644 Java/maven-changelog-plugin-ChangeLogHandler_114/buggy.java create mode 100644 Java/maven-changelog-plugin-ChangeLogHandler_114/metadata.json create mode 100644 Java/maven-changelog-plugin-ChangeLogHandler_114/npe.json create mode 100644 Java/maven-changelog-plugin-ChangeLogHandler_130/Dockerfile create mode 100644 Java/maven-changelog-plugin-ChangeLogHandler_130/buggy.java create mode 100644 Java/maven-changelog-plugin-ChangeLogHandler_130/metadata.json create mode 100644 Java/maven-changelog-plugin-ChangeLogHandler_130/npe.json create mode 100644 Java/maven-changelog-plugin-ChangeLogReport_1319/Dockerfile create mode 100644 Java/maven-changelog-plugin-ChangeLogReport_1319/buggy.java create mode 100644 Java/maven-changelog-plugin-ChangeLogReport_1319/metadata.json create mode 100644 Java/maven-changelog-plugin-ChangeLogReport_1319/npe.json create mode 100644 Java/maven-changelog-plugin-ChangeLogReport_1527/Dockerfile create mode 100644 Java/maven-changelog-plugin-ChangeLogReport_1527/buggy.java create mode 100644 Java/maven-changelog-plugin-ChangeLogReport_1527/metadata.json create mode 100644 Java/maven-changelog-plugin-ChangeLogReport_1527/npe.json create mode 100644 Java/maven-changelog-plugin-ChangeLogReport_446/Dockerfile create mode 100644 Java/maven-changelog-plugin-ChangeLogReport_446/buggy.java create mode 100644 Java/maven-changelog-plugin-ChangeLogReport_446/metadata.json create mode 100644 Java/maven-changelog-plugin-ChangeLogReport_446/npe.json create mode 100644 Java/maven-changelog-plugin-ChangeLogReport_465/Dockerfile create mode 100644 Java/maven-changelog-plugin-ChangeLogReport_465/buggy.java create mode 100644 Java/maven-changelog-plugin-ChangeLogReport_465/metadata.json create mode 100644 Java/maven-changelog-plugin-ChangeLogReport_465/npe.json create mode 100644 Java/maven-changelog-plugin-ChangeLogReport_506/Dockerfile create mode 100644 Java/maven-changelog-plugin-ChangeLogReport_506/buggy.java create mode 100644 Java/maven-changelog-plugin-ChangeLogReport_506/metadata.json create mode 100644 Java/maven-changelog-plugin-ChangeLogReport_506/npe.json diff --git a/Java/maven-changelog-plugin-ChangeLogHandler_114/Dockerfile b/Java/maven-changelog-plugin-ChangeLogHandler_114/Dockerfile new file mode 100644 index 000000000..9a945184a --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogHandler_114/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-changelog-plugin + +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-changelog-plugin-ChangeLogHandler_114/buggy.java b/Java/maven-changelog-plugin-ChangeLogHandler_114/buggy.java new file mode 100644 index 000000000..3bc79f7d3 --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogHandler_114/buggy.java @@ -0,0 +1,230 @@ +package org.apache.maven.plugin.changelog; + +/* + * 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.scm.ChangeFile; +import org.apache.maven.scm.ChangeSet; +import org.apache.maven.scm.ScmTag; +import org.apache.maven.scm.command.changelog.ChangeLogSet; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Collection; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.TimeZone; +import java.util.regex.Pattern; + +/** + * Change log generated xml parser. SAXParser listener for processing a previously generated xml into several + * change log sets. + * + * @version $Id$ + */ +public class ChangeLogHandler + extends DefaultHandler +{ + // Use the same time zone offset when reading and adding times + // It doesn't matter which one we use, as long we always use the same one + private static final String TIMEZONE_STRING = "GMT-00:00"; + + private static final TimeZone TIMEZONE = TimeZone.getTimeZone( TIMEZONE_STRING ); + + private Collection changeSets; + + private String bufData = ""; + + private ChangeFile bufFile; + + private ChangeSet bufEntry; + + private List bufEntries; + + private ChangeLogSet bufSet; + + private String currentPattern; + + private final Pattern nameRegex = Pattern.compile( " \\(from [^:]+:\\d+\\)" ); + + /** + * contructor + * + * @param changeSets collection object to store all change sets found within the xml document + */ + public ChangeLogHandler( Collection changeSets ) + { + this.changeSets = changeSets; + } + + /** + * {@inheritDoc} + */ + public void characters( char[] ch, int start, int length ) + throws SAXException + { + bufData += new String( ch, start, length ); + } + + /** + * {@inheritDoc} + */ +/** + * {@inheritDoc } + */ +public void endElement(java.lang.String uri, java.lang.String localName, java.lang.String qName) throws org.xml.sax.SAXException { + if ("changeset".equals(qName)) { + changeSets.add(bufSet); + } + if ("changelog-entry".equals(qName)) { + bufEntries.add(bufEntry); + } + if ("file".equals(qName)) { + bufEntry.addFile(bufFile); + } else if ("date".equals(qName)) { + try { + long ms = 0; + { + ms = /* NPEX_NULL_EXP */ + bufEntry.getDate().getTime(); + } + bufEntry.setDate(new java.util.Date(ms + new java.text.SimpleDateFormat(currentPattern).parse(bufData).getTime())); + } catch (java.text.ParseException e) { + throw new org.xml.sax.SAXException(e); + } + } else if ("time".equals(qName)) { + try { + long ms = 0; + if (bufEntry.getDate() != null) { + ms = bufEntry.getDate().getTime(); + } + java.text.SimpleDateFormat simpleDateFormat = new java.text.SimpleDateFormat(currentPattern); + // MCHANGELOG-68 Adjust for time zone when parsing the time + simpleDateFormat.setTimeZone(org.apache.maven.plugin.changelog.ChangeLogHandler.TIMEZONE); + // Adjust for time zone when adding up the milliseconds + bufEntry.setDate(new java.util.Date((ms + simpleDateFormat.parse(bufData).getTime()) + org.apache.maven.plugin.changelog.ChangeLogHandler.TIMEZONE.getRawOffset())); + } catch (java.text.ParseException e) { + throw new org.xml.sax.SAXException(e); + } + } else if ("author".equals(qName)) { + bufEntry.setAuthor(bufData); + } else if ("msg".equals(qName)) { + bufEntry.setComment(bufData); + } + if ("revision".equals(qName)) { + bufFile.setRevision(bufData); + } else if ("name".equals(qName)) { + bufFile.setName(nameRegex.matcher(bufData).replaceFirst("")); + } +} + + /** + * {@inheritDoc} + */ + public void startElement( String uri, String localName, String qName, Attributes attributes ) + throws SAXException + { + bufData = ""; + + if ( "file".equals( qName ) ) + { + bufFile = new ChangeFile( "" ); + } + else if ( "changelog-entry".equals( qName ) ) + { + bufEntry = new ChangeSet(); + } + else if ( "date".equals( qName ) ) + { + currentPattern = attributes.getValue( "pattern" ); + if ( currentPattern == null ) + { + currentPattern = "yyyy-MM-dd"; + } + } + else if ( "time".equals( qName ) ) + { + currentPattern = attributes.getValue( "pattern" ); + if ( currentPattern == null ) + { + currentPattern = "HH:mm:ss"; + } + } + else if ( "changeset".equals( qName ) ) + { + bufEntries = new LinkedList(); + + currentPattern = attributes.getValue( "datePattern" ); + if ( currentPattern == null ) + { + currentPattern = "yyyy-MM-dd"; + } + + SimpleDateFormat formatter = new SimpleDateFormat( currentPattern ); + + String start = attributes.getValue( "start" ); + + String end = attributes.getValue( "end" ); + + Date startDate = null; + + Date endDate = null; + + if ( start != null ) + { + try + { + startDate = formatter.parse( start ); + } + catch ( ParseException e ) + { + throw new SAXException( "Can't parse start date '" + start + "'.", e ); + } + } + + if ( end != null ) + { + try + { + endDate = formatter.parse( end ); + } + catch ( ParseException e ) + { + throw new SAXException( "Can't parse end date '" + end + "'.", e ); + } + } + + bufSet = new ChangeLogSet( bufEntries, startDate, endDate ); + String startVersion = attributes.getValue( "startVersion" ); + if ( startVersion != null ) + { + bufSet.setStartVersion( new ScmTag( startVersion ) ); + } + String endVersion = attributes.getValue( "endVersion" ); + if ( endVersion != null ) + { + bufSet.setEndVersion( new ScmTag( endVersion ) ); + } + } + } +} diff --git a/Java/maven-changelog-plugin-ChangeLogHandler_114/metadata.json b/Java/maven-changelog-plugin-ChangeLogHandler_114/metadata.json new file mode 100644 index 000000000..d3c044df4 --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogHandler_114/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-changelog-plugin-ChangeLogHandler_114", + "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 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": "src/main/java/org/apache/maven/plugin/changelog/ChangeLogHandler.java", + "line": 109, + "npe_method": "endElement", + "deref_field": "getDate", + "npe_class": "ChangeLogHandler", + "repo": "maven-changelog-plugin", + "bug_id": "ChangeLogHandler_114" + } +} diff --git a/Java/maven-changelog-plugin-ChangeLogHandler_114/npe.json b/Java/maven-changelog-plugin-ChangeLogHandler_114/npe.json new file mode 100644 index 000000000..2d5c99e88 --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogHandler_114/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/maven/plugin/changelog/ChangeLogHandler.java", + "line": 109, + "npe_method": "endElement", + "deref_field": "getDate", + "npe_class": "ChangeLogHandler" +} \ No newline at end of file diff --git a/Java/maven-changelog-plugin-ChangeLogHandler_130/Dockerfile b/Java/maven-changelog-plugin-ChangeLogHandler_130/Dockerfile new file mode 100644 index 000000000..9a945184a --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogHandler_130/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-changelog-plugin + +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-changelog-plugin-ChangeLogHandler_130/buggy.java b/Java/maven-changelog-plugin-ChangeLogHandler_130/buggy.java new file mode 100644 index 000000000..833cc1aaa --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogHandler_130/buggy.java @@ -0,0 +1,230 @@ +package org.apache.maven.plugin.changelog; + +/* + * 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.scm.ChangeFile; +import org.apache.maven.scm.ChangeSet; +import org.apache.maven.scm.ScmTag; +import org.apache.maven.scm.command.changelog.ChangeLogSet; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Collection; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.TimeZone; +import java.util.regex.Pattern; + +/** + * Change log generated xml parser. SAXParser listener for processing a previously generated xml into several + * change log sets. + * + * @version $Id$ + */ +public class ChangeLogHandler + extends DefaultHandler +{ + // Use the same time zone offset when reading and adding times + // It doesn't matter which one we use, as long we always use the same one + private static final String TIMEZONE_STRING = "GMT-00:00"; + + private static final TimeZone TIMEZONE = TimeZone.getTimeZone( TIMEZONE_STRING ); + + private Collection changeSets; + + private String bufData = ""; + + private ChangeFile bufFile; + + private ChangeSet bufEntry; + + private List bufEntries; + + private ChangeLogSet bufSet; + + private String currentPattern; + + private final Pattern nameRegex = Pattern.compile( " \\(from [^:]+:\\d+\\)" ); + + /** + * contructor + * + * @param changeSets collection object to store all change sets found within the xml document + */ + public ChangeLogHandler( Collection changeSets ) + { + this.changeSets = changeSets; + } + + /** + * {@inheritDoc} + */ + public void characters( char[] ch, int start, int length ) + throws SAXException + { + bufData += new String( ch, start, length ); + } + + /** + * {@inheritDoc} + */ +/** + * {@inheritDoc } + */ +public void endElement(java.lang.String uri, java.lang.String localName, java.lang.String qName) throws org.xml.sax.SAXException { + if ("changeset".equals(qName)) { + changeSets.add(bufSet); + } + if ("changelog-entry".equals(qName)) { + bufEntries.add(bufEntry); + } + if ("file".equals(qName)) { + bufEntry.addFile(bufFile); + } else if ("date".equals(qName)) { + try { + long ms = 0; + if (bufEntry.getDate() != null) { + ms = /* NPEX_NULL_EXP */ + bufEntry.getDate().getTime(); + } + bufEntry.setDate(new java.util.Date(ms + new java.text.SimpleDateFormat(currentPattern).parse(bufData).getTime())); + } catch (java.text.ParseException e) { + throw new org.xml.sax.SAXException(e); + } + } else if ("time".equals(qName)) { + try { + long ms = 0; + { + ms = bufEntry.getDate().getTime(); + } + java.text.SimpleDateFormat simpleDateFormat = new java.text.SimpleDateFormat(currentPattern); + // MCHANGELOG-68 Adjust for time zone when parsing the time + simpleDateFormat.setTimeZone(org.apache.maven.plugin.changelog.ChangeLogHandler.TIMEZONE); + // Adjust for time zone when adding up the milliseconds + bufEntry.setDate(new java.util.Date((ms + simpleDateFormat.parse(bufData).getTime()) + org.apache.maven.plugin.changelog.ChangeLogHandler.TIMEZONE.getRawOffset())); + } catch (java.text.ParseException e) { + throw new org.xml.sax.SAXException(e); + } + } else if ("author".equals(qName)) { + bufEntry.setAuthor(bufData); + } else if ("msg".equals(qName)) { + bufEntry.setComment(bufData); + } + if ("revision".equals(qName)) { + bufFile.setRevision(bufData); + } else if ("name".equals(qName)) { + bufFile.setName(nameRegex.matcher(bufData).replaceFirst("")); + } +} + + /** + * {@inheritDoc} + */ + public void startElement( String uri, String localName, String qName, Attributes attributes ) + throws SAXException + { + bufData = ""; + + if ( "file".equals( qName ) ) + { + bufFile = new ChangeFile( "" ); + } + else if ( "changelog-entry".equals( qName ) ) + { + bufEntry = new ChangeSet(); + } + else if ( "date".equals( qName ) ) + { + currentPattern = attributes.getValue( "pattern" ); + if ( currentPattern == null ) + { + currentPattern = "yyyy-MM-dd"; + } + } + else if ( "time".equals( qName ) ) + { + currentPattern = attributes.getValue( "pattern" ); + if ( currentPattern == null ) + { + currentPattern = "HH:mm:ss"; + } + } + else if ( "changeset".equals( qName ) ) + { + bufEntries = new LinkedList(); + + currentPattern = attributes.getValue( "datePattern" ); + if ( currentPattern == null ) + { + currentPattern = "yyyy-MM-dd"; + } + + SimpleDateFormat formatter = new SimpleDateFormat( currentPattern ); + + String start = attributes.getValue( "start" ); + + String end = attributes.getValue( "end" ); + + Date startDate = null; + + Date endDate = null; + + if ( start != null ) + { + try + { + startDate = formatter.parse( start ); + } + catch ( ParseException e ) + { + throw new SAXException( "Can't parse start date '" + start + "'.", e ); + } + } + + if ( end != null ) + { + try + { + endDate = formatter.parse( end ); + } + catch ( ParseException e ) + { + throw new SAXException( "Can't parse end date '" + end + "'.", e ); + } + } + + bufSet = new ChangeLogSet( bufEntries, startDate, endDate ); + String startVersion = attributes.getValue( "startVersion" ); + if ( startVersion != null ) + { + bufSet.setStartVersion( new ScmTag( startVersion ) ); + } + String endVersion = attributes.getValue( "endVersion" ); + if ( endVersion != null ) + { + bufSet.setEndVersion( new ScmTag( endVersion ) ); + } + } + } +} diff --git a/Java/maven-changelog-plugin-ChangeLogHandler_130/metadata.json b/Java/maven-changelog-plugin-ChangeLogHandler_130/metadata.json new file mode 100644 index 000000000..14a833271 --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogHandler_130/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-changelog-plugin-ChangeLogHandler_130", + "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 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": "src/main/java/org/apache/maven/plugin/changelog/ChangeLogHandler.java", + "line": 109, + "npe_method": "endElement", + "deref_field": "getDate", + "npe_class": "ChangeLogHandler", + "repo": "maven-changelog-plugin", + "bug_id": "ChangeLogHandler_130" + } +} diff --git a/Java/maven-changelog-plugin-ChangeLogHandler_130/npe.json b/Java/maven-changelog-plugin-ChangeLogHandler_130/npe.json new file mode 100644 index 000000000..2d5c99e88 --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogHandler_130/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/maven/plugin/changelog/ChangeLogHandler.java", + "line": 109, + "npe_method": "endElement", + "deref_field": "getDate", + "npe_class": "ChangeLogHandler" +} \ No newline at end of file diff --git a/Java/maven-changelog-plugin-ChangeLogReport_1319/Dockerfile b/Java/maven-changelog-plugin-ChangeLogReport_1319/Dockerfile new file mode 100644 index 000000000..9a945184a --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogReport_1319/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-changelog-plugin + +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-changelog-plugin-ChangeLogReport_1319/buggy.java b/Java/maven-changelog-plugin-ChangeLogReport_1319/buggy.java new file mode 100644 index 000000000..bc5cd03f8 --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogReport_1319/buggy.java @@ -0,0 +1,1904 @@ +package org.apache.maven.plugin.changelog; + +/* + * 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.sink.Sink; +import org.apache.maven.doxia.siterenderer.Renderer; +import org.apache.maven.model.Developer; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; +import org.apache.maven.reporting.AbstractMavenReport; +import org.apache.maven.reporting.MavenReportException; +import org.apache.maven.scm.ChangeFile; +import org.apache.maven.scm.ChangeSet; +import org.apache.maven.scm.ScmBranch; +import org.apache.maven.scm.ScmException; +import org.apache.maven.scm.ScmFileSet; +import org.apache.maven.scm.ScmResult; +import org.apache.maven.scm.ScmRevision; +import org.apache.maven.scm.command.changelog.ChangeLogScmResult; +import org.apache.maven.scm.command.changelog.ChangeLogSet; +import org.apache.maven.scm.command.info.InfoItem; +import org.apache.maven.scm.command.info.InfoScmResult; +import org.apache.maven.scm.log.DefaultLog; +import org.apache.maven.scm.manager.ScmManager; +import org.apache.maven.scm.provider.ScmProvider; +import org.apache.maven.scm.provider.ScmProviderRepository; +import org.apache.maven.scm.provider.ScmProviderRepositoryWithHost; +import org.apache.maven.scm.provider.svn.repository.SvnScmProviderRepository; +import org.apache.maven.scm.provider.svn.svnexe.command.info.SvnInfoCommandExpanded; +import org.apache.maven.scm.repository.ScmRepository; +import org.apache.maven.settings.Server; +import org.apache.maven.settings.Settings; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.util.WriterFactory; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.StringReader; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.net.URLEncoder; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.ResourceBundle; +import java.util.StringTokenizer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Generate a changelog report. + * + * @version $Id$ + */ +@Mojo( name = "changelog" ) +public class ChangeLogReport + extends AbstractMavenReport +{ + /** + * A special token that represents the SCM relative path for a file. + * It can be used in displayFileDetailUrl. + */ + private static final String FILE_TOKEN = "%FILE%"; + + /** + * A special token that represents a Mantis/Bugzilla/JIRA/etc issue ID. + * It can be used in the issueLinkUrl. + */ + private static final String ISSUE_TOKEN = "%ISSUE%"; + + /** + * A special token that represents the SCM revision number. + * It can be used in displayChangeSetDetailUrl + * and displayFileRevDetailUrl. + */ + private static final String REV_TOKEN = "%REV%"; + + /** + * The number of days to use as a range, when this is not specified. + */ + private static final int DEFAULT_RANGE = 30; + + public static final String DEFAULT_ISSUE_ID_REGEX_PATTERN = "[a-zA-Z]{2,}-\\d+"; + + private static final String DEFAULT_ISSUE_LINK_URL = "https://issues.apache.org/jira/browse/" + ISSUE_TOKEN; + + /** + * Used to specify the format to use for the dates in the headings of the + * report. + * + * @since 2.1 + */ + @Parameter( property = "changelog.headingDateFormat", defaultValue = "yyyy-MM-dd" ) + private String headingDateFormat = "yyyy-MM-dd"; + + /** + * Used to specify whether to build the log using range, tag or date. + */ + @Parameter( property = "changelog.type", defaultValue = "range", required = true ) + private String type; + + /** + * Used to specify the number of days of log entries to retrieve. + */ + @Parameter( property = "changelog.range", defaultValue = "-1" ) + private int range; + + /** + * Used to specify the absolute date (or list of dates) to start log entries from. + */ + @Parameter + private List dates; + + /** + * Used to specify the tag (or list of tags) to start log entries from. + */ + @Parameter + private List tags; + + /** + * Used to specify the date format of the log entries that are retrieved from your SCM system. + */ + @Parameter( property = "changelog.dateFormat", defaultValue = "yyyy-MM-dd HH:mm:ss", required = true ) + private String dateFormat; + + /** + * Input dir. Directory where the files under SCM control are located. + */ + @Parameter( property = "basedir", required = true ) + private File basedir; + + /** + * Output file for xml document + */ + @Parameter( defaultValue = "${project.build.directory}/changelog.xml", required = true ) + private File outputXML; + + /** + * Allows the user to make changelog regenerate the changelog.xml file for the specified time in minutes. + */ + @Parameter( property = "outputXMLExpiration", defaultValue = "60", required = true ) + private int outputXMLExpiration; + + /** + * Comment format string used for interrogating + * the revision control system. + * Currently only used by the ClearcaseChangeLogGenerator. + */ + @Parameter( property = "changelog.commentFormat" ) + private String commentFormat; + + /** + * The file encoding when writing non-HTML reports. + */ + @Parameter( property = "changelog.outputEncoding", defaultValue = "${project.reporting.outputEncoding}" ) + private String outputEncoding; + + /** + * The user name (used by svn and starteam protocol). + */ + @Parameter( property = "username" ) + private String username; + + /** + * The user password (used by svn and starteam protocol). + */ + @Parameter( property = "password" ) + private String password; + + /** + * The private key (used by java svn). + */ + @Parameter( property = "privateKey" ) + private String privateKey; + + /** + * The passphrase (used by java svn). + */ + @Parameter( property = "passphrase" ) + private String passphrase; + + /** + * The url of tags base directory (used by svn protocol). + */ + @Parameter( property = "tagBase" ) + private String tagBase; + + /** + * The URL to view the scm. Basis for external links from the generated report. + */ + @Parameter( property = "project.scm.url" ) + protected String scmUrl; + + /** + * Skip the Changelog report generation. Most useful on the command line + * via "-Dchangelog.skip=true". + * + * @since 2.3 + */ + @Parameter( property = "changelog.skip", defaultValue = "false" ) + protected boolean skip; + + /** + * Encodes slashes in file uri. Required for some repository browsers like gitblit + * + * @since 2.3 + */ + @Parameter( property = "encodeFileUri", defaultValue = "false" ) + protected boolean encodeFileUri; + + /** + * List of files to include. Specified as fileset patterns of files to include in the report + * + * @since 2.3 + */ + @Parameter + private String[] includes; + + /** + * List of files to include. Specified as fileset patterns of files to omit in the report + * + * @since 2.3 + */ + @Parameter + private String[] excludes; + + + /** + * The Maven Project Object + */ + @Parameter( defaultValue = "${project}", readonly = true, required = true ) + private MavenProject project; + + /** + * The directory where the report will be generated + */ + @Parameter( property = "project.reporting.outputDirectory", required = true, readonly = true ) + private File outputDirectory; + + /** + */ + @Component + private Renderer siteRenderer; + + /** + */ + @Parameter( property = "settings.offline", required = true, readonly = true ) + private boolean offline; + + /** + */ + @Component + private ScmManager manager; + + /** + */ + @Parameter( defaultValue = "${settings}", readonly = true, required = true ) + private Settings settings; + + /** + * Allows the user to choose which scm connection to use when connecting to the scm. + * Can either be "connection" or "developerConnection". + */ + @Parameter( defaultValue = "connection", required = true ) + private String connectionType; + + /** + * A template string that is used to create the URL to the file details. + * There is a special token that you can use in your template: + *
    + *
  • %FILE% - this is the path to a file
  • + *
+ *

+ * Example: + * http://checkstyle.cvs.sourceforge.net/checkstyle%FILE%?view=markup + *

+ *

+ * Note: If you don't supply the token in your template, + * the path of the file will simply be appended to your template URL. + *

+ */ + @Parameter( property = "displayFileDetailUrl", defaultValue = "${project.scm.url}" ) + protected String displayFileDetailUrl; + + /** + * A pattern used to identify 'issue tracker' IDs such as those used by JIRA, + * Bugzilla and alike in the SCM commit messages. Any matched patterns + * are replaced with issueLinkUrl URL. The default + * value is a JIRA-style issue identification pattern. + *

+ * Note: Default value is [a-zA-Z]{2,}-\d+ + * + * @since 2.2 + */ + @Parameter( property = "issueIDRegexPattern", defaultValue = DEFAULT_ISSUE_ID_REGEX_PATTERN, required = true ) + private String issueIDRegexPattern; + + /** + * The issue tracker URL used when replacing any matched issueIDRegexPattern + * found in the SCM commit messages. The default is URL is the codehaus JIRA + * URL. If %ISSUE% is found in the URL it is replaced with the matched issue ID, + * otherwise the matched issue ID is appended to the URL. + * + * @since 2.2 + */ + @Parameter( property = "issueLinkUrl", defaultValue = DEFAULT_ISSUE_LINK_URL, required = true ) + private String issueLinkUrl; + + /** + * A template string that is used to create the changeset URL. + *

+ * If not defined no change set link will be created. + *

+ * There is one special token that you can use in your template: + *

    + *
  • %REV% - this is the changeset revision
  • + *
+ *

+ * Example: + * http://fisheye.sourceforge.net/changelog/a-project/?cs=%REV% + *

+ *

+ * Note: If you don't supply the %REV% token in your template, + * the revision will simply be appended to your template URL. + *

+ * + * @since 2.2 + */ + @Parameter( property = "displayChangeSetDetailUrl" ) + protected String displayChangeSetDetailUrl; + + /** + * A template string that is used to create the revision aware URL to + * the file details in a similar fashion to the displayFileDetailUrl. + * When a report contains both file and file revision information, as in the + * Change Log report, this template string can be used to create a revision + * aware URL to the file details. + *

+ * If not defined this template string defaults to the same value as the + * displayFileDetailUrl and thus revision number aware links will + * not be used. + *

+ * There are two special tokens that you can use in your template: + *

    + *
  • %FILE% - this is the path to a file
  • + *
  • %REV% - this is the revision of the file
  • + *
+ *

+ * Example: + * http://fisheye.sourceforge.net/browse/a-project/%FILE%?r=%REV% + *

+ *

+ * Note: If you don't supply the %FILE% token in your template, + * the path of the file will simply be appended to your template URL. + *

+ * + * @since 2.2 + */ + @Parameter( property = "displayFileRevDetailUrl" ) + protected String displayFileRevDetailUrl; + + /** + * List of developers to be shown on the report. + * + * @since 2.2 + */ + @Parameter( property = "project.developers" ) + protected List developers; + + /** + * List of provider implementations. + */ + @Parameter + private Map providerImplementations; + + // temporary field holder while generating the report + private String rptRepository, rptOneRepoParam, rptMultiRepoParam; + + // field for SCM Connection URL + private String connection; + + // field used to hold a map of the developers by Id + private HashMap developersById; + + // field used to hold a map of the developers by Name + private HashMap developersByName; + + /** + * The system properties to use (needed by the perforce scm provider). + */ + @Parameter + private Properties systemProperties; + + private final Pattern sinkFileNamePattern = Pattern.compile( "\\\\" ); + + /** + * {@inheritDoc} + */ + public void executeReport( Locale locale ) + throws MavenReportException + { + //check if sources exists <-- required for parent poms + if ( !basedir.exists() ) + { + doGenerateEmptyReport( getBundle( locale ), getSink() ); + + return; + } + + if ( providerImplementations != null ) + { + for ( Map.Entry entry : providerImplementations.entrySet() ) + { + String providerType = entry.getKey(); + String providerImplementation = entry.getValue(); + getLog().info( + "Change the default '" + providerType + "' provider implementation to '" + providerImplementation + + "'." ); + manager.setScmProviderImplementation( providerType, providerImplementation ); + } + } + + initializeDefaultConfigurationParameters(); + + initializeDeveloperMaps(); + + verifySCMTypeParams(); + + if ( systemProperties != null ) + { + // Add all system properties configured by the user + Iterator iter = systemProperties.keySet().iterator(); + + while ( iter.hasNext() ) + { + String key = (String) iter.next(); + + String value = systemProperties.getProperty( key ); + + System.setProperty( key, value ); + + getLog().debug( "Setting system property: " + key + '=' + value ); + } + } + + doGenerateReport( getChangedSets(), getBundle( locale ), getSink() ); + } + + /** + * Initializes any configuration parameters that have not/can not be defined + * or defaulted by the Mojo API. + */ + private void initializeDefaultConfigurationParameters() + { + if ( displayFileRevDetailUrl == null || displayFileRevDetailUrl.length() == 0 ) + { + displayFileRevDetailUrl = displayFileDetailUrl; + } + } + + /** + * Creates maps of the project developers by developer Id and developer Name + * for quick lookups. + */ + private void initializeDeveloperMaps() + { + developersById = new HashMap(); + developersByName = new HashMap(); + + if ( developers != null ) + { + for ( Developer developer : developers ) + { + developersById.put( developer.getId(), developer ); + developersByName.put( developer.getName(), developer ); + } + } + } + + /** + * populates the changedSets field by either connecting to the SCM or using an existing XML generated in a previous + * run of the report + * + * @throws MavenReportException + */ + protected List getChangedSets() + throws MavenReportException + { + List changelogList = null; + + if ( !outputXML.isAbsolute() ) + { + outputXML = new File( project.getBasedir(), outputXML.getPath() ); + } + + if ( outputXML.exists() ) + { + // CHECKSTYLE_OFF: MagicNumber + if ( outputXMLExpiration > 0 + && outputXMLExpiration * 60000 > System.currentTimeMillis() - outputXML.lastModified() ) + // CHECKSTYLE_ON: MagicNumber + { + try + { + //ReaderFactory.newReader( outputXML, getOutputEncoding() ); + //FileInputStream fIn = new FileInputStream( outputXML ); + + getLog().info( "Using existing changelog.xml..." ); + + changelogList = + ChangeLog.loadChangedSets( ReaderFactory.newReader( outputXML, getOutputEncoding() ) ); + } + catch ( FileNotFoundException e ) + { + //do nothing, just regenerate + } + catch ( Exception e ) + { + throw new MavenReportException( "An error occurred while parsing " + outputXML.getAbsolutePath(), + e ); + } + } + } + + if ( changelogList == null ) + { + if ( offline ) + { + throw new MavenReportException( "This report requires online mode." ); + } + + getLog().info( "Generating changed sets xml to: " + outputXML.getAbsolutePath() ); + + changelogList = generateChangeSetsFromSCM(); + + try + { + writeChangelogXml( changelogList ); + } + catch ( FileNotFoundException e ) + { + throw new MavenReportException( "Can't create " + outputXML.getAbsolutePath(), e ); + } + catch ( UnsupportedEncodingException e ) + { + throw new MavenReportException( "Can't create " + outputXML.getAbsolutePath(), e ); + } + catch ( IOException e ) + { + throw new MavenReportException( "Can't create " + outputXML.getAbsolutePath(), e ); + } + } + + return changelogList; + } + + private void writeChangelogXml( List changelogList ) + throws IOException + { + StringBuilder changelogXml = new StringBuilder(); + + changelogXml.append( "\n" ); + changelogXml.append( "" ); + + for ( ChangeLogSet changelogSet : changelogList ) + { + changelogXml.append( "\n " ); + + String changeset = changelogSet.toXML( getOutputEncoding() ); + + //remove xml header + if ( changeset.startsWith( "" ) + 2; + changeset = changeset.substring( idx ); + } + + changelogXml.append( changeset ); + } + + changelogXml.append( "\n" ); + + outputXML.getParentFile().mkdirs(); + + //PrintWriter pw = new PrintWriter( new BufferedOutputStream( new FileOutputStream( outputXML ) ) ); + //pw.write( changelogXml.toString() ); + //pw.flush(); + //pw.close(); + // MCHANGELOG-86 + Writer writer = WriterFactory.newWriter( new BufferedOutputStream( new FileOutputStream( outputXML ) ), + getOutputEncoding() ); + writer.write( changelogXml.toString() ); + writer.flush(); + writer.close(); + } + + /** + * creates a ChangeLog object and then connects to the SCM to generate the changed sets + * + * @return changedlogsets generated from the SCM + * @throws MavenReportException + */ + protected List generateChangeSetsFromSCM() + throws MavenReportException + { + try + { + List changeSets = new ArrayList(); + + ScmRepository repository = getScmRepository(); + + ScmProvider provider = manager.getProviderByRepository( repository ); + + ChangeLogScmResult result; + + if ( "range".equals( type ) ) + { + result = provider.changeLog( repository, new ScmFileSet( basedir ), null, null, range, (ScmBranch) null, + dateFormat ); + + checkResult( result ); + + changeSets.add( result.getChangeLog() ); + } + else if ( "tag".equals( type ) ) + { + + Iterator tagsIter = tags.iterator(); + + String startTag = tagsIter.next(); + String endTag = null; + + if ( tagsIter.hasNext() ) + { + while ( tagsIter.hasNext() ) + { + endTag = tagsIter.next(); + String endRevision = getRevisionForTag( endTag, repository, provider ); + String startRevision = getRevisionForTag( startTag, repository, provider ); + result = provider.changeLog( repository, new ScmFileSet( basedir ), + new ScmRevision( startRevision ), + new ScmRevision( endRevision ) ); + + checkResult( result ); + result.getChangeLog().setStartVersion( new ScmRevision( startTag ) ); + result.getChangeLog().setEndVersion( new ScmRevision( endTag ) ); + + changeSets.add( result.getChangeLog() ); + + startTag = endTag; + } + } + else + { + String startRevision = getRevisionForTag( startTag, repository, provider ); + String endRevision = getRevisionForTag( endTag, repository, provider ); + result = provider.changeLog( repository, new ScmFileSet( basedir ), + new ScmRevision( startRevision ), + new ScmRevision( endRevision ) ); + + checkResult( result ); + result.getChangeLog().setStartVersion( new ScmRevision( startTag ) ); + result.getChangeLog().setEndVersion( null ); + changeSets.add( result.getChangeLog() ); + } + } + else if ( "date".equals( type ) ) + { + Iterator dateIter = dates.iterator(); + + String startDate = dateIter.next(); + String endDate = null; + + if ( dateIter.hasNext() ) + { + while ( dateIter.hasNext() ) + { + endDate = dateIter.next(); + + result = provider.changeLog( repository, new ScmFileSet( basedir ), parseDate( startDate ), + parseDate( endDate ), 0, (ScmBranch) null ); + + checkResult( result ); + + changeSets.add( result.getChangeLog() ); + + startDate = endDate; + } + } + else + { + result = provider.changeLog( repository, new ScmFileSet( basedir ), parseDate( startDate ), + parseDate( endDate ), 0, (ScmBranch) null ); + + checkResult( result ); + + changeSets.add( result.getChangeLog() ); + } + } + else + { + throw new MavenReportException( "The type '" + type + "' isn't supported." ); + } + filter( changeSets ); + return changeSets; + + } + catch ( ScmException e ) + { + throw new MavenReportException( "Cannot run changelog command : ", e ); + } + catch ( MojoExecutionException e ) + { + throw new MavenReportException( "An error has occurred during changelog command : ", e ); + } + } + + /** + * Resolves the given tag to the revision number. + * + * @param tag + * @param repository + * @param provider + * @return + * @throws ScmException + */ + private String getRevisionForTag( final String tag, final ScmRepository repository, final ScmProvider provider ) + throws ScmException + { + if ( repository.getProvider().equals( "svn" ) ) + { + if ( tag == null ) + { + return "HEAD"; + } + SvnInfoCommandExpanded infoCommand = new SvnInfoCommandExpanded(); + infoCommand.setLogger( new DefaultLog() ); + + InfoScmResult infoScmResult = + infoCommand.executeInfoTagCommand( (SvnScmProviderRepository) repository.getProviderRepository(), + new ScmFileSet( basedir ), tag, null, false, null ); + if ( infoScmResult.getInfoItems().size() == 0 ) + { + throw new ScmException( "There is no tag named '" + tag + "' in the Subversion repository." ); + } + InfoItem infoItem = infoScmResult.getInfoItems().get( 0 ); + String revision = infoItem.getLastChangedRevision(); + getLog().info( String.format( "Resolved tag '%s' to revision '%s'", tag, revision ) ); + return revision; + } + return tag; + } + + /** + * filters out unwanted files from the changesets + */ + private void filter( List changeSets ) + { + List include = compilePatterns( includes ); + List exclude = compilePatterns( excludes ); + if ( includes == null && excludes == null ) + { + return; + } + for ( ChangeLogSet changeLogSet : changeSets ) + { + List set = changeLogSet.getChangeSets(); + filter( set, include, exclude ); + } + + } + + private List compilePatterns( String[] patternArray ) + { + if ( patternArray == null ) + { + return new ArrayList(); + } + List patterns = new ArrayList( patternArray.length ); + for ( String string : patternArray ) + { + //replaces * with [/\]* (everything but file seperators) + //replaces ** with .* + //quotes the rest of the string + string = "\\Q" + string + "\\E"; + string = string.replace( "**", "\\E.?REPLACEMENT?\\Q" ); + string = string.replace( "*", "\\E[^/\\\\]?REPLACEMENT?\\Q" ); + string = string.replace( "?REPLACEMENT?", "*" ); + string = string.replace( "\\Q\\E", "" ); + patterns.add( Pattern.compile( string ) ); + } + return patterns; + } + + private void filter( List sets, List includes, List excludes ) + { + Iterator it = sets.iterator(); + while ( it.hasNext() ) + { + ChangeSet changeSet = it.next(); + List files = changeSet.getFiles(); + Iterator iterator = files.iterator(); + while ( iterator.hasNext() ) + { + ChangeFile changeFile = iterator.next(); + String name = changeFile.getName(); + if ( !isIncluded( includes, name ) || isExcluded( excludes, name ) ) + { + iterator.remove(); + } + } + if ( files.isEmpty() ) + { + it.remove(); + } + } + } + + private boolean isExcluded( List excludes, String name ) + { + if ( excludes == null || excludes.isEmpty() ) + { + return false; + } + for ( Pattern pattern : excludes ) + { + if ( pattern.matcher( name ).matches() ) + { + return true; + } + } + return false; + } + + private boolean isIncluded( List includes, String name ) + { + if ( includes == null || includes.isEmpty() ) + { + return true; + } + for ( Pattern pattern : includes ) + { + if ( pattern.matcher( name ).matches() ) + { + return true; + } + } + return false; + } + + /** + * Converts the localized date string pattern to date object. + * + * @return A date + */ + private Date parseDate( String date ) + throws MojoExecutionException + { + if ( date == null || date.trim().length() == 0 ) + { + return null; + } + + SimpleDateFormat formatter = new SimpleDateFormat( "yyyy-MM-dd" ); + + try + { + return formatter.parse( date ); + } + catch ( ParseException e ) + { + throw new MojoExecutionException( "Please use this date pattern: " + formatter.toLocalizedPattern(), e ); + } + } + + public ScmRepository getScmRepository() + throws ScmException + { + ScmRepository repository; + + try + { + repository = manager.makeScmRepository( getConnection() ); + + ScmProviderRepository providerRepo = repository.getProviderRepository(); + + if ( !StringUtils.isEmpty( username ) ) + { + providerRepo.setUser( username ); + } + + if ( !StringUtils.isEmpty( password ) ) + { + providerRepo.setPassword( password ); + } + + if ( repository.getProviderRepository() instanceof ScmProviderRepositoryWithHost ) + { + ScmProviderRepositoryWithHost repo = (ScmProviderRepositoryWithHost) repository.getProviderRepository(); + + loadInfosFromSettings( repo ); + + if ( !StringUtils.isEmpty( username ) ) + { + repo.setUser( username ); + } + + if ( !StringUtils.isEmpty( password ) ) + { + repo.setPassword( password ); + } + + if ( !StringUtils.isEmpty( privateKey ) ) + { + repo.setPrivateKey( privateKey ); + } + + if ( !StringUtils.isEmpty( passphrase ) ) + { + repo.setPassphrase( passphrase ); + } + } + + if ( !StringUtils.isEmpty( tagBase ) && repository.getProvider().equals( "svn" ) ) + { + SvnScmProviderRepository svnRepo = (SvnScmProviderRepository) repository.getProviderRepository(); + + svnRepo.setTagBase( tagBase ); + } + } + catch ( Exception e ) + { + throw new ScmException( "Can't load the scm provider.", e ); + } + + return repository; + } + + /** + * Load username password from settings if user has not set them in JVM properties + * + * @param repo + */ + private void loadInfosFromSettings( ScmProviderRepositoryWithHost repo ) + { + if ( username == null || password == null ) + { + String host = repo.getHost(); + + int port = repo.getPort(); + + if ( port > 0 ) + { + host += ":" + port; + } + + Server server = this.settings.getServer( host ); + + if ( server != null ) + { + if ( username == null ) + { + username = this.settings.getServer( host ).getUsername(); + } + + if ( password == null ) + { + password = this.settings.getServer( host ).getPassword(); + } + + if ( privateKey == null ) + { + privateKey = this.settings.getServer( host ).getPrivateKey(); + } + + if ( passphrase == null ) + { + passphrase = this.settings.getServer( host ).getPassphrase(); + } + } + } + } + + public void checkResult( ScmResult result ) + throws MojoExecutionException + { + if ( !result.isSuccess() ) + { + getLog().error( "Provider message:" ); + + getLog().error( result.getProviderMessage() == null ? "" : result.getProviderMessage() ); + + getLog().error( "Command output:" ); + + getLog().error( result.getCommandOutput() == null ? "" : result.getCommandOutput() ); + + throw new MojoExecutionException( "Command failed." ); + } + } + + /** + * used to retrieve the SCM connection string + * + * @return the url string used to connect to the SCM + * @throws MavenReportException when there is insufficient information to retrieve the SCM connection string + */ + protected String getConnection() + throws MavenReportException + { + if ( this.connection != null ) + { + return connection; + } + + if ( project.getScm() == null ) + { + throw new MavenReportException( "SCM Connection is not set." ); + } + + String scmConnection = project.getScm().getConnection(); + if ( StringUtils.isNotEmpty( scmConnection ) && "connection".equals( connectionType.toLowerCase() ) ) + { + connection = scmConnection; + } + + String scmDeveloper = project.getScm().getDeveloperConnection(); + if ( StringUtils.isNotEmpty( scmDeveloper ) && "developerconnection".equals( connectionType.toLowerCase() ) ) + { + connection = scmDeveloper; + } + + if ( StringUtils.isEmpty( connection ) ) + { + throw new MavenReportException( "SCM Connection is not set." ); + } + + return connection; + } + + /** + * checks whether there are enough configuration parameters to generate the report + * + * @throws MavenReportException when there is insufficient paramters to generate the report + */ + private void verifySCMTypeParams() + throws MavenReportException + { + if ( "range".equals( type ) ) + { + if ( range == -1 ) + { + range = DEFAULT_RANGE; + } + } + else if ( "date".equals( type ) ) + { + if ( dates == null ) + { + throw new MavenReportException( "The dates parameter is required when type=\"date\". The value " + + "should be the absolute date for the start of the log." ); + } + } + else if ( "tag".equals( type ) ) + { + if ( tags == null ) + { + throw new MavenReportException( "The tags parameter is required when type=\"tag\"." ); + } + } + else + { + throw new MavenReportException( "The type parameter has an invalid value: " + type + + ". The value should be \"range\", \"date\", or \"tag\"." ); + } + } + + /** + * generates an empty report in case there are no sources to generate a report with + * + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + protected void doGenerateEmptyReport( ResourceBundle bundle, Sink sink ) + { + sink.head(); + sink.title(); + sink.text( bundle.getString( "report.changelog.header" ) ); + sink.title_(); + sink.head_(); + + sink.body(); + sink.section1(); + + sink.sectionTitle1(); + sink.text( bundle.getString( "report.changelog.mainTitle" ) ); + sink.sectionTitle1_(); + + sink.paragraph(); + sink.text( bundle.getString( "report.changelog.nosources" ) ); + sink.paragraph_(); + + sink.section1_(); + + sink.body_(); + sink.flush(); + sink.close(); + } + + /** + * method that generates the report for this mojo. This method is overridden by dev-activity and file-activity mojo + * + * @param changeLogSets changed sets to generate the report from + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + protected void doGenerateReport( List changeLogSets, ResourceBundle bundle, Sink sink ) + { + sink.head(); + sink.title(); + sink.text( bundle.getString( "report.changelog.header" ) ); + sink.title_(); + sink.head_(); + + sink.body(); + sink.section1(); + + sink.sectionTitle1(); + sink.text( bundle.getString( "report.changelog.mainTitle" ) ); + sink.sectionTitle1_(); + + // Summary section + doSummarySection( changeLogSets, bundle, sink ); + + for ( ChangeLogSet changeLogSet : changeLogSets ) + { + doChangedSet( changeLogSet, bundle, sink ); + } + + sink.section1_(); + sink.body_(); + + sink.flush(); + sink.close(); + } + + /** + * generates the report summary section of the report + * + * @param changeLogSets changed sets to generate the report from + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + private void doSummarySection( List changeLogSets, ResourceBundle bundle, Sink sink ) + { + sink.paragraph(); + + sink.text( bundle.getString( "report.changelog.ChangedSetsTotal" ) ); + sink.text( ": " + changeLogSets.size() ); + + sink.paragraph_(); + } + + /** + * generates a section of the report referring to a changeset + * + * @param set the current ChangeSet to generate this section of the report + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + private void doChangedSet( ChangeLogSet set, ResourceBundle bundle, Sink sink ) + { + sink.section2(); + + doChangeSetTitle( set, bundle, sink ); + + doSummary( set, bundle, sink ); + + doChangedSetTable( set.getChangeSets(), bundle, sink ); + + sink.section2_(); + } + + /** + * Generate the title for the report. + * + * @param set change set to generate the report from + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + protected void doChangeSetTitle( ChangeLogSet set, ResourceBundle bundle, Sink sink ) + { + sink.sectionTitle2(); + + SimpleDateFormat headingDateFormater = new SimpleDateFormat( headingDateFormat ); + + if ( "tag".equals( type ) ) + { + if ( set.getStartVersion() == null || set.getStartVersion().getName() == null ) + { + sink.text( bundle.getString( "report.SetTagCreation" ) ); + if ( set.getEndVersion() != null && set.getEndVersion().getName() != null ) + { + sink.text( ' ' + bundle.getString( "report.SetTagUntil" ) + " '" + set.getEndVersion() + '\'' ); + } + } + else if ( set.getEndVersion() == null || set.getEndVersion().getName() == null ) + { + sink.text( bundle.getString( "report.SetTagSince" ) ); + sink.text( " '" + set.getStartVersion() + '\'' ); + } + else + { + sink.text( bundle.getString( "report.SetTagBetween" ) ); + sink.text( + " '" + set.getStartVersion() + "' " + bundle.getString( "report.And" ) + " '" + set.getEndVersion() + + '\'' ); + } + } + else if ( set.getStartDate() == null ) + { + sink.text( bundle.getString( "report.SetRangeUnknown" ) ); + } + else if ( set.getEndDate() == null ) + { + sink.text( bundle.getString( "report.SetRangeSince" ) ); + sink.text( ' ' + headingDateFormater.format( set.getStartDate() ) ); + } + else + { + sink.text( bundle.getString( "report.SetRangeBetween" ) ); + sink.text( + ' ' + headingDateFormater.format( set.getStartDate() ) + ' ' + bundle.getString( "report.And" ) + ' ' + + headingDateFormater.format( set.getEndDate() ) ); + } + sink.sectionTitle2_(); + } + + /** + * Generate the summary section of the report. + * + * @param set change set to generate the report from + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + protected void doSummary( ChangeLogSet set, ResourceBundle bundle, Sink sink ) + { + sink.paragraph(); + sink.text( bundle.getString( "report.TotalCommits" ) ); + sink.text( ": " + set.getChangeSets().size() ); + sink.lineBreak(); + sink.text( bundle.getString( "report.changelog.FilesChanged" ) ); + sink.text( ": " + countFilesChanged( set.getChangeSets() ) ); + sink.paragraph_(); + } + + /** + * counts the number of files that were changed in the specified SCM + * + * @param entries a collection of SCM changes + * @return number of files changed for the changedsets + */ +/** + * counts the number of files that were changed in the specified SCM + * + * @param entries + * a collection of SCM changes + * @return number of files changed for the changedsets + */ +protected long countFilesChanged(java.util.Collection entries) { + if (entries == null) { + return 0; + } + if (entries.isEmpty()) { + return 0; + } + java.util.HashMap> fileList = new java.util.HashMap>(); + for (org.apache.maven.scm.ChangeSet entry : entries) { + for (org.apache.maven.scm.ChangeFile file : entry.getFiles()) { + java.lang.String name = file.getName(); + java.util.List list = fileList.get(name); + { + /* NPEX_NULL_EXP */ + list.add(file); + } + } + } + return fileList.size(); +} + + /** + * generates the report table showing the SCM log entries + * + * @param entries a list of change log entries to generate the report from + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + private void doChangedSetTable( Collection entries, ResourceBundle bundle, Sink sink ) + { + sink.table(); + + sink.tableRow(); + sink.tableHeaderCell(); + sink.text( bundle.getString( "report.changelog.timestamp" ) ); + sink.tableHeaderCell_(); + sink.tableHeaderCell(); + sink.text( bundle.getString( "report.changelog.author" ) ); + sink.tableHeaderCell_(); + sink.tableHeaderCell(); + sink.text( bundle.getString( "report.changelog.details" ) ); + sink.tableHeaderCell_(); + sink.tableRow_(); + + initReportUrls(); + + List sortedEntries = new ArrayList( entries ); + Collections.sort( sortedEntries, new Comparator() + { + public int compare( ChangeSet changeSet0, ChangeSet changeSet1 ) + { + return changeSet1.getDate().compareTo( changeSet0.getDate() ); + } + } ); + + for ( ChangeSet entry : sortedEntries ) + { + doChangedSetDetail( entry, sink ); + } + + sink.table_(); + } + + /** + * reports on the details of an SCM entry log + * + * @param entry an SCM entry to generate the report from + * @param sink the report formatting tool + */ + private void doChangedSetDetail( ChangeSet entry, Sink sink ) + { + sink.tableRow(); + + sink.tableCell(); + sink.text( entry.getDateFormatted() + ' ' + entry.getTimeFormatted() ); + sink.tableCell_(); + + sink.tableCell(); + + sinkAuthorDetails( sink, entry.getAuthor() ); + + sink.tableCell_(); + + sink.tableCell(); + //doRevision( entry.getFiles(), bundle, sink ); + doChangedFiles( entry.getFiles(), sink ); + sink.lineBreak(); + StringReader sr = new StringReader( entry.getComment() ); + BufferedReader br = new BufferedReader( sr ); + String line; + try + { + if ( ( issueIDRegexPattern != null && issueIDRegexPattern.length() > 0 ) && ( issueLinkUrl != null + && issueLinkUrl.length() > 0 ) ) + { + Pattern pattern = Pattern.compile( issueIDRegexPattern ); + + line = br.readLine(); + + while ( line != null ) + { + sinkIssueLink( sink, line, pattern ); + + line = br.readLine(); + if ( line != null ) + { + sink.lineBreak(); + } + } + } + else + { + line = br.readLine(); + + while ( line != null ) + { + sink.text( line ); + line = br.readLine(); + if ( line != null ) + { + sink.lineBreak(); + } + } + } + } + catch ( IOException e ) + { + getLog().warn( "Unable to read the comment of a ChangeSet as a stream." ); + } + finally + { + try + { + br.close(); + } + catch ( IOException e ) + { + getLog().warn( "Unable to close a reader." ); + } + sr.close(); + } + sink.tableCell_(); + + sink.tableRow_(); + } + + private void sinkIssueLink( Sink sink, String line, Pattern pattern ) + { + // replace any ticket patterns found. + + Matcher matcher = pattern.matcher( line ); + + int currLoc = 0; + + while ( matcher.find() ) + { + String match = matcher.group(); + + String link; + + if ( issueLinkUrl.indexOf( ISSUE_TOKEN ) > 0 ) + { + link = issueLinkUrl.replaceAll( ISSUE_TOKEN, match ); + } + else + { + if ( issueLinkUrl.endsWith( "/" ) ) + { + link = issueLinkUrl; + } + else + { + link = issueLinkUrl + '/'; + } + + link += match; + } + + int startOfMatch = matcher.start(); + + String unmatchedText = line.substring( currLoc, startOfMatch ); + + currLoc = matcher.end(); + + sink.text( unmatchedText ); + + sink.link( link ); + sink.text( match ); + sink.link_(); + } + + sink.text( line.substring( currLoc ) ); + } + + /** + * If the supplied author is a known developer this method outputs a + * link to the team members report, or alternatively, if the supplied + * author is unknown, outputs the author's name as plain text. + * + * @param sink Sink to use for outputting + * @param author The author's name. + */ + protected void sinkAuthorDetails( Sink sink, String author ) + { + Developer developer = developersById.get( author ); + + if ( developer == null ) + { + developer = developersByName.get( author ); + } + + if ( developer != null ) + { + sink.link( "team-list.html#" + developer.getId() ); + sink.text( developer.getName() ); + sink.link_(); + } + else + { + sink.text( author ); + } + } + + /** + * populates the report url used to create links from certain elements of the report + */ + protected void initReportUrls() + { + if ( scmUrl != null ) + { + int idx = scmUrl.indexOf( '?' ); + + if ( idx > 0 ) + { + rptRepository = scmUrl.substring( 0, idx ); + + if ( scmUrl.equals( displayFileDetailUrl ) ) + { + String rptTmpMultiRepoParam = scmUrl.substring( rptRepository.length() ); + + rptOneRepoParam = '?' + rptTmpMultiRepoParam.substring( 1 ); + + rptMultiRepoParam = '&' + rptTmpMultiRepoParam.substring( 1 ); + } + } + else + { + rptRepository = scmUrl; + + rptOneRepoParam = ""; + + rptMultiRepoParam = ""; + } + } + } + + /** + * generates the section of the report listing all the files revisions + * + * @param files list of files to generate the reports from + * @param sink the report formatting tool + */ + private void doChangedFiles( List files, Sink sink ) + { + for ( ChangeFile file : files ) + { + sinkLogFile( sink, file.getName(), file.getRevision() ); + } + } + + /** + * generates the section of the report detailing the revisions made and the files changed + * + * @param sink the report formatting tool + * @param name filename of the changed file + * @param revision the revision code for this file + */ + private void sinkLogFile( Sink sink, String name, String revision ) + { + try + { + String connection = getConnection(); + + generateLinks( connection, name, revision, sink ); + } + catch ( Exception e ) + { + getLog().debug( e ); + + sink.text( name + " v " + revision ); + } + + sink.lineBreak(); + } + + /** + * attaches the http links from the changed files + * + * @param connection the string used to connect to the SCM + * @param name filename of the file that was changed + * @param sink the report formatting tool + */ + protected void generateLinks( String connection, String name, Sink sink ) + { + generateLinks( connection, name, null, sink ); + } + + /** + * attaches the http links from the changed files + * + * @param connection the string used to connect to the SCM + * @param name filename of the file that was changed + * @param revision the revision code + * @param sink the report formatting tool + */ + protected void generateLinks( String connection, String name, String revision, Sink sink ) + { + String linkFile; + String linkRev = null; + + if ( revision != null ) + { + linkFile = displayFileRevDetailUrl; + } + else + { + linkFile = displayFileDetailUrl; + } + + if ( linkFile != null ) + { + if ( !linkFile.equals( scmUrl ) ) + { + String linkName = name; + if ( encodeFileUri ) + { + try + { + linkName = URLEncoder.encode( linkName, "UTF-8" ); + } + catch ( UnsupportedEncodingException e ) + { + // UTF-8 is always supported + } + } + + // Use the given URL to create links to the files + + if ( linkFile.indexOf( FILE_TOKEN ) > 0 ) + { + linkFile = linkFile.replaceAll( FILE_TOKEN, linkName ); + } + else + { + // This is here so that we are backwards compatible with the + // format used before the special token was introduced + + linkFile += linkName; + } + + // Use the given URL to create links to the files + + if ( revision != null && linkFile.indexOf( REV_TOKEN ) > 0 ) + { + linkFile = linkFile.replaceAll( REV_TOKEN, revision ); + } + } + else if ( connection.startsWith( "scm:perforce" ) ) + { + String path = getAbsolutePath( displayFileDetailUrl, name ); + linkFile = path + "?ac=22"; + if ( revision != null ) + { + linkRev = path + "?ac=64&rev=" + revision; + } + } + else if ( connection.startsWith( "scm:clearcase" ) ) + { + String path = getAbsolutePath( displayFileDetailUrl, name ); + linkFile = path + rptOneRepoParam; + } + else if ( connection.indexOf( "cvsmonitor.pl" ) > 0 ) + { + Pattern cvsMonitorRegex = Pattern.compile( "^.*(&module=.*?(?:&|$)).*$" ); + String module = cvsMonitorRegex.matcher( rptOneRepoParam ).replaceAll( "$1" ); + linkFile = displayFileDetailUrl + "?cmd=viewBrowseFile" + module + "&file=" + name; + if ( revision != null ) + { + linkRev = + rptRepository + "?cmd=viewBrowseVersion" + module + "&file=" + name + "&version=" + revision; + } + } + else + { + String path = getAbsolutePath( displayFileDetailUrl, name ); + linkFile = path + rptOneRepoParam; + if ( revision != null ) + { + linkRev = path + "?rev=" + revision + "&content-type=text/vnd.viewcvs-markup" + rptMultiRepoParam; + } + } + } + + if ( linkFile != null ) + { + sink.link( linkFile ); + sinkFileName( name, sink ); + sink.link_(); + } + else + { + sinkFileName( name, sink ); + } + + sink.text( " " ); + + if ( linkRev == null && revision != null && displayChangeSetDetailUrl != null ) + { + if ( displayChangeSetDetailUrl.indexOf( REV_TOKEN ) > 0 ) + { + linkRev = displayChangeSetDetailUrl.replaceAll( REV_TOKEN, revision ); + } + else + { + linkRev = displayChangeSetDetailUrl + revision; + } + } + + if ( linkRev != null ) + { + sink.link( linkRev ); + sink.text( "v " + revision ); + sink.link_(); + } + else if ( revision != null ) + { + sink.text( "v " + revision ); + } + } + + /** + * Encapsulates the logic for rendering the name with a bolded markup. + * + * @param name filename of the file that was changed + * @param sink the report formatting tool + */ + private void sinkFileName( String name, Sink sink ) + { + name = sinkFileNamePattern.matcher( name ).replaceAll( "/" ); + int pos = name.lastIndexOf( '/' ); + + String head; + String tail; + if ( pos < 0 ) + { + head = ""; + tail = name; + } + else + { + head = name.substring( 0, pos ) + '/'; + tail = name.substring( pos + 1 ); + } + + sink.text( head ); + sink.bold(); + sink.text( tail ); + sink.bold_(); + } + + /** + * calculates the path from a base directory to a target file + * + * @param base base directory to create the absolute path from + * @param target target file to create the absolute path to + */ + private String getAbsolutePath( final String base, final String target ) + { + String absPath = ""; + + StringTokenizer baseTokens = + new StringTokenizer( sinkFileNamePattern.matcher( base ).replaceAll( "/" ), "/", true ); + + StringTokenizer targetTokens = + new StringTokenizer( sinkFileNamePattern.matcher( target ).replaceAll( "/" ), "/" ); + + String targetRoot = targetTokens.nextToken(); + + while ( baseTokens.hasMoreTokens() ) + { + String baseToken = baseTokens.nextToken(); + + if ( baseToken.equals( targetRoot ) ) + { + break; + } + + absPath += baseToken; + } + + if ( !absPath.endsWith( "/" ) ) + { + absPath += "/"; + } + + String newTarget = target; + if ( newTarget.startsWith( "/" ) ) + { + newTarget = newTarget.substring( 1 ); + } + + return absPath + newTarget; + } + + /** + * {@inheritDoc} + */ + protected MavenProject getProject() + { + return project; + } + + /** + * {@inheritDoc} + */ + protected String getOutputDirectory() + { + if ( !outputDirectory.isAbsolute() ) + { + outputDirectory = new File( project.getBasedir(), outputDirectory.getPath() ); + } + + return outputDirectory.getAbsolutePath(); + } + + /** + * Gets the effective reporting output files encoding. + * + * @return The effective reporting output file encoding, never null. + */ + protected String getOutputEncoding() + { + return ( outputEncoding != null ) ? outputEncoding : ReaderFactory.UTF_8; + } + + /** + * {@inheritDoc} + */ + protected Renderer getSiteRenderer() + { + return siteRenderer; + } + + /** + * {@inheritDoc} + */ + public String getDescription( Locale locale ) + { + return getBundle( locale ).getString( "report.changelog.description" ); + } + + /** + * {@inheritDoc} + */ + public String getName( Locale locale ) + { + return getBundle( locale ).getString( "report.changelog.name" ); + } + + /** + * {@inheritDoc} + */ + public String getOutputName() + { + return "changelog"; + } + + /** + * @param locale + * @return the current bundle + */ + protected ResourceBundle getBundle( Locale locale ) + { + return ResourceBundle.getBundle( "scm-activity", locale, this.getClass().getClassLoader() ); + } + + /** + * {@inheritDoc} + */ + public boolean canGenerateReport() + { + if ( offline && !outputXML.exists() ) + { + return false; + } + + return !skip; + + } +} diff --git a/Java/maven-changelog-plugin-ChangeLogReport_1319/metadata.json b/Java/maven-changelog-plugin-ChangeLogReport_1319/metadata.json new file mode 100644 index 000000000..724f8fb65 --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogReport_1319/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-changelog-plugin-ChangeLogReport_1319", + "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 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": "src/main/java/org/apache/maven/plugin/changelog/ChangeLogReport.java", + "line": 1319, + "npe_method": "countFilesChanged", + "deref_field": "list", + "npe_class": "ChangeLogReport", + "repo": "maven-changelog-plugin", + "bug_id": "ChangeLogReport_1319" + } +} diff --git a/Java/maven-changelog-plugin-ChangeLogReport_1319/npe.json b/Java/maven-changelog-plugin-ChangeLogReport_1319/npe.json new file mode 100644 index 000000000..3e83b57b5 --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogReport_1319/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/maven/plugin/changelog/ChangeLogReport.java", + "line": 1319, + "npe_method": "countFilesChanged", + "deref_field": "list", + "npe_class": "ChangeLogReport" +} \ No newline at end of file diff --git a/Java/maven-changelog-plugin-ChangeLogReport_1527/Dockerfile b/Java/maven-changelog-plugin-ChangeLogReport_1527/Dockerfile new file mode 100644 index 000000000..9a945184a --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogReport_1527/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-changelog-plugin + +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-changelog-plugin-ChangeLogReport_1527/buggy.java b/Java/maven-changelog-plugin-ChangeLogReport_1527/buggy.java new file mode 100644 index 000000000..dc077d4f7 --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogReport_1527/buggy.java @@ -0,0 +1,1917 @@ +package org.apache.maven.plugin.changelog; + +/* + * 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.sink.Sink; +import org.apache.maven.doxia.siterenderer.Renderer; +import org.apache.maven.model.Developer; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; +import org.apache.maven.reporting.AbstractMavenReport; +import org.apache.maven.reporting.MavenReportException; +import org.apache.maven.scm.ChangeFile; +import org.apache.maven.scm.ChangeSet; +import org.apache.maven.scm.ScmBranch; +import org.apache.maven.scm.ScmException; +import org.apache.maven.scm.ScmFileSet; +import org.apache.maven.scm.ScmResult; +import org.apache.maven.scm.ScmRevision; +import org.apache.maven.scm.command.changelog.ChangeLogScmResult; +import org.apache.maven.scm.command.changelog.ChangeLogSet; +import org.apache.maven.scm.command.info.InfoItem; +import org.apache.maven.scm.command.info.InfoScmResult; +import org.apache.maven.scm.log.DefaultLog; +import org.apache.maven.scm.manager.ScmManager; +import org.apache.maven.scm.provider.ScmProvider; +import org.apache.maven.scm.provider.ScmProviderRepository; +import org.apache.maven.scm.provider.ScmProviderRepositoryWithHost; +import org.apache.maven.scm.provider.svn.repository.SvnScmProviderRepository; +import org.apache.maven.scm.provider.svn.svnexe.command.info.SvnInfoCommandExpanded; +import org.apache.maven.scm.repository.ScmRepository; +import org.apache.maven.settings.Server; +import org.apache.maven.settings.Settings; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.util.WriterFactory; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.StringReader; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.net.URLEncoder; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.ResourceBundle; +import java.util.StringTokenizer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Generate a changelog report. + * + * @version $Id$ + */ +@Mojo( name = "changelog" ) +public class ChangeLogReport + extends AbstractMavenReport +{ + /** + * A special token that represents the SCM relative path for a file. + * It can be used in displayFileDetailUrl. + */ + private static final String FILE_TOKEN = "%FILE%"; + + /** + * A special token that represents a Mantis/Bugzilla/JIRA/etc issue ID. + * It can be used in the issueLinkUrl. + */ + private static final String ISSUE_TOKEN = "%ISSUE%"; + + /** + * A special token that represents the SCM revision number. + * It can be used in displayChangeSetDetailUrl + * and displayFileRevDetailUrl. + */ + private static final String REV_TOKEN = "%REV%"; + + /** + * The number of days to use as a range, when this is not specified. + */ + private static final int DEFAULT_RANGE = 30; + + public static final String DEFAULT_ISSUE_ID_REGEX_PATTERN = "[a-zA-Z]{2,}-\\d+"; + + private static final String DEFAULT_ISSUE_LINK_URL = "https://issues.apache.org/jira/browse/" + ISSUE_TOKEN; + + /** + * Used to specify the format to use for the dates in the headings of the + * report. + * + * @since 2.1 + */ + @Parameter( property = "changelog.headingDateFormat", defaultValue = "yyyy-MM-dd" ) + private String headingDateFormat = "yyyy-MM-dd"; + + /** + * Used to specify whether to build the log using range, tag or date. + */ + @Parameter( property = "changelog.type", defaultValue = "range", required = true ) + private String type; + + /** + * Used to specify the number of days of log entries to retrieve. + */ + @Parameter( property = "changelog.range", defaultValue = "-1" ) + private int range; + + /** + * Used to specify the absolute date (or list of dates) to start log entries from. + */ + @Parameter + private List dates; + + /** + * Used to specify the tag (or list of tags) to start log entries from. + */ + @Parameter + private List tags; + + /** + * Used to specify the date format of the log entries that are retrieved from your SCM system. + */ + @Parameter( property = "changelog.dateFormat", defaultValue = "yyyy-MM-dd HH:mm:ss", required = true ) + private String dateFormat; + + /** + * Input dir. Directory where the files under SCM control are located. + */ + @Parameter( property = "basedir", required = true ) + private File basedir; + + /** + * Output file for xml document + */ + @Parameter( defaultValue = "${project.build.directory}/changelog.xml", required = true ) + private File outputXML; + + /** + * Allows the user to make changelog regenerate the changelog.xml file for the specified time in minutes. + */ + @Parameter( property = "outputXMLExpiration", defaultValue = "60", required = true ) + private int outputXMLExpiration; + + /** + * Comment format string used for interrogating + * the revision control system. + * Currently only used by the ClearcaseChangeLogGenerator. + */ + @Parameter( property = "changelog.commentFormat" ) + private String commentFormat; + + /** + * The file encoding when writing non-HTML reports. + */ + @Parameter( property = "changelog.outputEncoding", defaultValue = "${project.reporting.outputEncoding}" ) + private String outputEncoding; + + /** + * The user name (used by svn and starteam protocol). + */ + @Parameter( property = "username" ) + private String username; + + /** + * The user password (used by svn and starteam protocol). + */ + @Parameter( property = "password" ) + private String password; + + /** + * The private key (used by java svn). + */ + @Parameter( property = "privateKey" ) + private String privateKey; + + /** + * The passphrase (used by java svn). + */ + @Parameter( property = "passphrase" ) + private String passphrase; + + /** + * The url of tags base directory (used by svn protocol). + */ + @Parameter( property = "tagBase" ) + private String tagBase; + + /** + * The URL to view the scm. Basis for external links from the generated report. + */ + @Parameter( property = "project.scm.url" ) + protected String scmUrl; + + /** + * Skip the Changelog report generation. Most useful on the command line + * via "-Dchangelog.skip=true". + * + * @since 2.3 + */ + @Parameter( property = "changelog.skip", defaultValue = "false" ) + protected boolean skip; + + /** + * Encodes slashes in file uri. Required for some repository browsers like gitblit + * + * @since 2.3 + */ + @Parameter( property = "encodeFileUri", defaultValue = "false" ) + protected boolean encodeFileUri; + + /** + * List of files to include. Specified as fileset patterns of files to include in the report + * + * @since 2.3 + */ + @Parameter + private String[] includes; + + /** + * List of files to include. Specified as fileset patterns of files to omit in the report + * + * @since 2.3 + */ + @Parameter + private String[] excludes; + + + /** + * The Maven Project Object + */ + @Parameter( defaultValue = "${project}", readonly = true, required = true ) + private MavenProject project; + + /** + * The directory where the report will be generated + */ + @Parameter( property = "project.reporting.outputDirectory", required = true, readonly = true ) + private File outputDirectory; + + /** + */ + @Component + private Renderer siteRenderer; + + /** + */ + @Parameter( property = "settings.offline", required = true, readonly = true ) + private boolean offline; + + /** + */ + @Component + private ScmManager manager; + + /** + */ + @Parameter( defaultValue = "${settings}", readonly = true, required = true ) + private Settings settings; + + /** + * Allows the user to choose which scm connection to use when connecting to the scm. + * Can either be "connection" or "developerConnection". + */ + @Parameter( defaultValue = "connection", required = true ) + private String connectionType; + + /** + * A template string that is used to create the URL to the file details. + * There is a special token that you can use in your template: + *
    + *
  • %FILE% - this is the path to a file
  • + *
+ *

+ * Example: + * http://checkstyle.cvs.sourceforge.net/checkstyle%FILE%?view=markup + *

+ *

+ * Note: If you don't supply the token in your template, + * the path of the file will simply be appended to your template URL. + *

+ */ + @Parameter( property = "displayFileDetailUrl", defaultValue = "${project.scm.url}" ) + protected String displayFileDetailUrl; + + /** + * A pattern used to identify 'issue tracker' IDs such as those used by JIRA, + * Bugzilla and alike in the SCM commit messages. Any matched patterns + * are replaced with issueLinkUrl URL. The default + * value is a JIRA-style issue identification pattern. + *

+ * Note: Default value is [a-zA-Z]{2,}-\d+ + * + * @since 2.2 + */ + @Parameter( property = "issueIDRegexPattern", defaultValue = DEFAULT_ISSUE_ID_REGEX_PATTERN, required = true ) + private String issueIDRegexPattern; + + /** + * The issue tracker URL used when replacing any matched issueIDRegexPattern + * found in the SCM commit messages. The default is URL is the codehaus JIRA + * URL. If %ISSUE% is found in the URL it is replaced with the matched issue ID, + * otherwise the matched issue ID is appended to the URL. + * + * @since 2.2 + */ + @Parameter( property = "issueLinkUrl", defaultValue = DEFAULT_ISSUE_LINK_URL, required = true ) + private String issueLinkUrl; + + /** + * A template string that is used to create the changeset URL. + *

+ * If not defined no change set link will be created. + *

+ * There is one special token that you can use in your template: + *

    + *
  • %REV% - this is the changeset revision
  • + *
+ *

+ * Example: + * http://fisheye.sourceforge.net/changelog/a-project/?cs=%REV% + *

+ *

+ * Note: If you don't supply the %REV% token in your template, + * the revision will simply be appended to your template URL. + *

+ * + * @since 2.2 + */ + @Parameter( property = "displayChangeSetDetailUrl" ) + protected String displayChangeSetDetailUrl; + + /** + * A template string that is used to create the revision aware URL to + * the file details in a similar fashion to the displayFileDetailUrl. + * When a report contains both file and file revision information, as in the + * Change Log report, this template string can be used to create a revision + * aware URL to the file details. + *

+ * If not defined this template string defaults to the same value as the + * displayFileDetailUrl and thus revision number aware links will + * not be used. + *

+ * There are two special tokens that you can use in your template: + *

    + *
  • %FILE% - this is the path to a file
  • + *
  • %REV% - this is the revision of the file
  • + *
+ *

+ * Example: + * http://fisheye.sourceforge.net/browse/a-project/%FILE%?r=%REV% + *

+ *

+ * Note: If you don't supply the %FILE% token in your template, + * the path of the file will simply be appended to your template URL. + *

+ * + * @since 2.2 + */ + @Parameter( property = "displayFileRevDetailUrl" ) + protected String displayFileRevDetailUrl; + + /** + * List of developers to be shown on the report. + * + * @since 2.2 + */ + @Parameter( property = "project.developers" ) + protected List developers; + + /** + * List of provider implementations. + */ + @Parameter + private Map providerImplementations; + + // temporary field holder while generating the report + private String rptRepository, rptOneRepoParam, rptMultiRepoParam; + + // field for SCM Connection URL + private String connection; + + // field used to hold a map of the developers by Id + private HashMap developersById; + + // field used to hold a map of the developers by Name + private HashMap developersByName; + + /** + * The system properties to use (needed by the perforce scm provider). + */ + @Parameter + private Properties systemProperties; + + private final Pattern sinkFileNamePattern = Pattern.compile( "\\\\" ); + + /** + * {@inheritDoc} + */ + public void executeReport( Locale locale ) + throws MavenReportException + { + //check if sources exists <-- required for parent poms + if ( !basedir.exists() ) + { + doGenerateEmptyReport( getBundle( locale ), getSink() ); + + return; + } + + if ( providerImplementations != null ) + { + for ( Map.Entry entry : providerImplementations.entrySet() ) + { + String providerType = entry.getKey(); + String providerImplementation = entry.getValue(); + getLog().info( + "Change the default '" + providerType + "' provider implementation to '" + providerImplementation + + "'." ); + manager.setScmProviderImplementation( providerType, providerImplementation ); + } + } + + initializeDefaultConfigurationParameters(); + + initializeDeveloperMaps(); + + verifySCMTypeParams(); + + if ( systemProperties != null ) + { + // Add all system properties configured by the user + Iterator iter = systemProperties.keySet().iterator(); + + while ( iter.hasNext() ) + { + String key = (String) iter.next(); + + String value = systemProperties.getProperty( key ); + + System.setProperty( key, value ); + + getLog().debug( "Setting system property: " + key + '=' + value ); + } + } + + doGenerateReport( getChangedSets(), getBundle( locale ), getSink() ); + } + + /** + * Initializes any configuration parameters that have not/can not be defined + * or defaulted by the Mojo API. + */ + private void initializeDefaultConfigurationParameters() + { + if ( displayFileRevDetailUrl == null || displayFileRevDetailUrl.length() == 0 ) + { + displayFileRevDetailUrl = displayFileDetailUrl; + } + } + + /** + * Creates maps of the project developers by developer Id and developer Name + * for quick lookups. + */ + private void initializeDeveloperMaps() + { + developersById = new HashMap(); + developersByName = new HashMap(); + + if ( developers != null ) + { + for ( Developer developer : developers ) + { + developersById.put( developer.getId(), developer ); + developersByName.put( developer.getName(), developer ); + } + } + } + + /** + * populates the changedSets field by either connecting to the SCM or using an existing XML generated in a previous + * run of the report + * + * @throws MavenReportException + */ + protected List getChangedSets() + throws MavenReportException + { + List changelogList = null; + + if ( !outputXML.isAbsolute() ) + { + outputXML = new File( project.getBasedir(), outputXML.getPath() ); + } + + if ( outputXML.exists() ) + { + // CHECKSTYLE_OFF: MagicNumber + if ( outputXMLExpiration > 0 + && outputXMLExpiration * 60000 > System.currentTimeMillis() - outputXML.lastModified() ) + // CHECKSTYLE_ON: MagicNumber + { + try + { + //ReaderFactory.newReader( outputXML, getOutputEncoding() ); + //FileInputStream fIn = new FileInputStream( outputXML ); + + getLog().info( "Using existing changelog.xml..." ); + + changelogList = + ChangeLog.loadChangedSets( ReaderFactory.newReader( outputXML, getOutputEncoding() ) ); + } + catch ( FileNotFoundException e ) + { + //do nothing, just regenerate + } + catch ( Exception e ) + { + throw new MavenReportException( "An error occurred while parsing " + outputXML.getAbsolutePath(), + e ); + } + } + } + + if ( changelogList == null ) + { + if ( offline ) + { + throw new MavenReportException( "This report requires online mode." ); + } + + getLog().info( "Generating changed sets xml to: " + outputXML.getAbsolutePath() ); + + changelogList = generateChangeSetsFromSCM(); + + try + { + writeChangelogXml( changelogList ); + } + catch ( FileNotFoundException e ) + { + throw new MavenReportException( "Can't create " + outputXML.getAbsolutePath(), e ); + } + catch ( UnsupportedEncodingException e ) + { + throw new MavenReportException( "Can't create " + outputXML.getAbsolutePath(), e ); + } + catch ( IOException e ) + { + throw new MavenReportException( "Can't create " + outputXML.getAbsolutePath(), e ); + } + } + + return changelogList; + } + + private void writeChangelogXml( List changelogList ) + throws IOException + { + StringBuilder changelogXml = new StringBuilder(); + + changelogXml.append( "\n" ); + changelogXml.append( "" ); + + for ( ChangeLogSet changelogSet : changelogList ) + { + changelogXml.append( "\n " ); + + String changeset = changelogSet.toXML( getOutputEncoding() ); + + //remove xml header + if ( changeset.startsWith( "" ) + 2; + changeset = changeset.substring( idx ); + } + + changelogXml.append( changeset ); + } + + changelogXml.append( "\n" ); + + outputXML.getParentFile().mkdirs(); + + //PrintWriter pw = new PrintWriter( new BufferedOutputStream( new FileOutputStream( outputXML ) ) ); + //pw.write( changelogXml.toString() ); + //pw.flush(); + //pw.close(); + // MCHANGELOG-86 + Writer writer = WriterFactory.newWriter( new BufferedOutputStream( new FileOutputStream( outputXML ) ), + getOutputEncoding() ); + writer.write( changelogXml.toString() ); + writer.flush(); + writer.close(); + } + + /** + * creates a ChangeLog object and then connects to the SCM to generate the changed sets + * + * @return changedlogsets generated from the SCM + * @throws MavenReportException + */ + protected List generateChangeSetsFromSCM() + throws MavenReportException + { + try + { + List changeSets = new ArrayList(); + + ScmRepository repository = getScmRepository(); + + ScmProvider provider = manager.getProviderByRepository( repository ); + + ChangeLogScmResult result; + + if ( "range".equals( type ) ) + { + result = provider.changeLog( repository, new ScmFileSet( basedir ), null, null, range, (ScmBranch) null, + dateFormat ); + + checkResult( result ); + + changeSets.add( result.getChangeLog() ); + } + else if ( "tag".equals( type ) ) + { + + Iterator tagsIter = tags.iterator(); + + String startTag = tagsIter.next(); + String endTag = null; + + if ( tagsIter.hasNext() ) + { + while ( tagsIter.hasNext() ) + { + endTag = tagsIter.next(); + String endRevision = getRevisionForTag( endTag, repository, provider ); + String startRevision = getRevisionForTag( startTag, repository, provider ); + result = provider.changeLog( repository, new ScmFileSet( basedir ), + new ScmRevision( startRevision ), + new ScmRevision( endRevision ) ); + + checkResult( result ); + result.getChangeLog().setStartVersion( new ScmRevision( startTag ) ); + result.getChangeLog().setEndVersion( new ScmRevision( endTag ) ); + + changeSets.add( result.getChangeLog() ); + + startTag = endTag; + } + } + else + { + String startRevision = getRevisionForTag( startTag, repository, provider ); + String endRevision = getRevisionForTag( endTag, repository, provider ); + result = provider.changeLog( repository, new ScmFileSet( basedir ), + new ScmRevision( startRevision ), + new ScmRevision( endRevision ) ); + + checkResult( result ); + result.getChangeLog().setStartVersion( new ScmRevision( startTag ) ); + result.getChangeLog().setEndVersion( null ); + changeSets.add( result.getChangeLog() ); + } + } + else if ( "date".equals( type ) ) + { + Iterator dateIter = dates.iterator(); + + String startDate = dateIter.next(); + String endDate = null; + + if ( dateIter.hasNext() ) + { + while ( dateIter.hasNext() ) + { + endDate = dateIter.next(); + + result = provider.changeLog( repository, new ScmFileSet( basedir ), parseDate( startDate ), + parseDate( endDate ), 0, (ScmBranch) null ); + + checkResult( result ); + + changeSets.add( result.getChangeLog() ); + + startDate = endDate; + } + } + else + { + result = provider.changeLog( repository, new ScmFileSet( basedir ), parseDate( startDate ), + parseDate( endDate ), 0, (ScmBranch) null ); + + checkResult( result ); + + changeSets.add( result.getChangeLog() ); + } + } + else + { + throw new MavenReportException( "The type '" + type + "' isn't supported." ); + } + filter( changeSets ); + return changeSets; + + } + catch ( ScmException e ) + { + throw new MavenReportException( "Cannot run changelog command : ", e ); + } + catch ( MojoExecutionException e ) + { + throw new MavenReportException( "An error has occurred during changelog command : ", e ); + } + } + + /** + * Resolves the given tag to the revision number. + * + * @param tag + * @param repository + * @param provider + * @return + * @throws ScmException + */ + private String getRevisionForTag( final String tag, final ScmRepository repository, final ScmProvider provider ) + throws ScmException + { + if ( repository.getProvider().equals( "svn" ) ) + { + if ( tag == null ) + { + return "HEAD"; + } + SvnInfoCommandExpanded infoCommand = new SvnInfoCommandExpanded(); + infoCommand.setLogger( new DefaultLog() ); + + InfoScmResult infoScmResult = + infoCommand.executeInfoTagCommand( (SvnScmProviderRepository) repository.getProviderRepository(), + new ScmFileSet( basedir ), tag, null, false, null ); + if ( infoScmResult.getInfoItems().size() == 0 ) + { + throw new ScmException( "There is no tag named '" + tag + "' in the Subversion repository." ); + } + InfoItem infoItem = infoScmResult.getInfoItems().get( 0 ); + String revision = infoItem.getLastChangedRevision(); + getLog().info( String.format( "Resolved tag '%s' to revision '%s'", tag, revision ) ); + return revision; + } + return tag; + } + + /** + * filters out unwanted files from the changesets + */ + private void filter( List changeSets ) + { + List include = compilePatterns( includes ); + List exclude = compilePatterns( excludes ); + if ( includes == null && excludes == null ) + { + return; + } + for ( ChangeLogSet changeLogSet : changeSets ) + { + List set = changeLogSet.getChangeSets(); + filter( set, include, exclude ); + } + + } + + private List compilePatterns( String[] patternArray ) + { + if ( patternArray == null ) + { + return new ArrayList(); + } + List patterns = new ArrayList( patternArray.length ); + for ( String string : patternArray ) + { + //replaces * with [/\]* (everything but file seperators) + //replaces ** with .* + //quotes the rest of the string + string = "\\Q" + string + "\\E"; + string = string.replace( "**", "\\E.?REPLACEMENT?\\Q" ); + string = string.replace( "*", "\\E[^/\\\\]?REPLACEMENT?\\Q" ); + string = string.replace( "?REPLACEMENT?", "*" ); + string = string.replace( "\\Q\\E", "" ); + patterns.add( Pattern.compile( string ) ); + } + return patterns; + } + + private void filter( List sets, List includes, List excludes ) + { + Iterator it = sets.iterator(); + while ( it.hasNext() ) + { + ChangeSet changeSet = it.next(); + List files = changeSet.getFiles(); + Iterator iterator = files.iterator(); + while ( iterator.hasNext() ) + { + ChangeFile changeFile = iterator.next(); + String name = changeFile.getName(); + if ( !isIncluded( includes, name ) || isExcluded( excludes, name ) ) + { + iterator.remove(); + } + } + if ( files.isEmpty() ) + { + it.remove(); + } + } + } + + private boolean isExcluded( List excludes, String name ) + { + if ( excludes == null || excludes.isEmpty() ) + { + return false; + } + for ( Pattern pattern : excludes ) + { + if ( pattern.matcher( name ).matches() ) + { + return true; + } + } + return false; + } + + private boolean isIncluded( List includes, String name ) + { + if ( includes == null || includes.isEmpty() ) + { + return true; + } + for ( Pattern pattern : includes ) + { + if ( pattern.matcher( name ).matches() ) + { + return true; + } + } + return false; + } + + /** + * Converts the localized date string pattern to date object. + * + * @return A date + */ + private Date parseDate( String date ) + throws MojoExecutionException + { + if ( date == null || date.trim().length() == 0 ) + { + return null; + } + + SimpleDateFormat formatter = new SimpleDateFormat( "yyyy-MM-dd" ); + + try + { + return formatter.parse( date ); + } + catch ( ParseException e ) + { + throw new MojoExecutionException( "Please use this date pattern: " + formatter.toLocalizedPattern(), e ); + } + } + + public ScmRepository getScmRepository() + throws ScmException + { + ScmRepository repository; + + try + { + repository = manager.makeScmRepository( getConnection() ); + + ScmProviderRepository providerRepo = repository.getProviderRepository(); + + if ( !StringUtils.isEmpty( username ) ) + { + providerRepo.setUser( username ); + } + + if ( !StringUtils.isEmpty( password ) ) + { + providerRepo.setPassword( password ); + } + + if ( repository.getProviderRepository() instanceof ScmProviderRepositoryWithHost ) + { + ScmProviderRepositoryWithHost repo = (ScmProviderRepositoryWithHost) repository.getProviderRepository(); + + loadInfosFromSettings( repo ); + + if ( !StringUtils.isEmpty( username ) ) + { + repo.setUser( username ); + } + + if ( !StringUtils.isEmpty( password ) ) + { + repo.setPassword( password ); + } + + if ( !StringUtils.isEmpty( privateKey ) ) + { + repo.setPrivateKey( privateKey ); + } + + if ( !StringUtils.isEmpty( passphrase ) ) + { + repo.setPassphrase( passphrase ); + } + } + + if ( !StringUtils.isEmpty( tagBase ) && repository.getProvider().equals( "svn" ) ) + { + SvnScmProviderRepository svnRepo = (SvnScmProviderRepository) repository.getProviderRepository(); + + svnRepo.setTagBase( tagBase ); + } + } + catch ( Exception e ) + { + throw new ScmException( "Can't load the scm provider.", e ); + } + + return repository; + } + + /** + * Load username password from settings if user has not set them in JVM properties + * + * @param repo + */ + private void loadInfosFromSettings( ScmProviderRepositoryWithHost repo ) + { + if ( username == null || password == null ) + { + String host = repo.getHost(); + + int port = repo.getPort(); + + if ( port > 0 ) + { + host += ":" + port; + } + + Server server = this.settings.getServer( host ); + + if ( server != null ) + { + if ( username == null ) + { + username = this.settings.getServer( host ).getUsername(); + } + + if ( password == null ) + { + password = this.settings.getServer( host ).getPassword(); + } + + if ( privateKey == null ) + { + privateKey = this.settings.getServer( host ).getPrivateKey(); + } + + if ( passphrase == null ) + { + passphrase = this.settings.getServer( host ).getPassphrase(); + } + } + } + } + + public void checkResult( ScmResult result ) + throws MojoExecutionException + { + if ( !result.isSuccess() ) + { + getLog().error( "Provider message:" ); + + getLog().error( result.getProviderMessage() == null ? "" : result.getProviderMessage() ); + + getLog().error( "Command output:" ); + + getLog().error( result.getCommandOutput() == null ? "" : result.getCommandOutput() ); + + throw new MojoExecutionException( "Command failed." ); + } + } + + /** + * used to retrieve the SCM connection string + * + * @return the url string used to connect to the SCM + * @throws MavenReportException when there is insufficient information to retrieve the SCM connection string + */ + protected String getConnection() + throws MavenReportException + { + if ( this.connection != null ) + { + return connection; + } + + if ( project.getScm() == null ) + { + throw new MavenReportException( "SCM Connection is not set." ); + } + + String scmConnection = project.getScm().getConnection(); + if ( StringUtils.isNotEmpty( scmConnection ) && "connection".equals( connectionType.toLowerCase() ) ) + { + connection = scmConnection; + } + + String scmDeveloper = project.getScm().getDeveloperConnection(); + if ( StringUtils.isNotEmpty( scmDeveloper ) && "developerconnection".equals( connectionType.toLowerCase() ) ) + { + connection = scmDeveloper; + } + + if ( StringUtils.isEmpty( connection ) ) + { + throw new MavenReportException( "SCM Connection is not set." ); + } + + return connection; + } + + /** + * checks whether there are enough configuration parameters to generate the report + * + * @throws MavenReportException when there is insufficient paramters to generate the report + */ + private void verifySCMTypeParams() + throws MavenReportException + { + if ( "range".equals( type ) ) + { + if ( range == -1 ) + { + range = DEFAULT_RANGE; + } + } + else if ( "date".equals( type ) ) + { + if ( dates == null ) + { + throw new MavenReportException( "The dates parameter is required when type=\"date\". The value " + + "should be the absolute date for the start of the log." ); + } + } + else if ( "tag".equals( type ) ) + { + if ( tags == null ) + { + throw new MavenReportException( "The tags parameter is required when type=\"tag\"." ); + } + } + else + { + throw new MavenReportException( "The type parameter has an invalid value: " + type + + ". The value should be \"range\", \"date\", or \"tag\"." ); + } + } + + /** + * generates an empty report in case there are no sources to generate a report with + * + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + protected void doGenerateEmptyReport( ResourceBundle bundle, Sink sink ) + { + sink.head(); + sink.title(); + sink.text( bundle.getString( "report.changelog.header" ) ); + sink.title_(); + sink.head_(); + + sink.body(); + sink.section1(); + + sink.sectionTitle1(); + sink.text( bundle.getString( "report.changelog.mainTitle" ) ); + sink.sectionTitle1_(); + + sink.paragraph(); + sink.text( bundle.getString( "report.changelog.nosources" ) ); + sink.paragraph_(); + + sink.section1_(); + + sink.body_(); + sink.flush(); + sink.close(); + } + + /** + * method that generates the report for this mojo. This method is overridden by dev-activity and file-activity mojo + * + * @param changeLogSets changed sets to generate the report from + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + protected void doGenerateReport( List changeLogSets, ResourceBundle bundle, Sink sink ) + { + sink.head(); + sink.title(); + sink.text( bundle.getString( "report.changelog.header" ) ); + sink.title_(); + sink.head_(); + + sink.body(); + sink.section1(); + + sink.sectionTitle1(); + sink.text( bundle.getString( "report.changelog.mainTitle" ) ); + sink.sectionTitle1_(); + + // Summary section + doSummarySection( changeLogSets, bundle, sink ); + + for ( ChangeLogSet changeLogSet : changeLogSets ) + { + doChangedSet( changeLogSet, bundle, sink ); + } + + sink.section1_(); + sink.body_(); + + sink.flush(); + sink.close(); + } + + /** + * generates the report summary section of the report + * + * @param changeLogSets changed sets to generate the report from + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + private void doSummarySection( List changeLogSets, ResourceBundle bundle, Sink sink ) + { + sink.paragraph(); + + sink.text( bundle.getString( "report.changelog.ChangedSetsTotal" ) ); + sink.text( ": " + changeLogSets.size() ); + + sink.paragraph_(); + } + + /** + * generates a section of the report referring to a changeset + * + * @param set the current ChangeSet to generate this section of the report + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + private void doChangedSet( ChangeLogSet set, ResourceBundle bundle, Sink sink ) + { + sink.section2(); + + doChangeSetTitle( set, bundle, sink ); + + doSummary( set, bundle, sink ); + + doChangedSetTable( set.getChangeSets(), bundle, sink ); + + sink.section2_(); + } + + /** + * Generate the title for the report. + * + * @param set change set to generate the report from + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + protected void doChangeSetTitle( ChangeLogSet set, ResourceBundle bundle, Sink sink ) + { + sink.sectionTitle2(); + + SimpleDateFormat headingDateFormater = new SimpleDateFormat( headingDateFormat ); + + if ( "tag".equals( type ) ) + { + if ( set.getStartVersion() == null || set.getStartVersion().getName() == null ) + { + sink.text( bundle.getString( "report.SetTagCreation" ) ); + if ( set.getEndVersion() != null && set.getEndVersion().getName() != null ) + { + sink.text( ' ' + bundle.getString( "report.SetTagUntil" ) + " '" + set.getEndVersion() + '\'' ); + } + } + else if ( set.getEndVersion() == null || set.getEndVersion().getName() == null ) + { + sink.text( bundle.getString( "report.SetTagSince" ) ); + sink.text( " '" + set.getStartVersion() + '\'' ); + } + else + { + sink.text( bundle.getString( "report.SetTagBetween" ) ); + sink.text( + " '" + set.getStartVersion() + "' " + bundle.getString( "report.And" ) + " '" + set.getEndVersion() + + '\'' ); + } + } + else if ( set.getStartDate() == null ) + { + sink.text( bundle.getString( "report.SetRangeUnknown" ) ); + } + else if ( set.getEndDate() == null ) + { + sink.text( bundle.getString( "report.SetRangeSince" ) ); + sink.text( ' ' + headingDateFormater.format( set.getStartDate() ) ); + } + else + { + sink.text( bundle.getString( "report.SetRangeBetween" ) ); + sink.text( + ' ' + headingDateFormater.format( set.getStartDate() ) + ' ' + bundle.getString( "report.And" ) + ' ' + + headingDateFormater.format( set.getEndDate() ) ); + } + sink.sectionTitle2_(); + } + + /** + * Generate the summary section of the report. + * + * @param set change set to generate the report from + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + protected void doSummary( ChangeLogSet set, ResourceBundle bundle, Sink sink ) + { + sink.paragraph(); + sink.text( bundle.getString( "report.TotalCommits" ) ); + sink.text( ": " + set.getChangeSets().size() ); + sink.lineBreak(); + sink.text( bundle.getString( "report.changelog.FilesChanged" ) ); + sink.text( ": " + countFilesChanged( set.getChangeSets() ) ); + sink.paragraph_(); + } + + /** + * counts the number of files that were changed in the specified SCM + * + * @param entries a collection of SCM changes + * @return number of files changed for the changedsets + */ + protected long countFilesChanged( Collection entries ) + { + if ( entries == null ) + { + return 0; + } + + if ( entries.isEmpty() ) + { + return 0; + } + + HashMap> fileList = new HashMap>(); + + for ( ChangeSet entry : entries ) + { + for ( ChangeFile file : entry.getFiles() ) + { + String name = file.getName(); + List list = fileList.get( name ); + + if ( list != null ) + { + list.add( file ); + } + else + { + list = new LinkedList(); + + list.add( file ); + + fileList.put( name, list ); + } + } + } + + return fileList.size(); + } + + /** + * generates the report table showing the SCM log entries + * + * @param entries a list of change log entries to generate the report from + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + private void doChangedSetTable( Collection entries, ResourceBundle bundle, Sink sink ) + { + sink.table(); + + sink.tableRow(); + sink.tableHeaderCell(); + sink.text( bundle.getString( "report.changelog.timestamp" ) ); + sink.tableHeaderCell_(); + sink.tableHeaderCell(); + sink.text( bundle.getString( "report.changelog.author" ) ); + sink.tableHeaderCell_(); + sink.tableHeaderCell(); + sink.text( bundle.getString( "report.changelog.details" ) ); + sink.tableHeaderCell_(); + sink.tableRow_(); + + initReportUrls(); + + List sortedEntries = new ArrayList( entries ); + Collections.sort( sortedEntries, new Comparator() + { + public int compare( ChangeSet changeSet0, ChangeSet changeSet1 ) + { + return changeSet1.getDate().compareTo( changeSet0.getDate() ); + } + } ); + + for ( ChangeSet entry : sortedEntries ) + { + doChangedSetDetail( entry, sink ); + } + + sink.table_(); + } + + /** + * reports on the details of an SCM entry log + * + * @param entry an SCM entry to generate the report from + * @param sink the report formatting tool + */ + private void doChangedSetDetail( ChangeSet entry, Sink sink ) + { + sink.tableRow(); + + sink.tableCell(); + sink.text( entry.getDateFormatted() + ' ' + entry.getTimeFormatted() ); + sink.tableCell_(); + + sink.tableCell(); + + sinkAuthorDetails( sink, entry.getAuthor() ); + + sink.tableCell_(); + + sink.tableCell(); + //doRevision( entry.getFiles(), bundle, sink ); + doChangedFiles( entry.getFiles(), sink ); + sink.lineBreak(); + StringReader sr = new StringReader( entry.getComment() ); + BufferedReader br = new BufferedReader( sr ); + String line; + try + { + if ( ( issueIDRegexPattern != null && issueIDRegexPattern.length() > 0 ) && ( issueLinkUrl != null + && issueLinkUrl.length() > 0 ) ) + { + Pattern pattern = Pattern.compile( issueIDRegexPattern ); + + line = br.readLine(); + + while ( line != null ) + { + sinkIssueLink( sink, line, pattern ); + + line = br.readLine(); + if ( line != null ) + { + sink.lineBreak(); + } + } + } + else + { + line = br.readLine(); + + while ( line != null ) + { + sink.text( line ); + line = br.readLine(); + if ( line != null ) + { + sink.lineBreak(); + } + } + } + } + catch ( IOException e ) + { + getLog().warn( "Unable to read the comment of a ChangeSet as a stream." ); + } + finally + { + try + { + br.close(); + } + catch ( IOException e ) + { + getLog().warn( "Unable to close a reader." ); + } + sr.close(); + } + sink.tableCell_(); + + sink.tableRow_(); + } + + private void sinkIssueLink( Sink sink, String line, Pattern pattern ) + { + // replace any ticket patterns found. + + Matcher matcher = pattern.matcher( line ); + + int currLoc = 0; + + while ( matcher.find() ) + { + String match = matcher.group(); + + String link; + + if ( issueLinkUrl.indexOf( ISSUE_TOKEN ) > 0 ) + { + link = issueLinkUrl.replaceAll( ISSUE_TOKEN, match ); + } + else + { + if ( issueLinkUrl.endsWith( "/" ) ) + { + link = issueLinkUrl; + } + else + { + link = issueLinkUrl + '/'; + } + + link += match; + } + + int startOfMatch = matcher.start(); + + String unmatchedText = line.substring( currLoc, startOfMatch ); + + currLoc = matcher.end(); + + sink.text( unmatchedText ); + + sink.link( link ); + sink.text( match ); + sink.link_(); + } + + sink.text( line.substring( currLoc ) ); + } + + /** + * If the supplied author is a known developer this method outputs a + * link to the team members report, or alternatively, if the supplied + * author is unknown, outputs the author's name as plain text. + * + * @param sink Sink to use for outputting + * @param author The author's name. + */ +/** + * If the supplied author is a known developer this method outputs a + * link to the team members report, or alternatively, if the supplied + * author is unknown, outputs the author's name as plain text. + * + * @param sink + * Sink to use for outputting + * @param author + * The author's name. + */ +protected void sinkAuthorDetails(org.apache.maven.doxia.sink.Sink sink, java.lang.String author) { + org.apache.maven.model.Developer developer = developersById.get(author); + if (developer == null) { + developer = developersByName.get(author); + } + { + sink.link("team-list.html#" + /* NPEX_NULL_EXP */ + developer.getId()); + sink.text(developer.getName()); + sink.link_(); + } +} + + /** + * populates the report url used to create links from certain elements of the report + */ + protected void initReportUrls() + { + if ( scmUrl != null ) + { + int idx = scmUrl.indexOf( '?' ); + + if ( idx > 0 ) + { + rptRepository = scmUrl.substring( 0, idx ); + + if ( scmUrl.equals( displayFileDetailUrl ) ) + { + String rptTmpMultiRepoParam = scmUrl.substring( rptRepository.length() ); + + rptOneRepoParam = '?' + rptTmpMultiRepoParam.substring( 1 ); + + rptMultiRepoParam = '&' + rptTmpMultiRepoParam.substring( 1 ); + } + } + else + { + rptRepository = scmUrl; + + rptOneRepoParam = ""; + + rptMultiRepoParam = ""; + } + } + } + + /** + * generates the section of the report listing all the files revisions + * + * @param files list of files to generate the reports from + * @param sink the report formatting tool + */ + private void doChangedFiles( List files, Sink sink ) + { + for ( ChangeFile file : files ) + { + sinkLogFile( sink, file.getName(), file.getRevision() ); + } + } + + /** + * generates the section of the report detailing the revisions made and the files changed + * + * @param sink the report formatting tool + * @param name filename of the changed file + * @param revision the revision code for this file + */ + private void sinkLogFile( Sink sink, String name, String revision ) + { + try + { + String connection = getConnection(); + + generateLinks( connection, name, revision, sink ); + } + catch ( Exception e ) + { + getLog().debug( e ); + + sink.text( name + " v " + revision ); + } + + sink.lineBreak(); + } + + /** + * attaches the http links from the changed files + * + * @param connection the string used to connect to the SCM + * @param name filename of the file that was changed + * @param sink the report formatting tool + */ + protected void generateLinks( String connection, String name, Sink sink ) + { + generateLinks( connection, name, null, sink ); + } + + /** + * attaches the http links from the changed files + * + * @param connection the string used to connect to the SCM + * @param name filename of the file that was changed + * @param revision the revision code + * @param sink the report formatting tool + */ + protected void generateLinks( String connection, String name, String revision, Sink sink ) + { + String linkFile; + String linkRev = null; + + if ( revision != null ) + { + linkFile = displayFileRevDetailUrl; + } + else + { + linkFile = displayFileDetailUrl; + } + + if ( linkFile != null ) + { + if ( !linkFile.equals( scmUrl ) ) + { + String linkName = name; + if ( encodeFileUri ) + { + try + { + linkName = URLEncoder.encode( linkName, "UTF-8" ); + } + catch ( UnsupportedEncodingException e ) + { + // UTF-8 is always supported + } + } + + // Use the given URL to create links to the files + + if ( linkFile.indexOf( FILE_TOKEN ) > 0 ) + { + linkFile = linkFile.replaceAll( FILE_TOKEN, linkName ); + } + else + { + // This is here so that we are backwards compatible with the + // format used before the special token was introduced + + linkFile += linkName; + } + + // Use the given URL to create links to the files + + if ( revision != null && linkFile.indexOf( REV_TOKEN ) > 0 ) + { + linkFile = linkFile.replaceAll( REV_TOKEN, revision ); + } + } + else if ( connection.startsWith( "scm:perforce" ) ) + { + String path = getAbsolutePath( displayFileDetailUrl, name ); + linkFile = path + "?ac=22"; + if ( revision != null ) + { + linkRev = path + "?ac=64&rev=" + revision; + } + } + else if ( connection.startsWith( "scm:clearcase" ) ) + { + String path = getAbsolutePath( displayFileDetailUrl, name ); + linkFile = path + rptOneRepoParam; + } + else if ( connection.indexOf( "cvsmonitor.pl" ) > 0 ) + { + Pattern cvsMonitorRegex = Pattern.compile( "^.*(&module=.*?(?:&|$)).*$" ); + String module = cvsMonitorRegex.matcher( rptOneRepoParam ).replaceAll( "$1" ); + linkFile = displayFileDetailUrl + "?cmd=viewBrowseFile" + module + "&file=" + name; + if ( revision != null ) + { + linkRev = + rptRepository + "?cmd=viewBrowseVersion" + module + "&file=" + name + "&version=" + revision; + } + } + else + { + String path = getAbsolutePath( displayFileDetailUrl, name ); + linkFile = path + rptOneRepoParam; + if ( revision != null ) + { + linkRev = path + "?rev=" + revision + "&content-type=text/vnd.viewcvs-markup" + rptMultiRepoParam; + } + } + } + + if ( linkFile != null ) + { + sink.link( linkFile ); + sinkFileName( name, sink ); + sink.link_(); + } + else + { + sinkFileName( name, sink ); + } + + sink.text( " " ); + + if ( linkRev == null && revision != null && displayChangeSetDetailUrl != null ) + { + if ( displayChangeSetDetailUrl.indexOf( REV_TOKEN ) > 0 ) + { + linkRev = displayChangeSetDetailUrl.replaceAll( REV_TOKEN, revision ); + } + else + { + linkRev = displayChangeSetDetailUrl + revision; + } + } + + if ( linkRev != null ) + { + sink.link( linkRev ); + sink.text( "v " + revision ); + sink.link_(); + } + else if ( revision != null ) + { + sink.text( "v " + revision ); + } + } + + /** + * Encapsulates the logic for rendering the name with a bolded markup. + * + * @param name filename of the file that was changed + * @param sink the report formatting tool + */ + private void sinkFileName( String name, Sink sink ) + { + name = sinkFileNamePattern.matcher( name ).replaceAll( "/" ); + int pos = name.lastIndexOf( '/' ); + + String head; + String tail; + if ( pos < 0 ) + { + head = ""; + tail = name; + } + else + { + head = name.substring( 0, pos ) + '/'; + tail = name.substring( pos + 1 ); + } + + sink.text( head ); + sink.bold(); + sink.text( tail ); + sink.bold_(); + } + + /** + * calculates the path from a base directory to a target file + * + * @param base base directory to create the absolute path from + * @param target target file to create the absolute path to + */ + private String getAbsolutePath( final String base, final String target ) + { + String absPath = ""; + + StringTokenizer baseTokens = + new StringTokenizer( sinkFileNamePattern.matcher( base ).replaceAll( "/" ), "/", true ); + + StringTokenizer targetTokens = + new StringTokenizer( sinkFileNamePattern.matcher( target ).replaceAll( "/" ), "/" ); + + String targetRoot = targetTokens.nextToken(); + + while ( baseTokens.hasMoreTokens() ) + { + String baseToken = baseTokens.nextToken(); + + if ( baseToken.equals( targetRoot ) ) + { + break; + } + + absPath += baseToken; + } + + if ( !absPath.endsWith( "/" ) ) + { + absPath += "/"; + } + + String newTarget = target; + if ( newTarget.startsWith( "/" ) ) + { + newTarget = newTarget.substring( 1 ); + } + + return absPath + newTarget; + } + + /** + * {@inheritDoc} + */ + protected MavenProject getProject() + { + return project; + } + + /** + * {@inheritDoc} + */ + protected String getOutputDirectory() + { + if ( !outputDirectory.isAbsolute() ) + { + outputDirectory = new File( project.getBasedir(), outputDirectory.getPath() ); + } + + return outputDirectory.getAbsolutePath(); + } + + /** + * Gets the effective reporting output files encoding. + * + * @return The effective reporting output file encoding, never null. + */ + protected String getOutputEncoding() + { + return ( outputEncoding != null ) ? outputEncoding : ReaderFactory.UTF_8; + } + + /** + * {@inheritDoc} + */ + protected Renderer getSiteRenderer() + { + return siteRenderer; + } + + /** + * {@inheritDoc} + */ + public String getDescription( Locale locale ) + { + return getBundle( locale ).getString( "report.changelog.description" ); + } + + /** + * {@inheritDoc} + */ + public String getName( Locale locale ) + { + return getBundle( locale ).getString( "report.changelog.name" ); + } + + /** + * {@inheritDoc} + */ + public String getOutputName() + { + return "changelog"; + } + + /** + * @param locale + * @return the current bundle + */ + protected ResourceBundle getBundle( Locale locale ) + { + return ResourceBundle.getBundle( "scm-activity", locale, this.getClass().getClassLoader() ); + } + + /** + * {@inheritDoc} + */ + public boolean canGenerateReport() + { + if ( offline && !outputXML.exists() ) + { + return false; + } + + return !skip; + + } +} diff --git a/Java/maven-changelog-plugin-ChangeLogReport_1527/metadata.json b/Java/maven-changelog-plugin-ChangeLogReport_1527/metadata.json new file mode 100644 index 000000000..d47ad82e9 --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogReport_1527/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-changelog-plugin-ChangeLogReport_1527", + "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 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": "src/main/java/org/apache/maven/plugin/changelog/ChangeLogReport.java", + "line": 1535, + "npe_method": "sinkAuthorDetails", + "deref_field": "developer", + "npe_class": "ChangeLogReport", + "repo": "maven-changelog-plugin", + "bug_id": "ChangeLogReport_1527" + } +} diff --git a/Java/maven-changelog-plugin-ChangeLogReport_1527/npe.json b/Java/maven-changelog-plugin-ChangeLogReport_1527/npe.json new file mode 100644 index 000000000..b85e53578 --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogReport_1527/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/maven/plugin/changelog/ChangeLogReport.java", + "line": 1535, + "npe_method": "sinkAuthorDetails", + "deref_field": "developer", + "npe_class": "ChangeLogReport" +} \ No newline at end of file diff --git a/Java/maven-changelog-plugin-ChangeLogReport_446/Dockerfile b/Java/maven-changelog-plugin-ChangeLogReport_446/Dockerfile new file mode 100644 index 000000000..9a945184a --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogReport_446/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-changelog-plugin + +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-changelog-plugin-ChangeLogReport_446/buggy.java b/Java/maven-changelog-plugin-ChangeLogReport_446/buggy.java new file mode 100644 index 000000000..f5876e054 --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogReport_446/buggy.java @@ -0,0 +1,1899 @@ +package org.apache.maven.plugin.changelog; + +/* + * 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.sink.Sink; +import org.apache.maven.doxia.siterenderer.Renderer; +import org.apache.maven.model.Developer; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; +import org.apache.maven.reporting.AbstractMavenReport; +import org.apache.maven.reporting.MavenReportException; +import org.apache.maven.scm.ChangeFile; +import org.apache.maven.scm.ChangeSet; +import org.apache.maven.scm.ScmBranch; +import org.apache.maven.scm.ScmException; +import org.apache.maven.scm.ScmFileSet; +import org.apache.maven.scm.ScmResult; +import org.apache.maven.scm.ScmRevision; +import org.apache.maven.scm.command.changelog.ChangeLogScmResult; +import org.apache.maven.scm.command.changelog.ChangeLogSet; +import org.apache.maven.scm.command.info.InfoItem; +import org.apache.maven.scm.command.info.InfoScmResult; +import org.apache.maven.scm.log.DefaultLog; +import org.apache.maven.scm.manager.ScmManager; +import org.apache.maven.scm.provider.ScmProvider; +import org.apache.maven.scm.provider.ScmProviderRepository; +import org.apache.maven.scm.provider.ScmProviderRepositoryWithHost; +import org.apache.maven.scm.provider.svn.repository.SvnScmProviderRepository; +import org.apache.maven.scm.provider.svn.svnexe.command.info.SvnInfoCommandExpanded; +import org.apache.maven.scm.repository.ScmRepository; +import org.apache.maven.settings.Server; +import org.apache.maven.settings.Settings; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.util.WriterFactory; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.StringReader; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.net.URLEncoder; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.ResourceBundle; +import java.util.StringTokenizer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Generate a changelog report. + * + * @version $Id$ + */ +@Mojo( name = "changelog" ) +public class ChangeLogReport + extends AbstractMavenReport +{ + /** + * A special token that represents the SCM relative path for a file. + * It can be used in displayFileDetailUrl. + */ + private static final String FILE_TOKEN = "%FILE%"; + + /** + * A special token that represents a Mantis/Bugzilla/JIRA/etc issue ID. + * It can be used in the issueLinkUrl. + */ + private static final String ISSUE_TOKEN = "%ISSUE%"; + + /** + * A special token that represents the SCM revision number. + * It can be used in displayChangeSetDetailUrl + * and displayFileRevDetailUrl. + */ + private static final String REV_TOKEN = "%REV%"; + + /** + * The number of days to use as a range, when this is not specified. + */ + private static final int DEFAULT_RANGE = 30; + + public static final String DEFAULT_ISSUE_ID_REGEX_PATTERN = "[a-zA-Z]{2,}-\\d+"; + + private static final String DEFAULT_ISSUE_LINK_URL = "https://issues.apache.org/jira/browse/" + ISSUE_TOKEN; + + /** + * Used to specify the format to use for the dates in the headings of the + * report. + * + * @since 2.1 + */ + @Parameter( property = "changelog.headingDateFormat", defaultValue = "yyyy-MM-dd" ) + private String headingDateFormat = "yyyy-MM-dd"; + + /** + * Used to specify whether to build the log using range, tag or date. + */ + @Parameter( property = "changelog.type", defaultValue = "range", required = true ) + private String type; + + /** + * Used to specify the number of days of log entries to retrieve. + */ + @Parameter( property = "changelog.range", defaultValue = "-1" ) + private int range; + + /** + * Used to specify the absolute date (or list of dates) to start log entries from. + */ + @Parameter + private List dates; + + /** + * Used to specify the tag (or list of tags) to start log entries from. + */ + @Parameter + private List tags; + + /** + * Used to specify the date format of the log entries that are retrieved from your SCM system. + */ + @Parameter( property = "changelog.dateFormat", defaultValue = "yyyy-MM-dd HH:mm:ss", required = true ) + private String dateFormat; + + /** + * Input dir. Directory where the files under SCM control are located. + */ + @Parameter( property = "basedir", required = true ) + private File basedir; + + /** + * Output file for xml document + */ + @Parameter( defaultValue = "${project.build.directory}/changelog.xml", required = true ) + private File outputXML; + + /** + * Allows the user to make changelog regenerate the changelog.xml file for the specified time in minutes. + */ + @Parameter( property = "outputXMLExpiration", defaultValue = "60", required = true ) + private int outputXMLExpiration; + + /** + * Comment format string used for interrogating + * the revision control system. + * Currently only used by the ClearcaseChangeLogGenerator. + */ + @Parameter( property = "changelog.commentFormat" ) + private String commentFormat; + + /** + * The file encoding when writing non-HTML reports. + */ + @Parameter( property = "changelog.outputEncoding", defaultValue = "${project.reporting.outputEncoding}" ) + private String outputEncoding; + + /** + * The user name (used by svn and starteam protocol). + */ + @Parameter( property = "username" ) + private String username; + + /** + * The user password (used by svn and starteam protocol). + */ + @Parameter( property = "password" ) + private String password; + + /** + * The private key (used by java svn). + */ + @Parameter( property = "privateKey" ) + private String privateKey; + + /** + * The passphrase (used by java svn). + */ + @Parameter( property = "passphrase" ) + private String passphrase; + + /** + * The url of tags base directory (used by svn protocol). + */ + @Parameter( property = "tagBase" ) + private String tagBase; + + /** + * The URL to view the scm. Basis for external links from the generated report. + */ + @Parameter( property = "project.scm.url" ) + protected String scmUrl; + + /** + * Skip the Changelog report generation. Most useful on the command line + * via "-Dchangelog.skip=true". + * + * @since 2.3 + */ + @Parameter( property = "changelog.skip", defaultValue = "false" ) + protected boolean skip; + + /** + * Encodes slashes in file uri. Required for some repository browsers like gitblit + * + * @since 2.3 + */ + @Parameter( property = "encodeFileUri", defaultValue = "false" ) + protected boolean encodeFileUri; + + /** + * List of files to include. Specified as fileset patterns of files to include in the report + * + * @since 2.3 + */ + @Parameter + private String[] includes; + + /** + * List of files to include. Specified as fileset patterns of files to omit in the report + * + * @since 2.3 + */ + @Parameter + private String[] excludes; + + + /** + * The Maven Project Object + */ + @Parameter( defaultValue = "${project}", readonly = true, required = true ) + private MavenProject project; + + /** + * The directory where the report will be generated + */ + @Parameter( property = "project.reporting.outputDirectory", required = true, readonly = true ) + private File outputDirectory; + + /** + */ + @Component + private Renderer siteRenderer; + + /** + */ + @Parameter( property = "settings.offline", required = true, readonly = true ) + private boolean offline; + + /** + */ + @Component + private ScmManager manager; + + /** + */ + @Parameter( defaultValue = "${settings}", readonly = true, required = true ) + private Settings settings; + + /** + * Allows the user to choose which scm connection to use when connecting to the scm. + * Can either be "connection" or "developerConnection". + */ + @Parameter( defaultValue = "connection", required = true ) + private String connectionType; + + /** + * A template string that is used to create the URL to the file details. + * There is a special token that you can use in your template: + *
    + *
  • %FILE% - this is the path to a file
  • + *
+ *

+ * Example: + * http://checkstyle.cvs.sourceforge.net/checkstyle%FILE%?view=markup + *

+ *

+ * Note: If you don't supply the token in your template, + * the path of the file will simply be appended to your template URL. + *

+ */ + @Parameter( property = "displayFileDetailUrl", defaultValue = "${project.scm.url}" ) + protected String displayFileDetailUrl; + + /** + * A pattern used to identify 'issue tracker' IDs such as those used by JIRA, + * Bugzilla and alike in the SCM commit messages. Any matched patterns + * are replaced with issueLinkUrl URL. The default + * value is a JIRA-style issue identification pattern. + *

+ * Note: Default value is [a-zA-Z]{2,}-\d+ + * + * @since 2.2 + */ + @Parameter( property = "issueIDRegexPattern", defaultValue = DEFAULT_ISSUE_ID_REGEX_PATTERN, required = true ) + private String issueIDRegexPattern; + + /** + * The issue tracker URL used when replacing any matched issueIDRegexPattern + * found in the SCM commit messages. The default is URL is the codehaus JIRA + * URL. If %ISSUE% is found in the URL it is replaced with the matched issue ID, + * otherwise the matched issue ID is appended to the URL. + * + * @since 2.2 + */ + @Parameter( property = "issueLinkUrl", defaultValue = DEFAULT_ISSUE_LINK_URL, required = true ) + private String issueLinkUrl; + + /** + * A template string that is used to create the changeset URL. + *

+ * If not defined no change set link will be created. + *

+ * There is one special token that you can use in your template: + *

    + *
  • %REV% - this is the changeset revision
  • + *
+ *

+ * Example: + * http://fisheye.sourceforge.net/changelog/a-project/?cs=%REV% + *

+ *

+ * Note: If you don't supply the %REV% token in your template, + * the revision will simply be appended to your template URL. + *

+ * + * @since 2.2 + */ + @Parameter( property = "displayChangeSetDetailUrl" ) + protected String displayChangeSetDetailUrl; + + /** + * A template string that is used to create the revision aware URL to + * the file details in a similar fashion to the displayFileDetailUrl. + * When a report contains both file and file revision information, as in the + * Change Log report, this template string can be used to create a revision + * aware URL to the file details. + *

+ * If not defined this template string defaults to the same value as the + * displayFileDetailUrl and thus revision number aware links will + * not be used. + *

+ * There are two special tokens that you can use in your template: + *

    + *
  • %FILE% - this is the path to a file
  • + *
  • %REV% - this is the revision of the file
  • + *
+ *

+ * Example: + * http://fisheye.sourceforge.net/browse/a-project/%FILE%?r=%REV% + *

+ *

+ * Note: If you don't supply the %FILE% token in your template, + * the path of the file will simply be appended to your template URL. + *

+ * + * @since 2.2 + */ + @Parameter( property = "displayFileRevDetailUrl" ) + protected String displayFileRevDetailUrl; + + /** + * List of developers to be shown on the report. + * + * @since 2.2 + */ + @Parameter( property = "project.developers" ) + protected List developers; + + /** + * List of provider implementations. + */ + @Parameter + private Map providerImplementations; + + // temporary field holder while generating the report + private String rptRepository, rptOneRepoParam, rptMultiRepoParam; + + // field for SCM Connection URL + private String connection; + + // field used to hold a map of the developers by Id + private HashMap developersById; + + // field used to hold a map of the developers by Name + private HashMap developersByName; + + /** + * The system properties to use (needed by the perforce scm provider). + */ + @Parameter + private Properties systemProperties; + + private final Pattern sinkFileNamePattern = Pattern.compile( "\\\\" ); + + /** + * {@inheritDoc} + */ +/** + * {@inheritDoc } + */ +public void executeReport(java.util.Locale locale) throws org.apache.maven.reporting.MavenReportException { + // check if sources exists <-- required for parent poms + if (!basedir.exists()) { + doGenerateEmptyReport(getBundle(locale), getSink()); + return; + } + { + for (java.util.Map.Entry entry : /* NPEX_NULL_EXP */ + providerImplementations.entrySet()) { + java.lang.String providerType = entry.getKey(); + java.lang.String providerImplementation = entry.getValue(); + getLog().info(((("Change the default '" + providerType) + "' provider implementation to '") + providerImplementation) + "'."); + manager.setScmProviderImplementation(providerType, providerImplementation); + } + } + initializeDefaultConfigurationParameters(); + initializeDeveloperMaps(); + verifySCMTypeParams(); + if (systemProperties != null) { + // Add all system properties configured by the user + java.util.Iterator iter = systemProperties.keySet().iterator(); + while (iter.hasNext()) { + java.lang.String key = ((java.lang.String) (iter.next())); + java.lang.String value = systemProperties.getProperty(key); + java.lang.System.setProperty(key, value); + getLog().debug((("Setting system property: " + key) + '=') + value); + } + } + doGenerateReport(getChangedSets(), getBundle(locale), getSink()); +} + + /** + * Initializes any configuration parameters that have not/can not be defined + * or defaulted by the Mojo API. + */ + private void initializeDefaultConfigurationParameters() + { + if ( displayFileRevDetailUrl == null || displayFileRevDetailUrl.length() == 0 ) + { + displayFileRevDetailUrl = displayFileDetailUrl; + } + } + + /** + * Creates maps of the project developers by developer Id and developer Name + * for quick lookups. + */ + private void initializeDeveloperMaps() + { + developersById = new HashMap(); + developersByName = new HashMap(); + + if ( developers != null ) + { + for ( Developer developer : developers ) + { + developersById.put( developer.getId(), developer ); + developersByName.put( developer.getName(), developer ); + } + } + } + + /** + * populates the changedSets field by either connecting to the SCM or using an existing XML generated in a previous + * run of the report + * + * @throws MavenReportException + */ + protected List getChangedSets() + throws MavenReportException + { + List changelogList = null; + + if ( !outputXML.isAbsolute() ) + { + outputXML = new File( project.getBasedir(), outputXML.getPath() ); + } + + if ( outputXML.exists() ) + { + // CHECKSTYLE_OFF: MagicNumber + if ( outputXMLExpiration > 0 + && outputXMLExpiration * 60000 > System.currentTimeMillis() - outputXML.lastModified() ) + // CHECKSTYLE_ON: MagicNumber + { + try + { + //ReaderFactory.newReader( outputXML, getOutputEncoding() ); + //FileInputStream fIn = new FileInputStream( outputXML ); + + getLog().info( "Using existing changelog.xml..." ); + + changelogList = + ChangeLog.loadChangedSets( ReaderFactory.newReader( outputXML, getOutputEncoding() ) ); + } + catch ( FileNotFoundException e ) + { + //do nothing, just regenerate + } + catch ( Exception e ) + { + throw new MavenReportException( "An error occurred while parsing " + outputXML.getAbsolutePath(), + e ); + } + } + } + + if ( changelogList == null ) + { + if ( offline ) + { + throw new MavenReportException( "This report requires online mode." ); + } + + getLog().info( "Generating changed sets xml to: " + outputXML.getAbsolutePath() ); + + changelogList = generateChangeSetsFromSCM(); + + try + { + writeChangelogXml( changelogList ); + } + catch ( FileNotFoundException e ) + { + throw new MavenReportException( "Can't create " + outputXML.getAbsolutePath(), e ); + } + catch ( UnsupportedEncodingException e ) + { + throw new MavenReportException( "Can't create " + outputXML.getAbsolutePath(), e ); + } + catch ( IOException e ) + { + throw new MavenReportException( "Can't create " + outputXML.getAbsolutePath(), e ); + } + } + + return changelogList; + } + + private void writeChangelogXml( List changelogList ) + throws IOException + { + StringBuilder changelogXml = new StringBuilder(); + + changelogXml.append( "\n" ); + changelogXml.append( "" ); + + for ( ChangeLogSet changelogSet : changelogList ) + { + changelogXml.append( "\n " ); + + String changeset = changelogSet.toXML( getOutputEncoding() ); + + //remove xml header + if ( changeset.startsWith( "" ) + 2; + changeset = changeset.substring( idx ); + } + + changelogXml.append( changeset ); + } + + changelogXml.append( "\n" ); + + outputXML.getParentFile().mkdirs(); + + //PrintWriter pw = new PrintWriter( new BufferedOutputStream( new FileOutputStream( outputXML ) ) ); + //pw.write( changelogXml.toString() ); + //pw.flush(); + //pw.close(); + // MCHANGELOG-86 + Writer writer = WriterFactory.newWriter( new BufferedOutputStream( new FileOutputStream( outputXML ) ), + getOutputEncoding() ); + writer.write( changelogXml.toString() ); + writer.flush(); + writer.close(); + } + + /** + * creates a ChangeLog object and then connects to the SCM to generate the changed sets + * + * @return changedlogsets generated from the SCM + * @throws MavenReportException + */ + protected List generateChangeSetsFromSCM() + throws MavenReportException + { + try + { + List changeSets = new ArrayList(); + + ScmRepository repository = getScmRepository(); + + ScmProvider provider = manager.getProviderByRepository( repository ); + + ChangeLogScmResult result; + + if ( "range".equals( type ) ) + { + result = provider.changeLog( repository, new ScmFileSet( basedir ), null, null, range, (ScmBranch) null, + dateFormat ); + + checkResult( result ); + + changeSets.add( result.getChangeLog() ); + } + else if ( "tag".equals( type ) ) + { + + Iterator tagsIter = tags.iterator(); + + String startTag = tagsIter.next(); + String endTag = null; + + if ( tagsIter.hasNext() ) + { + while ( tagsIter.hasNext() ) + { + endTag = tagsIter.next(); + String endRevision = getRevisionForTag( endTag, repository, provider ); + String startRevision = getRevisionForTag( startTag, repository, provider ); + result = provider.changeLog( repository, new ScmFileSet( basedir ), + new ScmRevision( startRevision ), + new ScmRevision( endRevision ) ); + + checkResult( result ); + result.getChangeLog().setStartVersion( new ScmRevision( startTag ) ); + result.getChangeLog().setEndVersion( new ScmRevision( endTag ) ); + + changeSets.add( result.getChangeLog() ); + + startTag = endTag; + } + } + else + { + String startRevision = getRevisionForTag( startTag, repository, provider ); + String endRevision = getRevisionForTag( endTag, repository, provider ); + result = provider.changeLog( repository, new ScmFileSet( basedir ), + new ScmRevision( startRevision ), + new ScmRevision( endRevision ) ); + + checkResult( result ); + result.getChangeLog().setStartVersion( new ScmRevision( startTag ) ); + result.getChangeLog().setEndVersion( null ); + changeSets.add( result.getChangeLog() ); + } + } + else if ( "date".equals( type ) ) + { + Iterator dateIter = dates.iterator(); + + String startDate = dateIter.next(); + String endDate = null; + + if ( dateIter.hasNext() ) + { + while ( dateIter.hasNext() ) + { + endDate = dateIter.next(); + + result = provider.changeLog( repository, new ScmFileSet( basedir ), parseDate( startDate ), + parseDate( endDate ), 0, (ScmBranch) null ); + + checkResult( result ); + + changeSets.add( result.getChangeLog() ); + + startDate = endDate; + } + } + else + { + result = provider.changeLog( repository, new ScmFileSet( basedir ), parseDate( startDate ), + parseDate( endDate ), 0, (ScmBranch) null ); + + checkResult( result ); + + changeSets.add( result.getChangeLog() ); + } + } + else + { + throw new MavenReportException( "The type '" + type + "' isn't supported." ); + } + filter( changeSets ); + return changeSets; + + } + catch ( ScmException e ) + { + throw new MavenReportException( "Cannot run changelog command : ", e ); + } + catch ( MojoExecutionException e ) + { + throw new MavenReportException( "An error has occurred during changelog command : ", e ); + } + } + + /** + * Resolves the given tag to the revision number. + * + * @param tag + * @param repository + * @param provider + * @return + * @throws ScmException + */ + private String getRevisionForTag( final String tag, final ScmRepository repository, final ScmProvider provider ) + throws ScmException + { + if ( repository.getProvider().equals( "svn" ) ) + { + if ( tag == null ) + { + return "HEAD"; + } + SvnInfoCommandExpanded infoCommand = new SvnInfoCommandExpanded(); + infoCommand.setLogger( new DefaultLog() ); + + InfoScmResult infoScmResult = + infoCommand.executeInfoTagCommand( (SvnScmProviderRepository) repository.getProviderRepository(), + new ScmFileSet( basedir ), tag, null, false, null ); + if ( infoScmResult.getInfoItems().size() == 0 ) + { + throw new ScmException( "There is no tag named '" + tag + "' in the Subversion repository." ); + } + InfoItem infoItem = infoScmResult.getInfoItems().get( 0 ); + String revision = infoItem.getLastChangedRevision(); + getLog().info( String.format( "Resolved tag '%s' to revision '%s'", tag, revision ) ); + return revision; + } + return tag; + } + + /** + * filters out unwanted files from the changesets + */ + private void filter( List changeSets ) + { + List include = compilePatterns( includes ); + List exclude = compilePatterns( excludes ); + if ( includes == null && excludes == null ) + { + return; + } + for ( ChangeLogSet changeLogSet : changeSets ) + { + List set = changeLogSet.getChangeSets(); + filter( set, include, exclude ); + } + + } + + private List compilePatterns( String[] patternArray ) + { + if ( patternArray == null ) + { + return new ArrayList(); + } + List patterns = new ArrayList( patternArray.length ); + for ( String string : patternArray ) + { + //replaces * with [/\]* (everything but file seperators) + //replaces ** with .* + //quotes the rest of the string + string = "\\Q" + string + "\\E"; + string = string.replace( "**", "\\E.?REPLACEMENT?\\Q" ); + string = string.replace( "*", "\\E[^/\\\\]?REPLACEMENT?\\Q" ); + string = string.replace( "?REPLACEMENT?", "*" ); + string = string.replace( "\\Q\\E", "" ); + patterns.add( Pattern.compile( string ) ); + } + return patterns; + } + + private void filter( List sets, List includes, List excludes ) + { + Iterator it = sets.iterator(); + while ( it.hasNext() ) + { + ChangeSet changeSet = it.next(); + List files = changeSet.getFiles(); + Iterator iterator = files.iterator(); + while ( iterator.hasNext() ) + { + ChangeFile changeFile = iterator.next(); + String name = changeFile.getName(); + if ( !isIncluded( includes, name ) || isExcluded( excludes, name ) ) + { + iterator.remove(); + } + } + if ( files.isEmpty() ) + { + it.remove(); + } + } + } + + private boolean isExcluded( List excludes, String name ) + { + if ( excludes == null || excludes.isEmpty() ) + { + return false; + } + for ( Pattern pattern : excludes ) + { + if ( pattern.matcher( name ).matches() ) + { + return true; + } + } + return false; + } + + private boolean isIncluded( List includes, String name ) + { + if ( includes == null || includes.isEmpty() ) + { + return true; + } + for ( Pattern pattern : includes ) + { + if ( pattern.matcher( name ).matches() ) + { + return true; + } + } + return false; + } + + /** + * Converts the localized date string pattern to date object. + * + * @return A date + */ + private Date parseDate( String date ) + throws MojoExecutionException + { + if ( date == null || date.trim().length() == 0 ) + { + return null; + } + + SimpleDateFormat formatter = new SimpleDateFormat( "yyyy-MM-dd" ); + + try + { + return formatter.parse( date ); + } + catch ( ParseException e ) + { + throw new MojoExecutionException( "Please use this date pattern: " + formatter.toLocalizedPattern(), e ); + } + } + + public ScmRepository getScmRepository() + throws ScmException + { + ScmRepository repository; + + try + { + repository = manager.makeScmRepository( getConnection() ); + + ScmProviderRepository providerRepo = repository.getProviderRepository(); + + if ( !StringUtils.isEmpty( username ) ) + { + providerRepo.setUser( username ); + } + + if ( !StringUtils.isEmpty( password ) ) + { + providerRepo.setPassword( password ); + } + + if ( repository.getProviderRepository() instanceof ScmProviderRepositoryWithHost ) + { + ScmProviderRepositoryWithHost repo = (ScmProviderRepositoryWithHost) repository.getProviderRepository(); + + loadInfosFromSettings( repo ); + + if ( !StringUtils.isEmpty( username ) ) + { + repo.setUser( username ); + } + + if ( !StringUtils.isEmpty( password ) ) + { + repo.setPassword( password ); + } + + if ( !StringUtils.isEmpty( privateKey ) ) + { + repo.setPrivateKey( privateKey ); + } + + if ( !StringUtils.isEmpty( passphrase ) ) + { + repo.setPassphrase( passphrase ); + } + } + + if ( !StringUtils.isEmpty( tagBase ) && repository.getProvider().equals( "svn" ) ) + { + SvnScmProviderRepository svnRepo = (SvnScmProviderRepository) repository.getProviderRepository(); + + svnRepo.setTagBase( tagBase ); + } + } + catch ( Exception e ) + { + throw new ScmException( "Can't load the scm provider.", e ); + } + + return repository; + } + + /** + * Load username password from settings if user has not set them in JVM properties + * + * @param repo + */ + private void loadInfosFromSettings( ScmProviderRepositoryWithHost repo ) + { + if ( username == null || password == null ) + { + String host = repo.getHost(); + + int port = repo.getPort(); + + if ( port > 0 ) + { + host += ":" + port; + } + + Server server = this.settings.getServer( host ); + + if ( server != null ) + { + if ( username == null ) + { + username = this.settings.getServer( host ).getUsername(); + } + + if ( password == null ) + { + password = this.settings.getServer( host ).getPassword(); + } + + if ( privateKey == null ) + { + privateKey = this.settings.getServer( host ).getPrivateKey(); + } + + if ( passphrase == null ) + { + passphrase = this.settings.getServer( host ).getPassphrase(); + } + } + } + } + + public void checkResult( ScmResult result ) + throws MojoExecutionException + { + if ( !result.isSuccess() ) + { + getLog().error( "Provider message:" ); + + getLog().error( result.getProviderMessage() == null ? "" : result.getProviderMessage() ); + + getLog().error( "Command output:" ); + + getLog().error( result.getCommandOutput() == null ? "" : result.getCommandOutput() ); + + throw new MojoExecutionException( "Command failed." ); + } + } + + /** + * used to retrieve the SCM connection string + * + * @return the url string used to connect to the SCM + * @throws MavenReportException when there is insufficient information to retrieve the SCM connection string + */ + protected String getConnection() + throws MavenReportException + { + if ( this.connection != null ) + { + return connection; + } + + if ( project.getScm() == null ) + { + throw new MavenReportException( "SCM Connection is not set." ); + } + + String scmConnection = project.getScm().getConnection(); + if ( StringUtils.isNotEmpty( scmConnection ) && "connection".equals( connectionType.toLowerCase() ) ) + { + connection = scmConnection; + } + + String scmDeveloper = project.getScm().getDeveloperConnection(); + if ( StringUtils.isNotEmpty( scmDeveloper ) && "developerconnection".equals( connectionType.toLowerCase() ) ) + { + connection = scmDeveloper; + } + + if ( StringUtils.isEmpty( connection ) ) + { + throw new MavenReportException( "SCM Connection is not set." ); + } + + return connection; + } + + /** + * checks whether there are enough configuration parameters to generate the report + * + * @throws MavenReportException when there is insufficient paramters to generate the report + */ + private void verifySCMTypeParams() + throws MavenReportException + { + if ( "range".equals( type ) ) + { + if ( range == -1 ) + { + range = DEFAULT_RANGE; + } + } + else if ( "date".equals( type ) ) + { + if ( dates == null ) + { + throw new MavenReportException( "The dates parameter is required when type=\"date\". The value " + + "should be the absolute date for the start of the log." ); + } + } + else if ( "tag".equals( type ) ) + { + if ( tags == null ) + { + throw new MavenReportException( "The tags parameter is required when type=\"tag\"." ); + } + } + else + { + throw new MavenReportException( "The type parameter has an invalid value: " + type + + ". The value should be \"range\", \"date\", or \"tag\"." ); + } + } + + /** + * generates an empty report in case there are no sources to generate a report with + * + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + protected void doGenerateEmptyReport( ResourceBundle bundle, Sink sink ) + { + sink.head(); + sink.title(); + sink.text( bundle.getString( "report.changelog.header" ) ); + sink.title_(); + sink.head_(); + + sink.body(); + sink.section1(); + + sink.sectionTitle1(); + sink.text( bundle.getString( "report.changelog.mainTitle" ) ); + sink.sectionTitle1_(); + + sink.paragraph(); + sink.text( bundle.getString( "report.changelog.nosources" ) ); + sink.paragraph_(); + + sink.section1_(); + + sink.body_(); + sink.flush(); + sink.close(); + } + + /** + * method that generates the report for this mojo. This method is overridden by dev-activity and file-activity mojo + * + * @param changeLogSets changed sets to generate the report from + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + protected void doGenerateReport( List changeLogSets, ResourceBundle bundle, Sink sink ) + { + sink.head(); + sink.title(); + sink.text( bundle.getString( "report.changelog.header" ) ); + sink.title_(); + sink.head_(); + + sink.body(); + sink.section1(); + + sink.sectionTitle1(); + sink.text( bundle.getString( "report.changelog.mainTitle" ) ); + sink.sectionTitle1_(); + + // Summary section + doSummarySection( changeLogSets, bundle, sink ); + + for ( ChangeLogSet changeLogSet : changeLogSets ) + { + doChangedSet( changeLogSet, bundle, sink ); + } + + sink.section1_(); + sink.body_(); + + sink.flush(); + sink.close(); + } + + /** + * generates the report summary section of the report + * + * @param changeLogSets changed sets to generate the report from + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + private void doSummarySection( List changeLogSets, ResourceBundle bundle, Sink sink ) + { + sink.paragraph(); + + sink.text( bundle.getString( "report.changelog.ChangedSetsTotal" ) ); + sink.text( ": " + changeLogSets.size() ); + + sink.paragraph_(); + } + + /** + * generates a section of the report referring to a changeset + * + * @param set the current ChangeSet to generate this section of the report + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + private void doChangedSet( ChangeLogSet set, ResourceBundle bundle, Sink sink ) + { + sink.section2(); + + doChangeSetTitle( set, bundle, sink ); + + doSummary( set, bundle, sink ); + + doChangedSetTable( set.getChangeSets(), bundle, sink ); + + sink.section2_(); + } + + /** + * Generate the title for the report. + * + * @param set change set to generate the report from + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + protected void doChangeSetTitle( ChangeLogSet set, ResourceBundle bundle, Sink sink ) + { + sink.sectionTitle2(); + + SimpleDateFormat headingDateFormater = new SimpleDateFormat( headingDateFormat ); + + if ( "tag".equals( type ) ) + { + if ( set.getStartVersion() == null || set.getStartVersion().getName() == null ) + { + sink.text( bundle.getString( "report.SetTagCreation" ) ); + if ( set.getEndVersion() != null && set.getEndVersion().getName() != null ) + { + sink.text( ' ' + bundle.getString( "report.SetTagUntil" ) + " '" + set.getEndVersion() + '\'' ); + } + } + else if ( set.getEndVersion() == null || set.getEndVersion().getName() == null ) + { + sink.text( bundle.getString( "report.SetTagSince" ) ); + sink.text( " '" + set.getStartVersion() + '\'' ); + } + else + { + sink.text( bundle.getString( "report.SetTagBetween" ) ); + sink.text( + " '" + set.getStartVersion() + "' " + bundle.getString( "report.And" ) + " '" + set.getEndVersion() + + '\'' ); + } + } + else if ( set.getStartDate() == null ) + { + sink.text( bundle.getString( "report.SetRangeUnknown" ) ); + } + else if ( set.getEndDate() == null ) + { + sink.text( bundle.getString( "report.SetRangeSince" ) ); + sink.text( ' ' + headingDateFormater.format( set.getStartDate() ) ); + } + else + { + sink.text( bundle.getString( "report.SetRangeBetween" ) ); + sink.text( + ' ' + headingDateFormater.format( set.getStartDate() ) + ' ' + bundle.getString( "report.And" ) + ' ' + + headingDateFormater.format( set.getEndDate() ) ); + } + sink.sectionTitle2_(); + } + + /** + * Generate the summary section of the report. + * + * @param set change set to generate the report from + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + protected void doSummary( ChangeLogSet set, ResourceBundle bundle, Sink sink ) + { + sink.paragraph(); + sink.text( bundle.getString( "report.TotalCommits" ) ); + sink.text( ": " + set.getChangeSets().size() ); + sink.lineBreak(); + sink.text( bundle.getString( "report.changelog.FilesChanged" ) ); + sink.text( ": " + countFilesChanged( set.getChangeSets() ) ); + sink.paragraph_(); + } + + /** + * counts the number of files that were changed in the specified SCM + * + * @param entries a collection of SCM changes + * @return number of files changed for the changedsets + */ + protected long countFilesChanged( Collection entries ) + { + if ( entries == null ) + { + return 0; + } + + if ( entries.isEmpty() ) + { + return 0; + } + + HashMap> fileList = new HashMap>(); + + for ( ChangeSet entry : entries ) + { + for ( ChangeFile file : entry.getFiles() ) + { + String name = file.getName(); + List list = fileList.get( name ); + + if ( list != null ) + { + list.add( file ); + } + else + { + list = new LinkedList(); + + list.add( file ); + + fileList.put( name, list ); + } + } + } + + return fileList.size(); + } + + /** + * generates the report table showing the SCM log entries + * + * @param entries a list of change log entries to generate the report from + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + private void doChangedSetTable( Collection entries, ResourceBundle bundle, Sink sink ) + { + sink.table(); + + sink.tableRow(); + sink.tableHeaderCell(); + sink.text( bundle.getString( "report.changelog.timestamp" ) ); + sink.tableHeaderCell_(); + sink.tableHeaderCell(); + sink.text( bundle.getString( "report.changelog.author" ) ); + sink.tableHeaderCell_(); + sink.tableHeaderCell(); + sink.text( bundle.getString( "report.changelog.details" ) ); + sink.tableHeaderCell_(); + sink.tableRow_(); + + initReportUrls(); + + List sortedEntries = new ArrayList( entries ); + Collections.sort( sortedEntries, new Comparator() + { + public int compare( ChangeSet changeSet0, ChangeSet changeSet1 ) + { + return changeSet1.getDate().compareTo( changeSet0.getDate() ); + } + } ); + + for ( ChangeSet entry : sortedEntries ) + { + doChangedSetDetail( entry, sink ); + } + + sink.table_(); + } + + /** + * reports on the details of an SCM entry log + * + * @param entry an SCM entry to generate the report from + * @param sink the report formatting tool + */ + private void doChangedSetDetail( ChangeSet entry, Sink sink ) + { + sink.tableRow(); + + sink.tableCell(); + sink.text( entry.getDateFormatted() + ' ' + entry.getTimeFormatted() ); + sink.tableCell_(); + + sink.tableCell(); + + sinkAuthorDetails( sink, entry.getAuthor() ); + + sink.tableCell_(); + + sink.tableCell(); + //doRevision( entry.getFiles(), bundle, sink ); + doChangedFiles( entry.getFiles(), sink ); + sink.lineBreak(); + StringReader sr = new StringReader( entry.getComment() ); + BufferedReader br = new BufferedReader( sr ); + String line; + try + { + if ( ( issueIDRegexPattern != null && issueIDRegexPattern.length() > 0 ) && ( issueLinkUrl != null + && issueLinkUrl.length() > 0 ) ) + { + Pattern pattern = Pattern.compile( issueIDRegexPattern ); + + line = br.readLine(); + + while ( line != null ) + { + sinkIssueLink( sink, line, pattern ); + + line = br.readLine(); + if ( line != null ) + { + sink.lineBreak(); + } + } + } + else + { + line = br.readLine(); + + while ( line != null ) + { + sink.text( line ); + line = br.readLine(); + if ( line != null ) + { + sink.lineBreak(); + } + } + } + } + catch ( IOException e ) + { + getLog().warn( "Unable to read the comment of a ChangeSet as a stream." ); + } + finally + { + try + { + br.close(); + } + catch ( IOException e ) + { + getLog().warn( "Unable to close a reader." ); + } + sr.close(); + } + sink.tableCell_(); + + sink.tableRow_(); + } + + private void sinkIssueLink( Sink sink, String line, Pattern pattern ) + { + // replace any ticket patterns found. + + Matcher matcher = pattern.matcher( line ); + + int currLoc = 0; + + while ( matcher.find() ) + { + String match = matcher.group(); + + String link; + + if ( issueLinkUrl.indexOf( ISSUE_TOKEN ) > 0 ) + { + link = issueLinkUrl.replaceAll( ISSUE_TOKEN, match ); + } + else + { + if ( issueLinkUrl.endsWith( "/" ) ) + { + link = issueLinkUrl; + } + else + { + link = issueLinkUrl + '/'; + } + + link += match; + } + + int startOfMatch = matcher.start(); + + String unmatchedText = line.substring( currLoc, startOfMatch ); + + currLoc = matcher.end(); + + sink.text( unmatchedText ); + + sink.link( link ); + sink.text( match ); + sink.link_(); + } + + sink.text( line.substring( currLoc ) ); + } + + /** + * If the supplied author is a known developer this method outputs a + * link to the team members report, or alternatively, if the supplied + * author is unknown, outputs the author's name as plain text. + * + * @param sink Sink to use for outputting + * @param author The author's name. + */ + protected void sinkAuthorDetails( Sink sink, String author ) + { + Developer developer = developersById.get( author ); + + if ( developer == null ) + { + developer = developersByName.get( author ); + } + + if ( developer != null ) + { + sink.link( "team-list.html#" + developer.getId() ); + sink.text( developer.getName() ); + sink.link_(); + } + else + { + sink.text( author ); + } + } + + /** + * populates the report url used to create links from certain elements of the report + */ + protected void initReportUrls() + { + if ( scmUrl != null ) + { + int idx = scmUrl.indexOf( '?' ); + + if ( idx > 0 ) + { + rptRepository = scmUrl.substring( 0, idx ); + + if ( scmUrl.equals( displayFileDetailUrl ) ) + { + String rptTmpMultiRepoParam = scmUrl.substring( rptRepository.length() ); + + rptOneRepoParam = '?' + rptTmpMultiRepoParam.substring( 1 ); + + rptMultiRepoParam = '&' + rptTmpMultiRepoParam.substring( 1 ); + } + } + else + { + rptRepository = scmUrl; + + rptOneRepoParam = ""; + + rptMultiRepoParam = ""; + } + } + } + + /** + * generates the section of the report listing all the files revisions + * + * @param files list of files to generate the reports from + * @param sink the report formatting tool + */ + private void doChangedFiles( List files, Sink sink ) + { + for ( ChangeFile file : files ) + { + sinkLogFile( sink, file.getName(), file.getRevision() ); + } + } + + /** + * generates the section of the report detailing the revisions made and the files changed + * + * @param sink the report formatting tool + * @param name filename of the changed file + * @param revision the revision code for this file + */ + private void sinkLogFile( Sink sink, String name, String revision ) + { + try + { + String connection = getConnection(); + + generateLinks( connection, name, revision, sink ); + } + catch ( Exception e ) + { + getLog().debug( e ); + + sink.text( name + " v " + revision ); + } + + sink.lineBreak(); + } + + /** + * attaches the http links from the changed files + * + * @param connection the string used to connect to the SCM + * @param name filename of the file that was changed + * @param sink the report formatting tool + */ + protected void generateLinks( String connection, String name, Sink sink ) + { + generateLinks( connection, name, null, sink ); + } + + /** + * attaches the http links from the changed files + * + * @param connection the string used to connect to the SCM + * @param name filename of the file that was changed + * @param revision the revision code + * @param sink the report formatting tool + */ + protected void generateLinks( String connection, String name, String revision, Sink sink ) + { + String linkFile; + String linkRev = null; + + if ( revision != null ) + { + linkFile = displayFileRevDetailUrl; + } + else + { + linkFile = displayFileDetailUrl; + } + + if ( linkFile != null ) + { + if ( !linkFile.equals( scmUrl ) ) + { + String linkName = name; + if ( encodeFileUri ) + { + try + { + linkName = URLEncoder.encode( linkName, "UTF-8" ); + } + catch ( UnsupportedEncodingException e ) + { + // UTF-8 is always supported + } + } + + // Use the given URL to create links to the files + + if ( linkFile.indexOf( FILE_TOKEN ) > 0 ) + { + linkFile = linkFile.replaceAll( FILE_TOKEN, linkName ); + } + else + { + // This is here so that we are backwards compatible with the + // format used before the special token was introduced + + linkFile += linkName; + } + + // Use the given URL to create links to the files + + if ( revision != null && linkFile.indexOf( REV_TOKEN ) > 0 ) + { + linkFile = linkFile.replaceAll( REV_TOKEN, revision ); + } + } + else if ( connection.startsWith( "scm:perforce" ) ) + { + String path = getAbsolutePath( displayFileDetailUrl, name ); + linkFile = path + "?ac=22"; + if ( revision != null ) + { + linkRev = path + "?ac=64&rev=" + revision; + } + } + else if ( connection.startsWith( "scm:clearcase" ) ) + { + String path = getAbsolutePath( displayFileDetailUrl, name ); + linkFile = path + rptOneRepoParam; + } + else if ( connection.indexOf( "cvsmonitor.pl" ) > 0 ) + { + Pattern cvsMonitorRegex = Pattern.compile( "^.*(&module=.*?(?:&|$)).*$" ); + String module = cvsMonitorRegex.matcher( rptOneRepoParam ).replaceAll( "$1" ); + linkFile = displayFileDetailUrl + "?cmd=viewBrowseFile" + module + "&file=" + name; + if ( revision != null ) + { + linkRev = + rptRepository + "?cmd=viewBrowseVersion" + module + "&file=" + name + "&version=" + revision; + } + } + else + { + String path = getAbsolutePath( displayFileDetailUrl, name ); + linkFile = path + rptOneRepoParam; + if ( revision != null ) + { + linkRev = path + "?rev=" + revision + "&content-type=text/vnd.viewcvs-markup" + rptMultiRepoParam; + } + } + } + + if ( linkFile != null ) + { + sink.link( linkFile ); + sinkFileName( name, sink ); + sink.link_(); + } + else + { + sinkFileName( name, sink ); + } + + sink.text( " " ); + + if ( linkRev == null && revision != null && displayChangeSetDetailUrl != null ) + { + if ( displayChangeSetDetailUrl.indexOf( REV_TOKEN ) > 0 ) + { + linkRev = displayChangeSetDetailUrl.replaceAll( REV_TOKEN, revision ); + } + else + { + linkRev = displayChangeSetDetailUrl + revision; + } + } + + if ( linkRev != null ) + { + sink.link( linkRev ); + sink.text( "v " + revision ); + sink.link_(); + } + else if ( revision != null ) + { + sink.text( "v " + revision ); + } + } + + /** + * Encapsulates the logic for rendering the name with a bolded markup. + * + * @param name filename of the file that was changed + * @param sink the report formatting tool + */ + private void sinkFileName( String name, Sink sink ) + { + name = sinkFileNamePattern.matcher( name ).replaceAll( "/" ); + int pos = name.lastIndexOf( '/' ); + + String head; + String tail; + if ( pos < 0 ) + { + head = ""; + tail = name; + } + else + { + head = name.substring( 0, pos ) + '/'; + tail = name.substring( pos + 1 ); + } + + sink.text( head ); + sink.bold(); + sink.text( tail ); + sink.bold_(); + } + + /** + * calculates the path from a base directory to a target file + * + * @param base base directory to create the absolute path from + * @param target target file to create the absolute path to + */ + private String getAbsolutePath( final String base, final String target ) + { + String absPath = ""; + + StringTokenizer baseTokens = + new StringTokenizer( sinkFileNamePattern.matcher( base ).replaceAll( "/" ), "/", true ); + + StringTokenizer targetTokens = + new StringTokenizer( sinkFileNamePattern.matcher( target ).replaceAll( "/" ), "/" ); + + String targetRoot = targetTokens.nextToken(); + + while ( baseTokens.hasMoreTokens() ) + { + String baseToken = baseTokens.nextToken(); + + if ( baseToken.equals( targetRoot ) ) + { + break; + } + + absPath += baseToken; + } + + if ( !absPath.endsWith( "/" ) ) + { + absPath += "/"; + } + + String newTarget = target; + if ( newTarget.startsWith( "/" ) ) + { + newTarget = newTarget.substring( 1 ); + } + + return absPath + newTarget; + } + + /** + * {@inheritDoc} + */ + protected MavenProject getProject() + { + return project; + } + + /** + * {@inheritDoc} + */ + protected String getOutputDirectory() + { + if ( !outputDirectory.isAbsolute() ) + { + outputDirectory = new File( project.getBasedir(), outputDirectory.getPath() ); + } + + return outputDirectory.getAbsolutePath(); + } + + /** + * Gets the effective reporting output files encoding. + * + * @return The effective reporting output file encoding, never null. + */ + protected String getOutputEncoding() + { + return ( outputEncoding != null ) ? outputEncoding : ReaderFactory.UTF_8; + } + + /** + * {@inheritDoc} + */ + protected Renderer getSiteRenderer() + { + return siteRenderer; + } + + /** + * {@inheritDoc} + */ + public String getDescription( Locale locale ) + { + return getBundle( locale ).getString( "report.changelog.description" ); + } + + /** + * {@inheritDoc} + */ + public String getName( Locale locale ) + { + return getBundle( locale ).getString( "report.changelog.name" ); + } + + /** + * {@inheritDoc} + */ + public String getOutputName() + { + return "changelog"; + } + + /** + * @param locale + * @return the current bundle + */ + protected ResourceBundle getBundle( Locale locale ) + { + return ResourceBundle.getBundle( "scm-activity", locale, this.getClass().getClassLoader() ); + } + + /** + * {@inheritDoc} + */ + public boolean canGenerateReport() + { + if ( offline && !outputXML.exists() ) + { + return false; + } + + return !skip; + + } +} diff --git a/Java/maven-changelog-plugin-ChangeLogReport_446/metadata.json b/Java/maven-changelog-plugin-ChangeLogReport_446/metadata.json new file mode 100644 index 000000000..82259d222 --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogReport_446/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-changelog-plugin-ChangeLogReport_446", + "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 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": "src/main/java/org/apache/maven/plugin/changelog/ChangeLogReport.java", + "line": 446, + "npe_method": "executeReport", + "deref_field": "providerImplementations", + "npe_class": "ChangeLogReport", + "repo": "maven-changelog-plugin", + "bug_id": "ChangeLogReport_446" + } +} diff --git a/Java/maven-changelog-plugin-ChangeLogReport_446/npe.json b/Java/maven-changelog-plugin-ChangeLogReport_446/npe.json new file mode 100644 index 000000000..cde54dce9 --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogReport_446/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/maven/plugin/changelog/ChangeLogReport.java", + "line": 446, + "npe_method": "executeReport", + "deref_field": "providerImplementations", + "npe_class": "ChangeLogReport" +} \ No newline at end of file diff --git a/Java/maven-changelog-plugin-ChangeLogReport_465/Dockerfile b/Java/maven-changelog-plugin-ChangeLogReport_465/Dockerfile new file mode 100644 index 000000000..9a945184a --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogReport_465/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-changelog-plugin + +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-changelog-plugin-ChangeLogReport_465/buggy.java b/Java/maven-changelog-plugin-ChangeLogReport_465/buggy.java new file mode 100644 index 000000000..4640419cd --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogReport_465/buggy.java @@ -0,0 +1,1899 @@ +package org.apache.maven.plugin.changelog; + +/* + * 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.sink.Sink; +import org.apache.maven.doxia.siterenderer.Renderer; +import org.apache.maven.model.Developer; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; +import org.apache.maven.reporting.AbstractMavenReport; +import org.apache.maven.reporting.MavenReportException; +import org.apache.maven.scm.ChangeFile; +import org.apache.maven.scm.ChangeSet; +import org.apache.maven.scm.ScmBranch; +import org.apache.maven.scm.ScmException; +import org.apache.maven.scm.ScmFileSet; +import org.apache.maven.scm.ScmResult; +import org.apache.maven.scm.ScmRevision; +import org.apache.maven.scm.command.changelog.ChangeLogScmResult; +import org.apache.maven.scm.command.changelog.ChangeLogSet; +import org.apache.maven.scm.command.info.InfoItem; +import org.apache.maven.scm.command.info.InfoScmResult; +import org.apache.maven.scm.log.DefaultLog; +import org.apache.maven.scm.manager.ScmManager; +import org.apache.maven.scm.provider.ScmProvider; +import org.apache.maven.scm.provider.ScmProviderRepository; +import org.apache.maven.scm.provider.ScmProviderRepositoryWithHost; +import org.apache.maven.scm.provider.svn.repository.SvnScmProviderRepository; +import org.apache.maven.scm.provider.svn.svnexe.command.info.SvnInfoCommandExpanded; +import org.apache.maven.scm.repository.ScmRepository; +import org.apache.maven.settings.Server; +import org.apache.maven.settings.Settings; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.util.WriterFactory; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.StringReader; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.net.URLEncoder; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.ResourceBundle; +import java.util.StringTokenizer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Generate a changelog report. + * + * @version $Id$ + */ +@Mojo( name = "changelog" ) +public class ChangeLogReport + extends AbstractMavenReport +{ + /** + * A special token that represents the SCM relative path for a file. + * It can be used in displayFileDetailUrl. + */ + private static final String FILE_TOKEN = "%FILE%"; + + /** + * A special token that represents a Mantis/Bugzilla/JIRA/etc issue ID. + * It can be used in the issueLinkUrl. + */ + private static final String ISSUE_TOKEN = "%ISSUE%"; + + /** + * A special token that represents the SCM revision number. + * It can be used in displayChangeSetDetailUrl + * and displayFileRevDetailUrl. + */ + private static final String REV_TOKEN = "%REV%"; + + /** + * The number of days to use as a range, when this is not specified. + */ + private static final int DEFAULT_RANGE = 30; + + public static final String DEFAULT_ISSUE_ID_REGEX_PATTERN = "[a-zA-Z]{2,}-\\d+"; + + private static final String DEFAULT_ISSUE_LINK_URL = "https://issues.apache.org/jira/browse/" + ISSUE_TOKEN; + + /** + * Used to specify the format to use for the dates in the headings of the + * report. + * + * @since 2.1 + */ + @Parameter( property = "changelog.headingDateFormat", defaultValue = "yyyy-MM-dd" ) + private String headingDateFormat = "yyyy-MM-dd"; + + /** + * Used to specify whether to build the log using range, tag or date. + */ + @Parameter( property = "changelog.type", defaultValue = "range", required = true ) + private String type; + + /** + * Used to specify the number of days of log entries to retrieve. + */ + @Parameter( property = "changelog.range", defaultValue = "-1" ) + private int range; + + /** + * Used to specify the absolute date (or list of dates) to start log entries from. + */ + @Parameter + private List dates; + + /** + * Used to specify the tag (or list of tags) to start log entries from. + */ + @Parameter + private List tags; + + /** + * Used to specify the date format of the log entries that are retrieved from your SCM system. + */ + @Parameter( property = "changelog.dateFormat", defaultValue = "yyyy-MM-dd HH:mm:ss", required = true ) + private String dateFormat; + + /** + * Input dir. Directory where the files under SCM control are located. + */ + @Parameter( property = "basedir", required = true ) + private File basedir; + + /** + * Output file for xml document + */ + @Parameter( defaultValue = "${project.build.directory}/changelog.xml", required = true ) + private File outputXML; + + /** + * Allows the user to make changelog regenerate the changelog.xml file for the specified time in minutes. + */ + @Parameter( property = "outputXMLExpiration", defaultValue = "60", required = true ) + private int outputXMLExpiration; + + /** + * Comment format string used for interrogating + * the revision control system. + * Currently only used by the ClearcaseChangeLogGenerator. + */ + @Parameter( property = "changelog.commentFormat" ) + private String commentFormat; + + /** + * The file encoding when writing non-HTML reports. + */ + @Parameter( property = "changelog.outputEncoding", defaultValue = "${project.reporting.outputEncoding}" ) + private String outputEncoding; + + /** + * The user name (used by svn and starteam protocol). + */ + @Parameter( property = "username" ) + private String username; + + /** + * The user password (used by svn and starteam protocol). + */ + @Parameter( property = "password" ) + private String password; + + /** + * The private key (used by java svn). + */ + @Parameter( property = "privateKey" ) + private String privateKey; + + /** + * The passphrase (used by java svn). + */ + @Parameter( property = "passphrase" ) + private String passphrase; + + /** + * The url of tags base directory (used by svn protocol). + */ + @Parameter( property = "tagBase" ) + private String tagBase; + + /** + * The URL to view the scm. Basis for external links from the generated report. + */ + @Parameter( property = "project.scm.url" ) + protected String scmUrl; + + /** + * Skip the Changelog report generation. Most useful on the command line + * via "-Dchangelog.skip=true". + * + * @since 2.3 + */ + @Parameter( property = "changelog.skip", defaultValue = "false" ) + protected boolean skip; + + /** + * Encodes slashes in file uri. Required for some repository browsers like gitblit + * + * @since 2.3 + */ + @Parameter( property = "encodeFileUri", defaultValue = "false" ) + protected boolean encodeFileUri; + + /** + * List of files to include. Specified as fileset patterns of files to include in the report + * + * @since 2.3 + */ + @Parameter + private String[] includes; + + /** + * List of files to include. Specified as fileset patterns of files to omit in the report + * + * @since 2.3 + */ + @Parameter + private String[] excludes; + + + /** + * The Maven Project Object + */ + @Parameter( defaultValue = "${project}", readonly = true, required = true ) + private MavenProject project; + + /** + * The directory where the report will be generated + */ + @Parameter( property = "project.reporting.outputDirectory", required = true, readonly = true ) + private File outputDirectory; + + /** + */ + @Component + private Renderer siteRenderer; + + /** + */ + @Parameter( property = "settings.offline", required = true, readonly = true ) + private boolean offline; + + /** + */ + @Component + private ScmManager manager; + + /** + */ + @Parameter( defaultValue = "${settings}", readonly = true, required = true ) + private Settings settings; + + /** + * Allows the user to choose which scm connection to use when connecting to the scm. + * Can either be "connection" or "developerConnection". + */ + @Parameter( defaultValue = "connection", required = true ) + private String connectionType; + + /** + * A template string that is used to create the URL to the file details. + * There is a special token that you can use in your template: + *
    + *
  • %FILE% - this is the path to a file
  • + *
+ *

+ * Example: + * http://checkstyle.cvs.sourceforge.net/checkstyle%FILE%?view=markup + *

+ *

+ * Note: If you don't supply the token in your template, + * the path of the file will simply be appended to your template URL. + *

+ */ + @Parameter( property = "displayFileDetailUrl", defaultValue = "${project.scm.url}" ) + protected String displayFileDetailUrl; + + /** + * A pattern used to identify 'issue tracker' IDs such as those used by JIRA, + * Bugzilla and alike in the SCM commit messages. Any matched patterns + * are replaced with issueLinkUrl URL. The default + * value is a JIRA-style issue identification pattern. + *

+ * Note: Default value is [a-zA-Z]{2,}-\d+ + * + * @since 2.2 + */ + @Parameter( property = "issueIDRegexPattern", defaultValue = DEFAULT_ISSUE_ID_REGEX_PATTERN, required = true ) + private String issueIDRegexPattern; + + /** + * The issue tracker URL used when replacing any matched issueIDRegexPattern + * found in the SCM commit messages. The default is URL is the codehaus JIRA + * URL. If %ISSUE% is found in the URL it is replaced with the matched issue ID, + * otherwise the matched issue ID is appended to the URL. + * + * @since 2.2 + */ + @Parameter( property = "issueLinkUrl", defaultValue = DEFAULT_ISSUE_LINK_URL, required = true ) + private String issueLinkUrl; + + /** + * A template string that is used to create the changeset URL. + *

+ * If not defined no change set link will be created. + *

+ * There is one special token that you can use in your template: + *

    + *
  • %REV% - this is the changeset revision
  • + *
+ *

+ * Example: + * http://fisheye.sourceforge.net/changelog/a-project/?cs=%REV% + *

+ *

+ * Note: If you don't supply the %REV% token in your template, + * the revision will simply be appended to your template URL. + *

+ * + * @since 2.2 + */ + @Parameter( property = "displayChangeSetDetailUrl" ) + protected String displayChangeSetDetailUrl; + + /** + * A template string that is used to create the revision aware URL to + * the file details in a similar fashion to the displayFileDetailUrl. + * When a report contains both file and file revision information, as in the + * Change Log report, this template string can be used to create a revision + * aware URL to the file details. + *

+ * If not defined this template string defaults to the same value as the + * displayFileDetailUrl and thus revision number aware links will + * not be used. + *

+ * There are two special tokens that you can use in your template: + *

    + *
  • %FILE% - this is the path to a file
  • + *
  • %REV% - this is the revision of the file
  • + *
+ *

+ * Example: + * http://fisheye.sourceforge.net/browse/a-project/%FILE%?r=%REV% + *

+ *

+ * Note: If you don't supply the %FILE% token in your template, + * the path of the file will simply be appended to your template URL. + *

+ * + * @since 2.2 + */ + @Parameter( property = "displayFileRevDetailUrl" ) + protected String displayFileRevDetailUrl; + + /** + * List of developers to be shown on the report. + * + * @since 2.2 + */ + @Parameter( property = "project.developers" ) + protected List developers; + + /** + * List of provider implementations. + */ + @Parameter + private Map providerImplementations; + + // temporary field holder while generating the report + private String rptRepository, rptOneRepoParam, rptMultiRepoParam; + + // field for SCM Connection URL + private String connection; + + // field used to hold a map of the developers by Id + private HashMap developersById; + + // field used to hold a map of the developers by Name + private HashMap developersByName; + + /** + * The system properties to use (needed by the perforce scm provider). + */ + @Parameter + private Properties systemProperties; + + private final Pattern sinkFileNamePattern = Pattern.compile( "\\\\" ); + + /** + * {@inheritDoc} + */ +/** + * {@inheritDoc } + */ +public void executeReport(java.util.Locale locale) throws org.apache.maven.reporting.MavenReportException { + // check if sources exists <-- required for parent poms + if (!basedir.exists()) { + doGenerateEmptyReport(getBundle(locale), getSink()); + return; + } + if (providerImplementations != null) { + for (java.util.Map.Entry entry : providerImplementations.entrySet()) { + java.lang.String providerType = entry.getKey(); + java.lang.String providerImplementation = entry.getValue(); + getLog().info(((("Change the default '" + providerType) + "' provider implementation to '") + providerImplementation) + "'."); + manager.setScmProviderImplementation(providerType, providerImplementation); + } + } + initializeDefaultConfigurationParameters(); + initializeDeveloperMaps(); + verifySCMTypeParams(); + { + // Add all system properties configured by the user + java.util.Iterator iter = /* NPEX_NULL_EXP */ + systemProperties.keySet().iterator(); + while (iter.hasNext()) { + java.lang.String key = ((java.lang.String) (iter.next())); + java.lang.String value = systemProperties.getProperty(key); + java.lang.System.setProperty(key, value); + getLog().debug((("Setting system property: " + key) + '=') + value); + } + } + doGenerateReport(getChangedSets(), getBundle(locale), getSink()); +} + + /** + * Initializes any configuration parameters that have not/can not be defined + * or defaulted by the Mojo API. + */ + private void initializeDefaultConfigurationParameters() + { + if ( displayFileRevDetailUrl == null || displayFileRevDetailUrl.length() == 0 ) + { + displayFileRevDetailUrl = displayFileDetailUrl; + } + } + + /** + * Creates maps of the project developers by developer Id and developer Name + * for quick lookups. + */ + private void initializeDeveloperMaps() + { + developersById = new HashMap(); + developersByName = new HashMap(); + + if ( developers != null ) + { + for ( Developer developer : developers ) + { + developersById.put( developer.getId(), developer ); + developersByName.put( developer.getName(), developer ); + } + } + } + + /** + * populates the changedSets field by either connecting to the SCM or using an existing XML generated in a previous + * run of the report + * + * @throws MavenReportException + */ + protected List getChangedSets() + throws MavenReportException + { + List changelogList = null; + + if ( !outputXML.isAbsolute() ) + { + outputXML = new File( project.getBasedir(), outputXML.getPath() ); + } + + if ( outputXML.exists() ) + { + // CHECKSTYLE_OFF: MagicNumber + if ( outputXMLExpiration > 0 + && outputXMLExpiration * 60000 > System.currentTimeMillis() - outputXML.lastModified() ) + // CHECKSTYLE_ON: MagicNumber + { + try + { + //ReaderFactory.newReader( outputXML, getOutputEncoding() ); + //FileInputStream fIn = new FileInputStream( outputXML ); + + getLog().info( "Using existing changelog.xml..." ); + + changelogList = + ChangeLog.loadChangedSets( ReaderFactory.newReader( outputXML, getOutputEncoding() ) ); + } + catch ( FileNotFoundException e ) + { + //do nothing, just regenerate + } + catch ( Exception e ) + { + throw new MavenReportException( "An error occurred while parsing " + outputXML.getAbsolutePath(), + e ); + } + } + } + + if ( changelogList == null ) + { + if ( offline ) + { + throw new MavenReportException( "This report requires online mode." ); + } + + getLog().info( "Generating changed sets xml to: " + outputXML.getAbsolutePath() ); + + changelogList = generateChangeSetsFromSCM(); + + try + { + writeChangelogXml( changelogList ); + } + catch ( FileNotFoundException e ) + { + throw new MavenReportException( "Can't create " + outputXML.getAbsolutePath(), e ); + } + catch ( UnsupportedEncodingException e ) + { + throw new MavenReportException( "Can't create " + outputXML.getAbsolutePath(), e ); + } + catch ( IOException e ) + { + throw new MavenReportException( "Can't create " + outputXML.getAbsolutePath(), e ); + } + } + + return changelogList; + } + + private void writeChangelogXml( List changelogList ) + throws IOException + { + StringBuilder changelogXml = new StringBuilder(); + + changelogXml.append( "\n" ); + changelogXml.append( "" ); + + for ( ChangeLogSet changelogSet : changelogList ) + { + changelogXml.append( "\n " ); + + String changeset = changelogSet.toXML( getOutputEncoding() ); + + //remove xml header + if ( changeset.startsWith( "" ) + 2; + changeset = changeset.substring( idx ); + } + + changelogXml.append( changeset ); + } + + changelogXml.append( "\n" ); + + outputXML.getParentFile().mkdirs(); + + //PrintWriter pw = new PrintWriter( new BufferedOutputStream( new FileOutputStream( outputXML ) ) ); + //pw.write( changelogXml.toString() ); + //pw.flush(); + //pw.close(); + // MCHANGELOG-86 + Writer writer = WriterFactory.newWriter( new BufferedOutputStream( new FileOutputStream( outputXML ) ), + getOutputEncoding() ); + writer.write( changelogXml.toString() ); + writer.flush(); + writer.close(); + } + + /** + * creates a ChangeLog object and then connects to the SCM to generate the changed sets + * + * @return changedlogsets generated from the SCM + * @throws MavenReportException + */ + protected List generateChangeSetsFromSCM() + throws MavenReportException + { + try + { + List changeSets = new ArrayList(); + + ScmRepository repository = getScmRepository(); + + ScmProvider provider = manager.getProviderByRepository( repository ); + + ChangeLogScmResult result; + + if ( "range".equals( type ) ) + { + result = provider.changeLog( repository, new ScmFileSet( basedir ), null, null, range, (ScmBranch) null, + dateFormat ); + + checkResult( result ); + + changeSets.add( result.getChangeLog() ); + } + else if ( "tag".equals( type ) ) + { + + Iterator tagsIter = tags.iterator(); + + String startTag = tagsIter.next(); + String endTag = null; + + if ( tagsIter.hasNext() ) + { + while ( tagsIter.hasNext() ) + { + endTag = tagsIter.next(); + String endRevision = getRevisionForTag( endTag, repository, provider ); + String startRevision = getRevisionForTag( startTag, repository, provider ); + result = provider.changeLog( repository, new ScmFileSet( basedir ), + new ScmRevision( startRevision ), + new ScmRevision( endRevision ) ); + + checkResult( result ); + result.getChangeLog().setStartVersion( new ScmRevision( startTag ) ); + result.getChangeLog().setEndVersion( new ScmRevision( endTag ) ); + + changeSets.add( result.getChangeLog() ); + + startTag = endTag; + } + } + else + { + String startRevision = getRevisionForTag( startTag, repository, provider ); + String endRevision = getRevisionForTag( endTag, repository, provider ); + result = provider.changeLog( repository, new ScmFileSet( basedir ), + new ScmRevision( startRevision ), + new ScmRevision( endRevision ) ); + + checkResult( result ); + result.getChangeLog().setStartVersion( new ScmRevision( startTag ) ); + result.getChangeLog().setEndVersion( null ); + changeSets.add( result.getChangeLog() ); + } + } + else if ( "date".equals( type ) ) + { + Iterator dateIter = dates.iterator(); + + String startDate = dateIter.next(); + String endDate = null; + + if ( dateIter.hasNext() ) + { + while ( dateIter.hasNext() ) + { + endDate = dateIter.next(); + + result = provider.changeLog( repository, new ScmFileSet( basedir ), parseDate( startDate ), + parseDate( endDate ), 0, (ScmBranch) null ); + + checkResult( result ); + + changeSets.add( result.getChangeLog() ); + + startDate = endDate; + } + } + else + { + result = provider.changeLog( repository, new ScmFileSet( basedir ), parseDate( startDate ), + parseDate( endDate ), 0, (ScmBranch) null ); + + checkResult( result ); + + changeSets.add( result.getChangeLog() ); + } + } + else + { + throw new MavenReportException( "The type '" + type + "' isn't supported." ); + } + filter( changeSets ); + return changeSets; + + } + catch ( ScmException e ) + { + throw new MavenReportException( "Cannot run changelog command : ", e ); + } + catch ( MojoExecutionException e ) + { + throw new MavenReportException( "An error has occurred during changelog command : ", e ); + } + } + + /** + * Resolves the given tag to the revision number. + * + * @param tag + * @param repository + * @param provider + * @return + * @throws ScmException + */ + private String getRevisionForTag( final String tag, final ScmRepository repository, final ScmProvider provider ) + throws ScmException + { + if ( repository.getProvider().equals( "svn" ) ) + { + if ( tag == null ) + { + return "HEAD"; + } + SvnInfoCommandExpanded infoCommand = new SvnInfoCommandExpanded(); + infoCommand.setLogger( new DefaultLog() ); + + InfoScmResult infoScmResult = + infoCommand.executeInfoTagCommand( (SvnScmProviderRepository) repository.getProviderRepository(), + new ScmFileSet( basedir ), tag, null, false, null ); + if ( infoScmResult.getInfoItems().size() == 0 ) + { + throw new ScmException( "There is no tag named '" + tag + "' in the Subversion repository." ); + } + InfoItem infoItem = infoScmResult.getInfoItems().get( 0 ); + String revision = infoItem.getLastChangedRevision(); + getLog().info( String.format( "Resolved tag '%s' to revision '%s'", tag, revision ) ); + return revision; + } + return tag; + } + + /** + * filters out unwanted files from the changesets + */ + private void filter( List changeSets ) + { + List include = compilePatterns( includes ); + List exclude = compilePatterns( excludes ); + if ( includes == null && excludes == null ) + { + return; + } + for ( ChangeLogSet changeLogSet : changeSets ) + { + List set = changeLogSet.getChangeSets(); + filter( set, include, exclude ); + } + + } + + private List compilePatterns( String[] patternArray ) + { + if ( patternArray == null ) + { + return new ArrayList(); + } + List patterns = new ArrayList( patternArray.length ); + for ( String string : patternArray ) + { + //replaces * with [/\]* (everything but file seperators) + //replaces ** with .* + //quotes the rest of the string + string = "\\Q" + string + "\\E"; + string = string.replace( "**", "\\E.?REPLACEMENT?\\Q" ); + string = string.replace( "*", "\\E[^/\\\\]?REPLACEMENT?\\Q" ); + string = string.replace( "?REPLACEMENT?", "*" ); + string = string.replace( "\\Q\\E", "" ); + patterns.add( Pattern.compile( string ) ); + } + return patterns; + } + + private void filter( List sets, List includes, List excludes ) + { + Iterator it = sets.iterator(); + while ( it.hasNext() ) + { + ChangeSet changeSet = it.next(); + List files = changeSet.getFiles(); + Iterator iterator = files.iterator(); + while ( iterator.hasNext() ) + { + ChangeFile changeFile = iterator.next(); + String name = changeFile.getName(); + if ( !isIncluded( includes, name ) || isExcluded( excludes, name ) ) + { + iterator.remove(); + } + } + if ( files.isEmpty() ) + { + it.remove(); + } + } + } + + private boolean isExcluded( List excludes, String name ) + { + if ( excludes == null || excludes.isEmpty() ) + { + return false; + } + for ( Pattern pattern : excludes ) + { + if ( pattern.matcher( name ).matches() ) + { + return true; + } + } + return false; + } + + private boolean isIncluded( List includes, String name ) + { + if ( includes == null || includes.isEmpty() ) + { + return true; + } + for ( Pattern pattern : includes ) + { + if ( pattern.matcher( name ).matches() ) + { + return true; + } + } + return false; + } + + /** + * Converts the localized date string pattern to date object. + * + * @return A date + */ + private Date parseDate( String date ) + throws MojoExecutionException + { + if ( date == null || date.trim().length() == 0 ) + { + return null; + } + + SimpleDateFormat formatter = new SimpleDateFormat( "yyyy-MM-dd" ); + + try + { + return formatter.parse( date ); + } + catch ( ParseException e ) + { + throw new MojoExecutionException( "Please use this date pattern: " + formatter.toLocalizedPattern(), e ); + } + } + + public ScmRepository getScmRepository() + throws ScmException + { + ScmRepository repository; + + try + { + repository = manager.makeScmRepository( getConnection() ); + + ScmProviderRepository providerRepo = repository.getProviderRepository(); + + if ( !StringUtils.isEmpty( username ) ) + { + providerRepo.setUser( username ); + } + + if ( !StringUtils.isEmpty( password ) ) + { + providerRepo.setPassword( password ); + } + + if ( repository.getProviderRepository() instanceof ScmProviderRepositoryWithHost ) + { + ScmProviderRepositoryWithHost repo = (ScmProviderRepositoryWithHost) repository.getProviderRepository(); + + loadInfosFromSettings( repo ); + + if ( !StringUtils.isEmpty( username ) ) + { + repo.setUser( username ); + } + + if ( !StringUtils.isEmpty( password ) ) + { + repo.setPassword( password ); + } + + if ( !StringUtils.isEmpty( privateKey ) ) + { + repo.setPrivateKey( privateKey ); + } + + if ( !StringUtils.isEmpty( passphrase ) ) + { + repo.setPassphrase( passphrase ); + } + } + + if ( !StringUtils.isEmpty( tagBase ) && repository.getProvider().equals( "svn" ) ) + { + SvnScmProviderRepository svnRepo = (SvnScmProviderRepository) repository.getProviderRepository(); + + svnRepo.setTagBase( tagBase ); + } + } + catch ( Exception e ) + { + throw new ScmException( "Can't load the scm provider.", e ); + } + + return repository; + } + + /** + * Load username password from settings if user has not set them in JVM properties + * + * @param repo + */ + private void loadInfosFromSettings( ScmProviderRepositoryWithHost repo ) + { + if ( username == null || password == null ) + { + String host = repo.getHost(); + + int port = repo.getPort(); + + if ( port > 0 ) + { + host += ":" + port; + } + + Server server = this.settings.getServer( host ); + + if ( server != null ) + { + if ( username == null ) + { + username = this.settings.getServer( host ).getUsername(); + } + + if ( password == null ) + { + password = this.settings.getServer( host ).getPassword(); + } + + if ( privateKey == null ) + { + privateKey = this.settings.getServer( host ).getPrivateKey(); + } + + if ( passphrase == null ) + { + passphrase = this.settings.getServer( host ).getPassphrase(); + } + } + } + } + + public void checkResult( ScmResult result ) + throws MojoExecutionException + { + if ( !result.isSuccess() ) + { + getLog().error( "Provider message:" ); + + getLog().error( result.getProviderMessage() == null ? "" : result.getProviderMessage() ); + + getLog().error( "Command output:" ); + + getLog().error( result.getCommandOutput() == null ? "" : result.getCommandOutput() ); + + throw new MojoExecutionException( "Command failed." ); + } + } + + /** + * used to retrieve the SCM connection string + * + * @return the url string used to connect to the SCM + * @throws MavenReportException when there is insufficient information to retrieve the SCM connection string + */ + protected String getConnection() + throws MavenReportException + { + if ( this.connection != null ) + { + return connection; + } + + if ( project.getScm() == null ) + { + throw new MavenReportException( "SCM Connection is not set." ); + } + + String scmConnection = project.getScm().getConnection(); + if ( StringUtils.isNotEmpty( scmConnection ) && "connection".equals( connectionType.toLowerCase() ) ) + { + connection = scmConnection; + } + + String scmDeveloper = project.getScm().getDeveloperConnection(); + if ( StringUtils.isNotEmpty( scmDeveloper ) && "developerconnection".equals( connectionType.toLowerCase() ) ) + { + connection = scmDeveloper; + } + + if ( StringUtils.isEmpty( connection ) ) + { + throw new MavenReportException( "SCM Connection is not set." ); + } + + return connection; + } + + /** + * checks whether there are enough configuration parameters to generate the report + * + * @throws MavenReportException when there is insufficient paramters to generate the report + */ + private void verifySCMTypeParams() + throws MavenReportException + { + if ( "range".equals( type ) ) + { + if ( range == -1 ) + { + range = DEFAULT_RANGE; + } + } + else if ( "date".equals( type ) ) + { + if ( dates == null ) + { + throw new MavenReportException( "The dates parameter is required when type=\"date\". The value " + + "should be the absolute date for the start of the log." ); + } + } + else if ( "tag".equals( type ) ) + { + if ( tags == null ) + { + throw new MavenReportException( "The tags parameter is required when type=\"tag\"." ); + } + } + else + { + throw new MavenReportException( "The type parameter has an invalid value: " + type + + ". The value should be \"range\", \"date\", or \"tag\"." ); + } + } + + /** + * generates an empty report in case there are no sources to generate a report with + * + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + protected void doGenerateEmptyReport( ResourceBundle bundle, Sink sink ) + { + sink.head(); + sink.title(); + sink.text( bundle.getString( "report.changelog.header" ) ); + sink.title_(); + sink.head_(); + + sink.body(); + sink.section1(); + + sink.sectionTitle1(); + sink.text( bundle.getString( "report.changelog.mainTitle" ) ); + sink.sectionTitle1_(); + + sink.paragraph(); + sink.text( bundle.getString( "report.changelog.nosources" ) ); + sink.paragraph_(); + + sink.section1_(); + + sink.body_(); + sink.flush(); + sink.close(); + } + + /** + * method that generates the report for this mojo. This method is overridden by dev-activity and file-activity mojo + * + * @param changeLogSets changed sets to generate the report from + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + protected void doGenerateReport( List changeLogSets, ResourceBundle bundle, Sink sink ) + { + sink.head(); + sink.title(); + sink.text( bundle.getString( "report.changelog.header" ) ); + sink.title_(); + sink.head_(); + + sink.body(); + sink.section1(); + + sink.sectionTitle1(); + sink.text( bundle.getString( "report.changelog.mainTitle" ) ); + sink.sectionTitle1_(); + + // Summary section + doSummarySection( changeLogSets, bundle, sink ); + + for ( ChangeLogSet changeLogSet : changeLogSets ) + { + doChangedSet( changeLogSet, bundle, sink ); + } + + sink.section1_(); + sink.body_(); + + sink.flush(); + sink.close(); + } + + /** + * generates the report summary section of the report + * + * @param changeLogSets changed sets to generate the report from + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + private void doSummarySection( List changeLogSets, ResourceBundle bundle, Sink sink ) + { + sink.paragraph(); + + sink.text( bundle.getString( "report.changelog.ChangedSetsTotal" ) ); + sink.text( ": " + changeLogSets.size() ); + + sink.paragraph_(); + } + + /** + * generates a section of the report referring to a changeset + * + * @param set the current ChangeSet to generate this section of the report + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + private void doChangedSet( ChangeLogSet set, ResourceBundle bundle, Sink sink ) + { + sink.section2(); + + doChangeSetTitle( set, bundle, sink ); + + doSummary( set, bundle, sink ); + + doChangedSetTable( set.getChangeSets(), bundle, sink ); + + sink.section2_(); + } + + /** + * Generate the title for the report. + * + * @param set change set to generate the report from + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + protected void doChangeSetTitle( ChangeLogSet set, ResourceBundle bundle, Sink sink ) + { + sink.sectionTitle2(); + + SimpleDateFormat headingDateFormater = new SimpleDateFormat( headingDateFormat ); + + if ( "tag".equals( type ) ) + { + if ( set.getStartVersion() == null || set.getStartVersion().getName() == null ) + { + sink.text( bundle.getString( "report.SetTagCreation" ) ); + if ( set.getEndVersion() != null && set.getEndVersion().getName() != null ) + { + sink.text( ' ' + bundle.getString( "report.SetTagUntil" ) + " '" + set.getEndVersion() + '\'' ); + } + } + else if ( set.getEndVersion() == null || set.getEndVersion().getName() == null ) + { + sink.text( bundle.getString( "report.SetTagSince" ) ); + sink.text( " '" + set.getStartVersion() + '\'' ); + } + else + { + sink.text( bundle.getString( "report.SetTagBetween" ) ); + sink.text( + " '" + set.getStartVersion() + "' " + bundle.getString( "report.And" ) + " '" + set.getEndVersion() + + '\'' ); + } + } + else if ( set.getStartDate() == null ) + { + sink.text( bundle.getString( "report.SetRangeUnknown" ) ); + } + else if ( set.getEndDate() == null ) + { + sink.text( bundle.getString( "report.SetRangeSince" ) ); + sink.text( ' ' + headingDateFormater.format( set.getStartDate() ) ); + } + else + { + sink.text( bundle.getString( "report.SetRangeBetween" ) ); + sink.text( + ' ' + headingDateFormater.format( set.getStartDate() ) + ' ' + bundle.getString( "report.And" ) + ' ' + + headingDateFormater.format( set.getEndDate() ) ); + } + sink.sectionTitle2_(); + } + + /** + * Generate the summary section of the report. + * + * @param set change set to generate the report from + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + protected void doSummary( ChangeLogSet set, ResourceBundle bundle, Sink sink ) + { + sink.paragraph(); + sink.text( bundle.getString( "report.TotalCommits" ) ); + sink.text( ": " + set.getChangeSets().size() ); + sink.lineBreak(); + sink.text( bundle.getString( "report.changelog.FilesChanged" ) ); + sink.text( ": " + countFilesChanged( set.getChangeSets() ) ); + sink.paragraph_(); + } + + /** + * counts the number of files that were changed in the specified SCM + * + * @param entries a collection of SCM changes + * @return number of files changed for the changedsets + */ + protected long countFilesChanged( Collection entries ) + { + if ( entries == null ) + { + return 0; + } + + if ( entries.isEmpty() ) + { + return 0; + } + + HashMap> fileList = new HashMap>(); + + for ( ChangeSet entry : entries ) + { + for ( ChangeFile file : entry.getFiles() ) + { + String name = file.getName(); + List list = fileList.get( name ); + + if ( list != null ) + { + list.add( file ); + } + else + { + list = new LinkedList(); + + list.add( file ); + + fileList.put( name, list ); + } + } + } + + return fileList.size(); + } + + /** + * generates the report table showing the SCM log entries + * + * @param entries a list of change log entries to generate the report from + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + private void doChangedSetTable( Collection entries, ResourceBundle bundle, Sink sink ) + { + sink.table(); + + sink.tableRow(); + sink.tableHeaderCell(); + sink.text( bundle.getString( "report.changelog.timestamp" ) ); + sink.tableHeaderCell_(); + sink.tableHeaderCell(); + sink.text( bundle.getString( "report.changelog.author" ) ); + sink.tableHeaderCell_(); + sink.tableHeaderCell(); + sink.text( bundle.getString( "report.changelog.details" ) ); + sink.tableHeaderCell_(); + sink.tableRow_(); + + initReportUrls(); + + List sortedEntries = new ArrayList( entries ); + Collections.sort( sortedEntries, new Comparator() + { + public int compare( ChangeSet changeSet0, ChangeSet changeSet1 ) + { + return changeSet1.getDate().compareTo( changeSet0.getDate() ); + } + } ); + + for ( ChangeSet entry : sortedEntries ) + { + doChangedSetDetail( entry, sink ); + } + + sink.table_(); + } + + /** + * reports on the details of an SCM entry log + * + * @param entry an SCM entry to generate the report from + * @param sink the report formatting tool + */ + private void doChangedSetDetail( ChangeSet entry, Sink sink ) + { + sink.tableRow(); + + sink.tableCell(); + sink.text( entry.getDateFormatted() + ' ' + entry.getTimeFormatted() ); + sink.tableCell_(); + + sink.tableCell(); + + sinkAuthorDetails( sink, entry.getAuthor() ); + + sink.tableCell_(); + + sink.tableCell(); + //doRevision( entry.getFiles(), bundle, sink ); + doChangedFiles( entry.getFiles(), sink ); + sink.lineBreak(); + StringReader sr = new StringReader( entry.getComment() ); + BufferedReader br = new BufferedReader( sr ); + String line; + try + { + if ( ( issueIDRegexPattern != null && issueIDRegexPattern.length() > 0 ) && ( issueLinkUrl != null + && issueLinkUrl.length() > 0 ) ) + { + Pattern pattern = Pattern.compile( issueIDRegexPattern ); + + line = br.readLine(); + + while ( line != null ) + { + sinkIssueLink( sink, line, pattern ); + + line = br.readLine(); + if ( line != null ) + { + sink.lineBreak(); + } + } + } + else + { + line = br.readLine(); + + while ( line != null ) + { + sink.text( line ); + line = br.readLine(); + if ( line != null ) + { + sink.lineBreak(); + } + } + } + } + catch ( IOException e ) + { + getLog().warn( "Unable to read the comment of a ChangeSet as a stream." ); + } + finally + { + try + { + br.close(); + } + catch ( IOException e ) + { + getLog().warn( "Unable to close a reader." ); + } + sr.close(); + } + sink.tableCell_(); + + sink.tableRow_(); + } + + private void sinkIssueLink( Sink sink, String line, Pattern pattern ) + { + // replace any ticket patterns found. + + Matcher matcher = pattern.matcher( line ); + + int currLoc = 0; + + while ( matcher.find() ) + { + String match = matcher.group(); + + String link; + + if ( issueLinkUrl.indexOf( ISSUE_TOKEN ) > 0 ) + { + link = issueLinkUrl.replaceAll( ISSUE_TOKEN, match ); + } + else + { + if ( issueLinkUrl.endsWith( "/" ) ) + { + link = issueLinkUrl; + } + else + { + link = issueLinkUrl + '/'; + } + + link += match; + } + + int startOfMatch = matcher.start(); + + String unmatchedText = line.substring( currLoc, startOfMatch ); + + currLoc = matcher.end(); + + sink.text( unmatchedText ); + + sink.link( link ); + sink.text( match ); + sink.link_(); + } + + sink.text( line.substring( currLoc ) ); + } + + /** + * If the supplied author is a known developer this method outputs a + * link to the team members report, or alternatively, if the supplied + * author is unknown, outputs the author's name as plain text. + * + * @param sink Sink to use for outputting + * @param author The author's name. + */ + protected void sinkAuthorDetails( Sink sink, String author ) + { + Developer developer = developersById.get( author ); + + if ( developer == null ) + { + developer = developersByName.get( author ); + } + + if ( developer != null ) + { + sink.link( "team-list.html#" + developer.getId() ); + sink.text( developer.getName() ); + sink.link_(); + } + else + { + sink.text( author ); + } + } + + /** + * populates the report url used to create links from certain elements of the report + */ + protected void initReportUrls() + { + if ( scmUrl != null ) + { + int idx = scmUrl.indexOf( '?' ); + + if ( idx > 0 ) + { + rptRepository = scmUrl.substring( 0, idx ); + + if ( scmUrl.equals( displayFileDetailUrl ) ) + { + String rptTmpMultiRepoParam = scmUrl.substring( rptRepository.length() ); + + rptOneRepoParam = '?' + rptTmpMultiRepoParam.substring( 1 ); + + rptMultiRepoParam = '&' + rptTmpMultiRepoParam.substring( 1 ); + } + } + else + { + rptRepository = scmUrl; + + rptOneRepoParam = ""; + + rptMultiRepoParam = ""; + } + } + } + + /** + * generates the section of the report listing all the files revisions + * + * @param files list of files to generate the reports from + * @param sink the report formatting tool + */ + private void doChangedFiles( List files, Sink sink ) + { + for ( ChangeFile file : files ) + { + sinkLogFile( sink, file.getName(), file.getRevision() ); + } + } + + /** + * generates the section of the report detailing the revisions made and the files changed + * + * @param sink the report formatting tool + * @param name filename of the changed file + * @param revision the revision code for this file + */ + private void sinkLogFile( Sink sink, String name, String revision ) + { + try + { + String connection = getConnection(); + + generateLinks( connection, name, revision, sink ); + } + catch ( Exception e ) + { + getLog().debug( e ); + + sink.text( name + " v " + revision ); + } + + sink.lineBreak(); + } + + /** + * attaches the http links from the changed files + * + * @param connection the string used to connect to the SCM + * @param name filename of the file that was changed + * @param sink the report formatting tool + */ + protected void generateLinks( String connection, String name, Sink sink ) + { + generateLinks( connection, name, null, sink ); + } + + /** + * attaches the http links from the changed files + * + * @param connection the string used to connect to the SCM + * @param name filename of the file that was changed + * @param revision the revision code + * @param sink the report formatting tool + */ + protected void generateLinks( String connection, String name, String revision, Sink sink ) + { + String linkFile; + String linkRev = null; + + if ( revision != null ) + { + linkFile = displayFileRevDetailUrl; + } + else + { + linkFile = displayFileDetailUrl; + } + + if ( linkFile != null ) + { + if ( !linkFile.equals( scmUrl ) ) + { + String linkName = name; + if ( encodeFileUri ) + { + try + { + linkName = URLEncoder.encode( linkName, "UTF-8" ); + } + catch ( UnsupportedEncodingException e ) + { + // UTF-8 is always supported + } + } + + // Use the given URL to create links to the files + + if ( linkFile.indexOf( FILE_TOKEN ) > 0 ) + { + linkFile = linkFile.replaceAll( FILE_TOKEN, linkName ); + } + else + { + // This is here so that we are backwards compatible with the + // format used before the special token was introduced + + linkFile += linkName; + } + + // Use the given URL to create links to the files + + if ( revision != null && linkFile.indexOf( REV_TOKEN ) > 0 ) + { + linkFile = linkFile.replaceAll( REV_TOKEN, revision ); + } + } + else if ( connection.startsWith( "scm:perforce" ) ) + { + String path = getAbsolutePath( displayFileDetailUrl, name ); + linkFile = path + "?ac=22"; + if ( revision != null ) + { + linkRev = path + "?ac=64&rev=" + revision; + } + } + else if ( connection.startsWith( "scm:clearcase" ) ) + { + String path = getAbsolutePath( displayFileDetailUrl, name ); + linkFile = path + rptOneRepoParam; + } + else if ( connection.indexOf( "cvsmonitor.pl" ) > 0 ) + { + Pattern cvsMonitorRegex = Pattern.compile( "^.*(&module=.*?(?:&|$)).*$" ); + String module = cvsMonitorRegex.matcher( rptOneRepoParam ).replaceAll( "$1" ); + linkFile = displayFileDetailUrl + "?cmd=viewBrowseFile" + module + "&file=" + name; + if ( revision != null ) + { + linkRev = + rptRepository + "?cmd=viewBrowseVersion" + module + "&file=" + name + "&version=" + revision; + } + } + else + { + String path = getAbsolutePath( displayFileDetailUrl, name ); + linkFile = path + rptOneRepoParam; + if ( revision != null ) + { + linkRev = path + "?rev=" + revision + "&content-type=text/vnd.viewcvs-markup" + rptMultiRepoParam; + } + } + } + + if ( linkFile != null ) + { + sink.link( linkFile ); + sinkFileName( name, sink ); + sink.link_(); + } + else + { + sinkFileName( name, sink ); + } + + sink.text( " " ); + + if ( linkRev == null && revision != null && displayChangeSetDetailUrl != null ) + { + if ( displayChangeSetDetailUrl.indexOf( REV_TOKEN ) > 0 ) + { + linkRev = displayChangeSetDetailUrl.replaceAll( REV_TOKEN, revision ); + } + else + { + linkRev = displayChangeSetDetailUrl + revision; + } + } + + if ( linkRev != null ) + { + sink.link( linkRev ); + sink.text( "v " + revision ); + sink.link_(); + } + else if ( revision != null ) + { + sink.text( "v " + revision ); + } + } + + /** + * Encapsulates the logic for rendering the name with a bolded markup. + * + * @param name filename of the file that was changed + * @param sink the report formatting tool + */ + private void sinkFileName( String name, Sink sink ) + { + name = sinkFileNamePattern.matcher( name ).replaceAll( "/" ); + int pos = name.lastIndexOf( '/' ); + + String head; + String tail; + if ( pos < 0 ) + { + head = ""; + tail = name; + } + else + { + head = name.substring( 0, pos ) + '/'; + tail = name.substring( pos + 1 ); + } + + sink.text( head ); + sink.bold(); + sink.text( tail ); + sink.bold_(); + } + + /** + * calculates the path from a base directory to a target file + * + * @param base base directory to create the absolute path from + * @param target target file to create the absolute path to + */ + private String getAbsolutePath( final String base, final String target ) + { + String absPath = ""; + + StringTokenizer baseTokens = + new StringTokenizer( sinkFileNamePattern.matcher( base ).replaceAll( "/" ), "/", true ); + + StringTokenizer targetTokens = + new StringTokenizer( sinkFileNamePattern.matcher( target ).replaceAll( "/" ), "/" ); + + String targetRoot = targetTokens.nextToken(); + + while ( baseTokens.hasMoreTokens() ) + { + String baseToken = baseTokens.nextToken(); + + if ( baseToken.equals( targetRoot ) ) + { + break; + } + + absPath += baseToken; + } + + if ( !absPath.endsWith( "/" ) ) + { + absPath += "/"; + } + + String newTarget = target; + if ( newTarget.startsWith( "/" ) ) + { + newTarget = newTarget.substring( 1 ); + } + + return absPath + newTarget; + } + + /** + * {@inheritDoc} + */ + protected MavenProject getProject() + { + return project; + } + + /** + * {@inheritDoc} + */ + protected String getOutputDirectory() + { + if ( !outputDirectory.isAbsolute() ) + { + outputDirectory = new File( project.getBasedir(), outputDirectory.getPath() ); + } + + return outputDirectory.getAbsolutePath(); + } + + /** + * Gets the effective reporting output files encoding. + * + * @return The effective reporting output file encoding, never null. + */ + protected String getOutputEncoding() + { + return ( outputEncoding != null ) ? outputEncoding : ReaderFactory.UTF_8; + } + + /** + * {@inheritDoc} + */ + protected Renderer getSiteRenderer() + { + return siteRenderer; + } + + /** + * {@inheritDoc} + */ + public String getDescription( Locale locale ) + { + return getBundle( locale ).getString( "report.changelog.description" ); + } + + /** + * {@inheritDoc} + */ + public String getName( Locale locale ) + { + return getBundle( locale ).getString( "report.changelog.name" ); + } + + /** + * {@inheritDoc} + */ + public String getOutputName() + { + return "changelog"; + } + + /** + * @param locale + * @return the current bundle + */ + protected ResourceBundle getBundle( Locale locale ) + { + return ResourceBundle.getBundle( "scm-activity", locale, this.getClass().getClassLoader() ); + } + + /** + * {@inheritDoc} + */ + public boolean canGenerateReport() + { + if ( offline && !outputXML.exists() ) + { + return false; + } + + return !skip; + + } +} diff --git a/Java/maven-changelog-plugin-ChangeLogReport_465/metadata.json b/Java/maven-changelog-plugin-ChangeLogReport_465/metadata.json new file mode 100644 index 000000000..2e8d28874 --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogReport_465/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-changelog-plugin-ChangeLogReport_465", + "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 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": "src/main/java/org/apache/maven/plugin/changelog/ChangeLogReport.java", + "line": 458, + "npe_method": "executeReport", + "deref_field": "systemProperties", + "npe_class": "ChangeLogReport", + "repo": "maven-changelog-plugin", + "bug_id": "ChangeLogReport_465" + } +} diff --git a/Java/maven-changelog-plugin-ChangeLogReport_465/npe.json b/Java/maven-changelog-plugin-ChangeLogReport_465/npe.json new file mode 100644 index 000000000..8abd6d460 --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogReport_465/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/maven/plugin/changelog/ChangeLogReport.java", + "line": 458, + "npe_method": "executeReport", + "deref_field": "systemProperties", + "npe_class": "ChangeLogReport" +} \ No newline at end of file diff --git a/Java/maven-changelog-plugin-ChangeLogReport_506/Dockerfile b/Java/maven-changelog-plugin-ChangeLogReport_506/Dockerfile new file mode 100644 index 000000000..9a945184a --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogReport_506/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:maven-changelog-plugin + +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-changelog-plugin-ChangeLogReport_506/buggy.java b/Java/maven-changelog-plugin-ChangeLogReport_506/buggy.java new file mode 100644 index 000000000..557e22480 --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogReport_506/buggy.java @@ -0,0 +1,1916 @@ +package org.apache.maven.plugin.changelog; + +/* + * 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.sink.Sink; +import org.apache.maven.doxia.siterenderer.Renderer; +import org.apache.maven.model.Developer; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; +import org.apache.maven.reporting.AbstractMavenReport; +import org.apache.maven.reporting.MavenReportException; +import org.apache.maven.scm.ChangeFile; +import org.apache.maven.scm.ChangeSet; +import org.apache.maven.scm.ScmBranch; +import org.apache.maven.scm.ScmException; +import org.apache.maven.scm.ScmFileSet; +import org.apache.maven.scm.ScmResult; +import org.apache.maven.scm.ScmRevision; +import org.apache.maven.scm.command.changelog.ChangeLogScmResult; +import org.apache.maven.scm.command.changelog.ChangeLogSet; +import org.apache.maven.scm.command.info.InfoItem; +import org.apache.maven.scm.command.info.InfoScmResult; +import org.apache.maven.scm.log.DefaultLog; +import org.apache.maven.scm.manager.ScmManager; +import org.apache.maven.scm.provider.ScmProvider; +import org.apache.maven.scm.provider.ScmProviderRepository; +import org.apache.maven.scm.provider.ScmProviderRepositoryWithHost; +import org.apache.maven.scm.provider.svn.repository.SvnScmProviderRepository; +import org.apache.maven.scm.provider.svn.svnexe.command.info.SvnInfoCommandExpanded; +import org.apache.maven.scm.repository.ScmRepository; +import org.apache.maven.settings.Server; +import org.apache.maven.settings.Settings; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.util.WriterFactory; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.StringReader; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.net.URLEncoder; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.ResourceBundle; +import java.util.StringTokenizer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Generate a changelog report. + * + * @version $Id$ + */ +@Mojo( name = "changelog" ) +public class ChangeLogReport + extends AbstractMavenReport +{ + /** + * A special token that represents the SCM relative path for a file. + * It can be used in displayFileDetailUrl. + */ + private static final String FILE_TOKEN = "%FILE%"; + + /** + * A special token that represents a Mantis/Bugzilla/JIRA/etc issue ID. + * It can be used in the issueLinkUrl. + */ + private static final String ISSUE_TOKEN = "%ISSUE%"; + + /** + * A special token that represents the SCM revision number. + * It can be used in displayChangeSetDetailUrl + * and displayFileRevDetailUrl. + */ + private static final String REV_TOKEN = "%REV%"; + + /** + * The number of days to use as a range, when this is not specified. + */ + private static final int DEFAULT_RANGE = 30; + + public static final String DEFAULT_ISSUE_ID_REGEX_PATTERN = "[a-zA-Z]{2,}-\\d+"; + + private static final String DEFAULT_ISSUE_LINK_URL = "https://issues.apache.org/jira/browse/" + ISSUE_TOKEN; + + /** + * Used to specify the format to use for the dates in the headings of the + * report. + * + * @since 2.1 + */ + @Parameter( property = "changelog.headingDateFormat", defaultValue = "yyyy-MM-dd" ) + private String headingDateFormat = "yyyy-MM-dd"; + + /** + * Used to specify whether to build the log using range, tag or date. + */ + @Parameter( property = "changelog.type", defaultValue = "range", required = true ) + private String type; + + /** + * Used to specify the number of days of log entries to retrieve. + */ + @Parameter( property = "changelog.range", defaultValue = "-1" ) + private int range; + + /** + * Used to specify the absolute date (or list of dates) to start log entries from. + */ + @Parameter + private List dates; + + /** + * Used to specify the tag (or list of tags) to start log entries from. + */ + @Parameter + private List tags; + + /** + * Used to specify the date format of the log entries that are retrieved from your SCM system. + */ + @Parameter( property = "changelog.dateFormat", defaultValue = "yyyy-MM-dd HH:mm:ss", required = true ) + private String dateFormat; + + /** + * Input dir. Directory where the files under SCM control are located. + */ + @Parameter( property = "basedir", required = true ) + private File basedir; + + /** + * Output file for xml document + */ + @Parameter( defaultValue = "${project.build.directory}/changelog.xml", required = true ) + private File outputXML; + + /** + * Allows the user to make changelog regenerate the changelog.xml file for the specified time in minutes. + */ + @Parameter( property = "outputXMLExpiration", defaultValue = "60", required = true ) + private int outputXMLExpiration; + + /** + * Comment format string used for interrogating + * the revision control system. + * Currently only used by the ClearcaseChangeLogGenerator. + */ + @Parameter( property = "changelog.commentFormat" ) + private String commentFormat; + + /** + * The file encoding when writing non-HTML reports. + */ + @Parameter( property = "changelog.outputEncoding", defaultValue = "${project.reporting.outputEncoding}" ) + private String outputEncoding; + + /** + * The user name (used by svn and starteam protocol). + */ + @Parameter( property = "username" ) + private String username; + + /** + * The user password (used by svn and starteam protocol). + */ + @Parameter( property = "password" ) + private String password; + + /** + * The private key (used by java svn). + */ + @Parameter( property = "privateKey" ) + private String privateKey; + + /** + * The passphrase (used by java svn). + */ + @Parameter( property = "passphrase" ) + private String passphrase; + + /** + * The url of tags base directory (used by svn protocol). + */ + @Parameter( property = "tagBase" ) + private String tagBase; + + /** + * The URL to view the scm. Basis for external links from the generated report. + */ + @Parameter( property = "project.scm.url" ) + protected String scmUrl; + + /** + * Skip the Changelog report generation. Most useful on the command line + * via "-Dchangelog.skip=true". + * + * @since 2.3 + */ + @Parameter( property = "changelog.skip", defaultValue = "false" ) + protected boolean skip; + + /** + * Encodes slashes in file uri. Required for some repository browsers like gitblit + * + * @since 2.3 + */ + @Parameter( property = "encodeFileUri", defaultValue = "false" ) + protected boolean encodeFileUri; + + /** + * List of files to include. Specified as fileset patterns of files to include in the report + * + * @since 2.3 + */ + @Parameter + private String[] includes; + + /** + * List of files to include. Specified as fileset patterns of files to omit in the report + * + * @since 2.3 + */ + @Parameter + private String[] excludes; + + + /** + * The Maven Project Object + */ + @Parameter( defaultValue = "${project}", readonly = true, required = true ) + private MavenProject project; + + /** + * The directory where the report will be generated + */ + @Parameter( property = "project.reporting.outputDirectory", required = true, readonly = true ) + private File outputDirectory; + + /** + */ + @Component + private Renderer siteRenderer; + + /** + */ + @Parameter( property = "settings.offline", required = true, readonly = true ) + private boolean offline; + + /** + */ + @Component + private ScmManager manager; + + /** + */ + @Parameter( defaultValue = "${settings}", readonly = true, required = true ) + private Settings settings; + + /** + * Allows the user to choose which scm connection to use when connecting to the scm. + * Can either be "connection" or "developerConnection". + */ + @Parameter( defaultValue = "connection", required = true ) + private String connectionType; + + /** + * A template string that is used to create the URL to the file details. + * There is a special token that you can use in your template: + *
    + *
  • %FILE% - this is the path to a file
  • + *
+ *

+ * Example: + * http://checkstyle.cvs.sourceforge.net/checkstyle%FILE%?view=markup + *

+ *

+ * Note: If you don't supply the token in your template, + * the path of the file will simply be appended to your template URL. + *

+ */ + @Parameter( property = "displayFileDetailUrl", defaultValue = "${project.scm.url}" ) + protected String displayFileDetailUrl; + + /** + * A pattern used to identify 'issue tracker' IDs such as those used by JIRA, + * Bugzilla and alike in the SCM commit messages. Any matched patterns + * are replaced with issueLinkUrl URL. The default + * value is a JIRA-style issue identification pattern. + *

+ * Note: Default value is [a-zA-Z]{2,}-\d+ + * + * @since 2.2 + */ + @Parameter( property = "issueIDRegexPattern", defaultValue = DEFAULT_ISSUE_ID_REGEX_PATTERN, required = true ) + private String issueIDRegexPattern; + + /** + * The issue tracker URL used when replacing any matched issueIDRegexPattern + * found in the SCM commit messages. The default is URL is the codehaus JIRA + * URL. If %ISSUE% is found in the URL it is replaced with the matched issue ID, + * otherwise the matched issue ID is appended to the URL. + * + * @since 2.2 + */ + @Parameter( property = "issueLinkUrl", defaultValue = DEFAULT_ISSUE_LINK_URL, required = true ) + private String issueLinkUrl; + + /** + * A template string that is used to create the changeset URL. + *

+ * If not defined no change set link will be created. + *

+ * There is one special token that you can use in your template: + *

    + *
  • %REV% - this is the changeset revision
  • + *
+ *

+ * Example: + * http://fisheye.sourceforge.net/changelog/a-project/?cs=%REV% + *

+ *

+ * Note: If you don't supply the %REV% token in your template, + * the revision will simply be appended to your template URL. + *

+ * + * @since 2.2 + */ + @Parameter( property = "displayChangeSetDetailUrl" ) + protected String displayChangeSetDetailUrl; + + /** + * A template string that is used to create the revision aware URL to + * the file details in a similar fashion to the displayFileDetailUrl. + * When a report contains both file and file revision information, as in the + * Change Log report, this template string can be used to create a revision + * aware URL to the file details. + *

+ * If not defined this template string defaults to the same value as the + * displayFileDetailUrl and thus revision number aware links will + * not be used. + *

+ * There are two special tokens that you can use in your template: + *

    + *
  • %FILE% - this is the path to a file
  • + *
  • %REV% - this is the revision of the file
  • + *
+ *

+ * Example: + * http://fisheye.sourceforge.net/browse/a-project/%FILE%?r=%REV% + *

+ *

+ * Note: If you don't supply the %FILE% token in your template, + * the path of the file will simply be appended to your template URL. + *

+ * + * @since 2.2 + */ + @Parameter( property = "displayFileRevDetailUrl" ) + protected String displayFileRevDetailUrl; + + /** + * List of developers to be shown on the report. + * + * @since 2.2 + */ + @Parameter( property = "project.developers" ) + protected List developers; + + /** + * List of provider implementations. + */ + @Parameter + private Map providerImplementations; + + // temporary field holder while generating the report + private String rptRepository, rptOneRepoParam, rptMultiRepoParam; + + // field for SCM Connection URL + private String connection; + + // field used to hold a map of the developers by Id + private HashMap developersById; + + // field used to hold a map of the developers by Name + private HashMap developersByName; + + /** + * The system properties to use (needed by the perforce scm provider). + */ + @Parameter + private Properties systemProperties; + + private final Pattern sinkFileNamePattern = Pattern.compile( "\\\\" ); + + /** + * {@inheritDoc} + */ + public void executeReport( Locale locale ) + throws MavenReportException + { + //check if sources exists <-- required for parent poms + if ( !basedir.exists() ) + { + doGenerateEmptyReport( getBundle( locale ), getSink() ); + + return; + } + + if ( providerImplementations != null ) + { + for ( Map.Entry entry : providerImplementations.entrySet() ) + { + String providerType = entry.getKey(); + String providerImplementation = entry.getValue(); + getLog().info( + "Change the default '" + providerType + "' provider implementation to '" + providerImplementation + + "'." ); + manager.setScmProviderImplementation( providerType, providerImplementation ); + } + } + + initializeDefaultConfigurationParameters(); + + initializeDeveloperMaps(); + + verifySCMTypeParams(); + + if ( systemProperties != null ) + { + // Add all system properties configured by the user + Iterator iter = systemProperties.keySet().iterator(); + + while ( iter.hasNext() ) + { + String key = (String) iter.next(); + + String value = systemProperties.getProperty( key ); + + System.setProperty( key, value ); + + getLog().debug( "Setting system property: " + key + '=' + value ); + } + } + + doGenerateReport( getChangedSets(), getBundle( locale ), getSink() ); + } + + /** + * Initializes any configuration parameters that have not/can not be defined + * or defaulted by the Mojo API. + */ + private void initializeDefaultConfigurationParameters() + { + if ( displayFileRevDetailUrl == null || displayFileRevDetailUrl.length() == 0 ) + { + displayFileRevDetailUrl = displayFileDetailUrl; + } + } + + /** + * Creates maps of the project developers by developer Id and developer Name + * for quick lookups. + */ +/** + * Creates maps of the project developers by developer Id and developer Name + * for quick lookups. + */ +private void initializeDeveloperMaps() { + developersById = new java.util.HashMap(); + developersByName = new java.util.HashMap(); + { + for (org.apache.maven.model.Developer developer : /* NPEX_NULL_EXP */ + developers) { + developersById.put(developer.getId(), developer); + developersByName.put(developer.getName(), developer); + } + } +} + + /** + * populates the changedSets field by either connecting to the SCM or using an existing XML generated in a previous + * run of the report + * + * @throws MavenReportException + */ + protected List getChangedSets() + throws MavenReportException + { + List changelogList = null; + + if ( !outputXML.isAbsolute() ) + { + outputXML = new File( project.getBasedir(), outputXML.getPath() ); + } + + if ( outputXML.exists() ) + { + // CHECKSTYLE_OFF: MagicNumber + if ( outputXMLExpiration > 0 + && outputXMLExpiration * 60000 > System.currentTimeMillis() - outputXML.lastModified() ) + // CHECKSTYLE_ON: MagicNumber + { + try + { + //ReaderFactory.newReader( outputXML, getOutputEncoding() ); + //FileInputStream fIn = new FileInputStream( outputXML ); + + getLog().info( "Using existing changelog.xml..." ); + + changelogList = + ChangeLog.loadChangedSets( ReaderFactory.newReader( outputXML, getOutputEncoding() ) ); + } + catch ( FileNotFoundException e ) + { + //do nothing, just regenerate + } + catch ( Exception e ) + { + throw new MavenReportException( "An error occurred while parsing " + outputXML.getAbsolutePath(), + e ); + } + } + } + + if ( changelogList == null ) + { + if ( offline ) + { + throw new MavenReportException( "This report requires online mode." ); + } + + getLog().info( "Generating changed sets xml to: " + outputXML.getAbsolutePath() ); + + changelogList = generateChangeSetsFromSCM(); + + try + { + writeChangelogXml( changelogList ); + } + catch ( FileNotFoundException e ) + { + throw new MavenReportException( "Can't create " + outputXML.getAbsolutePath(), e ); + } + catch ( UnsupportedEncodingException e ) + { + throw new MavenReportException( "Can't create " + outputXML.getAbsolutePath(), e ); + } + catch ( IOException e ) + { + throw new MavenReportException( "Can't create " + outputXML.getAbsolutePath(), e ); + } + } + + return changelogList; + } + + private void writeChangelogXml( List changelogList ) + throws IOException + { + StringBuilder changelogXml = new StringBuilder(); + + changelogXml.append( "\n" ); + changelogXml.append( "" ); + + for ( ChangeLogSet changelogSet : changelogList ) + { + changelogXml.append( "\n " ); + + String changeset = changelogSet.toXML( getOutputEncoding() ); + + //remove xml header + if ( changeset.startsWith( "" ) + 2; + changeset = changeset.substring( idx ); + } + + changelogXml.append( changeset ); + } + + changelogXml.append( "\n" ); + + outputXML.getParentFile().mkdirs(); + + //PrintWriter pw = new PrintWriter( new BufferedOutputStream( new FileOutputStream( outputXML ) ) ); + //pw.write( changelogXml.toString() ); + //pw.flush(); + //pw.close(); + // MCHANGELOG-86 + Writer writer = WriterFactory.newWriter( new BufferedOutputStream( new FileOutputStream( outputXML ) ), + getOutputEncoding() ); + writer.write( changelogXml.toString() ); + writer.flush(); + writer.close(); + } + + /** + * creates a ChangeLog object and then connects to the SCM to generate the changed sets + * + * @return changedlogsets generated from the SCM + * @throws MavenReportException + */ + protected List generateChangeSetsFromSCM() + throws MavenReportException + { + try + { + List changeSets = new ArrayList(); + + ScmRepository repository = getScmRepository(); + + ScmProvider provider = manager.getProviderByRepository( repository ); + + ChangeLogScmResult result; + + if ( "range".equals( type ) ) + { + result = provider.changeLog( repository, new ScmFileSet( basedir ), null, null, range, (ScmBranch) null, + dateFormat ); + + checkResult( result ); + + changeSets.add( result.getChangeLog() ); + } + else if ( "tag".equals( type ) ) + { + + Iterator tagsIter = tags.iterator(); + + String startTag = tagsIter.next(); + String endTag = null; + + if ( tagsIter.hasNext() ) + { + while ( tagsIter.hasNext() ) + { + endTag = tagsIter.next(); + String endRevision = getRevisionForTag( endTag, repository, provider ); + String startRevision = getRevisionForTag( startTag, repository, provider ); + result = provider.changeLog( repository, new ScmFileSet( basedir ), + new ScmRevision( startRevision ), + new ScmRevision( endRevision ) ); + + checkResult( result ); + result.getChangeLog().setStartVersion( new ScmRevision( startTag ) ); + result.getChangeLog().setEndVersion( new ScmRevision( endTag ) ); + + changeSets.add( result.getChangeLog() ); + + startTag = endTag; + } + } + else + { + String startRevision = getRevisionForTag( startTag, repository, provider ); + String endRevision = getRevisionForTag( endTag, repository, provider ); + result = provider.changeLog( repository, new ScmFileSet( basedir ), + new ScmRevision( startRevision ), + new ScmRevision( endRevision ) ); + + checkResult( result ); + result.getChangeLog().setStartVersion( new ScmRevision( startTag ) ); + result.getChangeLog().setEndVersion( null ); + changeSets.add( result.getChangeLog() ); + } + } + else if ( "date".equals( type ) ) + { + Iterator dateIter = dates.iterator(); + + String startDate = dateIter.next(); + String endDate = null; + + if ( dateIter.hasNext() ) + { + while ( dateIter.hasNext() ) + { + endDate = dateIter.next(); + + result = provider.changeLog( repository, new ScmFileSet( basedir ), parseDate( startDate ), + parseDate( endDate ), 0, (ScmBranch) null ); + + checkResult( result ); + + changeSets.add( result.getChangeLog() ); + + startDate = endDate; + } + } + else + { + result = provider.changeLog( repository, new ScmFileSet( basedir ), parseDate( startDate ), + parseDate( endDate ), 0, (ScmBranch) null ); + + checkResult( result ); + + changeSets.add( result.getChangeLog() ); + } + } + else + { + throw new MavenReportException( "The type '" + type + "' isn't supported." ); + } + filter( changeSets ); + return changeSets; + + } + catch ( ScmException e ) + { + throw new MavenReportException( "Cannot run changelog command : ", e ); + } + catch ( MojoExecutionException e ) + { + throw new MavenReportException( "An error has occurred during changelog command : ", e ); + } + } + + /** + * Resolves the given tag to the revision number. + * + * @param tag + * @param repository + * @param provider + * @return + * @throws ScmException + */ + private String getRevisionForTag( final String tag, final ScmRepository repository, final ScmProvider provider ) + throws ScmException + { + if ( repository.getProvider().equals( "svn" ) ) + { + if ( tag == null ) + { + return "HEAD"; + } + SvnInfoCommandExpanded infoCommand = new SvnInfoCommandExpanded(); + infoCommand.setLogger( new DefaultLog() ); + + InfoScmResult infoScmResult = + infoCommand.executeInfoTagCommand( (SvnScmProviderRepository) repository.getProviderRepository(), + new ScmFileSet( basedir ), tag, null, false, null ); + if ( infoScmResult.getInfoItems().size() == 0 ) + { + throw new ScmException( "There is no tag named '" + tag + "' in the Subversion repository." ); + } + InfoItem infoItem = infoScmResult.getInfoItems().get( 0 ); + String revision = infoItem.getLastChangedRevision(); + getLog().info( String.format( "Resolved tag '%s' to revision '%s'", tag, revision ) ); + return revision; + } + return tag; + } + + /** + * filters out unwanted files from the changesets + */ + private void filter( List changeSets ) + { + List include = compilePatterns( includes ); + List exclude = compilePatterns( excludes ); + if ( includes == null && excludes == null ) + { + return; + } + for ( ChangeLogSet changeLogSet : changeSets ) + { + List set = changeLogSet.getChangeSets(); + filter( set, include, exclude ); + } + + } + + private List compilePatterns( String[] patternArray ) + { + if ( patternArray == null ) + { + return new ArrayList(); + } + List patterns = new ArrayList( patternArray.length ); + for ( String string : patternArray ) + { + //replaces * with [/\]* (everything but file seperators) + //replaces ** with .* + //quotes the rest of the string + string = "\\Q" + string + "\\E"; + string = string.replace( "**", "\\E.?REPLACEMENT?\\Q" ); + string = string.replace( "*", "\\E[^/\\\\]?REPLACEMENT?\\Q" ); + string = string.replace( "?REPLACEMENT?", "*" ); + string = string.replace( "\\Q\\E", "" ); + patterns.add( Pattern.compile( string ) ); + } + return patterns; + } + + private void filter( List sets, List includes, List excludes ) + { + Iterator it = sets.iterator(); + while ( it.hasNext() ) + { + ChangeSet changeSet = it.next(); + List files = changeSet.getFiles(); + Iterator iterator = files.iterator(); + while ( iterator.hasNext() ) + { + ChangeFile changeFile = iterator.next(); + String name = changeFile.getName(); + if ( !isIncluded( includes, name ) || isExcluded( excludes, name ) ) + { + iterator.remove(); + } + } + if ( files.isEmpty() ) + { + it.remove(); + } + } + } + + private boolean isExcluded( List excludes, String name ) + { + if ( excludes == null || excludes.isEmpty() ) + { + return false; + } + for ( Pattern pattern : excludes ) + { + if ( pattern.matcher( name ).matches() ) + { + return true; + } + } + return false; + } + + private boolean isIncluded( List includes, String name ) + { + if ( includes == null || includes.isEmpty() ) + { + return true; + } + for ( Pattern pattern : includes ) + { + if ( pattern.matcher( name ).matches() ) + { + return true; + } + } + return false; + } + + /** + * Converts the localized date string pattern to date object. + * + * @return A date + */ + private Date parseDate( String date ) + throws MojoExecutionException + { + if ( date == null || date.trim().length() == 0 ) + { + return null; + } + + SimpleDateFormat formatter = new SimpleDateFormat( "yyyy-MM-dd" ); + + try + { + return formatter.parse( date ); + } + catch ( ParseException e ) + { + throw new MojoExecutionException( "Please use this date pattern: " + formatter.toLocalizedPattern(), e ); + } + } + + public ScmRepository getScmRepository() + throws ScmException + { + ScmRepository repository; + + try + { + repository = manager.makeScmRepository( getConnection() ); + + ScmProviderRepository providerRepo = repository.getProviderRepository(); + + if ( !StringUtils.isEmpty( username ) ) + { + providerRepo.setUser( username ); + } + + if ( !StringUtils.isEmpty( password ) ) + { + providerRepo.setPassword( password ); + } + + if ( repository.getProviderRepository() instanceof ScmProviderRepositoryWithHost ) + { + ScmProviderRepositoryWithHost repo = (ScmProviderRepositoryWithHost) repository.getProviderRepository(); + + loadInfosFromSettings( repo ); + + if ( !StringUtils.isEmpty( username ) ) + { + repo.setUser( username ); + } + + if ( !StringUtils.isEmpty( password ) ) + { + repo.setPassword( password ); + } + + if ( !StringUtils.isEmpty( privateKey ) ) + { + repo.setPrivateKey( privateKey ); + } + + if ( !StringUtils.isEmpty( passphrase ) ) + { + repo.setPassphrase( passphrase ); + } + } + + if ( !StringUtils.isEmpty( tagBase ) && repository.getProvider().equals( "svn" ) ) + { + SvnScmProviderRepository svnRepo = (SvnScmProviderRepository) repository.getProviderRepository(); + + svnRepo.setTagBase( tagBase ); + } + } + catch ( Exception e ) + { + throw new ScmException( "Can't load the scm provider.", e ); + } + + return repository; + } + + /** + * Load username password from settings if user has not set them in JVM properties + * + * @param repo + */ + private void loadInfosFromSettings( ScmProviderRepositoryWithHost repo ) + { + if ( username == null || password == null ) + { + String host = repo.getHost(); + + int port = repo.getPort(); + + if ( port > 0 ) + { + host += ":" + port; + } + + Server server = this.settings.getServer( host ); + + if ( server != null ) + { + if ( username == null ) + { + username = this.settings.getServer( host ).getUsername(); + } + + if ( password == null ) + { + password = this.settings.getServer( host ).getPassword(); + } + + if ( privateKey == null ) + { + privateKey = this.settings.getServer( host ).getPrivateKey(); + } + + if ( passphrase == null ) + { + passphrase = this.settings.getServer( host ).getPassphrase(); + } + } + } + } + + public void checkResult( ScmResult result ) + throws MojoExecutionException + { + if ( !result.isSuccess() ) + { + getLog().error( "Provider message:" ); + + getLog().error( result.getProviderMessage() == null ? "" : result.getProviderMessage() ); + + getLog().error( "Command output:" ); + + getLog().error( result.getCommandOutput() == null ? "" : result.getCommandOutput() ); + + throw new MojoExecutionException( "Command failed." ); + } + } + + /** + * used to retrieve the SCM connection string + * + * @return the url string used to connect to the SCM + * @throws MavenReportException when there is insufficient information to retrieve the SCM connection string + */ + protected String getConnection() + throws MavenReportException + { + if ( this.connection != null ) + { + return connection; + } + + if ( project.getScm() == null ) + { + throw new MavenReportException( "SCM Connection is not set." ); + } + + String scmConnection = project.getScm().getConnection(); + if ( StringUtils.isNotEmpty( scmConnection ) && "connection".equals( connectionType.toLowerCase() ) ) + { + connection = scmConnection; + } + + String scmDeveloper = project.getScm().getDeveloperConnection(); + if ( StringUtils.isNotEmpty( scmDeveloper ) && "developerconnection".equals( connectionType.toLowerCase() ) ) + { + connection = scmDeveloper; + } + + if ( StringUtils.isEmpty( connection ) ) + { + throw new MavenReportException( "SCM Connection is not set." ); + } + + return connection; + } + + /** + * checks whether there are enough configuration parameters to generate the report + * + * @throws MavenReportException when there is insufficient paramters to generate the report + */ + private void verifySCMTypeParams() + throws MavenReportException + { + if ( "range".equals( type ) ) + { + if ( range == -1 ) + { + range = DEFAULT_RANGE; + } + } + else if ( "date".equals( type ) ) + { + if ( dates == null ) + { + throw new MavenReportException( "The dates parameter is required when type=\"date\". The value " + + "should be the absolute date for the start of the log." ); + } + } + else if ( "tag".equals( type ) ) + { + if ( tags == null ) + { + throw new MavenReportException( "The tags parameter is required when type=\"tag\"." ); + } + } + else + { + throw new MavenReportException( "The type parameter has an invalid value: " + type + + ". The value should be \"range\", \"date\", or \"tag\"." ); + } + } + + /** + * generates an empty report in case there are no sources to generate a report with + * + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + protected void doGenerateEmptyReport( ResourceBundle bundle, Sink sink ) + { + sink.head(); + sink.title(); + sink.text( bundle.getString( "report.changelog.header" ) ); + sink.title_(); + sink.head_(); + + sink.body(); + sink.section1(); + + sink.sectionTitle1(); + sink.text( bundle.getString( "report.changelog.mainTitle" ) ); + sink.sectionTitle1_(); + + sink.paragraph(); + sink.text( bundle.getString( "report.changelog.nosources" ) ); + sink.paragraph_(); + + sink.section1_(); + + sink.body_(); + sink.flush(); + sink.close(); + } + + /** + * method that generates the report for this mojo. This method is overridden by dev-activity and file-activity mojo + * + * @param changeLogSets changed sets to generate the report from + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + protected void doGenerateReport( List changeLogSets, ResourceBundle bundle, Sink sink ) + { + sink.head(); + sink.title(); + sink.text( bundle.getString( "report.changelog.header" ) ); + sink.title_(); + sink.head_(); + + sink.body(); + sink.section1(); + + sink.sectionTitle1(); + sink.text( bundle.getString( "report.changelog.mainTitle" ) ); + sink.sectionTitle1_(); + + // Summary section + doSummarySection( changeLogSets, bundle, sink ); + + for ( ChangeLogSet changeLogSet : changeLogSets ) + { + doChangedSet( changeLogSet, bundle, sink ); + } + + sink.section1_(); + sink.body_(); + + sink.flush(); + sink.close(); + } + + /** + * generates the report summary section of the report + * + * @param changeLogSets changed sets to generate the report from + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + private void doSummarySection( List changeLogSets, ResourceBundle bundle, Sink sink ) + { + sink.paragraph(); + + sink.text( bundle.getString( "report.changelog.ChangedSetsTotal" ) ); + sink.text( ": " + changeLogSets.size() ); + + sink.paragraph_(); + } + + /** + * generates a section of the report referring to a changeset + * + * @param set the current ChangeSet to generate this section of the report + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + private void doChangedSet( ChangeLogSet set, ResourceBundle bundle, Sink sink ) + { + sink.section2(); + + doChangeSetTitle( set, bundle, sink ); + + doSummary( set, bundle, sink ); + + doChangedSetTable( set.getChangeSets(), bundle, sink ); + + sink.section2_(); + } + + /** + * Generate the title for the report. + * + * @param set change set to generate the report from + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + protected void doChangeSetTitle( ChangeLogSet set, ResourceBundle bundle, Sink sink ) + { + sink.sectionTitle2(); + + SimpleDateFormat headingDateFormater = new SimpleDateFormat( headingDateFormat ); + + if ( "tag".equals( type ) ) + { + if ( set.getStartVersion() == null || set.getStartVersion().getName() == null ) + { + sink.text( bundle.getString( "report.SetTagCreation" ) ); + if ( set.getEndVersion() != null && set.getEndVersion().getName() != null ) + { + sink.text( ' ' + bundle.getString( "report.SetTagUntil" ) + " '" + set.getEndVersion() + '\'' ); + } + } + else if ( set.getEndVersion() == null || set.getEndVersion().getName() == null ) + { + sink.text( bundle.getString( "report.SetTagSince" ) ); + sink.text( " '" + set.getStartVersion() + '\'' ); + } + else + { + sink.text( bundle.getString( "report.SetTagBetween" ) ); + sink.text( + " '" + set.getStartVersion() + "' " + bundle.getString( "report.And" ) + " '" + set.getEndVersion() + + '\'' ); + } + } + else if ( set.getStartDate() == null ) + { + sink.text( bundle.getString( "report.SetRangeUnknown" ) ); + } + else if ( set.getEndDate() == null ) + { + sink.text( bundle.getString( "report.SetRangeSince" ) ); + sink.text( ' ' + headingDateFormater.format( set.getStartDate() ) ); + } + else + { + sink.text( bundle.getString( "report.SetRangeBetween" ) ); + sink.text( + ' ' + headingDateFormater.format( set.getStartDate() ) + ' ' + bundle.getString( "report.And" ) + ' ' + + headingDateFormater.format( set.getEndDate() ) ); + } + sink.sectionTitle2_(); + } + + /** + * Generate the summary section of the report. + * + * @param set change set to generate the report from + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + protected void doSummary( ChangeLogSet set, ResourceBundle bundle, Sink sink ) + { + sink.paragraph(); + sink.text( bundle.getString( "report.TotalCommits" ) ); + sink.text( ": " + set.getChangeSets().size() ); + sink.lineBreak(); + sink.text( bundle.getString( "report.changelog.FilesChanged" ) ); + sink.text( ": " + countFilesChanged( set.getChangeSets() ) ); + sink.paragraph_(); + } + + /** + * counts the number of files that were changed in the specified SCM + * + * @param entries a collection of SCM changes + * @return number of files changed for the changedsets + */ + protected long countFilesChanged( Collection entries ) + { + if ( entries == null ) + { + return 0; + } + + if ( entries.isEmpty() ) + { + return 0; + } + + HashMap> fileList = new HashMap>(); + + for ( ChangeSet entry : entries ) + { + for ( ChangeFile file : entry.getFiles() ) + { + String name = file.getName(); + List list = fileList.get( name ); + + if ( list != null ) + { + list.add( file ); + } + else + { + list = new LinkedList(); + + list.add( file ); + + fileList.put( name, list ); + } + } + } + + return fileList.size(); + } + + /** + * generates the report table showing the SCM log entries + * + * @param entries a list of change log entries to generate the report from + * @param bundle the resource bundle to retrieve report phrases from + * @param sink the report formatting tool + */ + private void doChangedSetTable( Collection entries, ResourceBundle bundle, Sink sink ) + { + sink.table(); + + sink.tableRow(); + sink.tableHeaderCell(); + sink.text( bundle.getString( "report.changelog.timestamp" ) ); + sink.tableHeaderCell_(); + sink.tableHeaderCell(); + sink.text( bundle.getString( "report.changelog.author" ) ); + sink.tableHeaderCell_(); + sink.tableHeaderCell(); + sink.text( bundle.getString( "report.changelog.details" ) ); + sink.tableHeaderCell_(); + sink.tableRow_(); + + initReportUrls(); + + List sortedEntries = new ArrayList( entries ); + Collections.sort( sortedEntries, new Comparator() + { + public int compare( ChangeSet changeSet0, ChangeSet changeSet1 ) + { + return changeSet1.getDate().compareTo( changeSet0.getDate() ); + } + } ); + + for ( ChangeSet entry : sortedEntries ) + { + doChangedSetDetail( entry, sink ); + } + + sink.table_(); + } + + /** + * reports on the details of an SCM entry log + * + * @param entry an SCM entry to generate the report from + * @param sink the report formatting tool + */ + private void doChangedSetDetail( ChangeSet entry, Sink sink ) + { + sink.tableRow(); + + sink.tableCell(); + sink.text( entry.getDateFormatted() + ' ' + entry.getTimeFormatted() ); + sink.tableCell_(); + + sink.tableCell(); + + sinkAuthorDetails( sink, entry.getAuthor() ); + + sink.tableCell_(); + + sink.tableCell(); + //doRevision( entry.getFiles(), bundle, sink ); + doChangedFiles( entry.getFiles(), sink ); + sink.lineBreak(); + StringReader sr = new StringReader( entry.getComment() ); + BufferedReader br = new BufferedReader( sr ); + String line; + try + { + if ( ( issueIDRegexPattern != null && issueIDRegexPattern.length() > 0 ) && ( issueLinkUrl != null + && issueLinkUrl.length() > 0 ) ) + { + Pattern pattern = Pattern.compile( issueIDRegexPattern ); + + line = br.readLine(); + + while ( line != null ) + { + sinkIssueLink( sink, line, pattern ); + + line = br.readLine(); + if ( line != null ) + { + sink.lineBreak(); + } + } + } + else + { + line = br.readLine(); + + while ( line != null ) + { + sink.text( line ); + line = br.readLine(); + if ( line != null ) + { + sink.lineBreak(); + } + } + } + } + catch ( IOException e ) + { + getLog().warn( "Unable to read the comment of a ChangeSet as a stream." ); + } + finally + { + try + { + br.close(); + } + catch ( IOException e ) + { + getLog().warn( "Unable to close a reader." ); + } + sr.close(); + } + sink.tableCell_(); + + sink.tableRow_(); + } + + private void sinkIssueLink( Sink sink, String line, Pattern pattern ) + { + // replace any ticket patterns found. + + Matcher matcher = pattern.matcher( line ); + + int currLoc = 0; + + while ( matcher.find() ) + { + String match = matcher.group(); + + String link; + + if ( issueLinkUrl.indexOf( ISSUE_TOKEN ) > 0 ) + { + link = issueLinkUrl.replaceAll( ISSUE_TOKEN, match ); + } + else + { + if ( issueLinkUrl.endsWith( "/" ) ) + { + link = issueLinkUrl; + } + else + { + link = issueLinkUrl + '/'; + } + + link += match; + } + + int startOfMatch = matcher.start(); + + String unmatchedText = line.substring( currLoc, startOfMatch ); + + currLoc = matcher.end(); + + sink.text( unmatchedText ); + + sink.link( link ); + sink.text( match ); + sink.link_(); + } + + sink.text( line.substring( currLoc ) ); + } + + /** + * If the supplied author is a known developer this method outputs a + * link to the team members report, or alternatively, if the supplied + * author is unknown, outputs the author's name as plain text. + * + * @param sink Sink to use for outputting + * @param author The author's name. + */ + protected void sinkAuthorDetails( Sink sink, String author ) + { + Developer developer = developersById.get( author ); + + if ( developer == null ) + { + developer = developersByName.get( author ); + } + + if ( developer != null ) + { + sink.link( "team-list.html#" + developer.getId() ); + sink.text( developer.getName() ); + sink.link_(); + } + else + { + sink.text( author ); + } + } + + /** + * populates the report url used to create links from certain elements of the report + */ + protected void initReportUrls() + { + if ( scmUrl != null ) + { + int idx = scmUrl.indexOf( '?' ); + + if ( idx > 0 ) + { + rptRepository = scmUrl.substring( 0, idx ); + + if ( scmUrl.equals( displayFileDetailUrl ) ) + { + String rptTmpMultiRepoParam = scmUrl.substring( rptRepository.length() ); + + rptOneRepoParam = '?' + rptTmpMultiRepoParam.substring( 1 ); + + rptMultiRepoParam = '&' + rptTmpMultiRepoParam.substring( 1 ); + } + } + else + { + rptRepository = scmUrl; + + rptOneRepoParam = ""; + + rptMultiRepoParam = ""; + } + } + } + + /** + * generates the section of the report listing all the files revisions + * + * @param files list of files to generate the reports from + * @param sink the report formatting tool + */ + private void doChangedFiles( List files, Sink sink ) + { + for ( ChangeFile file : files ) + { + sinkLogFile( sink, file.getName(), file.getRevision() ); + } + } + + /** + * generates the section of the report detailing the revisions made and the files changed + * + * @param sink the report formatting tool + * @param name filename of the changed file + * @param revision the revision code for this file + */ + private void sinkLogFile( Sink sink, String name, String revision ) + { + try + { + String connection = getConnection(); + + generateLinks( connection, name, revision, sink ); + } + catch ( Exception e ) + { + getLog().debug( e ); + + sink.text( name + " v " + revision ); + } + + sink.lineBreak(); + } + + /** + * attaches the http links from the changed files + * + * @param connection the string used to connect to the SCM + * @param name filename of the file that was changed + * @param sink the report formatting tool + */ + protected void generateLinks( String connection, String name, Sink sink ) + { + generateLinks( connection, name, null, sink ); + } + + /** + * attaches the http links from the changed files + * + * @param connection the string used to connect to the SCM + * @param name filename of the file that was changed + * @param revision the revision code + * @param sink the report formatting tool + */ + protected void generateLinks( String connection, String name, String revision, Sink sink ) + { + String linkFile; + String linkRev = null; + + if ( revision != null ) + { + linkFile = displayFileRevDetailUrl; + } + else + { + linkFile = displayFileDetailUrl; + } + + if ( linkFile != null ) + { + if ( !linkFile.equals( scmUrl ) ) + { + String linkName = name; + if ( encodeFileUri ) + { + try + { + linkName = URLEncoder.encode( linkName, "UTF-8" ); + } + catch ( UnsupportedEncodingException e ) + { + // UTF-8 is always supported + } + } + + // Use the given URL to create links to the files + + if ( linkFile.indexOf( FILE_TOKEN ) > 0 ) + { + linkFile = linkFile.replaceAll( FILE_TOKEN, linkName ); + } + else + { + // This is here so that we are backwards compatible with the + // format used before the special token was introduced + + linkFile += linkName; + } + + // Use the given URL to create links to the files + + if ( revision != null && linkFile.indexOf( REV_TOKEN ) > 0 ) + { + linkFile = linkFile.replaceAll( REV_TOKEN, revision ); + } + } + else if ( connection.startsWith( "scm:perforce" ) ) + { + String path = getAbsolutePath( displayFileDetailUrl, name ); + linkFile = path + "?ac=22"; + if ( revision != null ) + { + linkRev = path + "?ac=64&rev=" + revision; + } + } + else if ( connection.startsWith( "scm:clearcase" ) ) + { + String path = getAbsolutePath( displayFileDetailUrl, name ); + linkFile = path + rptOneRepoParam; + } + else if ( connection.indexOf( "cvsmonitor.pl" ) > 0 ) + { + Pattern cvsMonitorRegex = Pattern.compile( "^.*(&module=.*?(?:&|$)).*$" ); + String module = cvsMonitorRegex.matcher( rptOneRepoParam ).replaceAll( "$1" ); + linkFile = displayFileDetailUrl + "?cmd=viewBrowseFile" + module + "&file=" + name; + if ( revision != null ) + { + linkRev = + rptRepository + "?cmd=viewBrowseVersion" + module + "&file=" + name + "&version=" + revision; + } + } + else + { + String path = getAbsolutePath( displayFileDetailUrl, name ); + linkFile = path + rptOneRepoParam; + if ( revision != null ) + { + linkRev = path + "?rev=" + revision + "&content-type=text/vnd.viewcvs-markup" + rptMultiRepoParam; + } + } + } + + if ( linkFile != null ) + { + sink.link( linkFile ); + sinkFileName( name, sink ); + sink.link_(); + } + else + { + sinkFileName( name, sink ); + } + + sink.text( " " ); + + if ( linkRev == null && revision != null && displayChangeSetDetailUrl != null ) + { + if ( displayChangeSetDetailUrl.indexOf( REV_TOKEN ) > 0 ) + { + linkRev = displayChangeSetDetailUrl.replaceAll( REV_TOKEN, revision ); + } + else + { + linkRev = displayChangeSetDetailUrl + revision; + } + } + + if ( linkRev != null ) + { + sink.link( linkRev ); + sink.text( "v " + revision ); + sink.link_(); + } + else if ( revision != null ) + { + sink.text( "v " + revision ); + } + } + + /** + * Encapsulates the logic for rendering the name with a bolded markup. + * + * @param name filename of the file that was changed + * @param sink the report formatting tool + */ + private void sinkFileName( String name, Sink sink ) + { + name = sinkFileNamePattern.matcher( name ).replaceAll( "/" ); + int pos = name.lastIndexOf( '/' ); + + String head; + String tail; + if ( pos < 0 ) + { + head = ""; + tail = name; + } + else + { + head = name.substring( 0, pos ) + '/'; + tail = name.substring( pos + 1 ); + } + + sink.text( head ); + sink.bold(); + sink.text( tail ); + sink.bold_(); + } + + /** + * calculates the path from a base directory to a target file + * + * @param base base directory to create the absolute path from + * @param target target file to create the absolute path to + */ + private String getAbsolutePath( final String base, final String target ) + { + String absPath = ""; + + StringTokenizer baseTokens = + new StringTokenizer( sinkFileNamePattern.matcher( base ).replaceAll( "/" ), "/", true ); + + StringTokenizer targetTokens = + new StringTokenizer( sinkFileNamePattern.matcher( target ).replaceAll( "/" ), "/" ); + + String targetRoot = targetTokens.nextToken(); + + while ( baseTokens.hasMoreTokens() ) + { + String baseToken = baseTokens.nextToken(); + + if ( baseToken.equals( targetRoot ) ) + { + break; + } + + absPath += baseToken; + } + + if ( !absPath.endsWith( "/" ) ) + { + absPath += "/"; + } + + String newTarget = target; + if ( newTarget.startsWith( "/" ) ) + { + newTarget = newTarget.substring( 1 ); + } + + return absPath + newTarget; + } + + /** + * {@inheritDoc} + */ + protected MavenProject getProject() + { + return project; + } + + /** + * {@inheritDoc} + */ + protected String getOutputDirectory() + { + if ( !outputDirectory.isAbsolute() ) + { + outputDirectory = new File( project.getBasedir(), outputDirectory.getPath() ); + } + + return outputDirectory.getAbsolutePath(); + } + + /** + * Gets the effective reporting output files encoding. + * + * @return The effective reporting output file encoding, never null. + */ + protected String getOutputEncoding() + { + return ( outputEncoding != null ) ? outputEncoding : ReaderFactory.UTF_8; + } + + /** + * {@inheritDoc} + */ + protected Renderer getSiteRenderer() + { + return siteRenderer; + } + + /** + * {@inheritDoc} + */ + public String getDescription( Locale locale ) + { + return getBundle( locale ).getString( "report.changelog.description" ); + } + + /** + * {@inheritDoc} + */ + public String getName( Locale locale ) + { + return getBundle( locale ).getString( "report.changelog.name" ); + } + + /** + * {@inheritDoc} + */ + public String getOutputName() + { + return "changelog"; + } + + /** + * @param locale + * @return the current bundle + */ + protected ResourceBundle getBundle( Locale locale ) + { + return ResourceBundle.getBundle( "scm-activity", locale, this.getClass().getClassLoader() ); + } + + /** + * {@inheritDoc} + */ + public boolean canGenerateReport() + { + if ( offline && !outputXML.exists() ) + { + return false; + } + + return !skip; + + } +} diff --git a/Java/maven-changelog-plugin-ChangeLogReport_506/metadata.json b/Java/maven-changelog-plugin-ChangeLogReport_506/metadata.json new file mode 100644 index 000000000..f27ef8469 --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogReport_506/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "maven-changelog-plugin-ChangeLogReport_506", + "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 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": "src/main/java/org/apache/maven/plugin/changelog/ChangeLogReport.java", + "line": 510, + "npe_method": "initializeDeveloperMaps", + "deref_field": "developers", + "npe_class": "ChangeLogReport", + "repo": "maven-changelog-plugin", + "bug_id": "ChangeLogReport_506" + } +} diff --git a/Java/maven-changelog-plugin-ChangeLogReport_506/npe.json b/Java/maven-changelog-plugin-ChangeLogReport_506/npe.json new file mode 100644 index 000000000..b2bfffda0 --- /dev/null +++ b/Java/maven-changelog-plugin-ChangeLogReport_506/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/maven/plugin/changelog/ChangeLogReport.java", + "line": 510, + "npe_method": "initializeDeveloperMaps", + "deref_field": "developers", + "npe_class": "ChangeLogReport" +} \ No newline at end of file