commit 5ae4bbf231325e2e7f095c258ad1c8415a671e20 Author: Paul Campbell Date: Mon Apr 6 16:04:06 2020 +0100 Initial import diff --git a/.github/GitHub-Actions.org b/.github/GitHub-Actions.org new file mode 100644 index 0000000..1c882b8 --- /dev/null +++ b/.github/GitHub-Actions.org @@ -0,0 +1,41 @@ +* Deploying using Github Actions + +** Actions definition: workflow/sonatype-deploy.yml + +When a GitHub Release is created, usually from a tag, this action will trigger. + +Using JDK8 the software will be packaged, including running any tests. + +Then the Deploy script will sign the created artifacts then deploy them according to the distributionManagement configuration in the `pom.xml`. + +** Deploy Script + +Uses a signing key provided from the GitHub Actions Secrets as an environment variable to sign the artifact(s) before they are then deployed. + +*** Inputs + +**** DEPLOY_PROJECTS (optional) + +An optional list of modules in a multi-module project to be deployed. If this value is not specified, then all projects will be deployed. + +** Maven Configuration + +Picks up the credentials from Environment variables for authenticating both with GPG and with the target deployment server (e.g. sonatype-nexus). + +*** Inputs + +**** NEXUS_USERNAME + +The username for your account on the deployment server. + +**** NEXUS_PASSWORD + +The password for your account on the deployement server. + +**** GPG_KEYNAME + +The key to use when signing. + +**** GPG_PASSPHRASE + +The passphrase to unlock the key to use when signing. diff --git a/.github/NOTES b/.github/NOTES new file mode 100644 index 0000000..64253c9 --- /dev/null +++ b/.github/NOTES @@ -0,0 +1,53 @@ +Add subkeys: + +???? + +Publish: + +gpg --send-keys --keyserver keyserver.ubuntu.com $KEYID +gpg --send-keys --keyserver pgp.mit.edu $KEYID +gpg --send-keys --keyserver pool.sks-keyservers.net $KEYID + +Backup: + +gpg --export --armor pcampbell@kemitix.net > gpg-key-backup.asc +gpg --export-secret-keys --armor pcampbell@kemitix.net >> gpg-key-backup.asc + +Export sub-keys: + +gpg --export-secret-subkeys pcampbell@kemitix.net > subkeys + +Remove master keys: + +gpg --delete-secret-key pcampbell@kemitix.net + +Import sub-keys and clean up: + +gpg --import subkeys + +shred --remove subkeys + +Delete any encryption subkeys: + +gpg --edit-key pcampbell@kemitix.net + +2 +delkey +save + +Change passphrase: + +gpg --edit-key pcampbell@kemitix.net +passwd +save + +Export keys: + +gpg --export --armor pcampbell@kemitix.net > codesigning.asc +gpg --export-secret-keys --armor pcampbell@kemitix.net >> codesigning.asc + +Encrypt keys: + +gpg --symmetric --cipher-algo AES256 codesigning.asc + +shred codesigning.asc diff --git a/.github/codesigning.asc.gpg b/.github/codesigning.asc.gpg new file mode 100644 index 0000000..f5c71e0 Binary files /dev/null and b/.github/codesigning.asc.gpg differ diff --git a/.github/settings.xml b/.github/settings.xml new file mode 100644 index 0000000..8791e47 --- /dev/null +++ b/.github/settings.xml @@ -0,0 +1,28 @@ + + + + + sonatype-nexus-snapshots + ${env.NEXUS_USERNAME} + ${env.NEXUS_PASSWORD} + + + sonatype-nexus-staging + ${env.NEXUS_USERNAME} + ${env.NEXUS_PASSWORD} + + + + + gpg-sign + + true + + + gpg + ${env.GPG_KEYNAME} + ${env.GPG_PASSPHRASE} + + + + diff --git a/.github/workflows/maven-build.yml b/.github/workflows/maven-build.yml new file mode 100644 index 0000000..470baeb --- /dev/null +++ b/.github/workflows/maven-build.yml @@ -0,0 +1,25 @@ +# This workflow will build a Java project with Maven +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven + +name: Java CI with Maven + +on: + push: + branches: '*' + pull_request: + branches: '*' + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + java: [ 8, 11, 13 ] + steps: + - uses: actions/checkout@v2 + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v1 + with: + java-version: ${{ matrix.java }} + - name: Build with Maven + run: mvn -B install diff --git a/.github/workflows/sonatype-deploy.yml b/.github/workflows/sonatype-deploy.yml new file mode 100644 index 0000000..63b14fe --- /dev/null +++ b/.github/workflows/sonatype-deploy.yml @@ -0,0 +1,38 @@ +name: Deploy to Sonatype Nexus + +on: + release: + types: [created] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Build with Maven + run: mvn -B install + - name: Nexus Repo Publish + run: | + gpg --quiet \ + --batch \ + --yes \ + --decrypt \ + --passphrase="${{ secrets.GPG_PASSPHRASE }}" \ + --output codesigning.asc \ + .github/codesigning.asc.gpg + gpg --batch \ + --fast-import codesigning.asc + mvn --settings .github/settings.xml \ + -Dskip-Tests=true \ + -P release \ + -B \ + deploy + env: + NEXUS_USERNAME: ${{ secrets.NEXUS_USERNAME }} + NEXUS_PASSWORD: ${{ secrets.NEXUS_PASSWORD }} + GPG_KEYNAME: ${{ secrets.GPG_KEYNAME }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} diff --git a/README.md b/README.md new file mode 100644 index 0000000..9951108 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# TypedProperties + +![Sonatype Nexus (Release)](https://img.shields.io/nexus/r/https/oss.sonatype.org/net.kemitix/typed-properties.svg?style=for-the-badge) +![Maven Central](https://img.shields.io/maven-central/v/net.kemitix/typed-properties.svg?style=for-the-badge) + +![Java 8](https://img.shields.io/badge/Java-8-success "Compatible with Java 8") +![Java 11](https://img.shields.io/badge/Java-11-success "Compatible with Java 11") +![Java 13](https://img.shields.io/badge/Java-13-success "Compatible with Java 13") +![Java 14](https://img.shields.io/badge/Java-14-important "Unresolved Issues with Java 14") + +A strongly-typed, immutable, simple alternative to `Properties` or, sanity-save-you, `Map`. + +## Usage + +```java +import net.kemitix.properties.typed.TypedProperties; +import net.kemitix.properties.typed.TypedProperty; + +class Usage { + public static void main(String[] args){ + // Create a new instance + TypedProperties properties = TypedProperties.create(); + + // Set a value - creates a new instance - immutability + // parameters: key, value + // - key is a class/interface, that extends TypedProperty, + // where T is the type of the value. + // - value is of type T + TypedProperties updated = properties.with(Name.class, "Name"); + + // Retrieve a value + // parameters: key, value-type + // - key is same as above + // - value-type is the type of the value - must match key + Optional result = updated.find(Name.class, String.class); + } +} +public interface Name + extends TypedProperty {} +``` + +```java + +``` \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..e49c246 --- /dev/null +++ b/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + + net.kemitix + kemitix-parent + 5.3.0 + + + + typed-properties + DEV-SNAPSHOT + + 2020 + + + 1.8 + ${java.version} + ${java.version} + 2.16 + 2.5.0 + 5.4.0 + 5.6.0 + 3.15.0 + + + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + org.assertj + assertj-core + ${assertj.version} + test + + + + + + + io.repaint.maven + tiles-maven-plugin + ${tiles-maven-plugin.version} + true + + + + net.kemitix.tiles:all:${kemitix-maven-tiles.version} + + + net.kemitix.checkstyle:tile:${kemitix-checkstyle.version} + + + + + + + + \ No newline at end of file diff --git a/src/main/java/net/kemitix/properties/typed/TypedProperties.java b/src/main/java/net/kemitix/properties/typed/TypedProperties.java new file mode 100644 index 0000000..c1f57b1 --- /dev/null +++ b/src/main/java/net/kemitix/properties/typed/TypedProperties.java @@ -0,0 +1,42 @@ +package net.kemitix.properties.typed; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class TypedProperties { + + private static final TypedProperties EMPTY_PROPERTIES = + new TypedProperties(Collections.emptyMap()); + + private final Map>, Object> valueMap; + + private TypedProperties( + final Map>, Object> valueMap + ) { + this.valueMap = valueMap; + } + + public static TypedProperties create() { + return EMPTY_PROPERTIES; + } + + public TypedProperties with( + final Class> key, + final T value + ) { + final Map>, Object> updated = + new HashMap<>(valueMap); + updated.put(key, value); + return new TypedProperties(updated); + } + + public Optional find( + final Class> key, + final Class tClass + ) { + return Optional.ofNullable(valueMap.get(key)) + .map(tClass::cast); + } +} diff --git a/src/main/java/net/kemitix/properties/typed/TypedProperty.java b/src/main/java/net/kemitix/properties/typed/TypedProperty.java new file mode 100644 index 0000000..28341d8 --- /dev/null +++ b/src/main/java/net/kemitix/properties/typed/TypedProperty.java @@ -0,0 +1,4 @@ +package net.kemitix.properties.typed; + +public interface TypedProperty { +} diff --git a/src/test/java/net/kemitix/properties/typed/TypedPropertiesTest.java b/src/test/java/net/kemitix/properties/typed/TypedPropertiesTest.java new file mode 100644 index 0000000..eddc906 --- /dev/null +++ b/src/test/java/net/kemitix/properties/typed/TypedPropertiesTest.java @@ -0,0 +1,116 @@ +package net.kemitix.properties.typed; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TypedPropertiesTest { + + @Test + @DisplayName("Can set and then find a value") + public void canSetAndFindValue() { + //given + final TypedProperties typedProperties = + TypedProperties.create() + .with(Name.class, "Name"); + //when + final Optional result = + typedProperties.find(Name.class, String.class); + //then + assertThat(result).contains("Name"); + } + + @Test + @DisplayName("Find a value not set is empty") + public void findMissingIsEmpty() { + //given + final TypedProperties typedProperties = TypedProperties.create(); + //when + final Optional result = + typedProperties.find(Name.class, String.class); + //then + assertThat(result).isEmpty(); + } + + @Test + @DisplayName("After replacing a value, find returns the new value") + public void replaceValueFindIsNewValue() { + //given + final TypedProperties typedProperties = + TypedProperties.create() + .with(Name.class, "Original Name") + .with(Name.class, "Updated Name"); + //when + final Optional result = + typedProperties.find(Name.class, String.class); + //then + assertThat(result).contains("Updated Name"); + } + + @Test + @DisplayName("Immutable - setting value creates new instance") + public void immutableCreatesNewInstanceOnSet() { + //given + final TypedProperties original = TypedProperties.create(); + //when + final TypedProperties updated = original.with(Name.class, "Name"); + //then + assertThat(updated).isNotSameAs(original); + } + + @Test + @DisplayName("Immutable - original instance lacks new value") + public void immutableOriginalInstanceUnmodified() { + //given + final TypedProperties original = TypedProperties.create(); + final TypedProperties updated = original.with(Name.class, "Name"); + //when + final Optional result = original.find(Name.class, String.class); + //then + assertThat(result).isEmpty(); + } + + @Test + @DisplayName("Multiple values of same underlying type are unique") + public void multipleValuesOfSameBaseTypeAreSafe() { + //given + final TypedProperties typedProperties = + TypedProperties.create() + .with(Name.class, "Name") + .with(Email.class, "name@example.org"); + //when + final Optional name = + typedProperties.find(Name.class, String.class); + final Optional email = + typedProperties.find(Email.class, String.class); + //then + assertThat(name).contains("Name"); + assertThat(email).contains("name@example.org"); + } + +// @Test +// @DisplayName("Attempt to find a value by wrong type is a compilation error") +// public void findWithWrongType() { +// TypedProperties.create() +// .set(Name.class, "Original Name") +// .find(Name.class, Integer.class); +// } + +// @Test +// @DisplayName("Attempt to set a value by wrong type is a compilation error") +// public void setWithWrongType() { +// TypedProperties.create() +// .set(Name.class, 42); +// } + + private interface Name + extends TypedProperty { + } + + private interface Email + extends TypedProperty { + } +} \ No newline at end of file