From 9c0c26de5b7ca01eae21748176f7b5d01e6b5735 Mon Sep 17 00:00:00 2001 From: Jan Galinski Date: Mon, 13 Sep 2021 22:25:19 +0200 Subject: [PATCH 01/30] Initial commit --- .editorconfig | 19 + .github/dependabot.yml | 7 + .github/workflows/development.yml | 53 ++ .github/workflows/master.yml | 59 +++ .gitignore | 46 ++ .java-version | 1 + .mvn/wrapper/MavenWrapperDownloader.java | 117 +++++ .mvn/wrapper/maven-wrapper.properties | 2 + LICENSE | 201 ++++++++ README.md | 12 + mvnw | 310 ++++++++++++ mvnw.cmd | 182 +++++++ pom.xml | 587 +++++++++++++++++++++++ src/main/kotlin/KotlinLibTemplate.kt | 7 + src/main/resources/.gitkeep | 0 src/test/java/.gitkeep | 0 src/test/kotlin/KotlinLibTemplateTest.kt | 12 + src/test/resources/.gitkeep | 0 18 files changed, 1615 insertions(+) create mode 100644 .editorconfig create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/development.yml create mode 100644 .github/workflows/master.yml create mode 100644 .gitignore create mode 100644 .java-version create mode 100644 .mvn/wrapper/MavenWrapperDownloader.java create mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100644 LICENSE create mode 100644 README.md create mode 100755 mvnw create mode 100644 mvnw.cmd create mode 100644 pom.xml create mode 100644 src/main/kotlin/KotlinLibTemplate.kt create mode 100644 src/main/resources/.gitkeep create mode 100644 src/test/java/.gitkeep create mode 100644 src/test/kotlin/KotlinLibTemplateTest.kt create mode 100644 src/test/resources/.gitkeep diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..2ab021a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# https://editorconfig.org + +root = true + +[*] +# Change these settings to your own preference +indent_style = space +indent_size = 2 +# We recommend you to keep these unchanged +end_of_line = lf +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true +max_line_length = 160 + +[*.md] +trim_trailing_whitespace = false diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..a217b34 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: +- package-ecosystem: maven + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml new file mode 100644 index 0000000..150c22e --- /dev/null +++ b/.github/workflows/development.yml @@ -0,0 +1,53 @@ +name: Development branches + +on: + push: + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + name: Build and run tests on JDK 11 + steps: + # Checkout the code + - name: Checkout code + uses: actions/checkout@v2 + + - name: Expose branch name + run: echo ${{ github.ref }} + + # Setup the cache + - name: Cache .m2 + uses: actions/cache@v1 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven + + # Setup JDK and Maven + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + server-id: ossrh + server-username: OSS_CENTRAL_USERNAME # env variable for Maven Central + server-password: OSS_CENTRAL_PASSWORD # env variable for Maven Central + + # Prepare + - name: Prepare Maven Wrapper + run: chmod +x ./mvnw + + # Build + - name: Build with Maven + run: ./mvnw clean verify -U -B -T4 + + # itest + - name: Run itest + run: ./mvnw integration-test failsafe:verify -Pitest -U -B -T4 + +# - name: Upload coverage to Codecov +# if: github.event_name == 'push' && github.actor != 'dependabot[bot]' +# uses: codecov/codecov-action@v1.0.2 +# with: +# token: ${{secrets.CODECOV_TOKEN}} diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml new file mode 100644 index 0000000..284de78 --- /dev/null +++ b/.github/workflows/master.yml @@ -0,0 +1,59 @@ +name: Produces and releases artifacts + +on: + push: + branches: + - master + +jobs: + build: + runs-on: ubuntu-latest + name: Build and run tests on JDK ${{ matrix.java }} + steps: + - name: Checkout code + uses: actions/checkout@v2 + + # Restore the cache first + - name: Cache .m2 + uses: actions/cache@v1 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven + + # Get GPG private key into GPG + - name: Import GPG Owner Trust + run: echo ${{ secrets.GPG_OWNERTRUST }} | base64 --decode | gpg --import-ownertrust + + - name: Import GPG key + run: echo ${{ secrets.GPG_SECRET_KEYS }} | base64 --decode | gpg --import --no-tty --batch --yes + + # Setup JDK and .m2/settings.xml + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + server-id: ossrh + server-username: OSS_CENTRAL_USERNAME # env variable for Maven Central + server-password: OSS_CENTRAL_PASSWORD # env variable for Maven Central + + # Prepare + - name: Prepare Maven Wrapper + run: chmod +x ./mvnw + + # Build + - name: Build with Maven + run: ./mvnw clean verify -U -B -T4 + + # Publish release + - name: Deploy a new release version to Maven Central + run: ./mvnw clean deploy -B -DskipTests -DskipExamples -Prelease -Dgpg.keyname="${{ secrets.GPG_KEYNAME }}" -Dgpg.passphrase="${{ secrets.GPG_PASSPHRASE }}" + env: + OSS_CENTRAL_USERNAME: ${{ secrets.SONATYPE_USERNAME }} + OSS_CENTRAL_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + +# - name: Upolad coverage information +# uses: codecov/codecov-action@v2 +# with: +# token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3123bc4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +target/ + +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next + +release.properties +dependency-reduced-pom.xml +buildNumber.properties + +.mvn/timing.properties +# https://github.com/takari/maven-wrapper#usage-without-binary-jar +.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea/ +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### DEV +_tmp/ +.repository/ diff --git a/.java-version b/.java-version new file mode 100644 index 0000000..2dbc24b --- /dev/null +++ b/.java-version @@ -0,0 +1 @@ +11.0 diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..e76d1f3 --- /dev/null +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,117 @@ +/* + * Copyright 2007-present the original author or authors. + * + * 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 + * + * https://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 java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if(mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if(mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if(!outputFile.getParentFile().exists()) { + if(!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..abd303b --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.2/apache-maven-3.8.2-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ec79ff5 --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# kotlin-lib-template + +toolisticon template repository + +[![Build Status](https://github.com/toolisticon/kotlin-lib-template/workflows/Development%20branches/badge.svg)](https://github.com/toolisticon/kotlin-lib-template/actions) +[![sponsored](https://img.shields.io/badge/sponsoredBy-Holisticon-RED.svg)](https://holisticon.de/) +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.toolisticon.git/kotlin-lib-template/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.toolisticon.git/kotlin-lib-template) + + +## Idea + +This repository is a **template repository** designed to be a copy&paste quick start for your next toolisticon kotlin lib project. diff --git a/mvnw b/mvnw new file mode 100755 index 0000000..a16b543 --- /dev/null +++ b/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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 +# +# https://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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..c8d4337 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..795754e --- /dev/null +++ b/pom.xml @@ -0,0 +1,587 @@ + + 4.0.0 + + io.toolisticon.git + kotlin-lib-template + 0.0.1-SNAPSHOT + ${project.artifactId} + https://github.com/toolisticon/${project.artifactId}/ + + + UTF-8 + UTF-8 + + 1.5.30 + 11 + ${java.version} + ${java.version} + + + + + + org.jetbrains.kotlin + kotlin-bom + ${kotlin.version} + import + pom + + + org.junit + junit-bom + 5.8.0 + pom + import + + + + + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + org.jetbrains.kotlin + kotlin-reflect + + + + org.mockito.kotlin + mockito-kotlin + 3.2.0 + test + + + + org.junit.jupiter + junit-jupiter + test + + + org.junit.jupiter + junit-jupiter-params + test + + + + org.slf4j + slf4j-simple + 1.7.32 + test + + + + org.assertj + assertj-core + 3.20.2 + test + + + + org.jetbrains.kotlin + kotlin-test-junit5 + test + + + + io.github.microutils + kotlin-logging-jvm + 2.0.11 + test + + + + + clean jacoco:prepare-agent package + + + + src/main/resources + + .gitkeep + + + + + + + + org.apache.maven.plugins + maven-clean-plugin + 3.1.0 + + + + + org.jacoco + jacoco-maven-plugin + 0.8.7 + + + pre-unit-test + + prepare-agent + + + + pre-integration-test + + prepare-agent-integration + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${project.build.sourceEncoding} + 11 + + + + + default-compile + none + + + + default-testCompile + none + + + java-compile + compile + + compile + + + + java-test-compile + test-compile + + testCompile + + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + ${java.version} + + + + no-arg + all-open + + + + + + + + + + compile + + compile + + + + ${project.basedir}/src/main/kotlin + + + + + test-compile + + test-compile + + + + ${project.basedir}/src/test/kotlin + ${project.basedir}/src/test/java + + + + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-maven-noarg + ${kotlin.version} + + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.2.0 + + UTF-8 + + + + + + maven-surefire-plugin + 2.22.2 + + false + kill + random + --illegal-access=permit -Djava.awt.headless=true ${argLine} -XX:+StartAttachListener -Xmx1024m -XX:MaxPermSize=256m + + + ${project.build.directory}/jgiven-reports + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + 2.22.2 + + + --illegal-access=permit + + + + + + + org.jetbrains.dokka + dokka-maven-plugin + 1.5.0 + + + package + attach-javadocs + + javadocJar + + + + + 11 + + false + true + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.3.0 + + + attach-javadocs + + jar + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.2.0 + + + generate-sources + + add-source + + + + ${project.basedir}/src/main/kotlin + + + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + attach-sources + package + + jar + + + true + true + + + + + + + + com.amashchenko.maven.plugin + gitflow-maven-plugin + 1.16.0 + + + master + develop + feature/ + release/ + hotfix/ + support/ + origin + + true + true + false + true + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.0.1 + + ${gpg.keyname} + + --batch + --yes + --pinentry-mode + loopback + + + + + sign-artifacts + verify + + sign + + + + + + + + maven-deploy-plugin + 3.0.0-M1 + + true + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + + true + ossrh + https://oss.sonatype.org/ + + + + default-deploy + deploy + + deploy + + + + + + + + maven-install-plugin + 2.5.2 + + + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.0.0-M3 + + + enforce-maven + + enforce + + + + + 3.8.0 + + + + + + + + + + + + org.jacoco + jacoco-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-enforcer-plugin + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + + + + + + + + 1.8 + + + + org.jetbrains.dokka + dokka-maven-plugin + + + + + + + + release + + + release + + + + + + org.apache.maven.plugins + maven-source-plugin + + + org.jetbrains.dokka + dokka-maven-plugin + + + org.codehaus.mojo + build-helper-maven-plugin + + + org.apache.maven.plugins + maven-gpg-plugin + + + org.sonatype.plugins + nexus-staging-maven-plugin + + + + + + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + + scm:git:git@github.com:toolisticon/${project.artifactId}.git + scm:git:git@github.com:toolisticon/${project.artifactId}.git + scm:git:git@github.com:toolisticon/${project.artifactId}.git + HEAD + + + + + README + https://github.com/toolisticon/${project.artifactId} + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + Jan Galinski + + Product Owner + Developer + + Holisticon AG + https://holisticon.de + + + diff --git a/src/main/kotlin/KotlinLibTemplate.kt b/src/main/kotlin/KotlinLibTemplate.kt new file mode 100644 index 0000000..c27f346 --- /dev/null +++ b/src/main/kotlin/KotlinLibTemplate.kt @@ -0,0 +1,7 @@ +package io.toolisticon.git + +object KotlinLibTemplate { + + // just a placeholder + +} diff --git a/src/main/resources/.gitkeep b/src/main/resources/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/test/java/.gitkeep b/src/test/java/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/test/kotlin/KotlinLibTemplateTest.kt b/src/test/kotlin/KotlinLibTemplateTest.kt new file mode 100644 index 0000000..f836a50 --- /dev/null +++ b/src/test/kotlin/KotlinLibTemplateTest.kt @@ -0,0 +1,12 @@ +package io.toolisticon.git + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class KotlinLibTemplateTest { + + @Test + internal fun `dummy test`() { + assertThat(1 + 1).isEqualTo(2) + } +} diff --git a/src/test/resources/.gitkeep b/src/test/resources/.gitkeep new file mode 100644 index 0000000..e69de29 From 91917c040d8e9f59cc835d944559c685821cc8ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Sep 2021 20:25:43 +0000 Subject: [PATCH 02/30] Bump maven-enforcer-plugin from 3.0.0-M3 to 3.0.0 Bumps [maven-enforcer-plugin](https://github.com/apache/maven-enforcer) from 3.0.0-M3 to 3.0.0. - [Release notes](https://github.com/apache/maven-enforcer/releases) - [Commits](https://github.com/apache/maven-enforcer/compare/enforcer-3.0.0-M3...enforcer-3.0.0) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-enforcer-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 795754e..e2958a5 100644 --- a/pom.xml +++ b/pom.xml @@ -435,7 +435,7 @@ org.apache.maven.plugins maven-enforcer-plugin - 3.0.0-M3 + 3.0.0 enforce-maven From aa4a6969477ac99173fe62e03c7d68d388a05631 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Sep 2021 20:25:45 +0000 Subject: [PATCH 03/30] Bump maven-javadoc-plugin from 3.3.0 to 3.3.1 Bumps [maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) from 3.3.0 to 3.3.1. - [Release notes](https://github.com/apache/maven-javadoc-plugin/releases) - [Commits](https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.3.0...maven-javadoc-plugin-3.3.1) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-javadoc-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 795754e..2ab01db 100644 --- a/pom.xml +++ b/pom.xml @@ -297,7 +297,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.3.0 + 3.3.1 attach-javadocs From 262fa7299c45126a33ec4b350b76f9149383e9b8 Mon Sep 17 00:00:00 2001 From: Jan Galinski Date: Mon, 13 Sep 2021 22:39:37 +0200 Subject: [PATCH 04/30] setup io.toolisticon.lib/krid coordinates --- .gitattributes | 6 ++++++ README.md | 10 +++++----- pom.xml | 4 ++-- src/main/kotlin/KotlinLibTemplate.kt | 7 ------- src/main/kotlin/Krids.kt | 7 +++++++ .../kotlin/{KotlinLibTemplateTest.kt => KridsTest.kt} | 4 ++-- 6 files changed, 22 insertions(+), 16 deletions(-) create mode 100644 .gitattributes delete mode 100644 src/main/kotlin/KotlinLibTemplate.kt create mode 100644 src/main/kotlin/Krids.kt rename src/test/kotlin/{KotlinLibTemplateTest.kt => KridsTest.kt} (71%) diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..00a51af --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# These are explicitly windows files and should use crlf +*.bat text eol=crlf + diff --git a/README.md b/README.md index ec79ff5..2b04b06 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ -# kotlin-lib-template +# krid -toolisticon template repository +Your one-stop library to support two dimensional kotlin grids. -[![Build Status](https://github.com/toolisticon/kotlin-lib-template/workflows/Development%20branches/badge.svg)](https://github.com/toolisticon/kotlin-lib-template/actions) +[![Build Status](https://github.com/toolisticon/krid/workflows/Development%20branches/badge.svg)](https://github.com/toolisticon/krid/actions) [![sponsored](https://img.shields.io/badge/sponsoredBy-Holisticon-RED.svg)](https://holisticon.de/) -[![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.toolisticon.git/kotlin-lib-template/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.toolisticon.git/kotlin-lib-template) +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.toolisticon.lib/krid/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.toolisticon.lib/krid) ## Idea -This repository is a **template repository** designed to be a copy&paste quick start for your next toolisticon kotlin lib project. +`TODO()` diff --git a/pom.xml b/pom.xml index 564e72c..f29d834 100644 --- a/pom.xml +++ b/pom.xml @@ -2,8 +2,8 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - io.toolisticon.git - kotlin-lib-template + io.toolisticon.lib + krid 0.0.1-SNAPSHOT ${project.artifactId} https://github.com/toolisticon/${project.artifactId}/ diff --git a/src/main/kotlin/KotlinLibTemplate.kt b/src/main/kotlin/KotlinLibTemplate.kt deleted file mode 100644 index c27f346..0000000 --- a/src/main/kotlin/KotlinLibTemplate.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.toolisticon.git - -object KotlinLibTemplate { - - // just a placeholder - -} diff --git a/src/main/kotlin/Krids.kt b/src/main/kotlin/Krids.kt new file mode 100644 index 0000000..a362df9 --- /dev/null +++ b/src/main/kotlin/Krids.kt @@ -0,0 +1,7 @@ +package io.toolisticon.lib + +object Krids { + + // just a placeholder + +} diff --git a/src/test/kotlin/KotlinLibTemplateTest.kt b/src/test/kotlin/KridsTest.kt similarity index 71% rename from src/test/kotlin/KotlinLibTemplateTest.kt rename to src/test/kotlin/KridsTest.kt index f836a50..7a849be 100644 --- a/src/test/kotlin/KotlinLibTemplateTest.kt +++ b/src/test/kotlin/KridsTest.kt @@ -1,9 +1,9 @@ -package io.toolisticon.git +package io.toolisticon.lib import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test -internal class KotlinLibTemplateTest { +internal class KridsTest { @Test internal fun `dummy test`() { From b343575e523b013d3b5cc3ca166df3fcaeb559de Mon Sep 17 00:00:00 2001 From: Jan Galinski Date: Tue, 14 Sep 2021 11:22:29 +0200 Subject: [PATCH 05/30] wip --- README.md | 6 ++ src/main/kotlin/AbstractKrid.kt | 15 ++++ src/main/kotlin/Krid.kt | 21 +++++ src/main/kotlin/KridExt.kt | 4 + src/main/kotlin/Krids.kt | 58 ++++++++++++- src/main/kotlin/model/Cell.kt | 32 ++++++++ src/main/kotlin/model/CellValue.kt | 13 +++ src/main/kotlin/model/Columns.kt | 5 ++ src/main/kotlin/model/Dimension.kt | 29 +++++++ src/main/kotlin/model/Rows.kt | 7 ++ src/test/kotlin/KridTest.kt | 25 ++++++ src/test/kotlin/KridsTest.kt | 65 ++++++++++++++- src/test/kotlin/_test/BooleanKridHelper.kt | 30 +++++++ src/test/kotlin/model/CellTest.kt | 94 ++++++++++++++++++++++ src/test/kotlin/model/CellValueTest.kt | 16 ++++ src/test/kotlin/model/DimensionTest.kt | 49 +++++++++++ src/test/kotlin/model/RowsTest.kt | 16 ++++ 17 files changed, 482 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/AbstractKrid.kt create mode 100644 src/main/kotlin/Krid.kt create mode 100644 src/main/kotlin/KridExt.kt create mode 100644 src/main/kotlin/model/Cell.kt create mode 100644 src/main/kotlin/model/CellValue.kt create mode 100644 src/main/kotlin/model/Columns.kt create mode 100644 src/main/kotlin/model/Dimension.kt create mode 100644 src/main/kotlin/model/Rows.kt create mode 100644 src/test/kotlin/KridTest.kt create mode 100644 src/test/kotlin/_test/BooleanKridHelper.kt create mode 100644 src/test/kotlin/model/CellTest.kt create mode 100644 src/test/kotlin/model/CellValueTest.kt create mode 100644 src/test/kotlin/model/DimensionTest.kt create mode 100644 src/test/kotlin/model/RowsTest.kt diff --git a/README.md b/README.md index 2b04b06..cde6a3b 100644 --- a/README.md +++ b/README.md @@ -10,3 +10,9 @@ Your one-stop library to support two dimensional kotlin grids. ## Idea `TODO()` + + +## Design + +This lib is designed to be as usable as possible, without being too invasive to your code base and dependency tree. Consequentially, it does not require any transitive dependencies. +It just uses the core features provided by **JDK 11.x** and **Kotlin 1.5.x**. diff --git a/src/main/kotlin/AbstractKrid.kt b/src/main/kotlin/AbstractKrid.kt new file mode 100644 index 0000000..46f22f8 --- /dev/null +++ b/src/main/kotlin/AbstractKrid.kt @@ -0,0 +1,15 @@ +package io.toolisticon.lib.krid + +import io.toolisticon.lib.krid.model.Cell +import io.toolisticon.lib.krid.model.Dimension + +abstract class AbstractKrid { + abstract val dimension: Dimension + abstract val emptyElement: E + + abstract fun isEmpty() : Boolean + abstract operator fun get(x:Int, y:Int) : E +} + +internal val AbstractKrid.isEmptyElement: (E) -> Boolean get() = { it == emptyElement } +operator fun AbstractKrid.get(cell: Cell) = get(cell.x, cell.y) diff --git a/src/main/kotlin/Krid.kt b/src/main/kotlin/Krid.kt new file mode 100644 index 0000000..ec0a2b4 --- /dev/null +++ b/src/main/kotlin/Krid.kt @@ -0,0 +1,21 @@ +package io.toolisticon.lib.krid + +import io.toolisticon.lib.krid.model.Dimension + +data class Krid( + override val dimension: Dimension, + override val emptyElement: E, + private val list: List +) : AbstractKrid() { + + init { + require(dimension.size == list.size) + } + + override fun get(x: Int, y: Int): E = list[requireInRows(y) * dimension.width + requireInColumns(x)] + + override fun isEmpty(): Boolean = list.all { isEmptyElement(it) } + + private fun requireInRows(index: Int): Int = index.apply { require(index in dimension.rowRange) { "$this has to be in ${dimension.rowRange}." } } + private fun requireInColumns(index: Int): Int = index.apply { require(this in dimension.columnRange) { "$this has to be in ${dimension.columnRange}." } } +} diff --git a/src/main/kotlin/KridExt.kt b/src/main/kotlin/KridExt.kt new file mode 100644 index 0000000..d5b3fbb --- /dev/null +++ b/src/main/kotlin/KridExt.kt @@ -0,0 +1,4 @@ +package io.toolisticon.lib.krid + +object KridExt { +} diff --git a/src/main/kotlin/Krids.kt b/src/main/kotlin/Krids.kt index a362df9..b2cc5bb 100644 --- a/src/main/kotlin/Krids.kt +++ b/src/main/kotlin/Krids.kt @@ -1,7 +1,61 @@ -package io.toolisticon.lib +package io.toolisticon.lib.krid +import io.toolisticon.lib.krid.model.Cell +import io.toolisticon.lib.krid.model.CellValue +import io.toolisticon.lib.krid.model.Dimension + +/** + * Core Utility class. Provides factory methods to create krid instances and + * helper methods and extensions for convenient usage. + */ object Krids { - // just a placeholder + fun krid(emptyElement: E) = krid(1, 1, emptyElement) + + fun krid(width: Int, height: Int, emptyElement: E) = krid(width, height, emptyElement) { _, _ -> emptyElement } + + fun krid(width: Int, height: Int, emptyElement: E, initialize: (Int, Int) -> E): Krid { + val cellOf = indexToCell(width) + return Krid( + dimension = Dimension(width, height), + emptyElement = emptyElement, + list = List(size = width * height) { index -> + cellOf(index).let { initialize(it.x, it.y) } + } + ) + } + + /** + * Creates Krid from given String. + */ + fun krid(string: String, emptyElement: Char = '.'): Krid = krid(string, emptyElement) { it } + + + fun krid(string: String, emptyElement: E, parse: (Char) -> E): Krid = krid( + rows = string.lines().map { it.trim() } + .filterNot { it.isEmpty() } + .map { it.map(parse) }, + emptyElement = emptyElement + ) + + fun krid(rows: List>, emptyElement: E): Krid { + require(rows.isNotEmpty()) { "rows must not be empty: $rows" } + require(rows.none { it.isEmpty() }) { "no rows must be empty: $rows" } + require(rows.maxOf { it.size } == rows.minOf { it.size }) { "all rows must have same size: $rows" } + + return Krid( + dimension = Dimension(width = rows[0].size, height = rows.size), + emptyElement = emptyElement, + list = rows.fold, List>(listOf()) { list, row -> list + row }.toList() + ) + } + + fun cell(x: Int, y: Int) = Cell(x, y) + fun cell(x: Int, y: Int, value: E) = CellValue(x, y, value) + + fun indexToCell(width: Int): (Int) -> Cell = { Cell(it / width, it % width) } + fun cellToIndex(width: Int): (Cell) -> Int = { it.x * width + it.y } + + fun Pair.toCell() = cell(first, second) } diff --git a/src/main/kotlin/model/Cell.kt b/src/main/kotlin/model/Cell.kt new file mode 100644 index 0000000..335b52b --- /dev/null +++ b/src/main/kotlin/model/Cell.kt @@ -0,0 +1,32 @@ +package io.toolisticon.lib.krid.model + +/** + * A Cell represents a (x,y)-coordinate inside a Krid. + */ +data class Cell(val x: Int, val y: Int) : Comparable { + + operator fun plus(other: Cell): Cell = copy(x = x + other.x, y = y + other.y) + + operator fun minus(other: Cell): Cell = copy(x = x - other.x, y = y - other.y) + + /** + * A cell is LT when it is up and/or left, RT when it is down and/or right, EQ if same + * ``` + * 00 01 + * 10 11 + * ``` + * + * * 00 EQ 00; 00 LT 01,10,11 + * * 01 GT 00; 01 EQ 01; 01 LT 00,10 + * * 10 EQ 10; 10 GT 00; 10 LT 01,11 + * * 11 EQ 11; 11 GT 00,01,10 + * + * return `0` if EQ, `-1` if LT, `+1` if GT + */ + override fun compareTo(other: Cell): Int = when { + this == other -> 0 + (x > other.x && y >= other.y) || + (y > other.y && x >= other.x) -> +1 + else -> -1 + } +} diff --git a/src/main/kotlin/model/CellValue.kt b/src/main/kotlin/model/CellValue.kt new file mode 100644 index 0000000..508037c --- /dev/null +++ b/src/main/kotlin/model/CellValue.kt @@ -0,0 +1,13 @@ +package io.toolisticon.lib.krid.model + +data class CellValue(val x: Int, val y: Int, val value: E) { + companion object { + val CellValue<*>.cell: Cell get() = Cell(x = x, y = y) + } + + operator fun plus(cell: Cell) : CellValue = (this.cell + cell).let { + copy(x = it.x, y=it.y) + } +} + + diff --git a/src/main/kotlin/model/Columns.kt b/src/main/kotlin/model/Columns.kt new file mode 100644 index 0000000..6c0f647 --- /dev/null +++ b/src/main/kotlin/model/Columns.kt @@ -0,0 +1,5 @@ +package io.toolisticon.lib.krid.model + +data class Column(val index: Int, val values: List) : List by values + +data class Columns(val columns: List>) : List> by columns diff --git a/src/main/kotlin/model/Dimension.kt b/src/main/kotlin/model/Dimension.kt new file mode 100644 index 0000000..cd26142 --- /dev/null +++ b/src/main/kotlin/model/Dimension.kt @@ -0,0 +1,29 @@ +package io.toolisticon.lib.krid.model + +/** + * Represents width and height of a [io.toolisticon.lib.krid.Krid]. + */ +data class Dimension( + val width: Int, + val height: Int +) { + companion object { + operator fun invoke(upperLeft: Cell, lowerRight: Cell): Dimension { + require(lowerRight >= upperLeft) { "$upperLeft has to be right and/or down from $lowerRight." } + return Dimension( + width = lowerRight.x - upperLeft.x + 1, + height = lowerRight.y - upperLeft.y + 1 + ) + } + } + + init { + require(width > 0 && height > 0) { "Values have to be positive numbers (was: $this)." } + } + + val size = width * height + val rowRange: IntRange = 0 until height + val columnRange: IntRange = 0 until width + val pair = width to height +} + diff --git a/src/main/kotlin/model/Rows.kt b/src/main/kotlin/model/Rows.kt new file mode 100644 index 0000000..bd52cf6 --- /dev/null +++ b/src/main/kotlin/model/Rows.kt @@ -0,0 +1,7 @@ +package io.toolisticon.lib.krid.model + +data class Row(val index: Int, val values: List) : List by values + +data class Rows(val rows: List>) : List> by rows { + constructor(vararg rows: Row) : this(rows = rows.toList()) +} diff --git a/src/test/kotlin/KridTest.kt b/src/test/kotlin/KridTest.kt new file mode 100644 index 0000000..baf996c --- /dev/null +++ b/src/test/kotlin/KridTest.kt @@ -0,0 +1,25 @@ +package io.toolisticon.lib.krid + +import io.toolisticon.lib.krid.Krids.krid +import io.toolisticon.lib.krid.model.Dimension +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.jupiter.api.Test + +internal class KridTest { + + @Test + internal fun `init fails - dimension#size ne list#size`() { + assertThatThrownBy { Krid(Dimension(4, 5), true, List(40) { false }) } + .isInstanceOf(IllegalArgumentException::class.java) + } + + @Test + internal fun `create empty with dimension`() { + val krid : Krid = krid(4,3, null) + assertThat(krid.isEmpty()).isTrue + } + + + +} diff --git a/src/test/kotlin/KridsTest.kt b/src/test/kotlin/KridsTest.kt index 7a849be..3fe0810 100644 --- a/src/test/kotlin/KridsTest.kt +++ b/src/test/kotlin/KridsTest.kt @@ -1,12 +1,75 @@ -package io.toolisticon.lib +package io.toolisticon.lib.krid +import io.toolisticon.lib.krid.Krids.cellToIndex +import io.toolisticon.lib.krid.Krids.indexToCell +import io.toolisticon.lib.krid.Krids.krid +import io.toolisticon.lib.krid.Krids.toCell +import io.toolisticon.lib.krid.model.Cell import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.Arguments.arguments +import org.junit.jupiter.params.provider.MethodSource +import java.util.stream.Stream internal class KridsTest { + companion object { + /** + * width = 3, height = 2 + * 0,t = 0,0,t + * 1,f = 0,1,f + * 2,t = 0,2,t + * + * 3,f = 1,0,f + * 4,t = 1,1,t + * 5,f = 1,2,t + */ + @JvmStatic + fun `index to cell and back - parameters`(): Stream = Stream.of( + arguments(3, 0, 0 to 0), + arguments(3, 1, 0 to 1), + arguments(3, 2, 0 to 2), + arguments(3, 3, 1 to 0), + arguments(3, 4, 1 to 1), + arguments(3, 5, 1 to 2), + ) + } + + @ParameterizedTest + @MethodSource("index to cell and back - parameters") + internal fun `index to cell and back`(width: Int, index: Int, pair: Pair) { + val cell = pair.toCell() + val toCell = indexToCell(width) + val toIndex = cellToIndex(width) + + assertThat(toCell(index)).isEqualTo(cell) + assertThat(toIndex(cell)).isEqualTo(index) + } @Test internal fun `dummy test`() { assertThat(1 + 1).isEqualTo(2) } + + @Test + internal fun `create krid(char)`() { + val krid = krid(""" + .... + .abc + .def + """.trimIndent()) + + assertThat(krid[2,1]).isEqualTo('b') + } + + @Test + internal fun `init single empty`() { + val krid: Krid = Krids.krid(null) + + assertThat(krid.dimension.pair).isEqualTo(1 to 1) + assertThat(krid.emptyElement).isEqualTo(null) + assertThat(krid.isEmpty()).isTrue + } + } diff --git a/src/test/kotlin/_test/BooleanKridHelper.kt b/src/test/kotlin/_test/BooleanKridHelper.kt new file mode 100644 index 0000000..932f2be --- /dev/null +++ b/src/test/kotlin/_test/BooleanKridHelper.kt @@ -0,0 +1,30 @@ +package io.toolisticon.lib.krid._test + +import io.toolisticon.lib.krid.Krids.krid + +object BooleanKridHelper { + + fun booleanKrid(string: String) = krid( + string, + null, + parseChar + ) + + val parseChar: (Char) -> Boolean? = { + when (it) { + 't' -> true + 'f' -> false + '.' -> null + else -> throw IllegalArgumentException("Cannot map '$it'") + } + } + + val toChar01: (Boolean?) -> Char = { + when (it) { + true -> '1' + false -> '0' + else -> '.' + } + } + +} diff --git a/src/test/kotlin/model/CellTest.kt b/src/test/kotlin/model/CellTest.kt new file mode 100644 index 0000000..2e8ebb3 --- /dev/null +++ b/src/test/kotlin/model/CellTest.kt @@ -0,0 +1,94 @@ +package io.toolisticon.lib.krid.model + +import io.toolisticon.lib.krid.Krids.cell +import io.toolisticon.lib.krid.model.CellTest.Companion.Comparison.* +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.Arguments.arguments +import org.junit.jupiter.params.provider.MethodSource +import java.util.stream.Stream + +internal class CellTest { + companion object { + enum class Comparison { LT, EQ, GT } + + /** + * 00 01 + * 10 11 + */ + @JvmStatic + fun `compare cells - parameters`(): Stream = Stream.of( + // 00 EQ 00 + arguments(cell(0, 0), cell(0, 0), EQ), + // 00 LT 01 + arguments(cell(0, 0), cell(0, 1), LT), + // 00 LT 10 + arguments(cell(0, 0), cell(1, 0), LT), + // 00 LT 11 + arguments(cell(0, 0), cell(1, 1), LT), + + // 01 GT 00 + arguments(cell(0, 1), cell(0, 0), GT), + // 01 EQ 01 + arguments(cell(0, 1), cell(0, 1), EQ), + // 01 LT 10 + arguments(cell(0, 1), cell(1, 0), LT), + // 01 LT 11 + arguments(cell(0, 1), cell(1, 1), LT), + + // 10 GT 00 + arguments(cell(1, 0), cell(0, 0), GT), + // 10 LT 01 + arguments(cell(1, 0), cell(0, 1), LT), + // 10 LT 10 + arguments(cell(1, 0), cell(1, 0), EQ), + // 10 LT 11 + arguments(cell(1, 0), cell(1, 1), LT), + + // 11 GT 00 + arguments(cell(1, 1), cell(0, 0), GT), + // 11 GT 01 + arguments(cell(1, 1), cell(0, 1), GT), + // 11 GT 10 + arguments(cell(1, 1), cell(1, 0), GT), + // 11 GT 11 + arguments(cell(1, 1), cell(1, 1), EQ), + ) + + + } + + @ParameterizedTest + @MethodSource("compare cells - parameters") + internal fun `compare cells`(c1: Cell, c2: Cell, expected: Comparison) { + when (expected) { + LT -> assertThat(c1).isLessThan(c2) + EQ -> assertThat(c1).isEqualByComparingTo(c2) + GT -> assertThat(c1).isGreaterThan(c2) + } + } + + + + @Test + internal fun `create a cell from pair`() { + val cell = cell(2, 2) + + println(cell) + } + + @Test + internal fun `sum two cells - add x and y coordinates`() { + assertThat(cell(2, 3) + cell(5, 7)) + .isEqualTo(cell(7, 10)) + } + + @Test + internal fun `difference of two cells - subtract x and y coordinates`() { + assertThat(cell(2, 3) - cell(1, 2)) + .isEqualTo(cell(1, 1)) + } + +} diff --git a/src/test/kotlin/model/CellValueTest.kt b/src/test/kotlin/model/CellValueTest.kt new file mode 100644 index 0000000..590500f --- /dev/null +++ b/src/test/kotlin/model/CellValueTest.kt @@ -0,0 +1,16 @@ +package io.toolisticon.lib.krid.model + +import io.toolisticon.lib.krid.Krids.cell +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + + +internal class CellValueTest { + + @Test + internal fun `add cell`() { + val cell = cell(2, 3, true) + + assertThat(cell + cell(5, 6)).isEqualTo(cell(7, 9, true)) + } +} diff --git a/src/test/kotlin/model/DimensionTest.kt b/src/test/kotlin/model/DimensionTest.kt new file mode 100644 index 0000000..5573ae9 --- /dev/null +++ b/src/test/kotlin/model/DimensionTest.kt @@ -0,0 +1,49 @@ +package io.toolisticon.lib.krid.model + +import io.toolisticon.lib.krid.Krids.cell +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.jupiter.api.Test + +internal class DimensionTest { + + @Test + internal fun `create with width and height`() { + val dimension = Dimension(5, 4) + assertThat(dimension.width).isEqualTo(5) + assertThat(dimension.height).isEqualTo(4) + assertThat(dimension.size).isEqualTo(20) + assertThat(dimension.rowRange).isEqualTo(0..3) + assertThat(dimension.columnRange).isEqualTo(0..4) + } + + @Test + internal fun `create from two cells - success`() { + val dimension = Dimension(cell(1, 0), cell(2, 3)) + assertThat(dimension.width).isEqualTo(2) + assertThat(dimension.height).isEqualTo(4) + assertThat(dimension.size).isEqualTo(8) + } + + @Test + internal fun `create from two cells - failure`() { + assertThatThrownBy { Dimension(cell(2, 3), cell(1, 0)) } + .isInstanceOf(IllegalArgumentException::class.java) + .hasMessage("Cell(x=2, y=3) has to be right and/or down from Cell(x=1, y=0).") + } + + @Test + internal fun `fails when values are not gt 0`() { + assertThatThrownBy { Dimension(width = 0, height = 1) } + .isInstanceOf(IllegalArgumentException::class.java) + .hasMessage("Values have to be positive numbers (was: Dimension(width=0, height=1)).") + + assertThatThrownBy { Dimension(width = 1, height = 0) } + .isInstanceOf(IllegalArgumentException::class.java) + .hasMessage("Values have to be positive numbers (was: Dimension(width=1, height=0)).") + + assertThatThrownBy { Dimension(width = 0, height = 0) } + .isInstanceOf(IllegalArgumentException::class.java) + .hasMessage("Values have to be positive numbers (was: Dimension(width=0, height=0)).") + } +} diff --git a/src/test/kotlin/model/RowsTest.kt b/src/test/kotlin/model/RowsTest.kt new file mode 100644 index 0000000..9662c09 --- /dev/null +++ b/src/test/kotlin/model/RowsTest.kt @@ -0,0 +1,16 @@ +package io.toolisticon.lib.krid.model + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + + +internal class RowsTest { + + @Test + internal fun `create rows`() { + val row = Row(0, listOf(true)) + val rows: Rows = Rows(row) + + assertThat(rows[0]).isEqualTo(row) + } +} From 230c4797d7b18c864412c17358113e38e626baba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Sep 2021 06:16:41 +0000 Subject: [PATCH 06/30] Bump dokka-maven-plugin from 1.5.0 to 1.5.30 Bumps [dokka-maven-plugin](https://github.com/Kotlin/dokka) from 1.5.0 to 1.5.30. - [Release notes](https://github.com/Kotlin/dokka/releases) - [Commits](https://github.com/Kotlin/dokka/compare/v1.5.0...v1.5.30) --- updated-dependencies: - dependency-name: org.jetbrains.dokka:dokka-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f29d834..f3e45a4 100644 --- a/pom.xml +++ b/pom.xml @@ -272,7 +272,7 @@ org.jetbrains.dokka dokka-maven-plugin - 1.5.0 + 1.5.30 package From ebb9d6936e1561efda21cec47b2e621520536842 Mon Sep 17 00:00:00 2001 From: Jan Galinski Date: Fri, 17 Sep 2021 00:09:23 +0200 Subject: [PATCH 07/30] move directions --- src/main/kotlin/AbstractKrid.kt | 16 ++- src/main/kotlin/Krid.kt | 26 +++- src/main/kotlin/KridExt.kt | 4 - src/main/kotlin/Krids.kt | 32 ++++- src/main/kotlin/model/Cell.kt | 32 ++++- src/main/kotlin/model/Columns.kt | 6 + src/main/kotlin/model/Dimension.kt | 14 ++- src/main/kotlin/model/Rows.kt | 6 + src/main/kotlin/model/Steps.kt | 85 +++++++++++++ src/test/kotlin/KridTest.kt | 57 ++++++++- src/test/kotlin/KridsTest.kt | 69 +++++----- src/test/kotlin/_test/ArgumentConverters.kt | 27 ++++ src/test/kotlin/_test/BooleanKridHelper.kt | 4 + src/test/kotlin/model/CellTest.kt | 132 +++++++++++--------- src/test/kotlin/model/DimensionTest.kt | 1 + src/test/kotlin/model/StepsTest.kt | 117 +++++++++++++++++ 16 files changed, 521 insertions(+), 107 deletions(-) delete mode 100644 src/main/kotlin/KridExt.kt create mode 100644 src/main/kotlin/model/Steps.kt create mode 100644 src/test/kotlin/_test/ArgumentConverters.kt create mode 100644 src/test/kotlin/model/StepsTest.kt diff --git a/src/main/kotlin/AbstractKrid.kt b/src/main/kotlin/AbstractKrid.kt index 46f22f8..6806e87 100644 --- a/src/main/kotlin/AbstractKrid.kt +++ b/src/main/kotlin/AbstractKrid.kt @@ -1,7 +1,7 @@ package io.toolisticon.lib.krid -import io.toolisticon.lib.krid.model.Cell -import io.toolisticon.lib.krid.model.Dimension +import io.toolisticon.lib.krid.Krids.indexToCell +import io.toolisticon.lib.krid.model.* abstract class AbstractKrid { abstract val dimension: Dimension @@ -9,7 +9,19 @@ abstract class AbstractKrid { abstract fun isEmpty() : Boolean abstract operator fun get(x:Int, y:Int) : E + + fun rows() : Rows = Rows(dimension.rowRange.map(this::row)) + fun columns() : Columns = Columns(dimension.columnRange.map(this::column)) + + abstract fun row(index: Int) : Row + abstract fun column(index: Int) : Column + + abstract fun iterator() : Iterator> } internal val AbstractKrid.isEmptyElement: (E) -> Boolean get() = { it == emptyElement } operator fun AbstractKrid.get(cell: Cell) = get(cell.x, cell.y) + +fun AbstractKrid.ascii(toChar: (E) -> Char? = { it?.toString()?.first() ?: '.' }): String = this.rows().joinToString(separator = "\n") { + it.map { e -> toChar(e) ?: '.' }.joinToString(separator = "") +} diff --git a/src/main/kotlin/Krid.kt b/src/main/kotlin/Krid.kt index ec0a2b4..0ca0734 100644 --- a/src/main/kotlin/Krid.kt +++ b/src/main/kotlin/Krid.kt @@ -1,6 +1,11 @@ package io.toolisticon.lib.krid +import io.toolisticon.lib.krid.Krids.cell +import io.toolisticon.lib.krid.Krids.indexToCell +import io.toolisticon.lib.krid.model.CellValue +import io.toolisticon.lib.krid.model.Column import io.toolisticon.lib.krid.model.Dimension +import io.toolisticon.lib.krid.model.Row data class Krid( override val dimension: Dimension, @@ -12,10 +17,27 @@ data class Krid( require(dimension.size == list.size) } + private val indexToCell = indexToCell(dimension.width) + private val rowCache: MutableMap> = mutableMapOf() + private val columnCache: MutableMap> = mutableMapOf() + private fun requireInRows(index: Int): Int = index.apply { require(index in dimension.rowRange) { "$this has to be in ${dimension.rowRange}." } } + private fun requireInColumns(index: Int): Int = index.apply { require(this in dimension.columnRange) { "$this has to be in ${dimension.columnRange}." } } + override fun get(x: Int, y: Int): E = list[requireInRows(y) * dimension.width + requireInColumns(x)] override fun isEmpty(): Boolean = list.all { isEmptyElement(it) } - private fun requireInRows(index: Int): Int = index.apply { require(index in dimension.rowRange) { "$this has to be in ${dimension.rowRange}." } } - private fun requireInColumns(index: Int): Int = index.apply { require(this in dimension.columnRange) { "$this has to be in ${dimension.columnRange}." } } + override fun row(index: Int): Row = rowCache.computeIfAbsent( + requireInRows(index) + ) { Row(index, dimension.columnRange.map { get(it, index) }) } + + override fun column(index: Int): Column = columnCache.computeIfAbsent( + requireInColumns(index) + ) { Column(index, dimension.rowRange.map { get(index, it) }) } + + override fun iterator(): Iterator> = list + .mapIndexed { index, e -> cell(indexToCell(index), e) } + .iterator() + } + diff --git a/src/main/kotlin/KridExt.kt b/src/main/kotlin/KridExt.kt deleted file mode 100644 index d5b3fbb..0000000 --- a/src/main/kotlin/KridExt.kt +++ /dev/null @@ -1,4 +0,0 @@ -package io.toolisticon.lib.krid - -object KridExt { -} diff --git a/src/main/kotlin/Krids.kt b/src/main/kotlin/Krids.kt index b2cc5bb..5518b1b 100644 --- a/src/main/kotlin/Krids.kt +++ b/src/main/kotlin/Krids.kt @@ -10,10 +10,19 @@ import io.toolisticon.lib.krid.model.Dimension */ object Krids { + /** + * Creates a new [Krid] with [Dimension](1,1) containing just the emptyElement value. + */ fun krid(emptyElement: E) = krid(1, 1, emptyElement) + /** + * Creates a new [Krid] with [Dimension](width, height), all cells initialized with the emptyElement value. + */ fun krid(width: Int, height: Int, emptyElement: E) = krid(width, height, emptyElement) { _, _ -> emptyElement } + /** + * Creates a new [Krid] with [Dimension](width, height), all cells initialized by the given BiFunction. + */ fun krid(width: Int, height: Int, emptyElement: E, initialize: (Int, Int) -> E): Krid { val cellOf = indexToCell(width) return Krid( @@ -52,10 +61,29 @@ object Krids { fun cell(x: Int, y: Int) = Cell(x, y) fun cell(x: Int, y: Int, value: E) = CellValue(x, y, value) + fun cell(cell: Cell, value: E) = cell(cell.x, cell.y, value) + + /** + * Given the width of the Krid, this calculates the Cell(x,y) coordinates based on the list index. + */ + fun indexToCell(width: Int): (Int) -> Cell = { Cell(it % width, it / width) } - fun indexToCell(width: Int): (Int) -> Cell = { Cell(it / width, it % width) } - fun cellToIndex(width: Int): (Cell) -> Int = { it.x * width + it.y } + /** + * Given the width of the Krid, this calculates the the list index value based on the Cell(x,y) coordinates. + */ + fun cellToIndex(width: Int): (Cell) -> Int = { it.y * width + it.x } + /** + * Transforms a pair of ints to a type safe [Cell]. + */ fun Pair.toCell() = cell(first, second) + fun List>.toCells() = map { it.toCell() } + + val Dimension.pair: Pair get() = width to height +// +// fun Krid.iterator(): Iterator> = sequence { +// val cellOf = indexToCell(dimension.width) +// list.forEachIndexed { i, e -> yield(CellValue(cellOf(i), e)) } +// }.iterator() } diff --git a/src/main/kotlin/model/Cell.kt b/src/main/kotlin/model/Cell.kt index 335b52b..25a314f 100644 --- a/src/main/kotlin/model/Cell.kt +++ b/src/main/kotlin/model/Cell.kt @@ -5,9 +5,37 @@ package io.toolisticon.lib.krid.model */ data class Cell(val x: Int, val y: Int) : Comparable { + /** + * List of orthogonal adjacent cells, starting with 12° clock (`UP`). + */ + val orthogonalAdjacent: List by lazy { + adjacent(Direction.UP, Direction.RIGHT, Direction.DOWN, Direction.LEFT) + } + + /** + * List of adjacent cells, starting with 12° clock (`UP`). + */ + val adjacent: List by lazy { + adjacent(*Direction.values()) + } + + /** + * Return the cell you get when you take the given step. + */ + operator fun invoke(step: StepFn): Cell = step(this) + + /** + * Create a new cell with coordinates `x=c1.x + c2.x, y=c1.y + c2.y`. + */ operator fun plus(other: Cell): Cell = copy(x = x + other.x, y = y + other.y) - operator fun minus(other: Cell): Cell = copy(x = x - other.x, y = y - other.y) + /** + * Create a new cell with coordinates `x=c1.x - c2.x, y=c1.y - c2.y`. + */ + operator fun minus(other: Cell): Cell { + require(this >= other) { "$this has to be >= $other." } + return copy(x = x - other.x, y = y - other.y) + } /** * A cell is LT when it is up and/or left, RT when it is down and/or right, EQ if same @@ -29,4 +57,6 @@ data class Cell(val x: Int, val y: Int) : Comparable { (y > other.y && x >= other.x) -> +1 else -> -1 } + + private fun adjacent(vararg directions: Direction): List = directions.map { this(it.singleStep) } } diff --git a/src/main/kotlin/model/Columns.kt b/src/main/kotlin/model/Columns.kt index 6c0f647..ed33101 100644 --- a/src/main/kotlin/model/Columns.kt +++ b/src/main/kotlin/model/Columns.kt @@ -1,5 +1,11 @@ package io.toolisticon.lib.krid.model +/** + * Represents a column (vertical line) in a [io.toolisticon.lib.krid.Krid]. + */ data class Column(val index: Int, val values: List) : List by values +/** + * Represents a collection of columns (vertical lines) in a [io.toolisticon.lib.krid.Krid]. + */ data class Columns(val columns: List>) : List> by columns diff --git a/src/main/kotlin/model/Dimension.kt b/src/main/kotlin/model/Dimension.kt index cd26142..fa5ad34 100644 --- a/src/main/kotlin/model/Dimension.kt +++ b/src/main/kotlin/model/Dimension.kt @@ -21,9 +21,19 @@ data class Dimension( require(width > 0 && height > 0) { "Values have to be positive numbers (was: $this)." } } + /** + * The total area of the 2D grid (width*height), which is also the size of an implementing List. + */ val size = width * height + + /** + * the row indices counting from `0` to `height - 1` + */ val rowRange: IntRange = 0 until height + + /** + * the column indices counting from `0` to `width - 1` + */ val columnRange: IntRange = 0 until width - val pair = width to height -} +} diff --git a/src/main/kotlin/model/Rows.kt b/src/main/kotlin/model/Rows.kt index bd52cf6..cf5bf1a 100644 --- a/src/main/kotlin/model/Rows.kt +++ b/src/main/kotlin/model/Rows.kt @@ -1,7 +1,13 @@ package io.toolisticon.lib.krid.model +/** + * Represents a row (horizontal line) in a [io.toolisticon.lib.krid.Krid]. + */ data class Row(val index: Int, val values: List) : List by values +/** + * Represents a collection of rows (horizontal lines) in a [io.toolisticon.lib.krid.Krid]. + */ data class Rows(val rows: List>) : List> by rows { constructor(vararg rows: Row) : this(rows = rows.toList()) } diff --git a/src/main/kotlin/model/Steps.kt b/src/main/kotlin/model/Steps.kt new file mode 100644 index 0000000..6599008 --- /dev/null +++ b/src/main/kotlin/model/Steps.kt @@ -0,0 +1,85 @@ +package io.toolisticon.lib.krid.model + +import io.toolisticon.lib.krid.Krids.cell + +/** + * Transformation of a cell to another cell, taking [Steps] given directions. + */ +sealed interface StepFn : (Cell) -> Cell { + operator fun plus(other: StepFn): Steps +} + +/** + * Take a number of steps in the given direction. + */ +data class Step(val direction: Direction, val number: Int) : StepFn { + + override fun invoke(start: Cell): Cell = direction.fn(start, number) + + override operator fun plus(other: StepFn): Steps = when (other) { + is Direction -> this + other.singleStep + is Step -> Steps(listOf(this, other)) + is Steps -> Steps(this) + other + } + + override fun toString(): String = "$direction($number)" +} + +/** + * A sequence of steps to take. + */ +data class Steps(private val list: List) : StepFn { + constructor(step: Step) : this(listOf(step)) + + override operator fun plus(other: StepFn): Steps = when (other) { + is Direction -> plus(other.singleStep) + is Step -> copy(list = list + other) + is Steps -> copy(list = list + other.list) + } + + /** + * Returns a sequence of cells reached by the single steps. + */ + fun walk(start:Cell) : Sequence = sequence { + var current = start + yield(current) + list.forEach { + current = it(current) + yield(current) + } + } + + override fun invoke(start: Cell): Cell = walk(start).last() + + override fun toString(): String = list.joinToString(" + ") { it.toString() } +} + +enum class Direction(val fn: (Cell, Int) -> Cell) : StepFn { + UP(fn = { start, num -> cell(start.x, start.y - num) }), + UP_RIGHT(fn = { start, num -> cell(start.x + num, start.y - num) }), + RIGHT(fn = { start, num -> cell(start.x + num, start.y) }), + DOWN_RIGHT(fn = { start, num -> cell(start.x + num, start.y + num) }), + DOWN(fn = { start, num -> cell(start.x, start.y + num) }), + DOWN_LEFT(fn = { start, num -> cell(start.x - num, start.y + num) }), + LEFT(fn = { start, num -> cell(start.x - num, start.y) }), + UP_LEFT(fn = { start, num -> cell(start.x - num, start.y - num) }), + ; + + val singleStep = this(1) + + override operator fun plus(other: StepFn): Steps = when (other) { + is Direction -> singleStep + other.singleStep + is Step, is Steps -> singleStep + other + } + + /** + * Sequence of cells in given direction. + */ + fun beam(cell: Cell, includeStart: Boolean = false): Sequence = generateSequence(if (includeStart) cell else singleStep(cell), singleStep::invoke) + + fun Cell.beam(direction: Direction, includeStart: Boolean = false) = direction.beam(this, includeStart) + fun Cell.take(direction: Direction, num: Int, includeStart: Boolean = false) = beam(direction, includeStart).take(num).toList() + + override fun invoke(cell: Cell) = singleStep(cell) + operator fun invoke(number: Int): Step = Step(this, number) +} diff --git a/src/test/kotlin/KridTest.kt b/src/test/kotlin/KridTest.kt index baf996c..1f7e75f 100644 --- a/src/test/kotlin/KridTest.kt +++ b/src/test/kotlin/KridTest.kt @@ -1,6 +1,8 @@ package io.toolisticon.lib.krid import io.toolisticon.lib.krid.Krids.krid +import io.toolisticon.lib.krid._test.BooleanKridHelper.booleanCellValue +import io.toolisticon.lib.krid._test.BooleanKridHelper.booleanKrid import io.toolisticon.lib.krid.model.Dimension import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy @@ -9,17 +11,68 @@ import org.junit.jupiter.api.Test internal class KridTest { @Test - internal fun `init fails - dimension#size ne list#size`() { + internal fun `init fails - dimension#size != list#size`() { assertThatThrownBy { Krid(Dimension(4, 5), true, List(40) { false }) } .isInstanceOf(IllegalArgumentException::class.java) } @Test internal fun `create empty with dimension`() { - val krid : Krid = krid(4,3, null) + val krid: Krid = krid(4, 3, null) assertThat(krid.isEmpty()).isTrue + assertThat(krid.dimension.width).isEqualTo(4) + assertThat(krid.dimension.height).isEqualTo(3) } + @Test + internal fun `get rows`() { + val krid = booleanKrid( + """ + tff + ftf + fft + """.trimIndent() + ) + assertThat(krid.row(0)).containsExactly( + true, false, false + ) + assertThat(krid.row(2)).containsExactly( + false, false, true + ) + } + @Test + internal fun `get columns`() { + val krid = booleanKrid( + """ + tff + ftt + fft + """.trimIndent() + ) + + assertThat(krid.column(0)).containsExactly( + true, false, false + ) + assertThat(krid.column(2)).containsExactly( + false, true, true + ) + } + + @Test + internal fun `iterate value cells`() { + val krid = booleanKrid(""" + t. + ft + """.trimIndent()) + + assertThat(krid.iterator().asSequence().toList()) + .containsExactly( + booleanCellValue(0,0,true), + booleanCellValue(1,0,null), + booleanCellValue(0,1,false), + booleanCellValue(1,1,true), + ) + } } diff --git a/src/test/kotlin/KridsTest.kt b/src/test/kotlin/KridsTest.kt index 3fe0810..0f97acf 100644 --- a/src/test/kotlin/KridsTest.kt +++ b/src/test/kotlin/KridsTest.kt @@ -3,43 +3,31 @@ package io.toolisticon.lib.krid import io.toolisticon.lib.krid.Krids.cellToIndex import io.toolisticon.lib.krid.Krids.indexToCell import io.toolisticon.lib.krid.Krids.krid -import io.toolisticon.lib.krid.Krids.toCell +import io.toolisticon.lib.krid.Krids.pair +import io.toolisticon.lib.krid._test.CellConverter import io.toolisticon.lib.krid.model.Cell +import io.toolisticon.lib.krid.model.Column +import io.toolisticon.lib.krid.model.Row import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.Arguments -import org.junit.jupiter.params.provider.Arguments.arguments -import org.junit.jupiter.params.provider.MethodSource -import java.util.stream.Stream +import org.junit.jupiter.params.converter.ConvertWith +import org.junit.jupiter.params.provider.CsvSource internal class KridsTest { - companion object { - /** - * width = 3, height = 2 - * 0,t = 0,0,t - * 1,f = 0,1,f - * 2,t = 0,2,t - * - * 3,f = 1,0,f - * 4,t = 1,1,t - * 5,f = 1,2,t - */ - @JvmStatic - fun `index to cell and back - parameters`(): Stream = Stream.of( - arguments(3, 0, 0 to 0), - arguments(3, 1, 0 to 1), - arguments(3, 2, 0 to 2), - arguments(3, 3, 1 to 0), - arguments(3, 4, 1 to 1), - arguments(3, 5, 1 to 2), - ) - } @ParameterizedTest - @MethodSource("index to cell and back - parameters") - internal fun `index to cell and back`(width: Int, index: Int, pair: Pair) { - val cell = pair.toCell() + @CsvSource( + value = [ + "3, 0, 0 to 0", + "3, 1, 1 to 0", + "3, 2, 2 to 0", + "3, 3, 0 to 1", + "3, 4, 1 to 1", + "3, 5, 2 to 1", + ] + ) + internal fun `index to cell and back`(width: Int, index: Int, @ConvertWith(CellConverter::class) cell: Cell) { val toCell = indexToCell(width) val toIndex = cellToIndex(width) @@ -54,13 +42,15 @@ internal class KridsTest { @Test internal fun `create krid(char)`() { - val krid = krid(""" + val krid = krid( + """ .... .abc .def - """.trimIndent()) + """.trimIndent() + ) - assertThat(krid[2,1]).isEqualTo('b') + assertThat(krid[2, 1]).isEqualTo('b') } @Test @@ -70,6 +60,21 @@ internal class KridsTest { assertThat(krid.dimension.pair).isEqualTo(1 to 1) assertThat(krid.emptyElement).isEqualTo(null) assertThat(krid.isEmpty()).isTrue + assertThat(krid.row(0)).isEqualTo(Row(0, listOf(null))) + assertThat(krid.column(0)).isEqualTo(Column(0, listOf(null))) } + @Test + internal fun `create new krid from init function`() { + val krid: Krid = krid(3, 4, null) { x, y -> x == y } + + assertThat(krid[0, 0]).isTrue + assertThat(krid[1, 1]).isTrue + assertThat(krid[2, 3]).isFalse + + assertThat(krid.column(0)).containsExactly(true, false, false, false) + assertThat(krid.column(1)).containsExactly(false, true, false, false) + assertThat(krid.column(2)).containsExactly(false, false, true, false) + + } } diff --git a/src/test/kotlin/_test/ArgumentConverters.kt b/src/test/kotlin/_test/ArgumentConverters.kt new file mode 100644 index 0000000..d22b407 --- /dev/null +++ b/src/test/kotlin/_test/ArgumentConverters.kt @@ -0,0 +1,27 @@ +package io.toolisticon.lib.krid._test + +import io.toolisticon.lib.krid.Krids.cell +import io.toolisticon.lib.krid.model.Cell +import io.toolisticon.lib.krid.model.Direction +import io.toolisticon.lib.krid.model.Step +import org.junit.jupiter.params.converter.TypedArgumentConverter + +class StepConverter : TypedArgumentConverter(String::class.java, Step::class.java) { + private val regex = """(\w+)\((\d+)\)""".toRegex() + + override fun convert(source: String): Step { + val (directionName, numberValue) = requireNotNull(regex.find(source)).destructured + + return Direction.valueOf(directionName.trim()).invoke(numberValue.trim().toInt()) + } + +} + +class CellConverter : TypedArgumentConverter(String::class.java, Cell::class.java) { + + override fun convert(source: String): Cell { + val (x, y) = source.split(" to ") + return cell(x.trim().toInt(), y.trim().toInt()) + } + +} diff --git a/src/test/kotlin/_test/BooleanKridHelper.kt b/src/test/kotlin/_test/BooleanKridHelper.kt index 932f2be..6370c91 100644 --- a/src/test/kotlin/_test/BooleanKridHelper.kt +++ b/src/test/kotlin/_test/BooleanKridHelper.kt @@ -1,6 +1,8 @@ package io.toolisticon.lib.krid._test +import io.toolisticon.lib.krid.Krids.cell import io.toolisticon.lib.krid.Krids.krid +import io.toolisticon.lib.krid.model.CellValue object BooleanKridHelper { @@ -10,6 +12,8 @@ object BooleanKridHelper { parseChar ) + fun booleanCellValue(x:Int, y:Int, value:Boolean?): CellValue = cell(x,y,value) + val parseChar: (Char) -> Boolean? = { when (it) { 't' -> true diff --git a/src/test/kotlin/model/CellTest.kt b/src/test/kotlin/model/CellTest.kt index 2e8ebb3..0a31df4 100644 --- a/src/test/kotlin/model/CellTest.kt +++ b/src/test/kotlin/model/CellTest.kt @@ -1,68 +1,45 @@ package io.toolisticon.lib.krid.model import io.toolisticon.lib.krid.Krids.cell -import io.toolisticon.lib.krid.model.CellTest.Companion.Comparison.* +import io.toolisticon.lib.krid.Krids.toCell +import io.toolisticon.lib.krid.Krids.toCells +import io.toolisticon.lib.krid._test.CellConverter +import io.toolisticon.lib.krid.model.CellTest.Comparison.* import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.Arguments -import org.junit.jupiter.params.provider.Arguments.arguments -import org.junit.jupiter.params.provider.MethodSource -import java.util.stream.Stream +import org.junit.jupiter.params.converter.ConvertWith +import org.junit.jupiter.params.provider.CsvSource internal class CellTest { - companion object { - enum class Comparison { LT, EQ, GT } - - /** - * 00 01 - * 10 11 - */ - @JvmStatic - fun `compare cells - parameters`(): Stream = Stream.of( - // 00 EQ 00 - arguments(cell(0, 0), cell(0, 0), EQ), - // 00 LT 01 - arguments(cell(0, 0), cell(0, 1), LT), - // 00 LT 10 - arguments(cell(0, 0), cell(1, 0), LT), - // 00 LT 11 - arguments(cell(0, 0), cell(1, 1), LT), - - // 01 GT 00 - arguments(cell(0, 1), cell(0, 0), GT), - // 01 EQ 01 - arguments(cell(0, 1), cell(0, 1), EQ), - // 01 LT 10 - arguments(cell(0, 1), cell(1, 0), LT), - // 01 LT 11 - arguments(cell(0, 1), cell(1, 1), LT), - - // 10 GT 00 - arguments(cell(1, 0), cell(0, 0), GT), - // 10 LT 01 - arguments(cell(1, 0), cell(0, 1), LT), - // 10 LT 10 - arguments(cell(1, 0), cell(1, 0), EQ), - // 10 LT 11 - arguments(cell(1, 0), cell(1, 1), LT), - - // 11 GT 00 - arguments(cell(1, 1), cell(0, 0), GT), - // 11 GT 01 - arguments(cell(1, 1), cell(0, 1), GT), - // 11 GT 10 - arguments(cell(1, 1), cell(1, 0), GT), - // 11 GT 11 - arguments(cell(1, 1), cell(1, 1), EQ), - ) - - - } + enum class Comparison { LT, EQ, GT } @ParameterizedTest - @MethodSource("compare cells - parameters") - internal fun `compare cells`(c1: Cell, c2: Cell, expected: Comparison) { + @CsvSource( + value = [ + "0 to 0, 0 to 0, EQ", + "0 to 0, 0 to 1, LT", + "0 to 0, 1 to 0, LT", + "0 to 0, 1 to 1, LT", + + "0 to 1, 0 to 0, GT", + "0 to 1, 0 to 1, EQ", + "0 to 1, 1 to 0, LT", + "0 to 1, 1 to 1, LT", + + "1 to 0, 0 to 0, GT", + "1 to 0, 0 to 1, LT", + "1 to 0, 1 to 0, EQ", + "1 to 0, 1 to 1, LT", + + "1 to 1, 0 to 0, GT", + "1 to 1, 0 to 1, GT", + "1 to 1, 1 to 0, GT", + "1 to 1, 1 to 1, EQ", + ] + ) + internal fun `compare cells`(@ConvertWith(CellConverter::class) c1: Cell, @ConvertWith(CellConverter::class) c2: Cell, expected: Comparison) { when (expected) { LT -> assertThat(c1).isLessThan(c2) EQ -> assertThat(c1).isEqualByComparingTo(c2) @@ -70,13 +47,9 @@ internal class CellTest { } } - - @Test internal fun `create a cell from pair`() { - val cell = cell(2, 2) - - println(cell) + assertThat((2 to 2).toCell()).isEqualTo(cell(2, 2)) } @Test @@ -91,4 +64,43 @@ internal class CellTest { .isEqualTo(cell(1, 1)) } + @Test + internal fun `cell minus cell - fails when other is not lower to the right`() { + assertThatThrownBy { cell(0, 0) - cell(1, 0) } + .isInstanceOf(IllegalArgumentException::class.java) + .hasMessage("Cell(x=0, y=0) has to be >= Cell(x=1, y=0).") + } + + + @Test + internal fun `orthogonal adjacent cells`() { + val seed = cell(1, 1) + assertThat(seed.orthogonalAdjacent) + .isEqualTo( + listOf( + 1 to 0, + 2 to 1, + 1 to 2, + 0 to 1, + ).toCells() + ) + } + + @Test + internal fun `adjacent cells`() { + val seed = cell(1, 1) + assertThat(seed.adjacent) + .isEqualTo( + listOf( + 1 to 0, + 2 to 0, + 2 to 1, + 2 to 2, + 1 to 2, + 0 to 2, + 0 to 1, + 0 to 0 + ).toCells() + ) + } } diff --git a/src/test/kotlin/model/DimensionTest.kt b/src/test/kotlin/model/DimensionTest.kt index 5573ae9..8da087e 100644 --- a/src/test/kotlin/model/DimensionTest.kt +++ b/src/test/kotlin/model/DimensionTest.kt @@ -10,6 +10,7 @@ internal class DimensionTest { @Test internal fun `create with width and height`() { val dimension = Dimension(5, 4) + assertThat(dimension.width).isEqualTo(5) assertThat(dimension.height).isEqualTo(4) assertThat(dimension.size).isEqualTo(20) diff --git a/src/test/kotlin/model/StepsTest.kt b/src/test/kotlin/model/StepsTest.kt new file mode 100644 index 0000000..44a2184 --- /dev/null +++ b/src/test/kotlin/model/StepsTest.kt @@ -0,0 +1,117 @@ +package io.toolisticon.lib.krid.model + +import io.toolisticon.lib.krid.Krids.cell +import io.toolisticon.lib.krid._test.CellConverter +import io.toolisticon.lib.krid._test.StepConverter +import io.toolisticon.lib.krid.model.Direction.* +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.converter.ConvertWith +import org.junit.jupiter.params.provider.CsvSource + +internal class StepsTest { + + @ParameterizedTest + @CsvSource( + value = [ + "UP(1), 5 to 4", + "UP(3), 5 to 2", + "UP_RIGHT(1), 6 to 4", + "UP_RIGHT(3), 8 to 2", + "RIGHT(1), 6 to 5", + "RIGHT(3), 8 to 5", + "DOWN_RIGHT(1), 6 to 6", + "DOWN_RIGHT(3), 8 to 8", + "DOWN(1), 5 to 6", + "DOWN(3), 5 to 8", + "DOWN_LEFT(1), 4 to 6", + "DOWN_LEFT(3), 2 to 8", + "LEFT(1), 4 to 5", + "LEFT(3), 2 to 5", + "UP_LEFT(1), 4 to 4", + "UP_LEFT(3), 2 to 2", + ] + ) + internal fun `evaluate step`(@ConvertWith(StepConverter::class) step: Step, @ConvertWith(CellConverter::class) expectedCell: Cell) { + val cell = cell(5, 5) + + assertThat(cell(step)).isEqualTo(expectedCell) + } + + @Test + internal fun `step to string`() { + assertThat(Direction.LEFT(5).toString()) + .isEqualTo("LEFT(5)") + } + + @Test + internal fun `can create steps from direction`() { + var steps = UP + LEFT + assertThat(steps.toString()).isEqualTo("UP(1) + LEFT(1)") + + steps += DOWN(2) + assertThat(steps.toString()).isEqualTo("UP(1) + LEFT(1) + DOWN(2)") + } + + @Test + internal fun `can add stepFn to direction enum`() { + assertThat((UP + DOWN(2)).toString()) + .isEqualTo("UP(1) + DOWN(2)") + assertThat((UP + UP).toString()) + .isEqualTo("UP(1) + UP(1)") + assertThat((UP + (LEFT + UP)).toString()) + .isEqualTo("UP(1) + LEFT(1) + UP(1)") + } + + @Test + internal fun `single step`() { + assertThat(cell(2, 2)(UP_LEFT(2))).isEqualTo(cell(0, 0)) + } + + @Test + internal fun `take multiple steps in directions`() { + val cell = Cell(2, 3) + + val steps: Steps = UP + UP + RIGHT(2) + UP_RIGHT + + + val all = steps.walk(cell) + + assertThat(all.toList()).containsExactly( + cell(2, 3), + cell(2, 2), + cell(2, 1), + cell(4, 1), + cell(5, 0), + ) + + // this returns only the final cell + assertThat(cell(steps)).isEqualTo(cell(5, 0)) + } + + + @Test + internal fun `beam down-right - include start`() { + val beam = DOWN_RIGHT.beam(cell(0, 0), true) + + assertThat(beam.take(3).toList()) + .containsExactly( + cell(0, 0), + cell(1, 1), + cell(2, 2), + ) + } + + @Test + internal fun `beam down-right - exclude start`() { + val beam = DOWN_RIGHT.beam(cell(0, 0)) + + assertThat(beam.take(3).toList()) + .containsExactly( + cell(1, 1), + cell(2, 2), + cell(3, 3), + ) + } +} From dfa92df42a102c3c46aceb85fe2df9b9bbd5ba92 Mon Sep 17 00:00:00 2001 From: Jan Galinski Date: Fri, 17 Sep 2021 00:32:42 +0200 Subject: [PATCH 08/30] in bounds --- src/main/kotlin/AbstractKrid.kt | 1 - src/main/kotlin/model/Dimension.kt | 10 ++++++++++ src/test/kotlin/model/DimensionTest.kt | 23 +++++++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/AbstractKrid.kt b/src/main/kotlin/AbstractKrid.kt index 6806e87..e11ded3 100644 --- a/src/main/kotlin/AbstractKrid.kt +++ b/src/main/kotlin/AbstractKrid.kt @@ -1,6 +1,5 @@ package io.toolisticon.lib.krid -import io.toolisticon.lib.krid.Krids.indexToCell import io.toolisticon.lib.krid.model.* abstract class AbstractKrid { diff --git a/src/main/kotlin/model/Dimension.kt b/src/main/kotlin/model/Dimension.kt index fa5ad34..e8f07d4 100644 --- a/src/main/kotlin/model/Dimension.kt +++ b/src/main/kotlin/model/Dimension.kt @@ -36,4 +36,14 @@ data class Dimension( */ val columnRange: IntRange = 0 until width + /** + * Filters given cells and returns only those that are in the bounds of this dimension. + */ + fun filterInBound(cells:List) : List = cells.filter { it.x in columnRange && it.y in rowRange } + } + +/** + * Vararg extension for [Dimension.filterInBound]. + */ +fun Dimension.filterInBound(vararg cells:Cell) : List = filterInBound(cells.toList()) diff --git a/src/test/kotlin/model/DimensionTest.kt b/src/test/kotlin/model/DimensionTest.kt index 8da087e..d3b973c 100644 --- a/src/test/kotlin/model/DimensionTest.kt +++ b/src/test/kotlin/model/DimensionTest.kt @@ -47,4 +47,27 @@ internal class DimensionTest { .isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Values have to be positive numbers (was: Dimension(width=0, height=0)).") } + + @Test + internal fun `cells in bound`() { + val dimension = Dimension(3, 2) + + assertThat( + dimension.filterInBound( + cell(5, 5) + ) + ).isEmpty() + + assertThat( + dimension.filterInBound( + cell(0, 0), + cell(1, 1), + cell(3, 0), + cell(0, 2), + ) + ).containsExactly( + cell(0,0), + cell(1,1), + ) + } } From 812766a6dccff4bc05926222a1c491df5734577d Mon Sep 17 00:00:00 2001 From: Jan Galinski Date: Fri, 17 Sep 2021 00:37:00 +0200 Subject: [PATCH 09/30] use hasToString --- src/test/kotlin/model/StepsTest.kt | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/test/kotlin/model/StepsTest.kt b/src/test/kotlin/model/StepsTest.kt index 44a2184..6a22a46 100644 --- a/src/test/kotlin/model/StepsTest.kt +++ b/src/test/kotlin/model/StepsTest.kt @@ -41,27 +41,27 @@ internal class StepsTest { @Test internal fun `step to string`() { - assertThat(Direction.LEFT(5).toString()) - .isEqualTo("LEFT(5)") + assertThat(LEFT(5)) + .hasToString("LEFT(5)") } @Test internal fun `can create steps from direction`() { var steps = UP + LEFT - assertThat(steps.toString()).isEqualTo("UP(1) + LEFT(1)") + assertThat(steps).hasToString("UP(1) + LEFT(1)") steps += DOWN(2) - assertThat(steps.toString()).isEqualTo("UP(1) + LEFT(1) + DOWN(2)") + assertThat(steps).hasToString("UP(1) + LEFT(1) + DOWN(2)") } @Test internal fun `can add stepFn to direction enum`() { - assertThat((UP + DOWN(2)).toString()) - .isEqualTo("UP(1) + DOWN(2)") - assertThat((UP + UP).toString()) - .isEqualTo("UP(1) + UP(1)") - assertThat((UP + (LEFT + UP)).toString()) - .isEqualTo("UP(1) + LEFT(1) + UP(1)") + assertThat(UP + DOWN(2)) + .hasToString("UP(1) + DOWN(2)") + assertThat(UP + UP) + .hasToString("UP(1) + UP(1)") + assertThat(UP + (LEFT + UP)) + .hasToString("UP(1) + LEFT(1) + UP(1)") } @Test @@ -73,8 +73,7 @@ internal class StepsTest { internal fun `take multiple steps in directions`() { val cell = Cell(2, 3) - val steps: Steps = UP + UP + RIGHT(2) + UP_RIGHT - + val steps: Steps = UP + (UP + RIGHT(2)) + UP_RIGHT val all = steps.walk(cell) From 2774163676f55780ab0a39cf2781d56615de374d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Sep 2021 06:16:22 +0000 Subject: [PATCH 10/30] Bump assertj-core from 3.20.2 to 3.21.0 Bumps [assertj-core](https://github.com/assertj/assertj-core) from 3.20.2 to 3.21.0. - [Release notes](https://github.com/assertj/assertj-core/releases) - [Commits](https://github.com/assertj/assertj-core/compare/assertj-core-3.20.2...assertj-core-3.21.0) --- updated-dependencies: - dependency-name: org.assertj:assertj-core dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f3e45a4..b7b6c17 100644 --- a/pom.xml +++ b/pom.xml @@ -75,7 +75,7 @@ org.assertj assertj-core - 3.20.2 + 3.21.0 test From 11d19913e0483830a2cae635af10236289d6a7c2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Sep 2021 06:18:09 +0000 Subject: [PATCH 11/30] Bump kotlin.version from 1.5.30 to 1.5.31 Bumps `kotlin.version` from 1.5.30 to 1.5.31. Updates `kotlin-bom` from 1.5.30 to 1.5.31 - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/commits) Updates `kotlin-maven-allopen` from 1.5.30 to 1.5.31 Updates `kotlin-maven-noarg` from 1.5.30 to 1.5.31 Updates `kotlin-maven-plugin` from 1.5.30 to 1.5.31 --- updated-dependencies: - dependency-name: org.jetbrains.kotlin:kotlin-bom dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.jetbrains.kotlin:kotlin-maven-allopen dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.jetbrains.kotlin:kotlin-maven-noarg dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.jetbrains.kotlin:kotlin-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f3e45a4..e695352 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ UTF-8 UTF-8 - 1.5.30 + 1.5.31 11 ${java.version} ${java.version} From fbf973ce51feff7b08ae99b0c6f0d2cdcbe5fdfd Mon Sep 17 00:00:00 2001 From: Jan Galinski Date: Mon, 20 Sep 2021 21:45:59 +0200 Subject: [PATCH 12/30] modify cell values --- src/main/kotlin/AbstractKrid.kt | 75 +++++++++++++++++++--- src/main/kotlin/Krid.kt | 36 ++++++----- src/main/kotlin/Krids.kt | 28 +------- src/main/kotlin/fn/IndexTransformer.kt | 35 ++++++++++ src/main/kotlin/model/Cell.kt | 12 ++++ src/main/kotlin/model/CellValue.kt | 13 ++-- src/main/kotlin/model/Columns.kt | 4 +- src/main/kotlin/model/Dimension.kt | 30 ++++++++- src/test/kotlin/KridTest.kt | 57 ++++++++++++++-- src/test/kotlin/KridsTest.kt | 72 +++++++++++++-------- src/test/kotlin/_test/AssertJExt.kt | 6 ++ src/test/kotlin/fn/IndexTransformerTest.kt | 33 ++++++++++ src/test/kotlin/model/CellTest.kt | 41 ++++++++---- src/test/kotlin/model/CellValueTest.kt | 9 +++ src/test/kotlin/model/ColumnsTest.kt | 16 +++++ src/test/kotlin/model/DimensionTest.kt | 29 ++++++++- src/test/kotlin/model/StepsTest.kt | 1 - 17 files changed, 386 insertions(+), 111 deletions(-) create mode 100644 src/main/kotlin/fn/IndexTransformer.kt create mode 100644 src/test/kotlin/_test/AssertJExt.kt create mode 100644 src/test/kotlin/fn/IndexTransformerTest.kt create mode 100644 src/test/kotlin/model/ColumnsTest.kt diff --git a/src/main/kotlin/AbstractKrid.kt b/src/main/kotlin/AbstractKrid.kt index e11ded3..9cb7327 100644 --- a/src/main/kotlin/AbstractKrid.kt +++ b/src/main/kotlin/AbstractKrid.kt @@ -1,26 +1,83 @@ package io.toolisticon.lib.krid +import io.toolisticon.lib.krid.Krids.cell +import io.toolisticon.lib.krid.fn.IndexTransformer import io.toolisticon.lib.krid.model.* +/** + * Common parent class for [Krid] and [ExpendableKrid]. + */ abstract class AbstractKrid { + + /** + * width/height dimension of this krid. + */ abstract val dimension: Dimension + + /** + * A value of type `` that represents an empty [Cell]. In case the krid type is ``, this might be null. + */ abstract val emptyElement: E - abstract fun isEmpty() : Boolean - abstract operator fun get(x:Int, y:Int) : E + /** + * Internal storage via list, visible only to subclasses. + */ + protected abstract val list: List + + protected val indexTransformer by lazy { IndexTransformer(dimension.width) } + + + /** + * Checks if the given row-index is in current [Dimension.rowRange]. + * + * @param rowIndex the row index to check + * @throws IllegalArgumentException if not in range + * @return the row index if in range + */ + protected fun requireInRows(rowIndex: Int): Int = rowIndex.apply { require(rowIndex in dimension.rowRange) { "$this has to be in ${dimension.rowRange}." } } + + /** + * Checks if the given column-index is in current [Dimension.columnRange]. + * + * @param columnIndex the column index to check + * @throws IllegalArgumentException if not in range + * @return the column index if in range + */ + protected fun requireInColumns(columnIndex: Int): Int = + columnIndex.apply { require(this in dimension.columnRange) { "$this has to be in ${dimension.columnRange}." } } - fun rows() : Rows = Rows(dimension.rowRange.map(this::row)) - fun columns() : Columns = Columns(dimension.columnRange.map(this::column)) + /** + * @return `true` if all cell values in this krid are the [emptyElement]. + */ + fun isEmpty(): Boolean = list.all { isEmptyElement(it) } - abstract fun row(index: Int) : Row - abstract fun column(index: Int) : Column - abstract fun iterator() : Iterator> + operator fun get(x: Int, y: Int): E = list[requireInRows(y) * dimension.width + requireInColumns(x)] + + fun rows(): Rows = Rows(dimension.rowRange.map(this::row)) + fun columns(): Columns = Columns(dimension.columnRange.map(this::column)) + + abstract fun row(index: Int): Row + abstract fun column(index: Int): Column + + fun iterator(): Iterator> = list + .mapIndexed { index, e -> cell(indexTransformer.toCell(index), e) } + .iterator() + + fun orthogonalAdjacentCells(cell: Cell): List = dimension.filterInBounds(cell.orthogonalAdjacent) + fun adjacentCells(cell: Cell): List = dimension.filterInBounds(cell.adjacent) } -internal val AbstractKrid.isEmptyElement: (E) -> Boolean get() = { it == emptyElement } -operator fun AbstractKrid.get(cell: Cell) = get(cell.x, cell.y) +fun AbstractKrid.orthogonalAdjacentCells(x: Int, y: Int) = orthogonalAdjacentCells(cell(x, y)) +fun AbstractKrid.adjacentCells(x: Int, y: Int) = adjacentCells(cell(x, y)) +internal val AbstractKrid.isEmptyElement: (E) -> Boolean get() = { it == emptyElement } + +/** + * Convenience extension for [AbstractKrid.get]. + */ +operator fun AbstractKrid.get(cell: Cell) = get(cell.x, cell.y) fun AbstractKrid.ascii(toChar: (E) -> Char? = { it?.toString()?.first() ?: '.' }): String = this.rows().joinToString(separator = "\n") { it.map { e -> toChar(e) ?: '.' }.joinToString(separator = "") } + diff --git a/src/main/kotlin/Krid.kt b/src/main/kotlin/Krid.kt index 0ca0734..5c1dac2 100644 --- a/src/main/kotlin/Krid.kt +++ b/src/main/kotlin/Krid.kt @@ -1,31 +1,22 @@ package io.toolisticon.lib.krid -import io.toolisticon.lib.krid.Krids.cell -import io.toolisticon.lib.krid.Krids.indexToCell -import io.toolisticon.lib.krid.model.CellValue -import io.toolisticon.lib.krid.model.Column -import io.toolisticon.lib.krid.model.Dimension -import io.toolisticon.lib.krid.model.Row +import io.toolisticon.lib.krid.model.* +/** + * A [Krid] of type `` with given [Dimension]. + */ data class Krid( override val dimension: Dimension, override val emptyElement: E, - private val list: List + override val list: List ) : AbstractKrid() { init { require(dimension.size == list.size) } - private val indexToCell = indexToCell(dimension.width) private val rowCache: MutableMap> = mutableMapOf() private val columnCache: MutableMap> = mutableMapOf() - private fun requireInRows(index: Int): Int = index.apply { require(index in dimension.rowRange) { "$this has to be in ${dimension.rowRange}." } } - private fun requireInColumns(index: Int): Int = index.apply { require(this in dimension.columnRange) { "$this has to be in ${dimension.columnRange}." } } - - override fun get(x: Int, y: Int): E = list[requireInRows(y) * dimension.width + requireInColumns(x)] - - override fun isEmpty(): Boolean = list.all { isEmptyElement(it) } override fun row(index: Int): Row = rowCache.computeIfAbsent( requireInRows(index) @@ -35,9 +26,20 @@ data class Krid( requireInColumns(index) ) { Column(index, dimension.rowRange.map { get(index, it) }) } - override fun iterator(): Iterator> = list - .mapIndexed { index, e -> cell(indexToCell(index), e) } - .iterator() + operator fun plus(cellValues: List>): Krid { + dimension.filterNotInBounds(cellValues.cells).also { + require(it.isEmpty()) { "Cannot modify values because cells are out of bounds: $it." } + } + val mutable = list.toMutableList() + + cellValues.forEach { + mutable[indexTransformer.toIndex(it.cell)] = it.value + } + return copy( + list = mutable.toList() + ) + } } +operator fun Krid.plus(cellValue: CellValue): Krid = plus(listOf(cellValue)) diff --git a/src/main/kotlin/Krids.kt b/src/main/kotlin/Krids.kt index 5518b1b..71fec83 100644 --- a/src/main/kotlin/Krids.kt +++ b/src/main/kotlin/Krids.kt @@ -1,5 +1,6 @@ package io.toolisticon.lib.krid +import io.toolisticon.lib.krid.fn.IndexTransformer import io.toolisticon.lib.krid.model.Cell import io.toolisticon.lib.krid.model.CellValue import io.toolisticon.lib.krid.model.Dimension @@ -24,12 +25,12 @@ object Krids { * Creates a new [Krid] with [Dimension](width, height), all cells initialized by the given BiFunction. */ fun krid(width: Int, height: Int, emptyElement: E, initialize: (Int, Int) -> E): Krid { - val cellOf = indexToCell(width) + val transformer = IndexTransformer(width) return Krid( dimension = Dimension(width, height), emptyElement = emptyElement, list = List(size = width * height) { index -> - cellOf(index).let { initialize(it.x, it.y) } + transformer.toCell(index).let { initialize(it.x, it.y) } } ) } @@ -63,27 +64,4 @@ object Krids { fun cell(x: Int, y: Int, value: E) = CellValue(x, y, value) fun cell(cell: Cell, value: E) = cell(cell.x, cell.y, value) - /** - * Given the width of the Krid, this calculates the Cell(x,y) coordinates based on the list index. - */ - fun indexToCell(width: Int): (Int) -> Cell = { Cell(it % width, it / width) } - - /** - * Given the width of the Krid, this calculates the the list index value based on the Cell(x,y) coordinates. - */ - fun cellToIndex(width: Int): (Cell) -> Int = { it.y * width + it.x } - - /** - * Transforms a pair of ints to a type safe [Cell]. - */ - fun Pair.toCell() = cell(first, second) - - fun List>.toCells() = map { it.toCell() } - - val Dimension.pair: Pair get() = width to height -// -// fun Krid.iterator(): Iterator> = sequence { -// val cellOf = indexToCell(dimension.width) -// list.forEachIndexed { i, e -> yield(CellValue(cellOf(i), e)) } -// }.iterator() } diff --git a/src/main/kotlin/fn/IndexTransformer.kt b/src/main/kotlin/fn/IndexTransformer.kt new file mode 100644 index 0000000..1b1507d --- /dev/null +++ b/src/main/kotlin/fn/IndexTransformer.kt @@ -0,0 +1,35 @@ +package io.toolisticon.lib.krid.fn + +import io.toolisticon.lib.krid.model.Cell + +/** + * Calculates cell coordinates and list index based on width. + */ +class IndexTransformer(private val width: Int) { + + /** + * Given the width of the Krid, this calculates the Cell(x,y) coordinates based on the list index. + * + * @param index the index in a list + * @return cell coordinates in list + */ + fun toCell(index: Int): Cell = Cell(index % width, index / width) + + /** + * Given the width of the Krid, this calculates the the list index value based on the Cell(x,y) coordinates. + * + * @param cell the (x,y) coordinates wrapped in a cell + * @return index in list + */ + fun toIndex(cell: Cell): Int = toIndex(cell.x, cell.y) + + /** + * Given the width of the Krid, this calculates the the list index value based on the (x,y) coordinates. + * + * @param x x coordinate + * @param y y coordinate + * @return index in list + */ + fun toIndex(x: Int, y: Int): Int = y * width + x + +} diff --git a/src/main/kotlin/model/Cell.kt b/src/main/kotlin/model/Cell.kt index 25a314f..80f239e 100644 --- a/src/main/kotlin/model/Cell.kt +++ b/src/main/kotlin/model/Cell.kt @@ -1,5 +1,7 @@ package io.toolisticon.lib.krid.model +import io.toolisticon.lib.krid.Krids.cell + /** * A Cell represents a (x,y)-coordinate inside a Krid. */ @@ -60,3 +62,13 @@ data class Cell(val x: Int, val y: Int) : Comparable { private fun adjacent(vararg directions: Direction): List = directions.map { this(it.singleStep) } } + +/** + * Transforms a pair of [Int]s to a type safe [Cell]. + */ +fun Pair.toCell() = cell(first, second) + +/** + * Tramsforms a list of pairs to list of cells. + */ +fun List>.toCells() = map { it.toCell() } diff --git a/src/main/kotlin/model/CellValue.kt b/src/main/kotlin/model/CellValue.kt index 508037c..2735c62 100644 --- a/src/main/kotlin/model/CellValue.kt +++ b/src/main/kotlin/model/CellValue.kt @@ -1,13 +1,14 @@ package io.toolisticon.lib.krid.model data class CellValue(val x: Int, val y: Int, val value: E) { - companion object { - val CellValue<*>.cell: Cell get() = Cell(x = x, y = y) - } + constructor(cell: Cell, value: E) : this(cell.x, cell.y, value) + constructor(pair: Pair, value: E) : this(pair.toCell(), value = value) + + val cell = Cell(x, y) - operator fun plus(cell: Cell) : CellValue = (this.cell + cell).let { - copy(x = it.x, y=it.y) + operator fun plus(cell: Cell): CellValue = (this.cell + cell).let { + copy(x = it.x, y = it.y) } } - +val List>.cells get() = map { it.cell } diff --git a/src/main/kotlin/model/Columns.kt b/src/main/kotlin/model/Columns.kt index ed33101..cbf0dac 100644 --- a/src/main/kotlin/model/Columns.kt +++ b/src/main/kotlin/model/Columns.kt @@ -8,4 +8,6 @@ data class Column(val index: Int, val values: List) : List by values /** * Represents a collection of columns (vertical lines) in a [io.toolisticon.lib.krid.Krid]. */ -data class Columns(val columns: List>) : List> by columns +data class Columns(val columns: List>) : List> by columns { + constructor(vararg columns: Column) : this(columns.toList()) +} diff --git a/src/main/kotlin/model/Dimension.kt b/src/main/kotlin/model/Dimension.kt index e8f07d4..e43fc78 100644 --- a/src/main/kotlin/model/Dimension.kt +++ b/src/main/kotlin/model/Dimension.kt @@ -8,6 +8,11 @@ data class Dimension( val height: Int ) { companion object { + /** + * Creates [Dimension] based on x/y diff of given cells. + * + * @throws IllegalArgumentException when [upperLeft] is not < [lowerRight]. + */ operator fun invoke(upperLeft: Cell, lowerRight: Cell): Dimension { require(lowerRight >= upperLeft) { "$upperLeft has to be right and/or down from $lowerRight." } return Dimension( @@ -36,14 +41,33 @@ data class Dimension( */ val columnRange: IntRange = 0 until width + /** + * Predicate that checks if the given cell is in the row- and col-ranges of this dimension. + */ + fun isInBounds(cell: Cell): Boolean = cell.x in columnRange && cell.y in rowRange + /** * Filters given cells and returns only those that are in the bounds of this dimension. */ - fun filterInBound(cells:List) : List = cells.filter { it.x in columnRange && it.y in rowRange } + fun filterInBounds(cells: List): List = cells.filter(this::isInBounds) + /** + * Filters given cells and returns only those that are not in the bounds of this dimension. + */ + fun filterNotInBounds(cells: List): List = cells.filterNot(this::isInBounds) } /** - * Vararg extension for [Dimension.filterInBound]. + * Vararg extension for [Dimension.filterInBounds]. + */ +fun Dimension.filterInBounds(vararg cells: Cell): List = filterInBounds(cells.toList()) + +/** + * Vararg extension for [Dimension.filterNotInBounds]. + */ +fun Dimension.filterNotInBounds(vararg cells: Cell): List = filterNotInBounds(cells.toList()) + +/** + * [Dimension.width] and [Dimension.height] as [Pair]. */ -fun Dimension.filterInBound(vararg cells:Cell) : List = filterInBound(cells.toList()) +val Dimension.pair: Pair get() = width to height diff --git a/src/test/kotlin/KridTest.kt b/src/test/kotlin/KridTest.kt index 1f7e75f..30b717f 100644 --- a/src/test/kotlin/KridTest.kt +++ b/src/test/kotlin/KridTest.kt @@ -1,9 +1,12 @@ package io.toolisticon.lib.krid +import io.toolisticon.lib.krid.Krids.cell import io.toolisticon.lib.krid.Krids.krid import io.toolisticon.lib.krid._test.BooleanKridHelper.booleanCellValue import io.toolisticon.lib.krid._test.BooleanKridHelper.booleanKrid +import io.toolisticon.lib.krid._test.isInstanceOf import io.toolisticon.lib.krid.model.Dimension +import org.assertj.core.api.AbstractThrowableAssert import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.jupiter.api.Test @@ -62,17 +65,59 @@ internal class KridTest { @Test internal fun `iterate value cells`() { - val krid = booleanKrid(""" + val krid = booleanKrid( + """ t. ft - """.trimIndent()) + """.trimIndent() + ) assertThat(krid.iterator().asSequence().toList()) .containsExactly( - booleanCellValue(0,0,true), - booleanCellValue(1,0,null), - booleanCellValue(0,1,false), - booleanCellValue(1,1,true), + booleanCellValue(0, 0, true), + booleanCellValue(1, 0, null), + booleanCellValue(0, 1, false), + booleanCellValue(1, 1, true), ) } + + @Test + internal fun `set cell value`() { + var krid = booleanKrid( + """ + t. + ft + """.trimIndent() + ) + + krid += cell(1, 0, true) + + assertThat(krid.row(0)).containsExactly(true, true) + assertThat(krid.row(1)).containsExactly(false, true) + + krid += listOf(cell(1, 0, false), cell(0, 1, true)) + assertThat(krid.row(0)).containsExactly(true, false) + assertThat(krid.row(1)).containsExactly(true, true) + } + + @Test + internal fun `fail to set cell values if cell out of bounds`() { + var krid = booleanKrid( + """ + t. + ft + """.trimIndent() + ) + + assertThatThrownBy { + krid += listOf( + cell(0, 0, false), + cell(2, 0, true), + cell(2, 2, null) + ) + }.isInstanceOf(IllegalArgumentException::class) + .hasMessage("Cannot modify values because cells are out of bounds: [Cell(x=2, y=0), Cell(x=2, y=2)].") + + + } } diff --git a/src/test/kotlin/KridsTest.kt b/src/test/kotlin/KridsTest.kt index 0f97acf..a6c166d 100644 --- a/src/test/kotlin/KridsTest.kt +++ b/src/test/kotlin/KridsTest.kt @@ -1,40 +1,17 @@ package io.toolisticon.lib.krid -import io.toolisticon.lib.krid.Krids.cellToIndex -import io.toolisticon.lib.krid.Krids.indexToCell +import io.toolisticon.lib.krid.Krids.cell import io.toolisticon.lib.krid.Krids.krid -import io.toolisticon.lib.krid.Krids.pair -import io.toolisticon.lib.krid._test.CellConverter -import io.toolisticon.lib.krid.model.Cell +import io.toolisticon.lib.krid._test.BooleanKridHelper.booleanKrid import io.toolisticon.lib.krid.model.Column +import io.toolisticon.lib.krid.model.Direction import io.toolisticon.lib.krid.model.Row +import io.toolisticon.lib.krid.model.pair import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.converter.ConvertWith -import org.junit.jupiter.params.provider.CsvSource internal class KridsTest { - @ParameterizedTest - @CsvSource( - value = [ - "3, 0, 0 to 0", - "3, 1, 1 to 0", - "3, 2, 2 to 0", - "3, 3, 0 to 1", - "3, 4, 1 to 1", - "3, 5, 2 to 1", - ] - ) - internal fun `index to cell and back`(width: Int, index: Int, @ConvertWith(CellConverter::class) cell: Cell) { - val toCell = indexToCell(width) - val toIndex = cellToIndex(width) - - assertThat(toCell(index)).isEqualTo(cell) - assertThat(toIndex(cell)).isEqualTo(index) - } - @Test internal fun `dummy test`() { assertThat(1 + 1).isEqualTo(2) @@ -55,7 +32,7 @@ internal class KridsTest { @Test internal fun `init single empty`() { - val krid: Krid = Krids.krid(null) + val krid: Krid = krid(null) assertThat(krid.dimension.pair).isEqualTo(1 to 1) assertThat(krid.emptyElement).isEqualTo(null) @@ -75,6 +52,45 @@ internal class KridsTest { assertThat(krid.column(0)).containsExactly(true, false, false, false) assertThat(krid.column(1)).containsExactly(false, true, false, false) assertThat(krid.column(2)).containsExactly(false, false, true, false) + } + + @Test + internal fun `create from rows`() { + val krid: Krid = krid(listOf( + listOf(true, null), + listOf(null, false), + ), null) + + assertThat(krid.column(0)).containsExactly(true, null) + assertThat(krid.column(1)).containsExactly(null, false) + } + @Test + internal fun `adjacent cells`() { + val krid = booleanKrid( + """ + tf. + .tf + f.t + """.trimIndent() + ) + + assertThat(krid.adjacentCells(cell(0, 0))).containsExactlyInAnyOrder( + cell(1, 0), + cell(1, 1), + cell(0, 1), + ) + + val c11 = cell(1, 1) + assertThat(krid.adjacentCells(1, 1)).containsExactlyInAnyOrder( + c11(Direction.UP), + c11(Direction.UP_RIGHT), + c11(Direction.RIGHT), + c11(Direction.DOWN_RIGHT), + c11(Direction.DOWN), + c11(Direction.DOWN_LEFT), + c11(Direction.LEFT), + c11(Direction.UP_LEFT), + ) } } diff --git a/src/test/kotlin/_test/AssertJExt.kt b/src/test/kotlin/_test/AssertJExt.kt new file mode 100644 index 0000000..03f965a --- /dev/null +++ b/src/test/kotlin/_test/AssertJExt.kt @@ -0,0 +1,6 @@ +package io.toolisticon.lib.krid._test + +import org.assertj.core.api.AbstractThrowableAssert +import kotlin.reflect.KClass + +fun AbstractThrowableAssert<*, out Throwable>.isInstanceOf(kclass:KClass<*>) = isInstanceOf(kclass.java) diff --git a/src/test/kotlin/fn/IndexTransformerTest.kt b/src/test/kotlin/fn/IndexTransformerTest.kt new file mode 100644 index 0000000..6312584 --- /dev/null +++ b/src/test/kotlin/fn/IndexTransformerTest.kt @@ -0,0 +1,33 @@ +package io.toolisticon.lib.krid.fn + +import io.toolisticon.lib.krid._test.CellConverter +import io.toolisticon.lib.krid.model.Cell +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.converter.ConvertWith +import org.junit.jupiter.params.provider.CsvSource + + +internal class IndexTransformerTest { + + @ParameterizedTest + @CsvSource( + value = [ + "3, 0, 0 to 0", + "3, 1, 1 to 0", + "3, 2, 2 to 0", + "3, 3, 0 to 1", + "3, 4, 1 to 1", + "3, 5, 2 to 1", + "4, 3, 3 to 0", + "4, 4, 0 to 1", + ] + ) + internal fun `index to cell and back`(width: Int, index: Int, @ConvertWith(CellConverter::class) cell: Cell) { + val transformer = IndexTransformer(width) + + assertThat(transformer.toCell(index)).isEqualTo(cell) + assertThat(transformer.toIndex(cell)).isEqualTo(index) + assertThat(transformer.toIndex(cell.x, cell.y)).isEqualTo(index) + } +} diff --git a/src/test/kotlin/model/CellTest.kt b/src/test/kotlin/model/CellTest.kt index 0a31df4..f2eb88d 100644 --- a/src/test/kotlin/model/CellTest.kt +++ b/src/test/kotlin/model/CellTest.kt @@ -1,8 +1,6 @@ package io.toolisticon.lib.krid.model import io.toolisticon.lib.krid.Krids.cell -import io.toolisticon.lib.krid.Krids.toCell -import io.toolisticon.lib.krid.Krids.toCells import io.toolisticon.lib.krid._test.CellConverter import io.toolisticon.lib.krid.model.CellTest.Comparison.* import org.assertj.core.api.Assertions.assertThat @@ -90,17 +88,34 @@ internal class CellTest { internal fun `adjacent cells`() { val seed = cell(1, 1) assertThat(seed.adjacent) - .isEqualTo( - listOf( - 1 to 0, - 2 to 0, - 2 to 1, - 2 to 2, - 1 to 2, - 0 to 2, - 0 to 1, - 0 to 0 - ).toCells() + .containsExactlyInAnyOrder( + Direction.UP(seed), + Direction.UP_RIGHT(seed), + Direction.RIGHT(seed), + Direction.DOWN_RIGHT(seed), + Direction.DOWN(seed), + Direction.DOWN_LEFT(seed), + Direction.LEFT(seed), + Direction.UP_LEFT(seed), ) } + + @ParameterizedTest + @CsvSource( + value = [ + "UP, 1 to 0", + "UP_RIGHT, 2 to 0", + "RIGHT, 2 to 1", + "DOWN_RIGHT, 2 to 2", + "DOWN, 1 to 2", + "DOWN_LEFT, 0 to 2", + "LEFT, 0 to 1", + "UP_LEFT, 0 to 0", + ] + ) + internal fun directions(direction: Direction, @ConvertWith(CellConverter::class) expected: Cell) { + val cell = cell(1, 1) + + assertThat(cell(direction)).isEqualTo(expected) + } } diff --git a/src/test/kotlin/model/CellValueTest.kt b/src/test/kotlin/model/CellValueTest.kt index 590500f..03b8fe5 100644 --- a/src/test/kotlin/model/CellValueTest.kt +++ b/src/test/kotlin/model/CellValueTest.kt @@ -13,4 +13,13 @@ internal class CellValueTest { assertThat(cell + cell(5, 6)).isEqualTo(cell(7, 9, true)) } + + @Test + internal fun `create cell value boolean`() { + val cellValue: CellValue = CellValue(1, 2, true) + + assertThat(CellValue(Cell(1, 2), true)).isEqualTo(cellValue) + assertThat(CellValue(Cell(1, 2), true).cell).isEqualTo(Cell(1, 2)) + } + } diff --git a/src/test/kotlin/model/ColumnsTest.kt b/src/test/kotlin/model/ColumnsTest.kt new file mode 100644 index 0000000..c98c286 --- /dev/null +++ b/src/test/kotlin/model/ColumnsTest.kt @@ -0,0 +1,16 @@ +package io.toolisticon.lib.krid.model + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class ColumnsTest { + + @Test + internal fun `create columns`() { + val column = Column(0, listOf(true)) + + val columns: Columns = Columns(column) + + assertThat(columns[0]).isEqualTo(column) + } +} diff --git a/src/test/kotlin/model/DimensionTest.kt b/src/test/kotlin/model/DimensionTest.kt index d3b973c..44b3912 100644 --- a/src/test/kotlin/model/DimensionTest.kt +++ b/src/test/kotlin/model/DimensionTest.kt @@ -53,13 +53,13 @@ internal class DimensionTest { val dimension = Dimension(3, 2) assertThat( - dimension.filterInBound( + dimension.filterInBounds( cell(5, 5) ) ).isEmpty() assertThat( - dimension.filterInBound( + dimension.filterInBounds( cell(0, 0), cell(1, 1), cell(3, 0), @@ -70,4 +70,29 @@ internal class DimensionTest { cell(1,1), ) } + + @Test + internal fun `cells not in bound`() { + val dimension = Dimension(3, 2) + + assertThat( + dimension.filterNotInBounds( + cell(0, 0), + cell(1, 1), + cell(3, 0), + cell(0, 2), + ) + ).containsExactly( + cell(3,0), + cell(0,2), + ) + } + + @Test + internal fun `predicate is in bounds`() { + val dimension = Dimension(2,2) + + assertThat(dimension.isInBounds(cell(1,1))).isTrue + assertThat(dimension.isInBounds(cell(1,2))).isFalse + } } diff --git a/src/test/kotlin/model/StepsTest.kt b/src/test/kotlin/model/StepsTest.kt index 6a22a46..9c6eb23 100644 --- a/src/test/kotlin/model/StepsTest.kt +++ b/src/test/kotlin/model/StepsTest.kt @@ -89,7 +89,6 @@ internal class StepsTest { assertThat(cell(steps)).isEqualTo(cell(5, 0)) } - @Test internal fun `beam down-right - include start`() { val beam = DOWN_RIGHT.beam(cell(0, 0), true) From ea2ce8807eeb1d98d8e391b5d775a2db81f6cb7d Mon Sep 17 00:00:00 2001 From: Jan Galinski Date: Mon, 20 Sep 2021 22:28:03 +0200 Subject: [PATCH 13/30] sub krid --- src/main/kotlin/Krid.kt | 17 +++++++++++ src/main/kotlin/Krids.kt | 14 ++++++--- src/main/kotlin/model/Cell.kt | 12 +++++++- src/main/kotlin/model/Dimension.kt | 4 ++- src/test/kotlin/KridTest.kt | 41 +++++++++++++++++++++++++- src/test/kotlin/model/DimensionTest.kt | 16 +++++----- 6 files changed, 89 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/Krid.kt b/src/main/kotlin/Krid.kt index 5c1dac2..04cb3e6 100644 --- a/src/main/kotlin/Krid.kt +++ b/src/main/kotlin/Krid.kt @@ -1,6 +1,8 @@ package io.toolisticon.lib.krid +import io.toolisticon.lib.krid.Krids.krid import io.toolisticon.lib.krid.model.* +import io.toolisticon.lib.krid.model.Cell.Companion.requireGreaterThanOrEqual /** * A [Krid] of type `` with given [Dimension]. @@ -40,6 +42,21 @@ data class Krid( list = mutable.toList() ) } + + fun subKrid(upperLeft: Cell, lowerRight: Cell): Krid { + require(dimension.isInBounds(upperLeft)) { "$upperLeft is out of bounds (dimension=$dimension)." } + require(dimension.isInBounds(lowerRight)) { "$lowerRight is out of bounds (dimension=$dimension)." } + + val newDimension = Dimension(upperLeft, lowerRight) + + return krid( + dimension = newDimension, + emptyElement = emptyElement, + initialize = { x, y -> + get(x + upperLeft.x, y + upperLeft.y) + } + ) + } } operator fun Krid.plus(cellValue: CellValue): Krid = plus(listOf(cellValue)) diff --git a/src/main/kotlin/Krids.kt b/src/main/kotlin/Krids.kt index 71fec83..02c74e3 100644 --- a/src/main/kotlin/Krids.kt +++ b/src/main/kotlin/Krids.kt @@ -21,15 +21,21 @@ object Krids { */ fun krid(width: Int, height: Int, emptyElement: E) = krid(width, height, emptyElement) { _, _ -> emptyElement } + fun krid(width: Int, height: Int, emptyElement: E, initialize: (Int, Int) -> E): Krid = krid( + dimension = Dimension(width, height), + emptyElement = emptyElement, + initialize = initialize + ) + /** * Creates a new [Krid] with [Dimension](width, height), all cells initialized by the given BiFunction. */ - fun krid(width: Int, height: Int, emptyElement: E, initialize: (Int, Int) -> E): Krid { - val transformer = IndexTransformer(width) + fun krid(dimension: Dimension, emptyElement: E, initialize: (Int, Int) -> E): Krid { + val transformer = IndexTransformer(dimension.width) return Krid( - dimension = Dimension(width, height), + dimension = dimension, emptyElement = emptyElement, - list = List(size = width * height) { index -> + list = List(size = dimension.size) { index -> transformer.toCell(index).let { initialize(it.x, it.y) } } ) diff --git a/src/main/kotlin/model/Cell.kt b/src/main/kotlin/model/Cell.kt index 80f239e..5e087ea 100644 --- a/src/main/kotlin/model/Cell.kt +++ b/src/main/kotlin/model/Cell.kt @@ -6,6 +6,14 @@ import io.toolisticon.lib.krid.Krids.cell * A Cell represents a (x,y)-coordinate inside a Krid. */ data class Cell(val x: Int, val y: Int) : Comparable { + companion object { + /** + * @throws IllegalArgumentException if first is not >= second + */ + fun requireGreaterThanOrEqual(first: Cell, second: Cell) = require(first >= second) { + "$first has to be right and/or down from $second." + } + } /** * List of orthogonal adjacent cells, starting with 12° clock (`UP`). @@ -69,6 +77,8 @@ data class Cell(val x: Int, val y: Int) : Comparable { fun Pair.toCell() = cell(first, second) /** - * Tramsforms a list of pairs to list of cells. + * Transforms a list of pairs to list of cells. */ fun List>.toCells() = map { it.toCell() } + + diff --git a/src/main/kotlin/model/Dimension.kt b/src/main/kotlin/model/Dimension.kt index e43fc78..872db93 100644 --- a/src/main/kotlin/model/Dimension.kt +++ b/src/main/kotlin/model/Dimension.kt @@ -1,5 +1,7 @@ package io.toolisticon.lib.krid.model +import io.toolisticon.lib.krid.model.Cell.Companion.requireGreaterThanOrEqual + /** * Represents width and height of a [io.toolisticon.lib.krid.Krid]. */ @@ -14,7 +16,7 @@ data class Dimension( * @throws IllegalArgumentException when [upperLeft] is not < [lowerRight]. */ operator fun invoke(upperLeft: Cell, lowerRight: Cell): Dimension { - require(lowerRight >= upperLeft) { "$upperLeft has to be right and/or down from $lowerRight." } + requireGreaterThanOrEqual(lowerRight, upperLeft) return Dimension( width = lowerRight.x - upperLeft.x + 1, height = lowerRight.y - upperLeft.y + 1 diff --git a/src/test/kotlin/KridTest.kt b/src/test/kotlin/KridTest.kt index 30b717f..7dc0d07 100644 --- a/src/test/kotlin/KridTest.kt +++ b/src/test/kotlin/KridTest.kt @@ -6,13 +6,21 @@ import io.toolisticon.lib.krid._test.BooleanKridHelper.booleanCellValue import io.toolisticon.lib.krid._test.BooleanKridHelper.booleanKrid import io.toolisticon.lib.krid._test.isInstanceOf import io.toolisticon.lib.krid.model.Dimension -import org.assertj.core.api.AbstractThrowableAssert import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.jupiter.api.Test internal class KridTest { + private val innerKrid = booleanKrid( + """ + .... + .tf. + .ft. + .... + """.trimIndent() + ) + @Test internal fun `init fails - dimension#size != list#size`() { assertThatThrownBy { Krid(Dimension(4, 5), true, List(40) { false }) } @@ -117,7 +125,38 @@ internal class KridTest { ) }.isInstanceOf(IllegalArgumentException::class) .hasMessage("Cannot modify values because cells are out of bounds: [Cell(x=2, y=0), Cell(x=2, y=2)].") + } + + @Test + internal fun `subKrid - ul out of bounds`() { + assertThatThrownBy { + innerKrid.subKrid(cell(0, 7), cell(2, 2)) + }.isInstanceOf(IllegalArgumentException::class) + .hasMessage("Cell(x=0, y=7) is out of bounds (dimension=Dimension(width=4, height=4)).") + } + + @Test + internal fun `subKrid - lr out of bounds`() { + assertThatThrownBy { + innerKrid.subKrid(cell(0, 0), cell(2, 7)) + }.isInstanceOf(IllegalArgumentException::class) + .hasMessage("Cell(x=2, y=7) is out of bounds (dimension=Dimension(width=4, height=4)).") + } + @Test + internal fun `subKrid - lr less than ur`() { + assertThatThrownBy { + innerKrid.subKrid(cell(2, 2), cell(1, 1)) + }.isInstanceOf(IllegalArgumentException::class) + .hasMessage("Cell(x=1, y=1) has to be right and/or down from Cell(x=2, y=2).") + } + @Test + internal fun `subKrid - success`() { + with(innerKrid.subKrid(cell(1, 1), cell(2, 2))) { + assertThat(dimension).isEqualTo(Dimension(2, 2)) + assertThat(row(0)).containsExactly(true, false) + assertThat(row(1)).containsExactly(false, true) + } } } diff --git a/src/test/kotlin/model/DimensionTest.kt b/src/test/kotlin/model/DimensionTest.kt index 44b3912..9d6098d 100644 --- a/src/test/kotlin/model/DimensionTest.kt +++ b/src/test/kotlin/model/DimensionTest.kt @@ -30,7 +30,7 @@ internal class DimensionTest { internal fun `create from two cells - failure`() { assertThatThrownBy { Dimension(cell(2, 3), cell(1, 0)) } .isInstanceOf(IllegalArgumentException::class.java) - .hasMessage("Cell(x=2, y=3) has to be right and/or down from Cell(x=1, y=0).") + .hasMessage("Cell(x=1, y=0) has to be right and/or down from Cell(x=2, y=3).") } @Test @@ -66,8 +66,8 @@ internal class DimensionTest { cell(0, 2), ) ).containsExactly( - cell(0,0), - cell(1,1), + cell(0, 0), + cell(1, 1), ) } @@ -83,16 +83,16 @@ internal class DimensionTest { cell(0, 2), ) ).containsExactly( - cell(3,0), - cell(0,2), + cell(3, 0), + cell(0, 2), ) } @Test internal fun `predicate is in bounds`() { - val dimension = Dimension(2,2) + val dimension = Dimension(2, 2) - assertThat(dimension.isInBounds(cell(1,1))).isTrue - assertThat(dimension.isInBounds(cell(1,2))).isFalse + assertThat(dimension.isInBounds(cell(1, 1))).isTrue + assertThat(dimension.isInBounds(cell(1, 2))).isFalse } } From 8a98fdb875e33bedca3f24ed585ba451eba240fe Mon Sep 17 00:00:00 2001 From: Jan Galinski Date: Mon, 20 Sep 2021 23:52:32 +0200 Subject: [PATCH 14/30] can add a krid to another krid --- src/main/kotlin/AbstractKrid.kt | 9 +++- src/main/kotlin/Krid.kt | 20 +++++++- src/main/kotlin/model/AddKrid.kt | 21 ++++++++ src/test/kotlin/KridTest.kt | 82 ++++++++++++++++++++++++++++++++ src/test/kotlin/KridsTest.kt | 35 ++++++++++++-- 5 files changed, 161 insertions(+), 6 deletions(-) create mode 100644 src/main/kotlin/model/AddKrid.kt diff --git a/src/main/kotlin/AbstractKrid.kt b/src/main/kotlin/AbstractKrid.kt index 9cb7327..a78c29e 100644 --- a/src/main/kotlin/AbstractKrid.kt +++ b/src/main/kotlin/AbstractKrid.kt @@ -26,7 +26,6 @@ abstract class AbstractKrid { protected val indexTransformer by lazy { IndexTransformer(dimension.width) } - /** * Checks if the given row-index is in current [Dimension.rowRange]. * @@ -77,6 +76,14 @@ internal val AbstractKrid.isEmptyElement: (E) -> Boolean get() = { it == e */ operator fun AbstractKrid.get(cell: Cell) = get(cell.x, cell.y) +operator fun AbstractKrid.get(cells: List): List> { + dimension.filterNotInBounds(cells).also { + require(it.isEmpty()) { "Cannot get() because some cells are out of bounds: $it." } + } + + return cells.map { cell(it, get(it)) } +} + fun AbstractKrid.ascii(toChar: (E) -> Char? = { it?.toString()?.first() ?: '.' }): String = this.rows().joinToString(separator = "\n") { it.map { e -> toChar(e) ?: '.' }.joinToString(separator = "") } diff --git a/src/main/kotlin/Krid.kt b/src/main/kotlin/Krid.kt index 04cb3e6..6604f31 100644 --- a/src/main/kotlin/Krid.kt +++ b/src/main/kotlin/Krid.kt @@ -1,8 +1,8 @@ package io.toolisticon.lib.krid +import io.toolisticon.lib.krid.Krids.cell import io.toolisticon.lib.krid.Krids.krid import io.toolisticon.lib.krid.model.* -import io.toolisticon.lib.krid.model.Cell.Companion.requireGreaterThanOrEqual /** * A [Krid] of type `` with given [Dimension]. @@ -43,6 +43,20 @@ data class Krid( ) } + operator fun plus(add: AddKrid): Krid { + dimension.filterNotInBounds(add.cells).also { + require(it.isEmpty()) { "Cannot modify values because cells are out of bounds: $it." } + } + + val values: List> = add.values().map { + val old: E = this[it.cell] + + cell(it.cell, add.operation(old, it.value)) + } + + return this + values + } + fun subKrid(upperLeft: Cell, lowerRight: Cell): Krid { require(dimension.isInBounds(upperLeft)) { "$upperLeft is out of bounds (dimension=$dimension)." } require(dimension.isInBounds(lowerRight)) { "$lowerRight is out of bounds (dimension=$dimension)." } @@ -60,3 +74,7 @@ data class Krid( } operator fun Krid.plus(cellValue: CellValue): Krid = plus(listOf(cellValue)) +fun Krid.plus(offset: Cell, krid: Krid, operation: (E, E) -> E = { _, new -> new }) = plus(AddKrid(offset, krid, operation)) + +fun Krid.toAddKrid(offset: Cell = Krids.cell(0, 0), operation: (E, E) -> E = { _, new -> new }) = + AddKrid(offset = offset, krid = this, operation = operation) diff --git a/src/main/kotlin/model/AddKrid.kt b/src/main/kotlin/model/AddKrid.kt new file mode 100644 index 0000000..7d699dc --- /dev/null +++ b/src/main/kotlin/model/AddKrid.kt @@ -0,0 +1,21 @@ +package io.toolisticon.lib.krid.model + +import io.toolisticon.lib.krid.Krid +import io.toolisticon.lib.krid.Krids.cell + +data class AddKrid( + val offset: Cell = cell(0, 0), + val krid: Krid, + val operation: (E, E) -> E = { _, new -> new } +) { + + val cells by lazy { + krid.iterator().asSequence().map { it.cell }.toList() + } + + fun values(): List> = krid.iterator() + .asSequence() + .map { it + offset } + .toList() + +} diff --git a/src/test/kotlin/KridTest.kt b/src/test/kotlin/KridTest.kt index 7dc0d07..e498904 100644 --- a/src/test/kotlin/KridTest.kt +++ b/src/test/kotlin/KridTest.kt @@ -35,6 +35,23 @@ internal class KridTest { assertThat(krid.dimension.height).isEqualTo(3) } + @Test + internal fun `get multiple values`() { + val krid = booleanKrid( + """ + tff + ftt + fft + """.trimIndent() + ) + + assertThat(krid[listOf(cell(0, 0), cell(1, 2))]) + .containsExactly( + cell(0, 0, true), + cell(1, 2, false), + ) + } + @Test internal fun `get rows`() { val krid = booleanKrid( @@ -159,4 +176,69 @@ internal class KridTest { assertThat(row(1)).containsExactly(false, true) } } + + @Test + internal fun `add two krids - defaults`() { + val k1 = booleanKrid( + """ + ... + ttt + ... + """.trimIndent() + ) + + val k2 = booleanKrid( + """ + tt + ff + """.trimIndent() + ).toAddKrid() + + with(k1 + k2) { + assertThat(row(0)).containsExactly(true, true, null) + assertThat(row(1)).containsExactly(false, false, true) + assertThat(row(2)).containsExactly(null, null, null) + } + } + + @Test + internal fun `add two krids - with transformation`() { + val k1 = booleanKrid( + """ + ... + ttf + ... + """.trimIndent() + ) + + val k2 = booleanKrid( + """ + tt + ff + """.trimIndent() + ).toAddKrid(offset = cell(1, 1)) { o, n -> (o ?: false) && (n ?: false) } + + with(k1 + k2) { + assertThat(row(0)).containsExactly(null, null, null) + assertThat(row(1)).containsExactly(true, true, false) + assertThat(row(2)).containsExactly(null, false, false) + } + } + + @Test + internal fun `add two krid - fail out of bounds`() { + assertThatThrownBy { + booleanKrid( + """ + .. + .. + """.trimIndent() + ) + booleanKrid( + """ + ttt + """.trimIndent() + ).toAddKrid() + }.isInstanceOf(IllegalArgumentException::class) + .hasMessage("Cannot modify values because cells are out of bounds: [Cell(x=2, y=0)].") + } } diff --git a/src/test/kotlin/KridsTest.kt b/src/test/kotlin/KridsTest.kt index a6c166d..575c22b 100644 --- a/src/test/kotlin/KridsTest.kt +++ b/src/test/kotlin/KridsTest.kt @@ -7,7 +7,9 @@ import io.toolisticon.lib.krid.model.Column import io.toolisticon.lib.krid.model.Direction import io.toolisticon.lib.krid.model.Row import io.toolisticon.lib.krid.model.pair +import org.assertj.core.api.Assertions import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.jupiter.api.Test internal class KridsTest { @@ -56,15 +58,40 @@ internal class KridsTest { @Test internal fun `create from rows`() { - val krid: Krid = krid(listOf( - listOf(true, null), - listOf(null, false), - ), null) + val krid: Krid = krid( + listOf( + listOf(true, null), + listOf(null, false), + ), null + ) assertThat(krid.column(0)).containsExactly(true, null) assertThat(krid.column(1)).containsExactly(null, false) } + + @Test + internal fun `fromRows fails when rows is empty`() { + assertThatThrownBy { krid(emptyList(), false) } + .isInstanceOf(IllegalArgumentException::class.java) + .hasMessage("rows must not be empty: []") + } + + @Test + internal fun `fromRows fails when a row is empty`() { + assertThatThrownBy { krid(listOf(listOf(true), emptyList()), false) } + .isInstanceOf(IllegalArgumentException::class.java) + .hasMessage("no rows must be empty: [[true], []]") + } + + @Test + internal fun `fromRows fails when rows have different size`() { + assertThatThrownBy { krid(listOf(listOf(true, true), listOf(true)), false) } + .isInstanceOf(IllegalArgumentException::class.java) + .hasMessage("all rows must have same size: [[true, true], [true]]") + } + + @Test internal fun `adjacent cells`() { val krid = booleanKrid( From 4a882d6a5b5cbc1c6451533bd65e25fc94ccdad1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Sep 2021 06:14:51 +0000 Subject: [PATCH 15/30] Bump junit-bom from 5.8.0 to 5.8.1 Bumps [junit-bom](https://github.com/junit-team/junit5) from 5.8.0 to 5.8.1. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.8.0...r5.8.1) --- updated-dependencies: - dependency-name: org.junit:junit-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b7b6c17..e7d9b6c 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,7 @@ org.junit junit-bom - 5.8.0 + 5.8.1 pom import From d0562700fda080df149183d71eb421511e7573bf Mon Sep 17 00:00:00 2001 From: Jan Galinski Date: Mon, 27 Sep 2021 13:20:21 +0200 Subject: [PATCH 16/30] refactor + docs --- src/main/kotlin/AbstractKrid.kt | 41 +++++++++++++-- src/main/kotlin/Krid.kt | 3 +- src/main/kotlin/model/AddKrid.kt | 35 +++++++++++-- src/main/kotlin/model/CellValue.kt | 3 +- src/test/kotlin/KridTest.kt | 4 +- src/test/kotlin/model/AddKridTest.kt | 76 ++++++++++++++++++++++++++++ 6 files changed, 150 insertions(+), 12 deletions(-) create mode 100644 src/test/kotlin/model/AddKridTest.kt diff --git a/src/main/kotlin/AbstractKrid.kt b/src/main/kotlin/AbstractKrid.kt index a78c29e..eb93b47 100644 --- a/src/main/kotlin/AbstractKrid.kt +++ b/src/main/kotlin/AbstractKrid.kt @@ -24,6 +24,9 @@ abstract class AbstractKrid { */ protected abstract val list: List + /** + * Helper to align cell coordinates and list index. + */ protected val indexTransformer by lazy { IndexTransformer(dimension.width) } /** @@ -50,8 +53,12 @@ abstract class AbstractKrid { */ fun isEmpty(): Boolean = list.all { isEmptyElement(it) } - - operator fun get(x: Int, y: Int): E = list[requireInRows(y) * dimension.width + requireInColumns(x)] + /** + * @param x x coordinate of cell (has to be in bound) + * @param y y coordinate of cell (has to be in bound) + * @return value at given coordinates. + */ + operator fun get(x: Int, y: Int): E = list[indexTransformer.toIndex(requireInColumns(x), requireInRows(y))] fun rows(): Rows = Rows(dimension.rowRange.map(this::row)) fun columns(): Columns = Columns(dimension.columnRange.map(this::column)) @@ -63,12 +70,29 @@ abstract class AbstractKrid { .mapIndexed { index, e -> cell(indexTransformer.toCell(index), e) } .iterator() + fun sequence() : Sequence> = iterator().asSequence() + fun orthogonalAdjacentCells(cell: Cell): List = dimension.filterInBounds(cell.orthogonalAdjacent) fun adjacentCells(cell: Cell): List = dimension.filterInBounds(cell.adjacent) } +/** + * Convenient extension for [AbstractKrid.orthogonalAdjacentCells]. + * + * @see AbstractKrid.orthogonalAdjacentCells + */ fun AbstractKrid.orthogonalAdjacentCells(x: Int, y: Int) = orthogonalAdjacentCells(cell(x, y)) + +/** + * Convenient extension for [AbstractKrid.adjacentCells]. + * + * @see AbstractKrid.adjacentCells + */ fun AbstractKrid.adjacentCells(x: Int, y: Int) = adjacentCells(cell(x, y)) + +/** + * `true` if a value is equal to the defined [AbstractKrid.emptyElement]. + */ internal val AbstractKrid.isEmptyElement: (E) -> Boolean get() = { it == emptyElement } /** @@ -76,6 +100,11 @@ internal val AbstractKrid.isEmptyElement: (E) -> Boolean get() = { it == e */ operator fun AbstractKrid.get(cell: Cell) = get(cell.x, cell.y) +/** + * Gets multiple [CellValue]s for given list of [Cell]s. + * + * @see [AbstractKrid.get] + */ operator fun AbstractKrid.get(cells: List): List> { dimension.filterNotInBounds(cells).also { require(it.isEmpty()) { "Cannot get() because some cells are out of bounds: $it." } @@ -84,7 +113,13 @@ operator fun AbstractKrid.get(cells: List): List> { return cells.map { cell(it, get(it)) } } +/** + * Prints the [Krid] to console by converting the cell contents + * to char using given function. + * + * Defaults to "first char", so `Boolean.TRUE` becomes `t`. + * `null` ist represented as `.`. + */ fun AbstractKrid.ascii(toChar: (E) -> Char? = { it?.toString()?.first() ?: '.' }): String = this.rows().joinToString(separator = "\n") { it.map { e -> toChar(e) ?: '.' }.joinToString(separator = "") } - diff --git a/src/main/kotlin/Krid.kt b/src/main/kotlin/Krid.kt index 6604f31..68d42ba 100644 --- a/src/main/kotlin/Krid.kt +++ b/src/main/kotlin/Krid.kt @@ -48,7 +48,8 @@ data class Krid( require(it.isEmpty()) { "Cannot modify values because cells are out of bounds: $it." } } - val values: List> = add.values().map { + + val values: List> = add.cellValues.map { val old: E = this[it.cell] cell(it.cell, add.operation(old, it.value)) diff --git a/src/main/kotlin/model/AddKrid.kt b/src/main/kotlin/model/AddKrid.kt index 7d699dc..89202cc 100644 --- a/src/main/kotlin/model/AddKrid.kt +++ b/src/main/kotlin/model/AddKrid.kt @@ -3,19 +3,44 @@ package io.toolisticon.lib.krid.model import io.toolisticon.lib.krid.Krid import io.toolisticon.lib.krid.Krids.cell +/** + * Command that wraps the parameters needed to add a krid to another krid. + */ data class AddKrid( + /** + * At which offset do you want to insert the upper left corner of krid? + */ val offset: Cell = cell(0, 0), + + /** + * The krid you want to add. + */ val krid: Krid, + + /** + * A transformation that takes the old value and the new value and calculates the new value for the cell. + * By default, this will just take the new value. + */ val operation: (E, E) -> E = { _, new -> new } ) { + /** + * Gets a value from the internal krid, using cell coordinates from the target krid. + * This means, that the offset is substracted from the target cell before getting the value. + */ + operator fun get(targetCell: Cell): E = with(targetCell - offset) { krid[x, y] } + + /** + * Calculates + */ + operator fun invoke(old: CellValue): CellValue = cell(old.x, old.y, operation(old.value, get(old.cell))) + val cells by lazy { - krid.iterator().asSequence().map { it.cell }.toList() + krid.sequence().map { it.cell + offset }.toList() } - fun values(): List> = krid.iterator() - .asSequence() - .map { it + offset } - .toList() + val cellValues by lazy { + krid.sequence().map { it + offset }.toList() + } } diff --git a/src/main/kotlin/model/CellValue.kt b/src/main/kotlin/model/CellValue.kt index 2735c62..c88202a 100644 --- a/src/main/kotlin/model/CellValue.kt +++ b/src/main/kotlin/model/CellValue.kt @@ -1,10 +1,11 @@ package io.toolisticon.lib.krid.model -data class CellValue(val x: Int, val y: Int, val value: E) { +data class CellValue(val x: Int, val y: Int, override val value: E) : Map.Entry { constructor(cell: Cell, value: E) : this(cell.x, cell.y, value) constructor(pair: Pair, value: E) : this(pair.toCell(), value = value) val cell = Cell(x, y) + override val key = cell operator fun plus(cell: Cell): CellValue = (this.cell + cell).let { copy(x = it.x, y = it.y) diff --git a/src/test/kotlin/KridTest.kt b/src/test/kotlin/KridTest.kt index e498904..48cf1ac 100644 --- a/src/test/kotlin/KridTest.kt +++ b/src/test/kotlin/KridTest.kt @@ -235,9 +235,9 @@ internal class KridTest { """.trimIndent() ) + booleanKrid( """ - ttt + tt """.trimIndent() - ).toAddKrid() + ).toAddKrid(offset = cell(1,0)) }.isInstanceOf(IllegalArgumentException::class) .hasMessage("Cannot modify values because cells are out of bounds: [Cell(x=2, y=0)].") } diff --git a/src/test/kotlin/model/AddKridTest.kt b/src/test/kotlin/model/AddKridTest.kt new file mode 100644 index 0000000..3db195d --- /dev/null +++ b/src/test/kotlin/model/AddKridTest.kt @@ -0,0 +1,76 @@ +package io.toolisticon.lib.krid.model + +import io.toolisticon.lib.krid.Krids +import io.toolisticon.lib.krid.Krids.cell +import io.toolisticon.lib.krid._test.BooleanKridHelper +import io.toolisticon.lib.krid._test.BooleanKridHelper.booleanKrid +import org.assertj.core.api.Assertions +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + + +internal class AddKridTest { + + private val krid = booleanKrid( + """ + tf + f. + """.trimIndent() + ) + + @Test + internal fun `create with defaults`() { + val add: AddKrid = AddKrid(krid = krid) + + assertThat(add.cells).containsExactly( + cell(0, 0), + cell(1, 0), + cell(0, 1), + cell(1, 1), + ) + + assertThat(add.cellValues).containsExactly( + cell(0, 0, true), + cell(1, 0, false), + cell(0, 1, false), + cell(1, 1, null), + ) + + assertThat(add[cell(1, 1)]).isEqualTo(null) + + assertThat(add(cell(0, 0, false))).isEqualTo(cell(0, 0, true)) + + assertThat(add[cell(1, 0)]).isEqualTo(false) + } + + @Test + internal fun `create with offset`() { + val add: AddKrid = AddKrid( + krid = krid, + offset = cell(1, 1) + ) + + assertThat(add.cells).containsExactly( + cell(1, 1), + cell(2, 1), + cell(1, 2), + cell(2, 2), + ) + + assertThat(add.cellValues).containsExactly( + cell(1, 1, true), + cell(2, 1, false), + cell(1, 2, false), + cell(2, 2, null), + ) + + // this effectively gets 0,0 + assertThat(add[cell(1, 1)]).isEqualTo(true) + + // this effectively updates 1,1 + assertThat(add(cell(2, 2, false))) + .isEqualTo(cell(2, 2, null)) + } + + +} From 5683c4b0a77ed873858f2412546062e57a025959 Mon Sep 17 00:00:00 2001 From: Jan Galinski Date: Mon, 27 Sep 2021 20:02:06 +0200 Subject: [PATCH 17/30] add docs & tests --- src/main/kotlin/AbstractKrid.kt | 47 +++++++++++++++++++++- src/main/kotlin/Krid.kt | 12 ++++-- src/main/kotlin/model/AddKrid.kt | 10 ++++- src/main/kotlin/model/CellValue.kt | 37 ++++++++++++++++-- src/test/kotlin/KridTest.kt | 54 ++++++++++++++++++++++++++ src/test/kotlin/model/AddKridTest.kt | 3 -- src/test/kotlin/model/CellValueTest.kt | 8 ++++ 7 files changed, 157 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/AbstractKrid.kt b/src/main/kotlin/AbstractKrid.kt index eb93b47..c1a02b3 100644 --- a/src/main/kotlin/AbstractKrid.kt +++ b/src/main/kotlin/AbstractKrid.kt @@ -54,28 +54,71 @@ abstract class AbstractKrid { fun isEmpty(): Boolean = list.all { isEmptyElement(it) } /** + * Get value at given coordinates. + * * @param x x coordinate of cell (has to be in bound) * @param y y coordinate of cell (has to be in bound) * @return value at given coordinates. */ operator fun get(x: Int, y: Int): E = list[indexTransformer.toIndex(requireInColumns(x), requireInRows(y))] + /** + * @return the rows of this krid. + */ fun rows(): Rows = Rows(dimension.rowRange.map(this::row)) + + /** + * @return the columns of this krid. + */ fun columns(): Columns = Columns(dimension.columnRange.map(this::column)) + /** + * @param index which row to get + * @return row with index. + */ abstract fun row(index: Int): Row + + /** + * @param index which column to get + * @return column with index. + */ abstract fun column(index: Int): Column + /** + * Iterate row by row, starting to left to bottom right. + * + * @return iterator of [CellValue]s. + */ fun iterator(): Iterator> = list .mapIndexed { index, e -> cell(indexTransformer.toCell(index), e) } .iterator() - fun sequence() : Sequence> = iterator().asSequence() - + /** + * Returns direct neighbors up, left, rdown and right of given cell. + * + * @param cell the base cell from which to check neighbors + * @return list of cells up, left, down, right (in bounds) + */ fun orthogonalAdjacentCells(cell: Cell): List = dimension.filterInBounds(cell.orthogonalAdjacent) + + /** + * Returns direct neighbors in all 8 directions of given cell. + * + * @param cell the base cell from which to check neighbors + * @return list of cells in all 8 directions (in bounds) + */ fun adjacentCells(cell: Cell): List = dimension.filterInBounds(cell.adjacent) } + +/** + * Sequence of [iterator]. + * + * @see iterator + * @return sequence of [CellValue]s. + */ +fun AbstractKrid.sequence(): Sequence> = iterator().asSequence() + /** * Convenient extension for [AbstractKrid.orthogonalAdjacentCells]. * diff --git a/src/main/kotlin/Krid.kt b/src/main/kotlin/Krid.kt index 68d42ba..bbb9753 100644 --- a/src/main/kotlin/Krid.kt +++ b/src/main/kotlin/Krid.kt @@ -14,7 +14,8 @@ data class Krid( ) : AbstractKrid() { init { - require(dimension.size == list.size) + // dimension must match the size of the internal list. + require(dimension.size == list.size) { "$dimension does not match internal size=${list.size}." } } private val rowCache: MutableMap> = mutableMapOf() @@ -48,7 +49,6 @@ data class Krid( require(it.isEmpty()) { "Cannot modify values because cells are out of bounds: $it." } } - val values: List> = add.cellValues.map { val old: E = this[it.cell] @@ -75,7 +75,11 @@ data class Krid( } operator fun Krid.plus(cellValue: CellValue): Krid = plus(listOf(cellValue)) -fun Krid.plus(offset: Cell, krid: Krid, operation: (E, E) -> E = { _, new -> new }) = plus(AddKrid(offset, krid, operation)) -fun Krid.toAddKrid(offset: Cell = Krids.cell(0, 0), operation: (E, E) -> E = { _, new -> new }) = +fun Krid.plus(offset: Cell, krid: Krid, operation: (E, E) -> E = { _, new -> new }) = plus(krid.toAddKrid(offset, operation)) + +/** + * Create [AddKrid] of given krid. + */ +fun Krid.toAddKrid(offset: Cell = cell(0, 0), operation: (E, E) -> E = { _, new -> new }) = AddKrid(offset = offset, krid = this, operation = operation) diff --git a/src/main/kotlin/model/AddKrid.kt b/src/main/kotlin/model/AddKrid.kt index 89202cc..9304ab4 100644 --- a/src/main/kotlin/model/AddKrid.kt +++ b/src/main/kotlin/model/AddKrid.kt @@ -2,6 +2,7 @@ package io.toolisticon.lib.krid.model import io.toolisticon.lib.krid.Krid import io.toolisticon.lib.krid.Krids.cell +import io.toolisticon.lib.krid.sequence /** * Command that wraps the parameters needed to add a krid to another krid. @@ -31,16 +32,21 @@ data class AddKrid( operator fun get(targetCell: Cell): E = with(targetCell - offset) { krid[x, y] } /** - * Calculates + * Calculates the new [CellValue] by applying the [operation] to the old value and the value of this [AddKrid]. */ operator fun invoke(old: CellValue): CellValue = cell(old.x, old.y, operation(old.value, get(old.cell))) + /** + * All cells of this [AddKrid]. + */ val cells by lazy { krid.sequence().map { it.cell + offset }.toList() } + /** + * All cells with values of the [AddKrid]. + */ val cellValues by lazy { krid.sequence().map { it + offset }.toList() } - } diff --git a/src/main/kotlin/model/CellValue.kt b/src/main/kotlin/model/CellValue.kt index c88202a..16991c1 100644 --- a/src/main/kotlin/model/CellValue.kt +++ b/src/main/kotlin/model/CellValue.kt @@ -1,15 +1,46 @@ package io.toolisticon.lib.krid.model -data class CellValue(val x: Int, val y: Int, override val value: E) : Map.Entry { +/** + * Represents coordinates and value of a cell in a krid. + */ +data class CellValue( + /** + * x coordinate + */ + val x: Int, + /** + * y coordinate + */ + val y: Int, + /** + * value + */ + val value: E){ + + /** + * Create from cell and value. + */ constructor(cell: Cell, value: E) : this(cell.x, cell.y, value) + + /** + * Create from pair and value. + */ constructor(pair: Pair, value: E) : this(pair.toCell(), value = value) + /** + * Cell value for x/y coordinates. + */ val cell = Cell(x, y) - override val key = cell + /** + * Move x/y coordinates by cell. + */ operator fun plus(cell: Cell): CellValue = (this.cell + cell).let { copy(x = it.x, y = it.y) } } -val List>.cells get() = map { it.cell } +/** + * Extract list of cells from [CellValue]s (dropping values). + */ +val List>.cells: List get() = map { it.cell } diff --git a/src/test/kotlin/KridTest.kt b/src/test/kotlin/KridTest.kt index 48cf1ac..6b7078e 100644 --- a/src/test/kotlin/KridTest.kt +++ b/src/test/kotlin/KridTest.kt @@ -241,4 +241,58 @@ internal class KridTest { }.isInstanceOf(IllegalArgumentException::class) .hasMessage("Cannot modify values because cells are out of bounds: [Cell(x=2, y=0)].") } + + @Test + internal fun `adjacent cells`() { + val krid: Krid = krid(""" + 123 + 456 + 789 + """.trimIndent()) + + assertThat(krid.adjacentCells(1,1)).containsExactly( + cell(1,0), // up + cell(2,0), // up-right + cell(2,1), // right + cell(2,2), // down-right + cell(1,2), // down + cell(0,2), + cell(0,1), + cell(0,0), + ) + assertThat(Krids.krid(false).adjacentCells(0,0)).isEmpty() + } + + + @Test + internal fun `orthogonalAdjacent cells`() { + val krid: Krid = krid(""" + 123 + 456 + 789 + """.trimIndent()) + + assertThat(krid.orthogonalAdjacentCells(1,1)).containsExactly( + cell(1,0), // up + cell(2,1), // right + cell(1,2), // down + cell(0,1), + ) + assertThat(Krids.krid(false).orthogonalAdjacentCells(0,0)).isEmpty() + } + + @Test + internal fun `print ascii`() { + val string = """ + abc + def + """.trimIndent() + + assertThat(krid(string).ascii()).isEqualTo(string) + + assertThat(krid(string).ascii { it.uppercaseChar() }).isEqualTo(""" + ABC + DEF + """.trimIndent()) + } } diff --git a/src/test/kotlin/model/AddKridTest.kt b/src/test/kotlin/model/AddKridTest.kt index 3db195d..cadb1f5 100644 --- a/src/test/kotlin/model/AddKridTest.kt +++ b/src/test/kotlin/model/AddKridTest.kt @@ -1,10 +1,7 @@ package io.toolisticon.lib.krid.model -import io.toolisticon.lib.krid.Krids import io.toolisticon.lib.krid.Krids.cell -import io.toolisticon.lib.krid._test.BooleanKridHelper import io.toolisticon.lib.krid._test.BooleanKridHelper.booleanKrid -import org.assertj.core.api.Assertions import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test diff --git a/src/test/kotlin/model/CellValueTest.kt b/src/test/kotlin/model/CellValueTest.kt index 03b8fe5..b41fb38 100644 --- a/src/test/kotlin/model/CellValueTest.kt +++ b/src/test/kotlin/model/CellValueTest.kt @@ -22,4 +22,12 @@ internal class CellValueTest { assertThat(CellValue(Cell(1, 2), true).cell).isEqualTo(Cell(1, 2)) } + @Test + internal fun `create from pair`() { + val cellValue = CellValue(1 to 2, true) + + assertThat(cellValue.x).isEqualTo(1) + assertThat(cellValue.y).isEqualTo(2) + assertThat(cellValue.cell).isEqualTo((1 to 2).toCell()) + } } From abca15bfaf5b0e555b9369702563ceabe5d9c63d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Oct 2021 06:17:52 +0000 Subject: [PATCH 18/30] Bump dokka-maven-plugin from 1.5.30 to 1.5.31 Bumps [dokka-maven-plugin](https://github.com/Kotlin/dokka) from 1.5.30 to 1.5.31. - [Release notes](https://github.com/Kotlin/dokka/releases) - [Commits](https://github.com/Kotlin/dokka/compare/v1.5.30...v1.5.31) --- updated-dependencies: - dependency-name: org.jetbrains.dokka:dokka-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6536995..091eb9a 100644 --- a/pom.xml +++ b/pom.xml @@ -272,7 +272,7 @@ org.jetbrains.dokka dokka-maven-plugin - 1.5.30 + 1.5.31 package From 7d08a984c220e84d42b39dfa25b022901dfc5a17 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Oct 2021 06:32:19 +0000 Subject: [PATCH 19/30] Bump mockito-kotlin from 3.2.0 to 4.0.0 Bumps [mockito-kotlin](https://github.com/mockito/mockito-kotlin) from 3.2.0 to 4.0.0. - [Release notes](https://github.com/mockito/mockito-kotlin/releases) - [Commits](https://github.com/mockito/mockito-kotlin/compare/3.2.0...4.0.0) --- updated-dependencies: - dependency-name: org.mockito.kotlin:mockito-kotlin dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 091eb9a..61d37a4 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,7 @@ org.mockito.kotlin mockito-kotlin - 3.2.0 + 4.0.0 test From 78341675854d5ff64b91abfed53f575b6f9da6ef Mon Sep 17 00:00:00 2001 From: Jan Galinski Date: Sun, 17 Oct 2021 10:34:57 +0200 Subject: [PATCH 20/30] doc and tests --- README.md | 8 ++++++++ pom.xml | 4 +++- src/main/kotlin/Krid.kt | 4 ++++ src/test/kotlin/KridTest.kt | 15 +++++++++++++++ src/test/kotlin/_test/ResourceHelper.kt | 11 +++++++++++ src/test/resources/.gitkeep | 0 src/test/resources/krid-ascii.txt | 6 ++++++ 7 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 src/test/kotlin/_test/ResourceHelper.kt delete mode 100644 src/test/resources/.gitkeep create mode 100644 src/test/resources/krid-ascii.txt diff --git a/README.md b/README.md index cde6a3b..4a1c70e 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,14 @@ Your one-stop library to support two dimensional kotlin grids. [![sponsored](https://img.shields.io/badge/sponsoredBy-Holisticon-RED.svg)](https://holisticon.de/) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.toolisticon.lib/krid/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.toolisticon.lib/krid) +```ascii +.##..##..#####...######..#####.. +.##.##...##..##....##....##..##. +.####....#####.....##....##..##. +.##.##...##..##....##....##..##. +.##..##..##..##..######..#####.. +................................ +``` ## Idea diff --git a/pom.xml b/pom.xml index 6536995..a0af993 100644 --- a/pom.xml +++ b/pom.xml @@ -278,12 +278,14 @@ package attach-javadocs + dokka + javadoc javadocJar - 11 + 8