From a42a6b2720f2398152cf0868c2b973dc972373e4 Mon Sep 17 00:00:00 2001 From: Juul Hobert Date: Fri, 18 Oct 2024 14:43:39 +0200 Subject: [PATCH 1/2] Maintenance score based on commit frequency --- pom.xml | 18 +- src/main/java/com/giovds/PomClient.java | 104 ++++++++ .../java/com/giovds/PomClientInterface.java | 9 + .../java/com/giovds/UnmaintainedMojo.java | 112 ++++++++ .../collector/github/GithubCollector.java | 183 +++++++++++++ .../github/GithubCollectorInterface.java | 9 + .../collector/github/GithubGuesser.java | 20 ++ src/main/java/com/giovds/dto/PomResponse.java | 11 + src/main/java/com/giovds/dto/Scm.java | 7 + .../com/giovds/dto/github/extenal/Author.java | 14 + .../dto/github/extenal/CommitActivity.java | 47 ++++ .../dto/github/extenal/ContributorStat.java | 30 +++ .../giovds/dto/github/extenal/IssueStats.java | 22 ++ .../com/giovds/dto/github/extenal/Owner.java | 240 ++++++++++++++++++ .../giovds/dto/github/extenal/Repository.java | 151 +++++++++++ .../giovds/dto/github/internal/Bucket.java | 67 +++++ .../giovds/dto/github/internal/Collected.java | 159 ++++++++++++ .../dto/github/internal/Contributor.java | 67 +++++ .../com/giovds/dto/github/internal/Point.java | 69 +++++ .../com/giovds/dto/github/internal/Range.java | 88 +++++++ .../dto/github/internal/RangeSummary.java | 87 +++++++ .../evaluator/MaintenanceEvaluator.java | 46 ++++ .../java/com/giovds/http/JsonBodyHandler.java | 44 ++++ .../giovds/normalizer/DefaultNormalizer.java | 40 +++ .../evaluator/MaintenanceEvaluatorTest.java | 95 +++++++ .../normalizer/DefaultNormalizerTest.java | 40 +++ .../giovds/poc/github/GithubGuesserTest.java | 34 +++ 27 files changed, 1808 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/giovds/PomClient.java create mode 100644 src/main/java/com/giovds/PomClientInterface.java create mode 100644 src/main/java/com/giovds/UnmaintainedMojo.java create mode 100644 src/main/java/com/giovds/collector/github/GithubCollector.java create mode 100644 src/main/java/com/giovds/collector/github/GithubCollectorInterface.java create mode 100644 src/main/java/com/giovds/collector/github/GithubGuesser.java create mode 100644 src/main/java/com/giovds/dto/PomResponse.java create mode 100644 src/main/java/com/giovds/dto/Scm.java create mode 100644 src/main/java/com/giovds/dto/github/extenal/Author.java create mode 100644 src/main/java/com/giovds/dto/github/extenal/CommitActivity.java create mode 100644 src/main/java/com/giovds/dto/github/extenal/ContributorStat.java create mode 100644 src/main/java/com/giovds/dto/github/extenal/IssueStats.java create mode 100644 src/main/java/com/giovds/dto/github/extenal/Owner.java create mode 100644 src/main/java/com/giovds/dto/github/extenal/Repository.java create mode 100644 src/main/java/com/giovds/dto/github/internal/Bucket.java create mode 100644 src/main/java/com/giovds/dto/github/internal/Collected.java create mode 100644 src/main/java/com/giovds/dto/github/internal/Contributor.java create mode 100644 src/main/java/com/giovds/dto/github/internal/Point.java create mode 100644 src/main/java/com/giovds/dto/github/internal/Range.java create mode 100644 src/main/java/com/giovds/dto/github/internal/RangeSummary.java create mode 100644 src/main/java/com/giovds/evaluator/MaintenanceEvaluator.java create mode 100644 src/main/java/com/giovds/http/JsonBodyHandler.java create mode 100644 src/main/java/com/giovds/normalizer/DefaultNormalizer.java create mode 100644 src/test/java/com/giovds/evaluator/MaintenanceEvaluatorTest.java create mode 100644 src/test/java/com/giovds/normalizer/DefaultNormalizerTest.java create mode 100644 src/test/java/com/giovds/poc/github/GithubGuesserTest.java diff --git a/pom.xml b/pom.xml index 7a1688e..c53d9c1 100644 --- a/pom.xml +++ b/pom.xml @@ -45,9 +45,9 @@ UTF-8 - 17 + 21 - 2.18.1 + 2.18.1 5.11.3 3.26.3 5.14.2 @@ -127,6 +127,13 @@ ${maven-dependencies.version} provided + + com.fasterxml.jackson + jackson-bom + ${jackson.version} + import + pom + @@ -143,13 +150,14 @@ ${maven-plugin-tools.version} provided - com.fasterxml.jackson.jr jackson-jr-objects - ${jackson-jr-objects.version} - + + com.fasterxml.jackson.jr + jackson-jr-annotation-support + org.junit.jupiter junit-jupiter diff --git a/src/main/java/com/giovds/PomClient.java b/src/main/java/com/giovds/PomClient.java new file mode 100644 index 0000000..d9339e4 --- /dev/null +++ b/src/main/java/com/giovds/PomClient.java @@ -0,0 +1,104 @@ +package com.giovds; + +import com.giovds.dto.PomResponse; +import com.giovds.dto.Scm; +import org.apache.maven.plugin.logging.Log; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; + +public class PomClient implements PomClientInterface { + + private final String basePath; + private final String pomPathTemplate; + + private final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + + private final HttpClient client = HttpClient.newBuilder() + .version(HttpClient.Version.HTTP_2) + .build(); + + private final Log log; + + public PomClient(Log log) { + this("https://repo1.maven.org", "/maven2/%s/%s/%s/%s-%s.pom", log); + } + + public PomClient(String basePath, String pomPathTemplate, Log log) { + this.basePath = basePath; + this.pomPathTemplate = pomPathTemplate; + this.log = log; + } + + public PomResponse getPom(String group, String artifact, String version) throws IOException, InterruptedException { + final String path = String.format(pomPathTemplate, group.replace(".", "/"), artifact, version, artifact, version); + final HttpRequest request = HttpRequest.newBuilder() + .GET() + .uri(URI.create(basePath + path)) + .build(); + + return client.send(request, new PomResponseBodyHandler()).body(); + } + + private class PomResponseBodyHandler implements HttpResponse.BodyHandler { + + @Override + public HttpResponse.BodySubscriber apply(final HttpResponse.ResponseInfo responseInfo) { + int statusCode = responseInfo.statusCode(); + + if (statusCode < 200 || statusCode >= 300) { + return HttpResponse.BodySubscribers.mapping(HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8), s -> { + throw new RuntimeException("Search failed: status: %d body: %s".formatted(responseInfo.statusCode(), s)); + }); + } + + HttpResponse.BodySubscriber stream = HttpResponse.BodySubscribers.ofInputStream(); + + return HttpResponse.BodySubscribers.mapping(stream, this::toPomResponse); + } + + private PomResponse toPomResponse(final InputStream inputStream) { + try (final InputStream input = inputStream) { + DocumentBuilder documentBuilder = PomClient.this.documentBuilderFactory.newDocumentBuilder(); + Document doc = documentBuilder.parse(input); + + doc.getDocumentElement().normalize(); + + Element root = doc.getDocumentElement(); + NodeList urlNodes = root.getElementsByTagName("url"); + + if (urlNodes.getLength() == 0) { + return PomResponse.empty(); + } + String url = urlNodes.item(0).getTextContent(); + + Scm scm = Scm.empty(); + NodeList scmNodes = root.getElementsByTagName("scm"); + if (scmNodes.getLength() > 0) { + Element scmElement = (Element) scmNodes.item(0); + NodeList scmUrlNodes = scmElement.getElementsByTagName("url"); + if (scmUrlNodes.getLength() > 0) { + String scmUrl = scmUrlNodes.item(0).getTextContent(); + scm = new Scm(scmUrl); + } + } + + return new PomResponse(url, scm); + } catch (IOException | ParserConfigurationException | SAXException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/src/main/java/com/giovds/PomClientInterface.java b/src/main/java/com/giovds/PomClientInterface.java new file mode 100644 index 0000000..94ad8f5 --- /dev/null +++ b/src/main/java/com/giovds/PomClientInterface.java @@ -0,0 +1,9 @@ +package com.giovds; + +import com.giovds.dto.PomResponse; + +import java.io.IOException; + +public interface PomClientInterface { + PomResponse getPom(String group, String artifact, String version) throws IOException, InterruptedException; +} diff --git a/src/main/java/com/giovds/UnmaintainedMojo.java b/src/main/java/com/giovds/UnmaintainedMojo.java new file mode 100644 index 0000000..1dbc3a7 --- /dev/null +++ b/src/main/java/com/giovds/UnmaintainedMojo.java @@ -0,0 +1,112 @@ +package com.giovds; + +import com.giovds.collector.github.GithubCollector; +import com.giovds.collector.github.GithubGuesser; +import com.giovds.dto.PomResponse; +import com.giovds.dto.github.internal.Collected; +import com.giovds.evaluator.MaintenanceEvaluator; +import org.apache.maven.model.Dependency; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.plugin.logging.SystemStreamLog; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; +import org.apache.maven.project.MavenProject; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; + +@Mojo(name = "unmaintained", + defaultPhase = LifecyclePhase.TEST_COMPILE, + requiresOnline = true, + requiresDependencyResolution = ResolutionScope.TEST) +public class UnmaintainedMojo extends AbstractMojo { + + private final PomClientInterface client; + private final GithubGuesser githubGuesser; + private final GithubCollector githubCollector; + private final MaintenanceEvaluator maintenanceEvaluator; + + @Parameter(readonly = true, required = true, defaultValue = "${project}") + private MavenProject project; + + /** + * Required for initialization by Maven + */ + public UnmaintainedMojo() { + this(new SystemStreamLog()); + } + + public UnmaintainedMojo(Log log) { + this(new PomClient(log), new GithubGuesser(), new GithubCollector(log), new MaintenanceEvaluator()); + } + + public UnmaintainedMojo( + final PomClientInterface client, + final GithubGuesser githubGuesser, + final GithubCollector githubCollector, + final MaintenanceEvaluator maintenanceEvaluator) { + this.client = client; + this.githubGuesser = githubGuesser; + this.githubCollector = githubCollector; + this.maintenanceEvaluator = maintenanceEvaluator; + } + + @Override + public void execute() throws MojoFailureException { + final List dependencies = project.getDependencies(); + + if (dependencies.isEmpty()) { + // When building a POM without any dependencies there will be nothing to query. + return; + } + + final Map pomResponses = dependencies.stream() + .map(dependency -> { + try { + PomResponse pomResponse = client.getPom(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion()); + + return new DependencyPomResponsePair(dependency, pomResponse); + } catch (Exception e) { + getLog().error("Failed to fetch POM for %s:%s:%s".formatted(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion()), e); + return new DependencyPomResponsePair(dependency, PomResponse.empty()); + } + }) + .collect(Collectors.toMap(DependencyPomResponsePair::dependency, DependencyPomResponsePair::pomResponse)); + + for (Dependency dependency : pomResponses.keySet()) { + final PomResponse pomResponse = pomResponses.get(dependency); + final String projectUrl = pomResponse.url(); + final String projectScmUrl = pomResponse.scmUrl(); + + // First try to get the Github owner and repo from the url otherwise try to get it from the SCM url + var guess = projectUrl != null ? githubGuesser.guess(projectUrl) : null; + if (guess == null && projectScmUrl != null) { + guess = githubGuesser.guess(projectScmUrl); + } + + if (guess == null) { + getLog().warn("Could not guess Github owner and repo for %s".formatted(dependency.getManagementKey())); + continue; + } + + Collected collected; + try { + collected = githubCollector.collect(guess.owner(), guess.repo()); + } catch (ExecutionException | InterruptedException e) { + throw new MojoFailureException("Failed to collect Github data for %s".formatted(dependency.getManagementKey()), e); + } + + double score = maintenanceEvaluator.evaluateCommitsFrequency(collected); + getLog().info("Maintenance score for %s: %f".formatted(dependency.getManagementKey(), score)); + } + } + + private record DependencyPomResponsePair(Dependency dependency, PomResponse pomResponse) { + } +} diff --git a/src/main/java/com/giovds/collector/github/GithubCollector.java b/src/main/java/com/giovds/collector/github/GithubCollector.java new file mode 100644 index 0000000..7569a9e --- /dev/null +++ b/src/main/java/com/giovds/collector/github/GithubCollector.java @@ -0,0 +1,183 @@ +package com.giovds.collector.github; + +import com.fasterxml.jackson.jr.annotationsupport.JacksonAnnotationExtension; +import com.fasterxml.jackson.jr.ob.JSON; +import com.giovds.dto.github.extenal.CommitActivity; +import com.giovds.dto.github.extenal.ContributorStat; +import com.giovds.dto.github.extenal.Repository; +import com.giovds.dto.github.internal.*; +import com.giovds.http.JsonBodyHandler; +import org.apache.maven.plugin.logging.Log; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; + +public class GithubCollector implements GithubCollectorInterface { + private static final String GITHUB_API = "https://api.github.com"; + private static final String GITHUB_TOKEN = System.getenv("GITHUB_TOKEN"); + + private final String baseUrl; + private final HttpClient httpClient; + private final Log log; + + public GithubCollector(Log log) { + this(GITHUB_API, HttpClient.newHttpClient(), log); + } + + public GithubCollector(String baseUrl, HttpClient httpClient, Log log) { + this.baseUrl = baseUrl; + this.httpClient = httpClient; + this.log = log; + + if (GITHUB_TOKEN == null || GITHUB_TOKEN.isEmpty()) { + log.warn("No GitHub token provided, rate limits will be enforced. Provide a token by setting the GITHUB_TOKEN environment variable."); + } else { + log.info("GitHub token provided"); + } + } + + @Override + public Collected collect(String owner, String repo) throws InterruptedException, ExecutionException { + var repository = getRepository(owner, repo).get(); + var contributors = getContributors(repository).get(); + var commitActivity = getCommitActivity(repository).get(); + + var summary = extractCommits(commitActivity); + + return Collected.builder() + .homepage(repository.getHomepage()) + .starsCount(repository.getStargazersCount()) + .forksCount(repository.getForksCount()) + .subscribersCount(repository.getSubscribersCount()) + .contributors(Arrays.stream(contributors).map( + contributor -> Contributor.builder() + .username(contributor.getAuthor().getLogin()) + .commitsCount(contributor.getTotal()) + .build() + ).toList().reversed()) + .commits(summary) + .build(); + } + + private CompletableFuture getRepository(String owner, String repo) throws InterruptedException { + return requestAsync(String.format("repos/%s/%s", owner, repo), Repository.class); + } + + private CompletableFuture getContributors(Repository repository) throws InterruptedException { + return requestAsync("%s/stats/contributors".formatted(repository.getUrl()), ContributorStat[].class, true); + } + + private CompletableFuture getCommitActivity(Repository repository) throws InterruptedException { + return requestAsync("%s/stats/commit_activity".formatted(repository.getUrl()), CommitActivity[].class, true); + } + + private CompletableFuture requestAsync(String path, Class responseType) throws InterruptedException { + return requestAsync(path, responseType, false); + } + + private CompletableFuture requestAsync(String path, Class responseType, boolean isUri) throws InterruptedException { + var res = sendRequestAsync(path, responseType, isUri); + + var remaining = Integer.parseInt(res.join().headers().firstValue("X-RateLimit-Remaining").orElse("0")); + if (remaining == 0) { + long delay = Math.max(0, Long.parseLong(res.join().headers().firstValue("X-RateLimit-Reset").orElse("0")) - System.currentTimeMillis()); + + log.info("Rate limit exceeded, waiting for %s ms".formatted(delay)); + Thread.sleep(delay); + + return requestAsync(path, responseType, isUri); + } + + if (res.join().statusCode() == 202) { + Thread.sleep(10); + return requestAsync(path, responseType, isUri); + } + + return res.thenApply(HttpResponse::body); + } + + private CompletableFuture> sendRequestAsync(String pathOrUri, Class responseType) { + return sendRequestAsync(pathOrUri, responseType, false); + } + + private CompletableFuture> sendRequestAsync(String pathOrUri, Class responseType, boolean isUri) { + String url = isUri ? pathOrUri : String.format("%s/%s", baseUrl, pathOrUri); + var requestBuilder = HttpRequest.newBuilder() + .uri(URI.create(url)) + .header("Accept", "application/vnd.github.v3+json") + .timeout(Duration.ofSeconds(30)); + + if (GITHUB_TOKEN != null && !GITHUB_TOKEN.isEmpty()) { + requestBuilder.header("Authorization", "Bearer %s".formatted(GITHUB_TOKEN)); + } + + var request = requestBuilder.build(); + + var json = JSON.builder() + .register(JacksonAnnotationExtension.std) + .build(); + + return httpClient.sendAsync(request, new JsonBodyHandler<>(json, responseType)); + } + + private static List extractCommits(CommitActivity[] commitActivity) { + List points = Arrays.stream(commitActivity) + .map(entry -> Point.builder() + .date(Instant.ofEpochSecond(entry.getWeek()).atZone(ZoneOffset.UTC).toOffsetDateTime()) + .total(entry.getTotal()) + .build()) + .toList(); + + var ranges = pointsToRanges(points, bucketsFromBreakpoints(List.of(7, 30, 90, 180, 365))); + + return ranges.stream() + .map(range -> { + int count = range.getPoints().stream() + .mapToInt(Point::getTotal) + .sum(); + + return RangeSummary.builder() + .start(range.getStart()) + .end(range.getEnd()) + .count(count) + .build(); + }) + .toList(); + } + + private static List pointsToRanges(List points, List buckets) { + return buckets.stream().map(bucket -> { + List filteredPoints = points.stream() + .filter(point -> !point.getDate().isBefore(bucket.getStart()) && point.getDate().isBefore(bucket.getEnd())) + .collect(Collectors.toList()); + + return Range.builder() + .start(bucket.getStart()) + .end(bucket.getEnd()) + .points(filteredPoints) + .build(); + }).collect(Collectors.toList()); + } + + private static List bucketsFromBreakpoints(List breakpoints) { + OffsetDateTime referenceDate = OffsetDateTime.now(ZoneOffset.UTC).toLocalDate().atStartOfDay().atOffset(ZoneOffset.UTC); + + return breakpoints.stream() + .map(breakpoint -> Bucket.builder() + .start(referenceDate.minusDays(breakpoint)) + .end(referenceDate) + .build()) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/giovds/collector/github/GithubCollectorInterface.java b/src/main/java/com/giovds/collector/github/GithubCollectorInterface.java new file mode 100644 index 0000000..b49a315 --- /dev/null +++ b/src/main/java/com/giovds/collector/github/GithubCollectorInterface.java @@ -0,0 +1,9 @@ +package com.giovds.collector.github; + +import com.giovds.dto.github.internal.Collected; + +import java.util.concurrent.ExecutionException; + +public interface GithubCollectorInterface { + Collected collect(String owner, String repo) throws ExecutionException, InterruptedException; +} diff --git a/src/main/java/com/giovds/collector/github/GithubGuesser.java b/src/main/java/com/giovds/collector/github/GithubGuesser.java new file mode 100644 index 0000000..f2cf5cb --- /dev/null +++ b/src/main/java/com/giovds/collector/github/GithubGuesser.java @@ -0,0 +1,20 @@ +package com.giovds.collector.github; + +import java.util.regex.Pattern; + +public class GithubGuesser { + + private final Pattern githubRepoPattern = Pattern.compile("^[a-zA-Z]+://github\\.com/([^/]+)/([^/]+)(/.*)?"); + + public Repository guess(String url) { + var matcher = githubRepoPattern.matcher(url); + if (matcher.matches()) { + return new Repository(matcher.group(1), matcher.group(2)); + } + + return null; + } + + public record Repository(String owner, String repo) { + } +} diff --git a/src/main/java/com/giovds/dto/PomResponse.java b/src/main/java/com/giovds/dto/PomResponse.java new file mode 100644 index 0000000..f85baa7 --- /dev/null +++ b/src/main/java/com/giovds/dto/PomResponse.java @@ -0,0 +1,11 @@ +package com.giovds.dto; + +public record PomResponse(String url, Scm scm) { + public static PomResponse empty() { + return new PomResponse(null, Scm.empty()); + } + + public String scmUrl() { + return scm.url(); + } +} diff --git a/src/main/java/com/giovds/dto/Scm.java b/src/main/java/com/giovds/dto/Scm.java new file mode 100644 index 0000000..f331b1a --- /dev/null +++ b/src/main/java/com/giovds/dto/Scm.java @@ -0,0 +1,7 @@ +package com.giovds.dto; + +public record Scm(String url) { + public static Scm empty() { + return new Scm(null); + } +} diff --git a/src/main/java/com/giovds/dto/github/extenal/Author.java b/src/main/java/com/giovds/dto/github/extenal/Author.java new file mode 100644 index 0000000..371b5f9 --- /dev/null +++ b/src/main/java/com/giovds/dto/github/extenal/Author.java @@ -0,0 +1,14 @@ +package com.giovds.dto.github.extenal; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class Author { + @JsonProperty("login") + private String login = ""; + + public String getLogin() { + return login; + } +} diff --git a/src/main/java/com/giovds/dto/github/extenal/CommitActivity.java b/src/main/java/com/giovds/dto/github/extenal/CommitActivity.java new file mode 100644 index 0000000..2b41189 --- /dev/null +++ b/src/main/java/com/giovds/dto/github/extenal/CommitActivity.java @@ -0,0 +1,47 @@ +package com.giovds.dto.github.extenal; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class CommitActivity { + /** + * The total number of commits for the week. + */ + @JsonProperty("total") + private int total; + + /** + * The start of the week as a UNIX timestamp. + */ + @JsonProperty("week") + private long week; + + /** + * The number of commits for each day of the week. 0 = Sunday, 1 = Monday, etc. + */ + @JsonProperty("days") + private final List days = List.of(); + + public int getTotal() { + return total; + } + + public void setTotal(int total) { + this.total = total; + } + + public long getWeek() { + return week; + } + + public void setWeek(long week) { + this.week = week; + } + + public List getDays() { + return days; + } +} diff --git a/src/main/java/com/giovds/dto/github/extenal/ContributorStat.java b/src/main/java/com/giovds/dto/github/extenal/ContributorStat.java new file mode 100644 index 0000000..e233ae4 --- /dev/null +++ b/src/main/java/com/giovds/dto/github/extenal/ContributorStat.java @@ -0,0 +1,30 @@ +package com.giovds.dto.github.extenal; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class ContributorStat { + + @JsonProperty("total") + private int total; + + @JsonProperty("author") + private Author author = new Author(); + + public int getTotal() { + return total; + } + + public void setTotal(int total) { + this.total = total; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } +} diff --git a/src/main/java/com/giovds/dto/github/extenal/IssueStats.java b/src/main/java/com/giovds/dto/github/extenal/IssueStats.java new file mode 100644 index 0000000..1850731 --- /dev/null +++ b/src/main/java/com/giovds/dto/github/extenal/IssueStats.java @@ -0,0 +1,22 @@ +package com.giovds.dto.github.extenal; + +public class IssueStats { + private int count; + private int openCount; + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + public int getOpenCount() { + return openCount; + } + + public void setOpenCount(int openCount) { + this.openCount = openCount; + } +} diff --git a/src/main/java/com/giovds/dto/github/extenal/Owner.java b/src/main/java/com/giovds/dto/github/extenal/Owner.java new file mode 100644 index 0000000..2353106 --- /dev/null +++ b/src/main/java/com/giovds/dto/github/extenal/Owner.java @@ -0,0 +1,240 @@ +package com.giovds.dto.github.extenal; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.time.OffsetDateTime; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class Owner { + @JsonProperty("name") + private String name; + + @JsonProperty("email") + private String email; + + @JsonProperty("login") + private String login = ""; + + @JsonProperty("id") + private long id; + + @JsonProperty("node_id") + private String nodeId = ""; + + @JsonProperty("avatar_url") + private String avatarUrl = ""; + + @JsonProperty("gravatar_id") + private String gravatarId; + + @JsonProperty("url") + private String url = ""; + + @JsonProperty("html_url") + private String htmlUrl = ""; + + @JsonProperty("followers_url") + private String followersUrl = ""; + + @JsonProperty("following_url") + private String followingUrl = ""; + + @JsonProperty("gists_url") + private String gistsUrl = ""; + + @JsonProperty("starred_url") + private String starredUrl = ""; + + @JsonProperty("subscriptions_url") + private String subscriptionsUrl = ""; + + @JsonProperty("organizations_url") + private String organizationsUrl = ""; + + @JsonProperty("repos_url") + private String reposUrl = ""; + + @JsonProperty("events_url") + private String eventsUrl = ""; + + @JsonProperty("received_events_url") + private String receivedEventsUrl = ""; + + @JsonProperty("type") + private String type = ""; + + @JsonProperty("site_admin") + private boolean siteAdmin; + + @JsonProperty("starred_at") + private OffsetDateTime starredAt; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getNodeId() { + return nodeId; + } + + public void setNodeId(String nodeId) { + this.nodeId = nodeId; + } + + public String getAvatarUrl() { + return avatarUrl; + } + + public void setAvatarUrl(String avatarUrl) { + this.avatarUrl = avatarUrl; + } + + public String getGravatarId() { + return gravatarId; + } + + public void setGravatarId(String gravatarId) { + this.gravatarId = gravatarId; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getHtmlUrl() { + return htmlUrl; + } + + public void setHtmlUrl(String htmlUrl) { + this.htmlUrl = htmlUrl; + } + + public String getFollowersUrl() { + return followersUrl; + } + + public void setFollowersUrl(String followersUrl) { + this.followersUrl = followersUrl; + } + + public String getFollowingUrl() { + return followingUrl; + } + + public void setFollowingUrl(String followingUrl) { + this.followingUrl = followingUrl; + } + + public String getGistsUrl() { + return gistsUrl; + } + + public void setGistsUrl(String gistsUrl) { + this.gistsUrl = gistsUrl; + } + + public String getStarredUrl() { + return starredUrl; + } + + public void setStarredUrl(String starredUrl) { + this.starredUrl = starredUrl; + } + + public String getSubscriptionsUrl() { + return subscriptionsUrl; + } + + public void setSubscriptionsUrl(String subscriptionsUrl) { + this.subscriptionsUrl = subscriptionsUrl; + } + + public String getOrganizationsUrl() { + return organizationsUrl; + } + + public void setOrganizationsUrl(String organizationsUrl) { + this.organizationsUrl = organizationsUrl; + } + + public String getReposUrl() { + return reposUrl; + } + + public void setReposUrl(String reposUrl) { + this.reposUrl = reposUrl; + } + + public String getEventsUrl() { + return eventsUrl; + } + + public void setEventsUrl(String eventsUrl) { + this.eventsUrl = eventsUrl; + } + + public String getReceivedEventsUrl() { + return receivedEventsUrl; + } + + public void setReceivedEventsUrl(String receivedEventsUrl) { + this.receivedEventsUrl = receivedEventsUrl; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public boolean isSiteAdmin() { + return siteAdmin; + } + + public void setSiteAdmin(boolean siteAdmin) { + this.siteAdmin = siteAdmin; + } + + public OffsetDateTime getStarredAt() { + return starredAt; + } + + public void setStarredAt(OffsetDateTime starredAt) { + this.starredAt = starredAt; + } +} diff --git a/src/main/java/com/giovds/dto/github/extenal/Repository.java b/src/main/java/com/giovds/dto/github/extenal/Repository.java new file mode 100644 index 0000000..19ffd44 --- /dev/null +++ b/src/main/java/com/giovds/dto/github/extenal/Repository.java @@ -0,0 +1,151 @@ +package com.giovds.dto.github.extenal; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class Repository { + @JsonProperty("contributors_url") + private String contributorsUrl = ""; + + @JsonProperty("forks_count") + private int forksCount; + + @JsonProperty("full_name") + private String fullName = ""; + + @JsonProperty("has_issues") + private boolean hasIssues; + + @JsonProperty("homepage") + private String homepage; + + @JsonProperty("id") + private long id; + + @JsonProperty("node_id") + private String nodeId = ""; + + @JsonProperty("name") + private String name = ""; + + @JsonProperty("owner") + private Owner owner = new Owner(); + + @JsonProperty("private") + private boolean _private; + + @JsonProperty("stargazers_count") + private int stargazersCount; + + @JsonProperty("subscribers_count") + private int subscribersCount; + + @JsonProperty("url") + private String url = ""; + + // Getters and Setters + public String getContributorsUrl() { + return contributorsUrl; + } + + public void setContributorsUrl(String contributorsUrl) { + this.contributorsUrl = contributorsUrl; + } + + public int getForksCount() { + return forksCount; + } + + public void setForksCount(int forksCount) { + this.forksCount = forksCount; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public boolean isHasIssues() { + return hasIssues; + } + + public void setHasIssues(boolean hasIssues) { + this.hasIssues = hasIssues; + } + + public String getHomepage() { + return homepage; + } + + public void setHomepage(String homepage) { + this.homepage = homepage; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getNodeId() { + return nodeId; + } + + public void setNodeId(String nodeId) { + this.nodeId = nodeId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Owner getOwner() { + return owner; + } + + public void setOwner(Owner owner) { + this.owner = owner; + } + + public boolean isPrivate() { + return _private; + } + + public void setPrivate(boolean _private) { + this._private = _private; + } + + public int getStargazersCount() { + return stargazersCount; + } + + public void setStargazersCount(int stargazersCount) { + this.stargazersCount = stargazersCount; + } + + public int getSubscribersCount() { + return subscribersCount; + } + + public void setSubscribersCount(int subscribersCount) { + this.subscribersCount = subscribersCount; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } +} diff --git a/src/main/java/com/giovds/dto/github/internal/Bucket.java b/src/main/java/com/giovds/dto/github/internal/Bucket.java new file mode 100644 index 0000000..f1a09a4 --- /dev/null +++ b/src/main/java/com/giovds/dto/github/internal/Bucket.java @@ -0,0 +1,67 @@ +package com.giovds.dto.github.internal; + +import java.time.OffsetDateTime; + +public class Bucket { + private OffsetDateTime start; + private OffsetDateTime end; + + public OffsetDateTime getStart() { + return start; + } + + public void setStart(OffsetDateTime start) { + this.start = start; + } + + public OffsetDateTime getEnd() { + return end; + } + + public void setEnd(OffsetDateTime end) { + this.end = end; + } + + @Override + public String toString() { + return "Bucket{" + + "start=" + start + + ", end=" + end + + '}'; + } + + public static BucketBuilder builder() { + return new BucketBuilder(); + } + + public static class BucketBuilder { + private OffsetDateTime start; + private OffsetDateTime end; + + BucketBuilder() { + } + + public BucketBuilder start(OffsetDateTime start) { + this.start = start; + return this; + } + + public BucketBuilder end(OffsetDateTime end) { + this.end = end; + return this; + } + + public Bucket build() { + return new Bucket(start, end); + } + + public String toString() { + return "Bucket.BucketBuilder(start=" + this.start + ", end=" + this.end + ")"; + } + } + + public Bucket(OffsetDateTime start, OffsetDateTime end) { + this.start = start; + this.end = end; + } +} diff --git a/src/main/java/com/giovds/dto/github/internal/Collected.java b/src/main/java/com/giovds/dto/github/internal/Collected.java new file mode 100644 index 0000000..b1eac38 --- /dev/null +++ b/src/main/java/com/giovds/dto/github/internal/Collected.java @@ -0,0 +1,159 @@ +package com.giovds.dto.github.internal; + +import java.util.List; + +public class Collected { + private String homepage; + private int starsCount; + private int forksCount; + private int subscribersCount; + private int issues; + private List contributors; + private List commits; + + public String getHomepage() { + return homepage; + } + + public void setHomepage(String homepage) { + this.homepage = homepage; + } + + public int getStarsCount() { + return starsCount; + } + + public void setStarsCount(int starsCount) { + this.starsCount = starsCount; + } + + public int getForksCount() { + return forksCount; + } + + public void setForksCount(int forksCount) { + this.forksCount = forksCount; + } + + public int getSubscribersCount() { + return subscribersCount; + } + + public void setSubscribersCount(int subscribersCount) { + this.subscribersCount = subscribersCount; + } + + public int getIssues() { + return issues; + } + + public void setIssues(int issues) { + this.issues = issues; + } + + public List getContributors() { + return contributors; + } + + public void setContributors(List contributors) { + this.contributors = contributors; + } + + public List getCommits() { + return commits; + } + + public void setCommits(List commits) { + this.commits = commits; + } + + @Override + public String toString() { + return "Collected{" + + "homepage='" + homepage + '\'' + + ", starsCount=" + starsCount + + ", forksCount=" + forksCount + + ", subscribersCount=" + subscribersCount + + ", issues=" + issues + + ", contributors=" + contributors + + ", commits=" + commits + + '}'; + } + + public static CollectedBuilder builder() { + return new CollectedBuilder(); + } + + public static class CollectedBuilder { + private String homepage; + private int starsCount; + private int forksCount; + private int subscribersCount; + private int issues; + private List contributors; + private List commits; + + CollectedBuilder() { + } + + public CollectedBuilder homepage(String homepage) { + this.homepage = homepage; + return this; + } + + public CollectedBuilder starsCount(int starsCount) { + this.starsCount = starsCount; + return this; + } + + public CollectedBuilder forksCount(int forksCount) { + this.forksCount = forksCount; + return this; + } + + public CollectedBuilder subscribersCount(int subscribersCount) { + this.subscribersCount = subscribersCount; + return this; + } + + public CollectedBuilder issues(int issues) { + this.issues = issues; + return this; + } + + public CollectedBuilder contributors(List contributors) { + this.contributors = contributors; + return this; + } + + public CollectedBuilder commits(List commits) { + this.commits = commits; + return this; + } + + public Collected build() { + Collected collected = new Collected(); + collected.setHomepage(this.homepage); + collected.setStarsCount(this.starsCount); + collected.setForksCount(this.forksCount); + collected.setSubscribersCount(this.subscribersCount); + collected.setIssues(this.issues); + collected.setContributors(this.contributors); + collected.setCommits(this.commits); + return collected; + } + + @Override + public String toString() { + return "Collected.CollectedBuilder{" + + "homepage='" + homepage + '\'' + + ", starsCount=" + starsCount + + ", forksCount=" + forksCount + + ", subscribersCount=" + subscribersCount + + ", issues=" + issues + + ", contributors=" + contributors + + ", commits=" + commits + + '}'; + } + } +} diff --git a/src/main/java/com/giovds/dto/github/internal/Contributor.java b/src/main/java/com/giovds/dto/github/internal/Contributor.java new file mode 100644 index 0000000..1557f6c --- /dev/null +++ b/src/main/java/com/giovds/dto/github/internal/Contributor.java @@ -0,0 +1,67 @@ +package com.giovds.dto.github.internal; + +public class Contributor { + private String username; + private int commitsCount; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public int getCommitsCount() { + return commitsCount; + } + + public void setCommitsCount(int commitsCount) { + this.commitsCount = commitsCount; + } + + @Override + public String toString() { + return "Contributor{" + + "username='" + username + '\'' + + ", commitsCount=" + commitsCount + + '}'; + } + + public static ContributorBuilder builder() { + return new ContributorBuilder(); + } + + public static class ContributorBuilder { + private String username; + private int commitsCount; + + ContributorBuilder() { + } + + public ContributorBuilder username(String username) { + this.username = username; + return this; + } + + public ContributorBuilder commitsCount(int commitsCount) { + this.commitsCount = commitsCount; + return this; + } + + public Contributor build() { + Contributor contributor = new Contributor(); + contributor.setUsername(this.username); + contributor.setCommitsCount(this.commitsCount); + return contributor; + } + + @Override + public String toString() { + return "Contributor.ContributorBuilder{" + + "username='" + username + '\'' + + ", commitsCount=" + commitsCount + + '}'; + } + } +} diff --git a/src/main/java/com/giovds/dto/github/internal/Point.java b/src/main/java/com/giovds/dto/github/internal/Point.java new file mode 100644 index 0000000..253e034 --- /dev/null +++ b/src/main/java/com/giovds/dto/github/internal/Point.java @@ -0,0 +1,69 @@ +package com.giovds.dto.github.internal; + +import java.time.OffsetDateTime; + +public class Point { + private OffsetDateTime date; + private int total; + + public OffsetDateTime getDate() { + return date; + } + + public void setDate(OffsetDateTime date) { + this.date = date; + } + + public int getTotal() { + return total; + } + + public void setTotal(int total) { + this.total = total; + } + + @Override + public String toString() { + return "Point{" + + "date=" + date + + ", total=" + total + + '}'; + } + + public static PointBuilder builder() { + return new PointBuilder(); + } + + public static class PointBuilder { + private OffsetDateTime date; + private int total; + + PointBuilder() { + } + + public PointBuilder date(OffsetDateTime date) { + this.date = date; + return this; + } + + public PointBuilder total(int total) { + this.total = total; + return this; + } + + public Point build() { + Point point = new Point(); + point.setDate(this.date); + point.setTotal(this.total); + return point; + } + + @Override + public String toString() { + return "Point.PointBuilder{" + + "date=" + date + + ", total=" + total + + '}'; + } + } +} diff --git a/src/main/java/com/giovds/dto/github/internal/Range.java b/src/main/java/com/giovds/dto/github/internal/Range.java new file mode 100644 index 0000000..bf4ad94 --- /dev/null +++ b/src/main/java/com/giovds/dto/github/internal/Range.java @@ -0,0 +1,88 @@ +package com.giovds.dto.github.internal; + +import java.time.OffsetDateTime; +import java.util.List; + +public class Range { + private OffsetDateTime start; + private OffsetDateTime end; + private List points; + + public OffsetDateTime getStart() { + return start; + } + + public void setStart(OffsetDateTime start) { + this.start = start; + } + + public OffsetDateTime getEnd() { + return end; + } + + public void setEnd(OffsetDateTime end) { + this.end = end; + } + + public List getPoints() { + return points; + } + + public void setPoints(List points) { + this.points = points; + } + + @Override + public String toString() { + return "Range{" + + "start=" + start + + ", end=" + end + + ", points=" + points + + '}'; + } + + public static RangeBuilder builder() { + return new RangeBuilder(); + } + + public static class RangeBuilder { + private OffsetDateTime start; + private OffsetDateTime end; + private List points; + + RangeBuilder() { + } + + public RangeBuilder start(OffsetDateTime start) { + this.start = start; + return this; + } + + public RangeBuilder end(OffsetDateTime end) { + this.end = end; + return this; + } + + public RangeBuilder points(List points) { + this.points = points; + return this; + } + + public Range build() { + Range range = new Range(); + range.setStart(this.start); + range.setEnd(this.end); + range.setPoints(this.points); + return range; + } + + @Override + public String toString() { + return "Range.RangeBuilder{" + + "start=" + start + + ", end=" + end + + ", points=" + points + + '}'; + } + } +} diff --git a/src/main/java/com/giovds/dto/github/internal/RangeSummary.java b/src/main/java/com/giovds/dto/github/internal/RangeSummary.java new file mode 100644 index 0000000..ce45ca0 --- /dev/null +++ b/src/main/java/com/giovds/dto/github/internal/RangeSummary.java @@ -0,0 +1,87 @@ +package com.giovds.dto.github.internal; + +import java.time.OffsetDateTime; + +public class RangeSummary { + private OffsetDateTime start; + private OffsetDateTime end; + private int count; + + public OffsetDateTime getStart() { + return start; + } + + public void setStart(OffsetDateTime start) { + this.start = start; + } + + public OffsetDateTime getEnd() { + return end; + } + + public void setEnd(OffsetDateTime end) { + this.end = end; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + @Override + public String toString() { + return "RangeSummary{" + + "start=" + start + + ", end=" + end + + ", count=" + count + + '}'; + } + + public static RangeSummaryBuilder builder() { + return new RangeSummaryBuilder(); + } + + public static class RangeSummaryBuilder { + private OffsetDateTime start; + private OffsetDateTime end; + private int count; + + RangeSummaryBuilder() { + } + + public RangeSummaryBuilder start(OffsetDateTime start) { + this.start = start; + return this; + } + + public RangeSummaryBuilder end(OffsetDateTime end) { + this.end = end; + return this; + } + + public RangeSummaryBuilder count(int count) { + this.count = count; + return this; + } + + public RangeSummary build() { + RangeSummary rangeSummary = new RangeSummary(); + rangeSummary.setStart(this.start); + rangeSummary.setEnd(this.end); + rangeSummary.setCount(this.count); + return rangeSummary; + } + + @Override + public String toString() { + return "RangeSummary.RangeSummaryBuilder{" + + "start=" + start + + ", end=" + end + + ", count=" + count + + '}'; + } + } +} diff --git a/src/main/java/com/giovds/evaluator/MaintenanceEvaluator.java b/src/main/java/com/giovds/evaluator/MaintenanceEvaluator.java new file mode 100644 index 0000000..d84c764 --- /dev/null +++ b/src/main/java/com/giovds/evaluator/MaintenanceEvaluator.java @@ -0,0 +1,46 @@ +package com.giovds.evaluator; + +import com.giovds.dto.github.internal.Collected; +import com.giovds.dto.github.internal.RangeSummary; +import com.giovds.normalizer.DefaultNormalizer; + +import java.time.Duration; +import java.util.List; + +public class MaintenanceEvaluator { + public double evaluateCommitsFrequency(Collected collected) { + var commits = collected.getCommits(); + if (commits.isEmpty()) { + return 0; + } + + var range30 = findRange(commits, 30); + var range180 = findRange(commits, 180); + var range365 = findRange(commits, 365); + + var mean30 = range30.getCount(); + var mean180 = range180.getCount() / (180.0d / 30.0d); + var mean365 = range365.getCount() / (365.0d / 30.0d); + + var monthlyMean = (mean30 * 0.35d) + + (mean180 * 0.45d) + + (mean365 * 0.2d); + + var normalizer = new DefaultNormalizer(); + + return normalizer.normalizeValue(monthlyMean, List.of( + new DefaultNormalizer.NormalizeStep(0d, 0d), + new DefaultNormalizer.NormalizeStep(1d, 0.7d), + new DefaultNormalizer.NormalizeStep(5d, 0.9d), + new DefaultNormalizer.NormalizeStep(10d, 1d) + )); + } + + private RangeSummary findRange(List commits, int days) { + return commits.stream() + .filter(range -> Duration.between(range.getStart(), range.getEnd()).toDays() == days) + .limit(1) + .toList() + .getFirst(); + } +} diff --git a/src/main/java/com/giovds/http/JsonBodyHandler.java b/src/main/java/com/giovds/http/JsonBodyHandler.java new file mode 100644 index 0000000..8d7b40b --- /dev/null +++ b/src/main/java/com/giovds/http/JsonBodyHandler.java @@ -0,0 +1,44 @@ +package com.giovds.http; + +import com.fasterxml.jackson.jr.ob.JSON; + +import java.io.IOException; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; + +public class JsonBodyHandler implements HttpResponse.BodyHandler { + private final JSON json; + private final Class resultClass; + + public JsonBodyHandler(JSON json, Class resultClass) { + this.json = json; + this.resultClass = resultClass; + } + + @Override + public HttpResponse.BodySubscriber apply(HttpResponse.ResponseInfo res) { + if (res.statusCode() == 202) { + return HttpResponse.BodySubscribers.replacing(null); + } + + var remaining = res.headers().firstValue("X-RateLimit-Remaining").orElse("0"); + if ("0".equals(remaining)) { + return HttpResponse.BodySubscribers.replacing(null); + } + + return asJSON(res, resultClass); + } + + public HttpResponse.BodySubscriber asJSON(HttpResponse.ResponseInfo res, Class targetType) { + HttpResponse.BodySubscriber upstream = HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8); + return HttpResponse.BodySubscribers.mapping( + upstream, + (String body) -> { + try { + return json.beanFrom(targetType, body); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } +} diff --git a/src/main/java/com/giovds/normalizer/DefaultNormalizer.java b/src/main/java/com/giovds/normalizer/DefaultNormalizer.java new file mode 100644 index 0000000..60e4aea --- /dev/null +++ b/src/main/java/com/giovds/normalizer/DefaultNormalizer.java @@ -0,0 +1,40 @@ +package com.giovds.normalizer; + +import java.util.List; +import java.util.function.Predicate; + +public class DefaultNormalizer { + public double normalizeValue(double value, List steps) { + var index = findLastIndex(steps, step -> step.value() <= value); + + if (index == -1) { + return steps.getFirst().norm(); + } + if (index == steps.size() - 1) { + return steps.getLast().norm(); + } + + var stepLow = steps.get(index); + var stepHigh = steps.get(index + 1); + + return stepLow.norm() + ((stepHigh.norm - stepLow.norm) * (value - stepLow.value)) / (stepHigh.value - stepLow.value); + } + + private static int findLastIndex(List list, Predicate predicate) { + List reversed = list.reversed(); + int reverseIdx = -1; + + for (int i = 0; i < reversed.size(); i++) { + if (predicate.test(reversed.get(i))) { + reverseIdx = i; + break; + + } + } + + return reverseIdx == -1 ? -1 : reversed.size() - (reverseIdx + 1); + } + + public record NormalizeStep(double value, double norm) { + } +} diff --git a/src/test/java/com/giovds/evaluator/MaintenanceEvaluatorTest.java b/src/test/java/com/giovds/evaluator/MaintenanceEvaluatorTest.java new file mode 100644 index 0000000..c5b0b92 --- /dev/null +++ b/src/test/java/com/giovds/evaluator/MaintenanceEvaluatorTest.java @@ -0,0 +1,95 @@ +package com.giovds.evaluator; + +import com.giovds.dto.github.internal.Collected; +import com.giovds.dto.github.internal.RangeSummary; +import org.junit.jupiter.api.Test; + +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class MaintenanceEvaluatorTest { + @Test + void evaluateCommitsFrequency_withLowMaintenance_expectLowScore() { + // Arrange + var evaluator = new MaintenanceEvaluator(); + var collected = Collected.builder() + .commits(List.of( + RangeSummary.builder() + .start(OffsetDateTime.of(2024, 11, 15, 0, 0, 0, 0, ZoneOffset.UTC)) + .end(OffsetDateTime.of(2024, 11, 22, 0, 0, 0, 0, ZoneOffset.UTC)) + .count(0) + .build(), + RangeSummary.builder() + .start(OffsetDateTime.of(2024, 10, 23, 0, 0, 0, 0, ZoneOffset.UTC)) + .end(OffsetDateTime.of(2024, 11, 22, 0, 0, 0, 0, ZoneOffset.UTC)) + .count(0) + .build(), + RangeSummary.builder() + .start(OffsetDateTime.of(2024, 8, 24, 0, 0, 0, 0, ZoneOffset.UTC)) + .end(OffsetDateTime.of(2024, 11, 22, 0, 0, 0, 0, ZoneOffset.UTC)) + .count(0) + .build(), + RangeSummary.builder() + .start(OffsetDateTime.of(2024, 5, 26, 0, 0, 0, 0, ZoneOffset.UTC)) + .end(OffsetDateTime.of(2024, 11, 22, 0, 0, 0, 0, ZoneOffset.UTC)) + .count(0) + .build(), + RangeSummary.builder() + .start(OffsetDateTime.of(2023, 11, 23, 0, 0, 0, 0, ZoneOffset.UTC)) + .end(OffsetDateTime.of(2024, 11, 22, 0, 0, 0, 0, ZoneOffset.UTC)) + .count(3) + .build() + )) + .build(); + + // Act + var result = evaluator.evaluateCommitsFrequency(collected); + + // Assert + assertThat(result).isEqualTo(0.03452054794520548); + } + + @Test + void evaluateCommitsFrequency_withHighMaintenance_expectHighScore() { + // Arrange + var evaluator = new MaintenanceEvaluator(); + var collected = Collected.builder() + .commits(List.of( + RangeSummary.builder() + .start(OffsetDateTime.of(2024, 11, 15, 0, 0, 0, 0, ZoneOffset.UTC)) + .end(OffsetDateTime.of(2024, 11, 22, 0, 0, 0, 0, ZoneOffset.UTC)) + .count(0) + .build(), + RangeSummary.builder() + .start(OffsetDateTime.of(2024, 10, 23, 0, 0, 0, 0, ZoneOffset.UTC)) + .end(OffsetDateTime.of(2024, 11, 22, 0, 0, 0, 0, ZoneOffset.UTC)) + .count(6) + .build(), + RangeSummary.builder() + .start(OffsetDateTime.of(2024, 8, 24, 0, 0, 0, 0, ZoneOffset.UTC)) + .end(OffsetDateTime.of(2024, 11, 22, 0, 0, 0, 0, ZoneOffset.UTC)) + .count(36) + .build(), + RangeSummary.builder() + .start(OffsetDateTime.of(2024, 5, 26, 0, 0, 0, 0, ZoneOffset.UTC)) + .end(OffsetDateTime.of(2024, 11, 22, 0, 0, 0, 0, ZoneOffset.UTC)) + .count(79) + .build(), + RangeSummary.builder() + .start(OffsetDateTime.of(2023, 11, 23, 0, 0, 0, 0, ZoneOffset.UTC)) + .end(OffsetDateTime.of(2024, 11, 22, 0, 0, 0, 0, ZoneOffset.UTC)) + .count(79) + .build() + )) + .build(); + + // Act + var result = evaluator.evaluateCommitsFrequency(collected); + + // Assert + assertThat(result).isEqualTo(0.986472602739726); + } +} diff --git a/src/test/java/com/giovds/normalizer/DefaultNormalizerTest.java b/src/test/java/com/giovds/normalizer/DefaultNormalizerTest.java new file mode 100644 index 0000000..09e5b44 --- /dev/null +++ b/src/test/java/com/giovds/normalizer/DefaultNormalizerTest.java @@ -0,0 +1,40 @@ +package com.giovds.normalizer; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class DefaultNormalizerTest { + private DefaultNormalizer normalizer; + + @BeforeEach + void setUp() { + normalizer = new DefaultNormalizer(); + } + + @Test + void normalizeValue_withLowMaintenance_expectLowValueGreaterThanZero() { + var value = 0.04931506849315069d; + + assertThat(normalizer.normalizeValue(value, createDefaultSteps())).isEqualTo(0.03452054794520548d); + } + + @Test + void normalizeValue_withHighMaintenance_expectHighValueLowerThanOne() { + var value = 9.3236301369863d; + + assertThat(normalizer.normalizeValue(value, createDefaultSteps())).isEqualTo(0.986472602739726d); + } + + private List createDefaultSteps() { + return List.of( + new DefaultNormalizer.NormalizeStep(0, 0), + new DefaultNormalizer.NormalizeStep(1, 0.7), + new DefaultNormalizer.NormalizeStep(5, 0.9), + new DefaultNormalizer.NormalizeStep(10, 1) + ); + } +} diff --git a/src/test/java/com/giovds/poc/github/GithubGuesserTest.java b/src/test/java/com/giovds/poc/github/GithubGuesserTest.java new file mode 100644 index 0000000..678449c --- /dev/null +++ b/src/test/java/com/giovds/poc/github/GithubGuesserTest.java @@ -0,0 +1,34 @@ +package com.giovds.poc.github; + +import com.giovds.collector.github.GithubGuesser; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class GithubGuesserTest { + @Test + public void non_github_url_returns_null() { + GithubGuesser githubGuesser = new GithubGuesser(); + assertNull(githubGuesser.guess("https://gitlab.com/owner/repo")); + } + + @Test + public void github_url_returns_owner_and_repository() { + GithubGuesser githubGuesser = new GithubGuesser(); + GithubGuesser.Repository repository = githubGuesser.guess("https://github.com/Giovds/outdated-maven-plugin"); + + assertNotNull(repository); + assertEquals("Giovds", repository.owner()); + assertEquals("outdated-maven-plugin", repository.repo()); + } + + @Test + public void github_url_additional_slash_returns_owner_and_repository() { + GithubGuesser githubGuesser = new GithubGuesser(); + GithubGuesser.Repository repository = githubGuesser.guess("https://github.com/Giovds/outdated-maven-plugin/"); + + assertNotNull(repository); + assertEquals("Giovds", repository.owner()); + assertEquals("outdated-maven-plugin", repository.repo()); + } +} From a595721c7dd450f245d71ae4d61ffc0a068ba370 Mon Sep 17 00:00:00 2001 From: Juul Hobert Date: Mon, 25 Nov 2024 08:03:09 +0100 Subject: [PATCH 2/2] Replaced github dto classes with records --- .../collector/github/GithubCollector.java | 34 +-- .../com/giovds/dto/github/extenal/Author.java | 8 +- .../dto/github/extenal/CommitActivity.java | 43 +-- .../dto/github/extenal/ContributorStat.java | 27 +- .../giovds/dto/github/extenal/IssueStats.java | 20 +- .../com/giovds/dto/github/extenal/Owner.java | 254 ++---------------- .../giovds/dto/github/extenal/Repository.java | 159 ++--------- .../giovds/dto/github/internal/Bucket.java | 38 +-- .../giovds/dto/github/internal/Collected.java | 110 +------- .../dto/github/internal/Contributor.java | 42 +-- .../com/giovds/dto/github/internal/Point.java | 42 +-- .../com/giovds/dto/github/internal/Range.java | 54 +--- .../dto/github/internal/RangeSummary.java | 49 +--- .../evaluator/MaintenanceEvaluator.java | 10 +- 14 files changed, 94 insertions(+), 796 deletions(-) diff --git a/src/main/java/com/giovds/collector/github/GithubCollector.java b/src/main/java/com/giovds/collector/github/GithubCollector.java index 7569a9e..517d80f 100644 --- a/src/main/java/com/giovds/collector/github/GithubCollector.java +++ b/src/main/java/com/giovds/collector/github/GithubCollector.java @@ -56,14 +56,14 @@ public Collected collect(String owner, String repo) throws InterruptedException, var summary = extractCommits(commitActivity); return Collected.builder() - .homepage(repository.getHomepage()) - .starsCount(repository.getStargazersCount()) - .forksCount(repository.getForksCount()) - .subscribersCount(repository.getSubscribersCount()) + .homepage(repository.homepage()) + .starsCount(repository.stargazersCount()) + .forksCount(repository.forksCount()) + .subscribersCount(repository.subscribersCount()) .contributors(Arrays.stream(contributors).map( contributor -> Contributor.builder() - .username(contributor.getAuthor().getLogin()) - .commitsCount(contributor.getTotal()) + .username(contributor.author().login()) + .commitsCount(contributor.total()) .build() ).toList().reversed()) .commits(summary) @@ -75,11 +75,11 @@ private CompletableFuture getRepository(String owner, String repo) t } private CompletableFuture getContributors(Repository repository) throws InterruptedException { - return requestAsync("%s/stats/contributors".formatted(repository.getUrl()), ContributorStat[].class, true); + return requestAsync("%s/stats/contributors".formatted(repository.url()), ContributorStat[].class, true); } private CompletableFuture getCommitActivity(Repository repository) throws InterruptedException { - return requestAsync("%s/stats/commit_activity".formatted(repository.getUrl()), CommitActivity[].class, true); + return requestAsync("%s/stats/commit_activity".formatted(repository.url()), CommitActivity[].class, true); } private CompletableFuture requestAsync(String path, Class responseType) throws InterruptedException { @@ -134,8 +134,8 @@ private CompletableFuture> sendRequestAsync(String pathOrUri private static List extractCommits(CommitActivity[] commitActivity) { List points = Arrays.stream(commitActivity) .map(entry -> Point.builder() - .date(Instant.ofEpochSecond(entry.getWeek()).atZone(ZoneOffset.UTC).toOffsetDateTime()) - .total(entry.getTotal()) + .date(Instant.ofEpochSecond(entry.week()).atZone(ZoneOffset.UTC).toOffsetDateTime()) + .total(entry.total()) .build()) .toList(); @@ -143,13 +143,13 @@ private static List extractCommits(CommitActivity[] commitActivity return ranges.stream() .map(range -> { - int count = range.getPoints().stream() - .mapToInt(Point::getTotal) + int count = range.points().stream() + .mapToInt(Point::total) .sum(); return RangeSummary.builder() - .start(range.getStart()) - .end(range.getEnd()) + .start(range.start()) + .end(range.end()) .count(count) .build(); }) @@ -159,12 +159,12 @@ private static List extractCommits(CommitActivity[] commitActivity private static List pointsToRanges(List points, List buckets) { return buckets.stream().map(bucket -> { List filteredPoints = points.stream() - .filter(point -> !point.getDate().isBefore(bucket.getStart()) && point.getDate().isBefore(bucket.getEnd())) + .filter(point -> !point.date().isBefore(bucket.start()) && point.date().isBefore(bucket.end())) .collect(Collectors.toList()); return Range.builder() - .start(bucket.getStart()) - .end(bucket.getEnd()) + .start(bucket.start()) + .end(bucket.end()) .points(filteredPoints) .build(); }).collect(Collectors.toList()); diff --git a/src/main/java/com/giovds/dto/github/extenal/Author.java b/src/main/java/com/giovds/dto/github/extenal/Author.java index 371b5f9..679e385 100644 --- a/src/main/java/com/giovds/dto/github/extenal/Author.java +++ b/src/main/java/com/giovds/dto/github/extenal/Author.java @@ -4,11 +4,5 @@ import com.fasterxml.jackson.annotation.JsonProperty; @JsonIgnoreProperties(ignoreUnknown = true) -public class Author { - @JsonProperty("login") - private String login = ""; - - public String getLogin() { - return login; - } +public record Author(@JsonProperty("login") String login) { } diff --git a/src/main/java/com/giovds/dto/github/extenal/CommitActivity.java b/src/main/java/com/giovds/dto/github/extenal/CommitActivity.java index 2b41189..c7d5fca 100644 --- a/src/main/java/com/giovds/dto/github/extenal/CommitActivity.java +++ b/src/main/java/com/giovds/dto/github/extenal/CommitActivity.java @@ -6,42 +6,9 @@ import java.util.List; @JsonIgnoreProperties(ignoreUnknown = true) -public class CommitActivity { - /** - * The total number of commits for the week. - */ - @JsonProperty("total") - private int total; - - /** - * The start of the week as a UNIX timestamp. - */ - @JsonProperty("week") - private long week; - - /** - * The number of commits for each day of the week. 0 = Sunday, 1 = Monday, etc. - */ - @JsonProperty("days") - private final List days = List.of(); - - public int getTotal() { - return total; - } - - public void setTotal(int total) { - this.total = total; - } - - public long getWeek() { - return week; - } - - public void setWeek(long week) { - this.week = week; - } - - public List getDays() { - return days; - } +public record CommitActivity( + @JsonProperty("days") List days, + @JsonProperty("total") int total, + @JsonProperty("week") long week +) { } diff --git a/src/main/java/com/giovds/dto/github/extenal/ContributorStat.java b/src/main/java/com/giovds/dto/github/extenal/ContributorStat.java index e233ae4..5c1d64c 100644 --- a/src/main/java/com/giovds/dto/github/extenal/ContributorStat.java +++ b/src/main/java/com/giovds/dto/github/extenal/ContributorStat.java @@ -4,27 +4,8 @@ import com.fasterxml.jackson.annotation.JsonProperty; @JsonIgnoreProperties(ignoreUnknown = true) -public class ContributorStat { - - @JsonProperty("total") - private int total; - - @JsonProperty("author") - private Author author = new Author(); - - public int getTotal() { - return total; - } - - public void setTotal(int total) { - this.total = total; - } - - public Author getAuthor() { - return author; - } - - public void setAuthor(Author author) { - this.author = author; - } +public record ContributorStat( + @JsonProperty("author") Author author, + @JsonProperty("total") int total +) { } diff --git a/src/main/java/com/giovds/dto/github/extenal/IssueStats.java b/src/main/java/com/giovds/dto/github/extenal/IssueStats.java index 1850731..4398d97 100644 --- a/src/main/java/com/giovds/dto/github/extenal/IssueStats.java +++ b/src/main/java/com/giovds/dto/github/extenal/IssueStats.java @@ -1,22 +1,4 @@ package com.giovds.dto.github.extenal; -public class IssueStats { - private int count; - private int openCount; - - public int getCount() { - return count; - } - - public void setCount(int count) { - this.count = count; - } - - public int getOpenCount() { - return openCount; - } - - public void setOpenCount(int openCount) { - this.openCount = openCount; - } +public record IssueStats(int count, int openCount) { } diff --git a/src/main/java/com/giovds/dto/github/extenal/Owner.java b/src/main/java/com/giovds/dto/github/extenal/Owner.java index 2353106..462cb44 100644 --- a/src/main/java/com/giovds/dto/github/extenal/Owner.java +++ b/src/main/java/com/giovds/dto/github/extenal/Owner.java @@ -6,235 +6,27 @@ import java.time.OffsetDateTime; @JsonIgnoreProperties(ignoreUnknown = true) -public class Owner { - @JsonProperty("name") - private String name; - - @JsonProperty("email") - private String email; - - @JsonProperty("login") - private String login = ""; - - @JsonProperty("id") - private long id; - - @JsonProperty("node_id") - private String nodeId = ""; - - @JsonProperty("avatar_url") - private String avatarUrl = ""; - - @JsonProperty("gravatar_id") - private String gravatarId; - - @JsonProperty("url") - private String url = ""; - - @JsonProperty("html_url") - private String htmlUrl = ""; - - @JsonProperty("followers_url") - private String followersUrl = ""; - - @JsonProperty("following_url") - private String followingUrl = ""; - - @JsonProperty("gists_url") - private String gistsUrl = ""; - - @JsonProperty("starred_url") - private String starredUrl = ""; - - @JsonProperty("subscriptions_url") - private String subscriptionsUrl = ""; - - @JsonProperty("organizations_url") - private String organizationsUrl = ""; - - @JsonProperty("repos_url") - private String reposUrl = ""; - - @JsonProperty("events_url") - private String eventsUrl = ""; - - @JsonProperty("received_events_url") - private String receivedEventsUrl = ""; - - @JsonProperty("type") - private String type = ""; - - @JsonProperty("site_admin") - private boolean siteAdmin; - - @JsonProperty("starred_at") - private OffsetDateTime starredAt; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getLogin() { - return login; - } - - public void setLogin(String login) { - this.login = login; - } - - public long getId() { - return id; - } - - public void setId(long id) { - this.id = id; - } - - public String getNodeId() { - return nodeId; - } - - public void setNodeId(String nodeId) { - this.nodeId = nodeId; - } - - public String getAvatarUrl() { - return avatarUrl; - } - - public void setAvatarUrl(String avatarUrl) { - this.avatarUrl = avatarUrl; - } - - public String getGravatarId() { - return gravatarId; - } - - public void setGravatarId(String gravatarId) { - this.gravatarId = gravatarId; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getHtmlUrl() { - return htmlUrl; - } - - public void setHtmlUrl(String htmlUrl) { - this.htmlUrl = htmlUrl; - } - - public String getFollowersUrl() { - return followersUrl; - } - - public void setFollowersUrl(String followersUrl) { - this.followersUrl = followersUrl; - } - - public String getFollowingUrl() { - return followingUrl; - } - - public void setFollowingUrl(String followingUrl) { - this.followingUrl = followingUrl; - } - - public String getGistsUrl() { - return gistsUrl; - } - - public void setGistsUrl(String gistsUrl) { - this.gistsUrl = gistsUrl; - } - - public String getStarredUrl() { - return starredUrl; - } - - public void setStarredUrl(String starredUrl) { - this.starredUrl = starredUrl; - } - - public String getSubscriptionsUrl() { - return subscriptionsUrl; - } - - public void setSubscriptionsUrl(String subscriptionsUrl) { - this.subscriptionsUrl = subscriptionsUrl; - } - - public String getOrganizationsUrl() { - return organizationsUrl; - } - - public void setOrganizationsUrl(String organizationsUrl) { - this.organizationsUrl = organizationsUrl; - } - - public String getReposUrl() { - return reposUrl; - } - - public void setReposUrl(String reposUrl) { - this.reposUrl = reposUrl; - } - - public String getEventsUrl() { - return eventsUrl; - } - - public void setEventsUrl(String eventsUrl) { - this.eventsUrl = eventsUrl; - } - - public String getReceivedEventsUrl() { - return receivedEventsUrl; - } - - public void setReceivedEventsUrl(String receivedEventsUrl) { - this.receivedEventsUrl = receivedEventsUrl; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public boolean isSiteAdmin() { - return siteAdmin; - } - - public void setSiteAdmin(boolean siteAdmin) { - this.siteAdmin = siteAdmin; - } - - public OffsetDateTime getStarredAt() { - return starredAt; - } - - public void setStarredAt(OffsetDateTime starredAt) { - this.starredAt = starredAt; - } +public record Owner( + @JsonProperty("avatar_url") String avatarUrl, + @JsonProperty("email") String email, + @JsonProperty("events_url") String eventsUrl, + @JsonProperty("followers_url") String followersUrl, + @JsonProperty("following_url") String followingUrl, + @JsonProperty("gists_url") String gistsUrl, + @JsonProperty("gravatar_id") String gravatarId, + @JsonProperty("html_url") String htmlUrl, + @JsonProperty("id") long id, + @JsonProperty("login") String login, + @JsonProperty("name") String name, + @JsonProperty("node_id") String nodeId, + @JsonProperty("organizations_url") String organizationsUrl, + @JsonProperty("received_events_url") String receivedEventsUrl, + @JsonProperty("repos_url") String reposUrl, + @JsonProperty("site_admin") boolean siteAdmin, + @JsonProperty("starred_at") OffsetDateTime starredAt, + @JsonProperty("starred_url") String starredUrl, + @JsonProperty("subscriptions_url") String subscriptionsUrl, + @JsonProperty("type") String type, + @JsonProperty("url") String url +) { } diff --git a/src/main/java/com/giovds/dto/github/extenal/Repository.java b/src/main/java/com/giovds/dto/github/extenal/Repository.java index 19ffd44..1a88536 100644 --- a/src/main/java/com/giovds/dto/github/extenal/Repository.java +++ b/src/main/java/com/giovds/dto/github/extenal/Repository.java @@ -4,148 +4,19 @@ import com.fasterxml.jackson.annotation.JsonProperty; @JsonIgnoreProperties(ignoreUnknown = true) -public class Repository { - @JsonProperty("contributors_url") - private String contributorsUrl = ""; - - @JsonProperty("forks_count") - private int forksCount; - - @JsonProperty("full_name") - private String fullName = ""; - - @JsonProperty("has_issues") - private boolean hasIssues; - - @JsonProperty("homepage") - private String homepage; - - @JsonProperty("id") - private long id; - - @JsonProperty("node_id") - private String nodeId = ""; - - @JsonProperty("name") - private String name = ""; - - @JsonProperty("owner") - private Owner owner = new Owner(); - - @JsonProperty("private") - private boolean _private; - - @JsonProperty("stargazers_count") - private int stargazersCount; - - @JsonProperty("subscribers_count") - private int subscribersCount; - - @JsonProperty("url") - private String url = ""; - - // Getters and Setters - public String getContributorsUrl() { - return contributorsUrl; - } - - public void setContributorsUrl(String contributorsUrl) { - this.contributorsUrl = contributorsUrl; - } - - public int getForksCount() { - return forksCount; - } - - public void setForksCount(int forksCount) { - this.forksCount = forksCount; - } - - public String getFullName() { - return fullName; - } - - public void setFullName(String fullName) { - this.fullName = fullName; - } - - public boolean isHasIssues() { - return hasIssues; - } - - public void setHasIssues(boolean hasIssues) { - this.hasIssues = hasIssues; - } - - public String getHomepage() { - return homepage; - } - - public void setHomepage(String homepage) { - this.homepage = homepage; - } - - public long getId() { - return id; - } - - public void setId(long id) { - this.id = id; - } - - public String getNodeId() { - return nodeId; - } - - public void setNodeId(String nodeId) { - this.nodeId = nodeId; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Owner getOwner() { - return owner; - } - - public void setOwner(Owner owner) { - this.owner = owner; - } - - public boolean isPrivate() { - return _private; - } - - public void setPrivate(boolean _private) { - this._private = _private; - } - - public int getStargazersCount() { - return stargazersCount; - } - - public void setStargazersCount(int stargazersCount) { - this.stargazersCount = stargazersCount; - } - - public int getSubscribersCount() { - return subscribersCount; - } - - public void setSubscribersCount(int subscribersCount) { - this.subscribersCount = subscribersCount; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } +public record Repository( + @JsonProperty("contributors_url") String contributorsUrl, + @JsonProperty("forks_count") int forksCount, + @JsonProperty("full_name") String fullName, + @JsonProperty("has_issues") boolean hasIssues, + @JsonProperty("homepage") String homepage, + @JsonProperty("id") long id, + @JsonProperty("name") String name, + @JsonProperty("node_id") String nodeId, + @JsonProperty("owner") Owner owner, + @JsonProperty("private") boolean _private, + @JsonProperty("stargazers_count") int stargazersCount, + @JsonProperty("subscribers_count") int subscribersCount, + @JsonProperty("url") String url +) { } diff --git a/src/main/java/com/giovds/dto/github/internal/Bucket.java b/src/main/java/com/giovds/dto/github/internal/Bucket.java index f1a09a4..daa93a6 100644 --- a/src/main/java/com/giovds/dto/github/internal/Bucket.java +++ b/src/main/java/com/giovds/dto/github/internal/Bucket.java @@ -2,34 +2,7 @@ import java.time.OffsetDateTime; -public class Bucket { - private OffsetDateTime start; - private OffsetDateTime end; - - public OffsetDateTime getStart() { - return start; - } - - public void setStart(OffsetDateTime start) { - this.start = start; - } - - public OffsetDateTime getEnd() { - return end; - } - - public void setEnd(OffsetDateTime end) { - this.end = end; - } - - @Override - public String toString() { - return "Bucket{" + - "start=" + start + - ", end=" + end + - '}'; - } - +public record Bucket(OffsetDateTime start, OffsetDateTime end) { public static BucketBuilder builder() { return new BucketBuilder(); } @@ -54,14 +27,5 @@ public BucketBuilder end(OffsetDateTime end) { public Bucket build() { return new Bucket(start, end); } - - public String toString() { - return "Bucket.BucketBuilder(start=" + this.start + ", end=" + this.end + ")"; - } - } - - public Bucket(OffsetDateTime start, OffsetDateTime end) { - this.start = start; - this.end = end; } } diff --git a/src/main/java/com/giovds/dto/github/internal/Collected.java b/src/main/java/com/giovds/dto/github/internal/Collected.java index b1eac38..b738eff 100644 --- a/src/main/java/com/giovds/dto/github/internal/Collected.java +++ b/src/main/java/com/giovds/dto/github/internal/Collected.java @@ -2,84 +2,15 @@ import java.util.List; -public class Collected { - private String homepage; - private int starsCount; - private int forksCount; - private int subscribersCount; - private int issues; - private List contributors; - private List commits; - - public String getHomepage() { - return homepage; - } - - public void setHomepage(String homepage) { - this.homepage = homepage; - } - - public int getStarsCount() { - return starsCount; - } - - public void setStarsCount(int starsCount) { - this.starsCount = starsCount; - } - - public int getForksCount() { - return forksCount; - } - - public void setForksCount(int forksCount) { - this.forksCount = forksCount; - } - - public int getSubscribersCount() { - return subscribersCount; - } - - public void setSubscribersCount(int subscribersCount) { - this.subscribersCount = subscribersCount; - } - - public int getIssues() { - return issues; - } - - public void setIssues(int issues) { - this.issues = issues; - } - - public List getContributors() { - return contributors; - } - - public void setContributors(List contributors) { - this.contributors = contributors; - } - - public List getCommits() { - return commits; - } - - public void setCommits(List commits) { - this.commits = commits; - } - - @Override - public String toString() { - return "Collected{" + - "homepage='" + homepage + '\'' + - ", starsCount=" + starsCount + - ", forksCount=" + forksCount + - ", subscribersCount=" + subscribersCount + - ", issues=" + issues + - ", contributors=" + contributors + - ", commits=" + commits + - '}'; - } - +public record Collected( + String homepage, + int starsCount, + int forksCount, + int subscribersCount, + int issues, + List contributors, + List commits +) { public static CollectedBuilder builder() { return new CollectedBuilder(); } @@ -132,28 +63,7 @@ public CollectedBuilder commits(List commits) { } public Collected build() { - Collected collected = new Collected(); - collected.setHomepage(this.homepage); - collected.setStarsCount(this.starsCount); - collected.setForksCount(this.forksCount); - collected.setSubscribersCount(this.subscribersCount); - collected.setIssues(this.issues); - collected.setContributors(this.contributors); - collected.setCommits(this.commits); - return collected; - } - - @Override - public String toString() { - return "Collected.CollectedBuilder{" + - "homepage='" + homepage + '\'' + - ", starsCount=" + starsCount + - ", forksCount=" + forksCount + - ", subscribersCount=" + subscribersCount + - ", issues=" + issues + - ", contributors=" + contributors + - ", commits=" + commits + - '}'; + return new Collected(homepage, starsCount, forksCount, subscribersCount, issues, contributors, commits); } } } diff --git a/src/main/java/com/giovds/dto/github/internal/Contributor.java b/src/main/java/com/giovds/dto/github/internal/Contributor.java index 1557f6c..7f2bd73 100644 --- a/src/main/java/com/giovds/dto/github/internal/Contributor.java +++ b/src/main/java/com/giovds/dto/github/internal/Contributor.java @@ -1,33 +1,6 @@ package com.giovds.dto.github.internal; -public class Contributor { - private String username; - private int commitsCount; - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public int getCommitsCount() { - return commitsCount; - } - - public void setCommitsCount(int commitsCount) { - this.commitsCount = commitsCount; - } - - @Override - public String toString() { - return "Contributor{" + - "username='" + username + '\'' + - ", commitsCount=" + commitsCount + - '}'; - } - +public record Contributor(String username, int commitsCount) { public static ContributorBuilder builder() { return new ContributorBuilder(); } @@ -50,18 +23,7 @@ public ContributorBuilder commitsCount(int commitsCount) { } public Contributor build() { - Contributor contributor = new Contributor(); - contributor.setUsername(this.username); - contributor.setCommitsCount(this.commitsCount); - return contributor; - } - - @Override - public String toString() { - return "Contributor.ContributorBuilder{" + - "username='" + username + '\'' + - ", commitsCount=" + commitsCount + - '}'; + return new Contributor(username, commitsCount); } } } diff --git a/src/main/java/com/giovds/dto/github/internal/Point.java b/src/main/java/com/giovds/dto/github/internal/Point.java index 253e034..1f2f51d 100644 --- a/src/main/java/com/giovds/dto/github/internal/Point.java +++ b/src/main/java/com/giovds/dto/github/internal/Point.java @@ -2,34 +2,7 @@ import java.time.OffsetDateTime; -public class Point { - private OffsetDateTime date; - private int total; - - public OffsetDateTime getDate() { - return date; - } - - public void setDate(OffsetDateTime date) { - this.date = date; - } - - public int getTotal() { - return total; - } - - public void setTotal(int total) { - this.total = total; - } - - @Override - public String toString() { - return "Point{" + - "date=" + date + - ", total=" + total + - '}'; - } - +public record Point(OffsetDateTime date, int total) { public static PointBuilder builder() { return new PointBuilder(); } @@ -52,18 +25,7 @@ public PointBuilder total(int total) { } public Point build() { - Point point = new Point(); - point.setDate(this.date); - point.setTotal(this.total); - return point; - } - - @Override - public String toString() { - return "Point.PointBuilder{" + - "date=" + date + - ", total=" + total + - '}'; + return new Point(date, total); } } } diff --git a/src/main/java/com/giovds/dto/github/internal/Range.java b/src/main/java/com/giovds/dto/github/internal/Range.java index bf4ad94..1a7d798 100644 --- a/src/main/java/com/giovds/dto/github/internal/Range.java +++ b/src/main/java/com/giovds/dto/github/internal/Range.java @@ -3,44 +3,7 @@ import java.time.OffsetDateTime; import java.util.List; -public class Range { - private OffsetDateTime start; - private OffsetDateTime end; - private List points; - - public OffsetDateTime getStart() { - return start; - } - - public void setStart(OffsetDateTime start) { - this.start = start; - } - - public OffsetDateTime getEnd() { - return end; - } - - public void setEnd(OffsetDateTime end) { - this.end = end; - } - - public List getPoints() { - return points; - } - - public void setPoints(List points) { - this.points = points; - } - - @Override - public String toString() { - return "Range{" + - "start=" + start + - ", end=" + end + - ", points=" + points + - '}'; - } - +public record Range(OffsetDateTime start, OffsetDateTime end, List points) { public static RangeBuilder builder() { return new RangeBuilder(); } @@ -69,20 +32,7 @@ public RangeBuilder points(List points) { } public Range build() { - Range range = new Range(); - range.setStart(this.start); - range.setEnd(this.end); - range.setPoints(this.points); - return range; - } - - @Override - public String toString() { - return "Range.RangeBuilder{" + - "start=" + start + - ", end=" + end + - ", points=" + points + - '}'; + return new Range(start, end, points); } } } diff --git a/src/main/java/com/giovds/dto/github/internal/RangeSummary.java b/src/main/java/com/giovds/dto/github/internal/RangeSummary.java index ce45ca0..f289a10 100644 --- a/src/main/java/com/giovds/dto/github/internal/RangeSummary.java +++ b/src/main/java/com/giovds/dto/github/internal/RangeSummary.java @@ -2,44 +2,11 @@ import java.time.OffsetDateTime; -public class RangeSummary { - private OffsetDateTime start; - private OffsetDateTime end; - private int count; - - public OffsetDateTime getStart() { - return start; - } - - public void setStart(OffsetDateTime start) { - this.start = start; - } - - public OffsetDateTime getEnd() { - return end; - } - - public void setEnd(OffsetDateTime end) { - this.end = end; - } - - public int getCount() { - return count; - } - - public void setCount(int count) { - this.count = count; - } - - @Override - public String toString() { - return "RangeSummary{" + - "start=" + start + - ", end=" + end + - ", count=" + count + - '}'; - } - +public record RangeSummary( + OffsetDateTime start, + OffsetDateTime end, + int count +) { public static RangeSummaryBuilder builder() { return new RangeSummaryBuilder(); } @@ -68,11 +35,7 @@ public RangeSummaryBuilder count(int count) { } public RangeSummary build() { - RangeSummary rangeSummary = new RangeSummary(); - rangeSummary.setStart(this.start); - rangeSummary.setEnd(this.end); - rangeSummary.setCount(this.count); - return rangeSummary; + return new RangeSummary(start, end, count); } @Override diff --git a/src/main/java/com/giovds/evaluator/MaintenanceEvaluator.java b/src/main/java/com/giovds/evaluator/MaintenanceEvaluator.java index d84c764..c89b062 100644 --- a/src/main/java/com/giovds/evaluator/MaintenanceEvaluator.java +++ b/src/main/java/com/giovds/evaluator/MaintenanceEvaluator.java @@ -9,7 +9,7 @@ public class MaintenanceEvaluator { public double evaluateCommitsFrequency(Collected collected) { - var commits = collected.getCommits(); + var commits = collected.commits(); if (commits.isEmpty()) { return 0; } @@ -18,9 +18,9 @@ public double evaluateCommitsFrequency(Collected collected) { var range180 = findRange(commits, 180); var range365 = findRange(commits, 365); - var mean30 = range30.getCount(); - var mean180 = range180.getCount() / (180.0d / 30.0d); - var mean365 = range365.getCount() / (365.0d / 30.0d); + var mean30 = range30.count(); + var mean180 = range180.count() / (180.0d / 30.0d); + var mean365 = range365.count() / (365.0d / 30.0d); var monthlyMean = (mean30 * 0.35d) + (mean180 * 0.45d) + @@ -38,7 +38,7 @@ public double evaluateCommitsFrequency(Collected collected) { private RangeSummary findRange(List commits, int days) { return commits.stream() - .filter(range -> Duration.between(range.getStart(), range.getEnd()).toDays() == days) + .filter(range -> Duration.between(range.start(), range.end()).toDays() == days) .limit(1) .toList() .getFirst();