Skip to content

Commit

Permalink
feat: ✨ Custom extensions for updating compact constructor can be add…
Browse files Browse the repository at this point in the history
…ed (#102)

* feat: Allowed to implement an extension for compact constructor updating
  • Loading branch information
create-issue-branch[bot] authored and pawellabaj committed Jul 21, 2023
1 parent 340d0c6 commit 9e4c36f
Show file tree
Hide file tree
Showing 200 changed files with 4,203 additions and 1,035 deletions.
2 changes: 1 addition & 1 deletion .github/semantic.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
titleAndCommits: true
anyCommit: true
anyCommit: true
2 changes: 1 addition & 1 deletion .github/workflows/release-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,4 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create v${{ env.RELEASE_VERSION }} target/auto-record-${{ env.RELEASE_VERSION }}.jar --generate-notes --verify-tag
gh release create v${{ env.RELEASE_VERSION }} modules/auto-record/target/auto-record-${{ env.RELEASE_VERSION }}.jar --generate-notes --verify-tag
4 changes: 3 additions & 1 deletion .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: mvn -B -U -ntp verify -P verify
run: |
mvn -B -U -ntp install -P verify -e
mvn -B -U -ntp sonar:sonar -P verify -e
- name: Build Surefire report
uses: ScaCap/action-surefire-report@v1
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@
./*.txt

/.idea/
/target/
**/target/
42 changes: 25 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,38 @@ Java record newContext generator

[![Maven Central version](https://img.shields.io/maven-central/v/pl.com.labaj/auto-record)](https://mvnrepository.com/artifact/pl.com.labaj/auto-record)
[![javadoc](https://javadoc.io/badge2/pl.com.labaj/auto-record/javadoc.svg)](https://javadoc.io/doc/pl.com.labaj/auto-record)
[![Reproducible Builds](https://img.shields.io/badge/Reproducible_Builds-ok-success?labelColor=1e5b96)](https://github.com/jvm-repo-rebuild/reproducible-central#pl.com.labaj:auto-record)


[![CI Verify Status](https://github.com/pawellabaj/auto-record/actions/workflows/verify.yml/badge.svg?branch=main)](https://github.com/pawellabaj/auto-record/actions/workflows/verify.yml)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=pawellabaj%3Aauto-record&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=pawellabaj%3Aauto-record)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=pl.com.labaj%3Aauto-record-project&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=pl.com.labaj%3Aauto-record-project)
[![Sonatype Lift Status](https://lift.sonatype.com/api/badge/github.com/pawellabaj/auto-record)](https://lift.sonatype.com/results/github.com/pawellabaj/auto-record)
[![Reproducible Builds](https://img.shields.io/badge/Reproducible_Builds-ok-success?labelColor=1e5b96)](https://github.com/jvm-repo-rebuild/reproducible-central#pl.com.labaj:auto-record)
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](.github/CODE_OF_CONDUCT.md)

## What is AutoRecord

AutoRecord is a code generator that helps you easily generate Java records.
It provides an easy way to avoid writing repetitive boilerplate code. It generates the code with features such as:
* nullability checking
* builders - incorporating [Randgalt/record-builder](https://github.com/Randgalt/record-builder) library
* [memoization](https://en.wikipedia.org/wiki/Memoization)
* ignoring specified fields in `hashCode` and `equals` methods
* [nullability](https://github.com/pawellabaj/auto-record/wiki/Nullability) checking
* [builders](https://github.com/pawellabaj/auto-record/wiki/Record-Builder) - incorporating [Randgalt/record-builder](https://github.com/Randgalt/record-builder) library
* [memoization](https://github.com/pawellabaj/auto-record/wiki/Memoization)
* [ignoring fields](https://github.com/pawellabaj/auto-record/wiki/Ignored-components) in `hashCode()` and `equals()` methods
* generated _common_ methods if the record has an [array component](https://github.com/pawellabaj/auto-record/wiki/Array-components)
* exclusion from [JaCoCo test coverage](https://github.com/pawellabaj/auto-record/wiki/JaCoCo-exclusion) analysis

AutoRecord allows users to customize record generation process by:
* specifying [options](https://github.com/pawellabaj/auto-record/wiki/Single-record-options)
* using [custom annotation](https://github.com/pawellabaj/auto-record/wiki/Custom-annotations) templates
* implementing [custom extensions](https://github.com/pawellabaj/auto-record/wiki/Extensions)

## Why AutoRecord was created

Google [AutoValue](https://github.com/google/auto) has long been used as a way to work with _Value Classes_ in an easy way.
However, when Java [records](https://docs.oracle.com/en/java/javase/17/language/records.html) were introduced, they lacked some features that AutoValue had, such as nullability checking, builders, and memoization.
This is why AutoRecord was created.

## How to use AutoRecord

To use AutoRecord, simply annotate your interface with @AutoRecord:
To use AutoRecord, simply annotate your interface with [@AutoRecord](https://github.com/pawellabaj/auto-record/blob/main/modules/auto-record/src/main/java/pl/com/labaj/autorecord/AutoRecord.java) annotation:

```java
import pl.com.labaj.autorecord.AutoRecord;
Expand All @@ -42,22 +50,22 @@ interface Person {
AutoRecord will then generate a Java record class that implements your interface. The constructor parameters correspond, in order, to the interface methods:

```java
import static java.util.Objects.requireNonNull;

import java.lang.String;
import pl.com.labaj.autorecord.GeneratedWithAutoRecord;
import javax.annotation.processing.Generated;
import static java.util.Objects.requireNonNull;

@Generated("pl.com.labaj.autorecord.AutoRecord")
@GeneratedWithAutoRecord
record PersonRecord(String name, int age) implements Person {
PersonRecord {
requireNonNull(name, "name must not be null");
}
PersonRecord {
requireNonNull(name, "name must not be null");
}
}
```

| :memo: Note |
|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Here you can see example of generated record with all features provided by the library: [WithAllFeaturesRecord.java](https://gist.github.com/pawellabaj/c773e35a17e3f7f4d75d2829d75680df#file-withallfeaturesrecord-java) |
| :memo: Note |
|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Here you can see example of generated record with all features provided by the library: [Full.java](https://gist.github.com/pawellabaj/c773e35a17e3f7f4d75d2829d75680df) |

## Documentation

Expand Down
76 changes: 76 additions & 0 deletions modules/auto-record-tests/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>pl.com.labaj</groupId>
<artifactId>auto-record-project</artifactId>
<version>2.0.1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

<artifactId>auto-record-tests</artifactId>

<properties>
<maven.install.skip>true</maven.install.skip>
</properties>

<dependencies>
<dependency>
<groupId>pl.com.labaj</groupId>
<artifactId>auto-record</artifactId>
<version>${project.parent.version}</version>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.testing.compile</groupId>
<artifactId>compile-testing</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<profiles>
<profile>
<id>verify</id>

<properties>
<sonar.coverage.exclusions>src/main/java/**/*</sonar.coverage.exclusions>
</properties>
</profile>
<profile>
<id>release</id>

<build>
<plugins>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<id>default-deploy</id>
<phase>none</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package pl.com.labaj.autorecord.test.extension.compact;

/*-
* Copyright © 2023 Auto Record
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import com.squareup.javapoet.CodeBlock;
import pl.com.labaj.autorecord.context.Context;
import pl.com.labaj.autorecord.context.StaticImports;
import pl.com.labaj.autorecord.extension.CompactConstructorExtension;

import java.util.Arrays;

public class AlwaysCompactConstructorExtension implements CompactConstructorExtension {
private String[] parameters;

@Override
public void setParameters(String[] parameters) {
this.parameters = parameters;
}

@Override
public boolean shouldGenerate(boolean isGeneratedByProcessor, Context context) {
return true;
}

@Override
public CodeBlock suffixCompactConstructorContent(Context context, StaticImports staticImports) {
staticImports.add(System.class, "out");

return CodeBlock.builder()
.addStatement("var params = $S", Arrays.toString(parameters))
.addStatement("out.println(params)")
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package pl.com.labaj.autorecord.test.extension.compact;

/*-
* Copyright © 2023 Auto Record
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import com.squareup.javapoet.CodeBlock;
import pl.com.labaj.autorecord.context.Context;
import pl.com.labaj.autorecord.context.StaticImports;
import pl.com.labaj.autorecord.extension.CompactConstructorExtension;
import pl.com.labaj.autorecord.processor.AutoRecordProcessorException;

public class IsPersonAdultVerifierExtension implements CompactConstructorExtension {

private static final Class<RuntimeException> RUNTIME_EXCEPTION_CLASS = RuntimeException.class;
private Class<? extends RuntimeException> exceptionClass;

@SuppressWarnings("java:S112")
@Override
public void setParameters(String[] parameters) {
if (parameters.length < 1) {
exceptionClass = RUNTIME_EXCEPTION_CLASS;
} else {
var className = parameters[0];

try {
var aClass = Class.forName(className);
exceptionClass = getExceptionClass(aClass);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Cannot load " + className + " class", e);
}
}
}

@SuppressWarnings("unchecked")
private Class<? extends RuntimeException> getExceptionClass(Class<?> aClass) {
if (RUNTIME_EXCEPTION_CLASS.isAssignableFrom(aClass)) {
return (Class<? extends RuntimeException>) aClass;
} else {
throw new AutoRecordProcessorException("Class " + aClass.getName() + " does not extend " + RUNTIME_EXCEPTION_CLASS.getName());
}
}

@Override
public CodeBlock suffixCompactConstructorContent(Context context, StaticImports staticImports) {
return CodeBlock.builder()
.beginControlFlow("if (age < 18)")
.addStatement("var message = \"%s is not adult. She/he is only %d!\".formatted(name, age)")
.addStatement("throw new $T(message)", exceptionClass)
.endControlFlow()
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package pl.com.labaj.autorecord.test.extension.compact;

/*-
* Copyright © 2023 Auto Record
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import com.squareup.javapoet.CodeBlock;
import pl.com.labaj.autorecord.context.Context;
import pl.com.labaj.autorecord.context.RecordComponent;
import pl.com.labaj.autorecord.context.StaticImports;
import pl.com.labaj.autorecord.extension.CompactConstructorExtension;

import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import static java.util.logging.Level.FINE;

public class LoggingExtension implements CompactConstructorExtension {

private Level level;

@Override
public void setParameters(String[] parameters) {
if (parameters.length < 1) {
level = FINE;
} else {
level = Level.parse(parameters[0].toUpperCase());
}
}

@Override
public boolean shouldGenerate(boolean isGeneratedByProcessor, Context context) {
return true;
}

@Override
public CodeBlock prefixCompactConstructorContent(Context context, StaticImports staticImports) {
var codeBuilder = CodeBlock.builder();

codeBuilder.addStatement("var map = new $T<$T, $T>()", HashMap.class, String.class, Object.class);

context.components().stream()
.map(RecordComponent::name)
.forEach(name -> codeBuilder.addStatement("map.put($S, $L)", name, name));

codeBuilder.addStatement("var logger = getLogger($L.class.getName())", context.recordName())
.addStatement("logger.log($L, $S, $L)", level, "Parameters passed to record: {0}", "map");

staticImports.add(Logger.class, "getLogger")
.add(Level.class, level.getName());

return codeBuilder.build();
}
}
Loading

0 comments on commit 9e4c36f

Please sign in to comment.