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

[#2119] Implement Proper Deep Cloning for RepoConfiguration and CliArguments #2124

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
11 changes: 10 additions & 1 deletion src/main/java/reposense/model/Author.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
/**
* Represents a Git Author.
*/
public class Author {
public class Author implements Cloneable {
public static final String NAME_NO_AUTHOR_WITH_COMMITS_FOUND =
"NO AUTHOR WITH COMMITS FOUND WITHIN THIS PERIOD OF TIME";
private static final String UNKNOWN_AUTHOR_GIT_ID = "-";
Expand Down Expand Up @@ -208,5 +208,14 @@ private void addStandardGitHostEmails(List<String> emails) {
emails.add(standardGitLabEmail);
}
}

@Override
public Author clone() throws CloneNotSupportedException {
Author clone = (Author) super.clone();
clone.emails = new ArrayList<>(this.emails);
clone.authorAliases = new ArrayList<>(this.authorAliases);
clone.ignoreGlobList = new ArrayList<>(this.ignoreGlobList);
return clone;
}
}

36 changes: 35 additions & 1 deletion src/main/java/reposense/model/AuthorConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
/**
* Represents author configuration information from CSV config file for a single repository.
*/
public class AuthorConfiguration {
public class AuthorConfiguration implements Cloneable {
public static final String DEFAULT_BRANCH = "HEAD";
public static final boolean DEFAULT_HAS_AUTHOR_CONFIG_FILE = false;
private static final Logger logger = LogsManager.getLogger(AuthorConfiguration.class);
Expand Down Expand Up @@ -336,4 +336,38 @@ public void setHasAuthorConfigFile(boolean hasAuthorConfigFile) {
public boolean hasAuthorConfigFile() {
return hasAuthorConfigFile;
}

/**
* Creates an identical deep copy of this {@code AuthorConfiguration} object.
*
* @return Deep copy of this {@code AuthorConfiguration} object.
* @throws CloneNotSupportedException if the cloning operation fails.
*/
@Override
public AuthorConfiguration clone() throws CloneNotSupportedException {
AuthorConfiguration clone = (AuthorConfiguration) super.clone();
clone.location = this.location.clone();
clone.authorNamesToAuthorMap = new HashMap<>();
clone.authorEmailsToAuthorMap = new HashMap<>();
clone.authorDisplayNameMap = new HashMap<>();
clone.authorList = new ArrayList<>();

for (Author a : this.authorList) {
clone.authorList.add(a.clone());
}

for (Map.Entry<String, Author> entry : this.authorNamesToAuthorMap.entrySet()) {
clone.authorNamesToAuthorMap.put(entry.getKey(), entry.getValue().clone());
}

for (Map.Entry<String, Author> entry : this.authorEmailsToAuthorMap.entrySet()) {
clone.authorEmailsToAuthorMap.put(entry.getKey(), entry.getValue().clone());
}

for (Map.Entry<Author, String> entry : this.authorDisplayNameMap.entrySet()) {
clone.authorDisplayNameMap.put(entry.getKey().clone(), entry.getValue());
}

return clone;
}
}
37 changes: 35 additions & 2 deletions src/main/java/reposense/model/CliArguments.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,21 @@
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import reposense.parser.ArgsParser;
import reposense.parser.AuthorConfigCsvParser;
import reposense.parser.ConfigurationBuildException;
import reposense.parser.GroupConfigCsvParser;
import reposense.parser.RepoConfigCsvParser;
import reposense.parser.ReportConfigJsonParser;

/**
* Represents command line arguments user supplied when running the program.
*/
public class CliArguments {
public class CliArguments implements Cloneable {
private static final Path EMPTY_PATH = Paths.get("");

private final Path outputFilePath;
Expand All @@ -25,7 +27,7 @@ public class CliArguments {
private final LocalDateTime untilDate;
private final boolean isSinceDateProvided;
private final boolean isUntilDateProvided;
private final List<FileType> formats;
private List<FileType> formats;
private final boolean isLastModifiedDateIncluded;
private final boolean isShallowCloningPerformed;
private final boolean isAutomaticallyLaunching;
Expand Down Expand Up @@ -229,6 +231,36 @@ public boolean equals(Object other) {
&& Objects.equals(this.reportConfigFilePath, otherCliArguments.reportConfigFilePath);
}

/**
* Clones the current instance of {@code CliArguments}. This method will only explicitly clone
* objects that are not immutable (e.g. List, Lists of mutable objects, other ordinary mutable
* objects).
*
* @return {@code CliArguments} instance that is semantically identical to this instance of
* {@code CliArguments}.
* @throws CloneNotSupportedException if cloning for certain objects is not permitted.
*/
@Override
public CliArguments clone() throws CloneNotSupportedException {
CliArguments clone = (CliArguments) super.clone();

// clone the formats, each FileType object needs to be individually cloned
clone.formats = new ArrayList<>();
for (FileType ft : this.formats) {
clone.formats.add(ft.clone());
}

// clone the string list; its ok to do a shallow copy since strings are mutable
clone.locations = this.locations == null ? clone.locations : new ArrayList<>(this.locations);

// clone the report config; use the clone method to clone the object for us
clone.reportConfiguration = this.reportConfiguration == null
? clone.reportConfiguration
: this.reportConfiguration.clone();

return clone;
}

/**
* Builder used to build CliArguments.
*/
Expand Down Expand Up @@ -506,6 +538,7 @@ public Builder reportConfiguration(ReportConfiguration reportConfiguration) {
* Builds CliArguments.
*
* @return CliArguments
* @throws ConfigurationBuildException if the object cannot be cloned
*/
public CliArguments build() {
return new CliArguments(this);
Expand Down
13 changes: 12 additions & 1 deletion src/main/java/reposense/model/CommitHash.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
/**
* Represents a git commit hash in {@code RepoConfiguration}.
*/
public class CommitHash {
public class CommitHash implements Cloneable {
private static final String COMMIT_HASH_REGEX = "^[0-9a-f]+$";
private static final String COMMIT_RANGED_HASH_REGEX = "^[0-9a-f]+\\.\\.[0-9a-f]+$";
private static final String INVALID_COMMIT_HASH_MESSAGE =
Expand Down Expand Up @@ -109,5 +109,16 @@ private static void validateCommit(String commitHash) throws IllegalArgumentExce
throw new IllegalArgumentException(String.format(INVALID_COMMIT_HASH_MESSAGE, commitHash));
}
}

/**
* Creates a deep copy of this {@code CommitHash} object.
*
* @return Deep copy of this {@code CommitHash} object.
* @throws CloneNotSupportedException if the cloning operation fails.
*/
@Override
public CommitHash clone() throws CloneNotSupportedException {
return (CommitHash) super.clone();
}
}

19 changes: 18 additions & 1 deletion src/main/java/reposense/model/FileType.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.nio.file.FileSystems;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
Expand All @@ -17,7 +18,7 @@
/**
* Represents a file type for use in {@link FileTypeManager}.
*/
public class FileType {
public class FileType implements Cloneable {
private static final Pattern FILE_FORMAT_VALIDATION_PATTERN = Pattern.compile("^\\w+$");
private static final String MESSAGE_ILLEGAL_FILE_FORMAT = "The provided file format, %s, contains illegal "
+ "characters.";
Expand Down Expand Up @@ -110,6 +111,22 @@ public boolean equals(Object other) {
return this.label.equals(otherFileType.label) && this.paths.equals(otherFileType.paths);
}

/**
* Clones this instance of {@code FileType}.
*
* @return New instance of {@code FileType} that is semantically similar to this instance
* of {@code FileType}.
* @throws CloneNotSupportedException if there are any objects that cannot be cloned.
*/
@Override
public FileType clone() throws CloneNotSupportedException {
// it suffices to do a shallow clone since strings are immutable
FileType clone = (FileType) super.clone();
clone.paths = new ArrayList<>(this.paths);

return clone;
}

/**
* Overrides the Gson serializer to serialize only the label of each file type instead on the entire object.
*/
Expand Down
13 changes: 12 additions & 1 deletion src/main/java/reposense/model/FileTypeManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* Holds a list of whitelisted formats and user-specified custom
* groupings.
*/
public class FileTypeManager {
public class FileTypeManager implements Cloneable {
private static final String DEFAULT_GROUP = "other";
private static final FileType DEFAULT_GROUP_TYPE = new FileType(DEFAULT_GROUP, Collections.singletonList("**"));

Expand Down Expand Up @@ -126,4 +126,15 @@ public boolean equals(Object other) {
FileTypeManager otherFileType = (FileTypeManager) other;
return this.groups.equals(otherFileType.groups) && this.formats.equals(otherFileType.formats);
}

@Override
public FileTypeManager clone() throws CloneNotSupportedException {
FileTypeManager clone = (FileTypeManager) super.clone();

// do a shallow copy since strings are immutable
clone.formats = new ArrayList<>(this.formats);
clone.groups = new ArrayList<>(this.groups);

return clone;
}
}
39 changes: 30 additions & 9 deletions src/main/java/reposense/model/RepoConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
/**
* Represents configuration information from CSV config file for a single repository.
*/
public class RepoConfiguration {
public class RepoConfiguration implements Cloneable {
public static final String DEFAULT_BRANCH = "HEAD";
public static final String DEFAULT_EXTRA_OUTPUT_FOLDER_NAME = "";
public static final long DEFAULT_FILE_SIZE_LIMIT = 500000;
Expand Down Expand Up @@ -60,6 +60,29 @@
*/
private RepoConfiguration() {}

/**
* Creates a deep copy of this {@code RepoConfiguration} object.
*
* @return Deep copy of this {@code RepoConfiguration} object.
* @throws CloneNotSupportedException if the cloning operation fails.
*/
@Override
public RepoConfiguration clone() throws CloneNotSupportedException {
RepoConfiguration clone = (RepoConfiguration) super.clone();
clone.location = this.location == null ? clone.location : this.location.clone();
clone.fileTypeManager = this.fileTypeManager == null ? clone.fileTypeManager : this.fileTypeManager.clone();
clone.ignoreGlobList = new ArrayList<>(this.ignoreGlobList);
clone.ignoredAuthorsList = new ArrayList<>(this.ignoredAuthorsList);
clone.authorConfig = this.authorConfig == null ? clone.authorConfig : this.authorConfig.clone();
clone.ignoreCommitList = new ArrayList<>();

for (CommitHash hash : this.ignoreCommitList) {
clone.ignoreCommitList.add(hash.clone());
}

return clone;
}

/**
* Builds the necessary configurations for RepoConfiguration.
* Obeys the Builder pattern as described in {@link CliArguments}.
Expand All @@ -68,7 +91,7 @@
private String displayName;
private String outputFolderName;
private String repoFolderName;
private RepoConfiguration repoConfiguration;
private final RepoConfiguration repoConfiguration;

/**
* Returns an empty instance of the RepoConfiguration Builder.
Expand Down Expand Up @@ -426,13 +449,11 @@
this.processNames();

// save a reference to the current built object
RepoConfiguration toReturn = this.repoConfiguration;

// reset the internal reference to avoid aliasing
this.repoConfiguration = new RepoConfiguration();

// return the reference to the built RepoConfiguration object
return toReturn;
try {
return this.repoConfiguration.clone();
} catch (CloneNotSupportedException ex) {
throw new ConfigurationBuildException();

Check warning on line 455 in src/main/java/reposense/model/RepoConfiguration.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/reposense/model/RepoConfiguration.java#L454-L455

Added lines #L454 - L455 were not covered by tests
}
}

/**
Expand Down
16 changes: 15 additions & 1 deletion src/main/java/reposense/model/RepoLocation.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
/**
* Represents a repository location.
*/
public class RepoLocation {
public class RepoLocation implements Cloneable {
protected static final String UNSUPPORTED_DOMAIN_NAME = "NOT_RECOGNIZED";

private static final String MESSAGE_INVALID_LOCATION = "%s is an invalid location.";
Expand Down Expand Up @@ -269,4 +269,18 @@ public boolean equals(Object other) {
public int hashCode() {
return location.hashCode();
}

/**
* Clones this {@code RepoLocation} instance.
*
* @return A new instance of {@code RepoLocation} that is sematically identical to this
* {@code RepoLocation} instance.
* @throws CloneNotSupportedException if there is a problem when cloning this
* {@code RepoLocation} object.
*/
@Override
public RepoLocation clone() throws CloneNotSupportedException {
// regular shallow clone will suffice since strings are immutable
return (RepoLocation) super.clone();
}
}
7 changes: 6 additions & 1 deletion src/main/java/reposense/model/ReportConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
/**
* Represents configuration information from JSON config file for generated report.
*/
public class ReportConfiguration {
public class ReportConfiguration implements Cloneable {
private static final String DEFAULT_TITLE = "RepoSense Report";
private String title;

public String getTitle() {
return (title == null) ? DEFAULT_TITLE : title;
}

@Override
public ReportConfiguration clone() throws CloneNotSupportedException {
return (ReportConfiguration) super.clone();
}
}
Loading
Loading