Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrote the PropertiesProvider API to support initialization, close and better multithreading #339

Merged
merged 1 commit into from
Mar 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,41 @@
import com.mycila.maven.plugin.license.AbstractLicenseMojo;
import com.mycila.maven.plugin.license.PropertiesProvider;
import com.mycila.maven.plugin.license.document.Document;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
* An implementation of {@link PropertiesProvider} that adds {@value #COPYRIGHT_CREATION_AUTHOR_NAME_KEY} and
* {@value #COPYRIGHT_CREATION_AUTHOR_EMAIL_KEY} values - see
* {@link #getAdditionalProperties(AbstractLicenseMojo, Properties, Document)}.
* An implementation of {@link PropertiesProvider} that adds {@value
* #COPYRIGHT_CREATION_AUTHOR_NAME_KEY} and {@value #COPYRIGHT_CREATION_AUTHOR_EMAIL_KEY} values -
* see {@link #adjustProperties(AbstractLicenseMojo, Map, Document)}.
*
* @author masakimu
*/
public class CopyrightAuthorProvider extends GitPropertiesProvider implements PropertiesProvider {
public class CopyrightAuthorProvider implements PropertiesProvider {

public static final String COPYRIGHT_CREATION_AUTHOR_NAME_KEY = "license.git.CreationAuthorName";
public static final String COPYRIGHT_CREATION_AUTHOR_EMAIL_KEY = "license.git.CreationAuthorEmail";

private GitLookup gitLookup;

public CopyrightAuthorProvider() {
super();
@Override
public void init(AbstractLicenseMojo mojo, Map<String, String> currentProperties) {
gitLookup = GitLookup.create(mojo.defaultBasedir, currentProperties);

// One-time warning for shallow repo
if (mojo.warnIfShallow && gitLookup.isShallowRepository()) {
mojo.warn("Shallow git repository detected. Author property values may not be accurate.");
}
dbwiddis marked this conversation as resolved.
Show resolved Hide resolved
}

@Override
public void close() {
if (gitLookup != null) {
gitLookup.close();
}
}

/**
Expand All @@ -49,21 +64,20 @@ public CopyrightAuthorProvider() {
* <li>{@value #COPYRIGHT_CREATION_AUTHOR_EMAIL_KEY} key stores the author's email address of the first git commit.
* </ul>
*/
public Map<String, String> getAdditionalProperties(AbstractLicenseMojo mojo, Properties properties,
Document document) {

@Override
public Map<String, String> adjustProperties(AbstractLicenseMojo mojo,
Map<String, String> properties, Document document) {
try {
Map<String, String> result = new HashMap<>(3);
GitLookup gitLookup = getGitLookup(mojo, document.getFile(), properties);

result.put(COPYRIGHT_CREATION_AUTHOR_NAME_KEY, gitLookup.getAuthorNameOfCreation(document.getFile()));
result.put(COPYRIGHT_CREATION_AUTHOR_EMAIL_KEY, gitLookup.getAuthorEmailOfCreation(document.getFile()));
result.put(COPYRIGHT_CREATION_AUTHOR_NAME_KEY,
gitLookup.getAuthorNameOfCreation(document.getFile()));
result.put(COPYRIGHT_CREATION_AUTHOR_EMAIL_KEY,
gitLookup.getAuthorEmailOfCreation(document.getFile()));
return Collections.unmodifiableMap(result);
} catch (Exception e) {
throw new RuntimeException("Could not compute the year of the last git commit for file "
+ document.getFile().getAbsolutePath(), e);
} catch (IOException e) {
throw new UncheckedIOException(
"CopyrightAuthorProvider error on file: " + document.getFile().getAbsolutePath() + ": "
+ e.getMessage(), e);
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,44 @@
import com.mycila.maven.plugin.license.AbstractLicenseMojo;
import com.mycila.maven.plugin.license.PropertiesProvider;
import com.mycila.maven.plugin.license.document.Document;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.eclipse.jgit.api.errors.GitAPIException;

/**
* An implementation of {@link PropertiesProvider} that adds {@value #COPYRIGHT_LAST_YEAR_KEY} and
* {@value #COPYRIGHT_YEARS_KEY} values - see
* {@link #getAdditionalProperties(AbstractLicenseMojo, Properties, Document)}.
* {@value #COPYRIGHT_YEARS_KEY} values - see {@link #adjustProperties(AbstractLicenseMojo, Map,
* Document)}.
*
* @author <a href="mailto:ppalaga@redhat.com">Peter Palaga</a>
*/
public class CopyrightRangeProvider extends GitPropertiesProvider implements PropertiesProvider {
public class CopyrightRangeProvider implements PropertiesProvider {

public static final String COPYRIGHT_LAST_YEAR_KEY = "license.git.copyrightLastYear";
public static final String COPYRIGHT_CREATION_YEAR_KEY = "license.git.copyrightCreationYear";
public static final String COPYRIGHT_EXISTENCE_YEARS_KEY = "license.git.copyrightExistenceYears";
public static final String COPYRIGHT_YEARS_KEY = "license.git.copyrightYears";
public static final String INCEPTION_YEAR_KEY = "project.inceptionYear";

private GitLookup gitLookup;

public CopyrightRangeProvider() {
super();
@Override
public void init(AbstractLicenseMojo mojo, Map<String, String> currentProperties) {
gitLookup = GitLookup.create(mojo.defaultBasedir, currentProperties);

// One-time warning for shallow repo
if (mojo.warnIfShallow && gitLookup.isShallowRepository()) {
mojo.warn("Shallow git repository detected. Year property values may not be accurate.");
}
dbwiddis marked this conversation as resolved.
Show resolved Hide resolved
}

@Override
public void close() {
if (gitLookup != null) {
gitLookup.close();
}
}

/**
Expand All @@ -54,16 +69,17 @@ public CopyrightRangeProvider() {
* returned; otherwise, the two values are combined using dash, so that the result is e.g. {@code "2000-2010"}.</li>
* <li>{@value #COPYRIGHT_CREATION_YEAR_KEY} key stores the year from the committer date of the first git commit for
* the supplied {@code document}.</li>
* <li>{@value #COPYRIGHT_EXISTENCE_YEARS_KEY} key stores the range from {@value #COPYRIGHT_CREATION_YEAR_KEY} value to
* <li>{@value #COPYRIGHT_EXISTENCE_YEARS_KEY} key stores the range from {@value #COPYRIGHT_CREATION_YEAR_KEY} value to
* {@value #COPYRIGHT_LAST_YEAR_KEY} value. If both values are equal only the {@value #COPYRIGHT_CREATION_YEAR_KEY} is returned;
* otherwise, the two values are combined using dash, so that the result is e.g. {@code "2005-2010"}.</li>
* </ul>
* The {@value #INCEPTION_YEAR_KEY} value is read from the supplied properties and it must available. Otherwise a
* {@link RuntimeException} is thrown.
*/
public Map<String, String> getAdditionalProperties(AbstractLicenseMojo mojo, Properties properties,
Document document) {
String inceptionYear = properties.getProperty(INCEPTION_YEAR_KEY);
@Override
public Map<String, String> adjustProperties(AbstractLicenseMojo mojo,
Map<String, String> properties, Document document) {
String inceptionYear = properties.get(INCEPTION_YEAR_KEY);
if (inceptionYear == null) {
throw new RuntimeException("'" + INCEPTION_YEAR_KEY + "' must have a value for file "
+ document.getFile().getAbsolutePath());
Expand All @@ -72,12 +88,13 @@ public Map<String, String> getAdditionalProperties(AbstractLicenseMojo mojo, Pro
try {
inceptionYearInt = Integer.parseInt(inceptionYear);
} catch (NumberFormatException e1) {
throw new RuntimeException("'" + INCEPTION_YEAR_KEY + "' must be an integer ; found = " + inceptionYear + " file: "
+ document.getFile().getAbsolutePath());
throw new RuntimeException(
"'" + INCEPTION_YEAR_KEY + "' must be an integer ; found = " + inceptionYear + " file: "
+ document.getFile().getAbsolutePath());
}
try {
Map<String, String> result = new HashMap<>(4);
GitLookup gitLookup = getGitLookup(mojo, document.getFile(), properties);

int copyrightEnd = gitLookup.getYearOfLastChange(document.getFile());
result.put(COPYRIGHT_LAST_YEAR_KEY, Integer.toString(copyrightEnd));
final String copyrightYears;
Expand All @@ -90,19 +107,20 @@ public Map<String, String> getAdditionalProperties(AbstractLicenseMojo mojo, Pro

int copyrightStart = gitLookup.getYearOfCreation(document.getFile());
result.put(COPYRIGHT_CREATION_YEAR_KEY, Integer.toString(copyrightStart));

final String copyrightExistenceYears;
if (copyrightStart >= copyrightEnd) {
copyrightExistenceYears = Integer.toString(copyrightStart);
copyrightExistenceYears = Integer.toString(copyrightStart);
} else {
copyrightExistenceYears = copyrightStart + "-" + copyrightEnd;
copyrightExistenceYears = copyrightStart + "-" + copyrightEnd;
}
result.put(COPYRIGHT_EXISTENCE_YEARS_KEY, copyrightExistenceYears);

return Collections.unmodifiableMap(result);
} catch (Exception e) {
throw new RuntimeException("Could not compute the year of the last git commit for file "
+ document.getFile().getAbsolutePath(), e);
} catch (IOException | GitAPIException e) {
throw new RuntimeException(
"CopyrightRangeProvider error on file: " + document.getFile().getAbsolutePath() + ": "
+ e.getMessage(), e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@
*/
package com.mycila.maven.plugin.license.git;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.Status;
Expand All @@ -44,9 +48,16 @@
*
* @author <a href="mailto:ppalaga@redhat.com">Peter Palaga</a>
*/
public class GitLookup {
public class GitLookup implements Closeable {

public static final TimeZone DEFAULT_ZONE = TimeZone.getTimeZone("GMT");

public static final String MAX_COMMITS_LOOKUP_KEY = "license.git.maxCommitsLookup";
// keep for compatibility
private static final String COPYRIGHT_LAST_YEAR_MAX_COMMITS_LOOKUP_KEY = "license.git.copyrightLastYearMaxCommitsLookup";
public static final String COPYRIGHT_LAST_YEAR_SOURCE_KEY = "license.git.copyrightLastYearSource";
public static final String COPYRIGHT_LAST_YEAR_TIME_ZONE_KEY = "license.git.copyrightLastYearTimeZone";

public enum DateSource {
AUTHOR, COMMITER
}
Expand All @@ -59,41 +70,94 @@ public enum DateSource {
private final boolean shallow;

/**
* Creates a new {@link GitLookup} for a repository that is detected from the supplied {@code anyFile}.
* <p>
* Note on time zones:
*
* @param anyFile - any path from the working tree of the git repository to consider in all subsequent calls to
* {@link #getYearOfLastChange(File)}
* @param dateSource where to read the commit dates from - committer date or author date
* @param timeZone the time zone if {@code dateSource} is {@link DateSource#COMMITER}; otherwise must be {@code null}.
* @param checkCommitsCount
* @throws IOException
* Lazily initializes #gitLookup assuming that all subsequent calls to this method will be related
* to the same git repository.
*/
public GitLookup(File anyFile, DateSource dateSource, TimeZone timeZone, int checkCommitsCount) throws IOException {
super();
this.repository = new FileRepositoryBuilder().findGitDir(anyFile).build();
/* A workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=457961 */
// Also contains contents of .git/shallow and can detect shallow repo
this.shallow = !this.repository.getObjectDatabase().newReader().getShallowCommits().isEmpty();

this.pathResolver = new GitPathResolver(repository.getWorkTree().getAbsolutePath());
this.dateSource = dateSource;
public static GitLookup create(File file, Map<String, String> props) {
String dateSourceString = props.get(COPYRIGHT_LAST_YEAR_SOURCE_KEY);
if (dateSourceString == null) {
dateSourceString = GitLookup.DateSource.AUTHOR.name();
}
GitLookup.DateSource dateSource = GitLookup.DateSource.valueOf(
dateSourceString.toUpperCase(Locale.US));
String checkCommitsCountString = props.get(MAX_COMMITS_LOOKUP_KEY);
// Backwads compatibility
if (checkCommitsCountString == null) {
checkCommitsCountString = props.get(COPYRIGHT_LAST_YEAR_MAX_COMMITS_LOOKUP_KEY);
}
int checkCommitsCount = Integer.MAX_VALUE;
if (checkCommitsCountString != null) {
checkCommitsCountString = checkCommitsCountString.trim();
checkCommitsCount = Integer.parseInt(checkCommitsCountString);
}
final TimeZone timeZone;
String tzString = props.get(COPYRIGHT_LAST_YEAR_TIME_ZONE_KEY);
switch (dateSource) {
case COMMITER:
this.timeZone = timeZone == null ? DEFAULT_ZONE : timeZone;
timeZone = tzString == null ? GitLookup.DEFAULT_ZONE : TimeZone.getTimeZone(tzString);
break;
case AUTHOR:
if (timeZone != null) {
throw new IllegalArgumentException("Time zone must be null with dateSource " + DateSource.AUTHOR.name()
+ " because git author name already contrains time zone information.");
if (tzString != null) {
throw new RuntimeException(COPYRIGHT_LAST_YEAR_TIME_ZONE_KEY + " must not be set with "
+ COPYRIGHT_LAST_YEAR_SOURCE_KEY + " = " + GitLookup.DateSource.AUTHOR.name()
+ " because git author name already contains time zone information.");
}
this.timeZone = null;
timeZone = null;
break;
default:
throw new IllegalStateException("Unexpected " + DateSource.class.getName() + " " + dateSource);
throw new IllegalStateException(
"Unexpected " + GitLookup.DateSource.class.getName() + " " + dateSource);
}
return new GitLookup(file, dateSource, timeZone, checkCommitsCount);
}

/**
* Creates a new {@link GitLookup} for a repository that is detected from the supplied {@code
* anyFile}.
* <p>
* Note on time zones:
*
* @param anyFile - any path from the working tree of the git repository to consider in
* all subsequent calls to {@link #getYearOfLastChange(File)}
* @param dateSource where to read the commit dates from - committer date or author date
* @param timeZone the time zone if {@code dateSource} is {@link DateSource#COMMITER};
* otherwise must be {@code null}.
* @param checkCommitsCount
* @throws IOException
*/
private GitLookup(File anyFile, DateSource dateSource, TimeZone timeZone, int checkCommitsCount) {
try {
this.repository = new FileRepositoryBuilder().findGitDir(anyFile).build();
/* A workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=457961 */
// Also contains contents of .git/shallow and can detect shallow repo
// the line below reads and caches the entries in the FileObjectDatabase of the repository to
// avoid concurrent modifications during RevWalk
// Closing the repository will close the FileObjectDatabase.
// Here the newReader() is a WindowCursor which delegates the getShallowCommits() to the FileObjectDatabase.
this.shallow = !this.repository.getObjectDatabase().newReader().getShallowCommits().isEmpty();
dbwiddis marked this conversation as resolved.
Show resolved Hide resolved

this.pathResolver = new GitPathResolver(repository.getWorkTree().getAbsolutePath());
this.dateSource = dateSource;
switch (dateSource) {
case COMMITER:
this.timeZone = timeZone == null ? DEFAULT_ZONE : timeZone;
break;
case AUTHOR:
if (timeZone != null) {
throw new IllegalArgumentException(
"Time zone must be null with dateSource " + DateSource.AUTHOR.name()
+ " because git author name already contains time zone information.");
}
this.timeZone = null;
break;
default:
throw new IllegalStateException(
"Unexpected " + DateSource.class.getName() + " " + dateSource);
}
this.checkCommitsCount = checkCommitsCount;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
this.checkCommitsCount = checkCommitsCount;
}

/**
Expand Down Expand Up @@ -148,12 +212,12 @@ int getYearOfCreation(File file) throws IOException, GitAPIException {
commitYear = getYearFromCommit(commit);
}
walk.dispose();

// If we couldn't find a creation year from Git assume newly created file
if (commitYear == 0) {
return getCurrentYear();
}
return getCurrentYear();
}

return commitYear;
}

Expand Down Expand Up @@ -182,7 +246,7 @@ String getAuthorEmailOfCreation(File file) throws IOException {
walk.dispose();
return authorEmail;
}

boolean isShallowRepository() {
return this.shallow;
}
Expand Down Expand Up @@ -245,4 +309,9 @@ private String getAuthorEmailFromCommit(RevCommit commit) {
PersonIdent id = commit.getAuthorIdent();
return id.getEmailAddress();
}

@Override
public void close() {
repository.close();
}
}
Loading