├── .java-version ├── .mvn ├── maven.config ├── wrapper │ ├── maven-wrapper.jar │ ├── maven-wrapper.properties │ └── MavenWrapperDownloader.java └── jvm.config ├── .gitignore ├── config ├── logging.properties └── detekt.yml ├── .gitattributes ├── src ├── test │ ├── resources │ │ └── junit-platform.properties │ └── kotlin │ │ └── hm │ │ └── binkley │ │ └── veil │ │ └── VeilTest.kt └── main │ └── kotlin │ └── hm │ └── binkley │ └── veil │ ├── noise.kt │ ├── data-source.kt │ ├── fake-bob-data-source.kt │ ├── bob.kt │ ├── main.kt │ └── veil.kt ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .editorconfig ├── images └── public-domain.svg ├── batect.yml ├── LICENSE.md ├── run.sh ├── README.md ├── coverage ├── batect ├── mvnw.cmd ├── batect.cmd ├── mvnw └── pom.xml /.java-version: -------------------------------------------------------------------------------- 1 | 21 2 | -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | --strict-checksums 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /*.iml 3 | /target/ 4 | -------------------------------------------------------------------------------- /config/logging.properties: -------------------------------------------------------------------------------- 1 | .level = WARNING 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.bat text eol=crlf 3 | *.cmd text eol=crlf 4 | -------------------------------------------------------------------------------- /src/test/resources/junit-platform.properties: -------------------------------------------------------------------------------- 1 | junit.jupiter.testinstance.lifecycle.default=per_class 2 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binkley/kotlin-veil/HEAD/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/jvm.config: -------------------------------------------------------------------------------- 1 | --add-opens java.base/java.lang=ALL-UNNAMED 2 | -Djava.util.logging.config.file=config/logging.properties 3 | -XX:+UseCodeCacheFlushing 4 | -XX:TieredStopAtLevel=1 5 | -------------------------------------------------------------------------------- /src/main/kotlin/hm/binkley/veil/noise.kt: -------------------------------------------------------------------------------- 1 | package hm.binkley.veil 2 | 3 | /** Yes, a global mutable. This is a proof-of-concept project (spike). */ 4 | var beNoisy = false 5 | 6 | fun println(msg: String) { 7 | if (beNoisy) kotlin.io.println(msg) 8 | } 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "maven" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: "daily" 11 | -------------------------------------------------------------------------------- /config/detekt.yml: -------------------------------------------------------------------------------- 1 | build: 2 | maxIssues: 0 3 | 4 | console-reports: 5 | exclude: 6 | # - 'ProjectStatisticsReport' 7 | # - 'ComplexityReport' 8 | # - 'NotificationReport' 9 | # - 'FindingsReport' 10 | # - 'FileBasedFindingsReport' 11 | # - 'BuildFailureReport' 12 | 13 | #complexity: 14 | # excludes: '**/test/**' 15 | # 16 | #style: 17 | # excludes: '**/test/**' 18 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [**] 4 | end_of_line = lf 5 | # TODO: Impose max line length 6 | #max_line_length = 80 7 | 8 | [*.{kt,kts}] 9 | ktlint_code_style = intellij_idea 10 | ktlint_standard_filename = disabled 11 | # Oh, the irony. IntelliJ insists on sorting java* imports last 12 | ktlint_standard_import-ordering = disabled 13 | ktlint_standard_trailing-comma-on-call-site = disabled 14 | ktlint_standard_trailing-comma-on-declaration-site = disabled 15 | -------------------------------------------------------------------------------- /images/public-domain.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/kotlin/hm/binkley/veil/data-source.kt: -------------------------------------------------------------------------------- 1 | package hm.binkley.veil 2 | 3 | interface DataSource { 4 | fun fetch(query: String, vararg args: Any?): Sequence> 5 | } 6 | 7 | /** 8 | * Fetches the value of one column from one row. Assumptions: 9 | * - The query returns only one row 10 | * - The row is specific to an `ID` column 11 | * - [table] and [prop] are simple strings: no whitespace, no SQL injection 12 | * 13 | * These assumptions do not make sense for a general ORM: this is demo code. 14 | * 15 | * The query is constructed as: 16 | * ``` 17 | * SELECT $prop FROM $table WHERE ID = :id 18 | * ``` 19 | */ 20 | @Suppress("UNCHECKED_CAST") 21 | internal fun DataSource.fetchProperty( 22 | table: String, 23 | id: ID, 24 | prop: String 25 | ): T = 26 | fetch("SELECT $prop FROM $table WHERE ID = :id", id).first()[prop] as T 27 | -------------------------------------------------------------------------------- /batect.yml: -------------------------------------------------------------------------------- 1 | containers: 2 | build-env: 3 | image: eclipse-temurin:21-jdk-jammy 4 | run_as_current_user: 5 | enabled: true 6 | home_directory: /home/container-user 7 | volumes: 8 | - local: . 9 | container: /code 10 | options: cached 11 | - local: ~/.m2 12 | container: /home/container-user/.m2 13 | options: cached 14 | working_directory: /code 15 | 16 | tasks: 17 | # NB -- local build need "clean", but CI (GitHub actions) does not: 18 | # Docker copies in the local repo clone, including any artifacts such as 19 | # build/ or target/ directories; CI starts from a fresh clone 20 | build: 21 | description: Build and test with Maven 22 | run: 23 | container: build-env 24 | command: ./mvnw --no-transfer-progress clean verify site 25 | run: 26 | description: Runs the demo program 27 | run: 28 | container: build-env 29 | command: ./run.sh 30 | shell: 31 | description: Opens a shell in the container 32 | run: 33 | container: build-env 34 | command: /bin/bash 35 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 19 | -------------------------------------------------------------------------------- /src/main/kotlin/hm/binkley/veil/fake-bob-data-source.kt: -------------------------------------------------------------------------------- 1 | package hm.binkley.veil 2 | 3 | class FakeBobDataSource( 4 | var rowOneA: Int, 5 | private val rowOneB: String? 6 | ) : DataSource { 7 | override fun fetch( 8 | query: String, 9 | vararg args: Any? 10 | ): Sequence> { 11 | println("FETCHING${args.contentToString()} -> $query") 12 | return when (query) { 13 | SELECT_ALL_BOBS -> sequenceOf( 14 | mapOf( 15 | "id" to 1, 16 | "a" to rowOneA, 17 | "b" to rowOneB 18 | ) 19 | ) 20 | SELECT_BOB_A -> when (args[0]) { 21 | 1 -> sequenceOf(mapOf("a" to rowOneA)) 22 | else -> sequenceOf(mapOf()) 23 | } 24 | SELECT_BOB_B -> when (args[0]) { 25 | 1 -> sequenceOf(mapOf("b" to rowOneB)) 26 | else -> sequenceOf(mapOf()) 27 | } 28 | else -> error("Unknown: $query") 29 | } 30 | } 31 | } 32 | 33 | internal const val SELECT_ALL_BOBS = "SELECT * FROM Bob" 34 | internal const val SELECT_BOB_A = "SELECT a FROM Bob WHERE ID = :id" 35 | internal const val SELECT_BOB_B = "SELECT b FROM Bob WHERE ID = :id" 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License # 2 | 3 | This is free and unencumbered software released into the public domain. 4 | 5 | Anyone is free to copy, modify, publish, use, compile, sell, or 6 | distribute this software, either in source code form or as a compiled 7 | binary, for any purpose, commercial or non-commercial, and by any 8 | means. 9 | 10 | In jurisdictions that recognize copyright laws, the author or authors 11 | of this software dedicate any and all copyright interest in the 12 | software to the public domain. We make this dedication for the benefit 13 | of the public at large and to the detriment of our heirs and 14 | successors. We intend this dedication to be an overt act of 15 | relinquishment in perpetuity of all present and future rights to this 16 | software under copyright law. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 22 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 23 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | 26 | For more information, please refer to . 27 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '**.md' 7 | - 'images/**' 8 | pull_request: 9 | paths-ignore: 10 | - '**.md' 11 | - 'images/**' 12 | 13 | jobs: 14 | build-maven: 15 | name: Builds and tests for Maven under Batect 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Clone repository 20 | uses: actions/checkout@master 21 | 22 | - name: Restore Batect caches 23 | uses: actions/cache@master 24 | env: 25 | cache-name: batect-cache 26 | with: 27 | path: ~/.batect 28 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/batect.xml') }} 29 | restore-keys: | 30 | ${{ runner.os }}-build-${{ env.cache-name }}- 31 | ${{ runner.os }}-build- 32 | ${{ runner.os }}- 33 | 34 | - name: Restore Maven caches 35 | uses: actions/cache@master 36 | env: 37 | cache-name: maven-cache 38 | with: 39 | path: ~/.m2 40 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/pom.xml') }} 41 | restore-keys: | 42 | ${{ runner.os }}-build-${{ env.cache-name }}- 43 | ${{ runner.os }}-build- 44 | ${{ runner.os }}- 45 | -------------------------------------------------------------------------------- /src/main/kotlin/hm/binkley/veil/bob.kt: -------------------------------------------------------------------------------- 1 | package hm.binkley.veil 2 | 3 | import java.util.Objects.hash 4 | 5 | /** 6 | * Ideally this would be an abstract class with `equals` and `hashCode` 7 | * defined based on values to treat implementations as value objects. Using 8 | * JDK proxies, an interface is needed rather than an abstract class. 9 | * 10 | * Note that `id` is not a property. This respects that some implementations 11 | * may be database rows with an ID, but others could be memory-only test 12 | * objects. 13 | * 14 | * Some interesting use cases might arise when mixing objects read from a 15 | * database with objects from other sources. 16 | */ 17 | interface Bob { 18 | /** Meant to be veiled */ 19 | val a: Int 20 | 21 | /** Meant to not be veiled */ 22 | val b: String? 23 | 24 | val veiled: Int // TODO: Please break! 25 | } 26 | 27 | class BobFromDataSource(private val ds: DataSource, val id: Int) : Bob { 28 | override val a: Int get() = ds.fetchProperty("Bob", id, "a") 29 | override val b: String? get() = ds.fetchProperty("Bob", id, "b") 30 | override val veiled = 17 31 | 32 | override fun equals(other: Any?) = this === other || 33 | other is BobFromDataSource && 34 | id == other.id 35 | 36 | override fun hashCode() = hash(this::class, id) 37 | override fun toString() = "Bob($id){a=$a, b=$b, veiled=$veiled}" 38 | } 39 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # shellcheck disable=SC2214,SC2215 3 | 4 | export PS4='+${BASH_SOURCE}:${LINENO}:${FUNCNAME[0]:+${FUNCNAME[0]}():} ' 5 | 6 | set -e 7 | set -u 8 | set -o pipefail 9 | 10 | package=hm.binkley 11 | artifactId=kotlin-veil 12 | version=0-SNAPSHOT 13 | 14 | function mangle-kotlin-classname() { 15 | local IFS=. 16 | 17 | local -a parts 18 | read -r -a parts <<<"$1" 19 | local last="${parts[-1]}" 20 | 21 | case "$last" in 22 | *-* | *Kt) ;; 23 | *) last="${last}Kt" ;; 24 | esac 25 | last="${last//-/_}" 26 | last=""${last^} 27 | 28 | parts[-1]="$last" 29 | 30 | echo "${parts[*]}" 31 | } 32 | 33 | function rebuild-if-needed() { 34 | [[ -e "$jar" && -z "$(find src/main -type f -newer "$jar")" ]] && return 35 | 36 | ./mvnw -C -Dmaven.test.skip=true package 37 | } 38 | 39 | debug=false 40 | while getopts :d-: opt; do 41 | [[ $opt == - ]] && opt=${OPTARG%%=*} OPTARG=${OPTARG#*=} 42 | case $opt in 43 | d | debug) debug=true ;; 44 | *) exit 2 ;; 45 | esac 46 | done 47 | shift $((OPTIND - 1)) 48 | 49 | $debug && set -x 50 | 51 | readonly jar=target/$artifactId-$version-jar-with-dependencies.jar 52 | 53 | case $# in 54 | 0) set - -jar "$jar" ;; 55 | *) 56 | class="$1" 57 | shift 58 | set - -cp "$jar" "$(mangle-kotlin-classname "$package.$class")" "$@" 59 | ;; 60 | esac 61 | $debug && set -x # "set - ..." clears the -x flag 62 | 63 | rebuild-if-needed 64 | 65 | exec java "$@" 66 | -------------------------------------------------------------------------------- /src/main/kotlin/hm/binkley/veil/main.kt: -------------------------------------------------------------------------------- 1 | package hm.binkley.veil 2 | 3 | private val fakeDs = FakeBobDataSource(2, "apple") 4 | private val initialData = fakeDs.fetch(SELECT_ALL_BOBS) 5 | private val pierceableBobs = bobs().pierceable 6 | private val unpierceableBobs = bobs().unpierceable 7 | 8 | fun main() { 9 | beNoisy = true 10 | 11 | fakeDs.rowOneA = 222 12 | 13 | println() 14 | println("NOTE: Bob has props: a, b, veiled.") 15 | println( 16 | "NOTE: Prop veiled is to show that the data value is not masked " + 17 | "by Veilable." 18 | ) 19 | 20 | println() 21 | println("PIERCED") 22 | println("-------") 23 | pierceableBobs.forEach { 24 | dumpVeiled(it, true) 25 | } 26 | 27 | println() 28 | println("UNPIERCED") 29 | println("---------") 30 | unpierceableBobs.forEach { 31 | dumpVeiled(it, false) 32 | } 33 | } 34 | 35 | private fun bobs() = object { 36 | val pierceable: Sequence get() = bobs(true) 37 | val unpierceable: Sequence get() = bobs(false) 38 | 39 | private fun bobs(pierceable: Boolean) = 40 | veil( 41 | pierceable = pierceable, 42 | ds = fakeDs, 43 | initialData = initialData, 44 | idProp = "id", 45 | "a" 46 | ) { ds, id -> 47 | BobFromDataSource(ds, id) 48 | } 49 | } 50 | 51 | private fun dumpVeiled(it: Bob, pierceable: Boolean) { 52 | println() 53 | println( 54 | "== Read veiled, then pierced if pierceable ($pierceable), then " + 55 | "underlying real object" 56 | ) 57 | println("VEILED: Bob{a=${it.a}, b=${it.b}, veiled=${it.veiled}}") 58 | println() 59 | println("MAYBE-PIERCED: Bob{a=${it.a}, b=${it.b}, veiled=${it.veiled}}") 60 | println() 61 | println("REAL: $it") // Relies on "toString" forwarding to real obj 62 | println() 63 | @Suppress("UNCHECKED_CAST") 64 | it as Veilable 65 | println("PIERCED? ${it.pierced}") 66 | println("VEILED-A? ${it.veiled(Bob::a)}") 67 | println("VEILED-B? ${it.veiled(Bob::b)}") 68 | println("IS REFLECTIVE VEILED MASKED? ${it.veiled(Bob::veiled)}") 69 | } 70 | -------------------------------------------------------------------------------- /src/main/kotlin/hm/binkley/veil/veil.kt: -------------------------------------------------------------------------------- 1 | package hm.binkley.veil 2 | 3 | import java.lang.reflect.InvocationHandler 4 | import java.lang.reflect.Method 5 | import java.lang.reflect.Proxy.newProxyInstance 6 | import kotlin.reflect.KProperty1 7 | 8 | interface Veilable { 9 | val pierced: Boolean 10 | 11 | fun veiled(prop: KProperty1): Boolean 12 | } 13 | 14 | inline fun veil( 15 | pierceable: Boolean, 16 | ds: DataSource, 17 | initialData: Sequence>, 18 | idProp: String, 19 | vararg veiledProps: String, 20 | crossinline ctorOfReal: (DataSource, ID) -> T 21 | ) = initialData.map { 22 | @Suppress("UNCHECKED_CAST") 23 | newProxyInstance( 24 | T::class.java.classLoader, 25 | arrayOf(T::class.java, Veilable::class.java), 26 | Veiler( 27 | pierceable, 28 | ctorOfReal(ds, it[idProp] as ID)!!, 29 | it, 30 | *veiledProps 31 | ) 32 | ) as T 33 | } 34 | 35 | /** 36 | * *NB* — Access scope is not optimal: 37 | * - JDK proxies require class tokens 38 | * - [veil] is `inline` so `reified` works, and caller does not need to pass 39 | * in a class token for the JDK proxy: the code does it for you 40 | * - [veil] requires `crossinline` for the lambda constructor 41 | * - [Veiler] is `public` because `veil(...)` is inlined (a Kotlin requirement) 42 | */ 43 | class Veiler( 44 | private val pierceable: Boolean = false, 45 | private val real: Any, 46 | private val data: Map, 47 | private vararg val veiledProps: String 48 | ) : InvocationHandler { 49 | private var pierced = false 50 | 51 | override fun invoke( 52 | proxy: Any, 53 | method: Method, 54 | args: Array? 55 | ): Any? { 56 | val prop = prop(method.name) 57 | 58 | if (method.`belongs to Veilable`) { 59 | return when (prop) { 60 | "pierced" -> pierced 61 | "veiled" -> !pierced && argsVeiled(args) 62 | else -> error( 63 | "Invocation handler out of sync with Veilable: $method" 64 | ) 65 | } 66 | } 67 | 68 | if (prop.veiled) { 69 | println("VEILING -> ${method.name}=${data[prop]}") 70 | return data[prop] 71 | } 72 | 73 | if (piercing) { 74 | println("PIERCING VEIL") 75 | pierced = true 76 | } 77 | 78 | println("CALLING $method") 79 | return method(real, *(args ?: arrayOf())) // TODO: Not nice syntax 80 | } 81 | 82 | @Suppress("PrivatePropertyName") 83 | private val Method.`belongs to Veilable` 84 | get() = Veilable::class.java == declaringClass 85 | 86 | private fun argsVeiled(args: Array?) = 87 | (args?.get(0) as KProperty1<*, *>).name in veiledProps 88 | 89 | /** 90 | * @todo How to cleanly combine check for args veiled and the prop itself? 91 | */ 92 | private val String.veiled get() = !pierced && this in veiledProps 93 | private val piercing get() = pierceable && !pierced 94 | } 95 | 96 | private fun prop(methodName: String) = 97 | if (methodName.startsWith("get")) { 98 | methodName 99 | .removePrefix("get") 100 | .replaceFirstChar { it.lowercase() } 101 | } else { 102 | methodName 103 | } 104 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | import java.net.Authenticator; 23 | import java.net.PasswordAuthentication; 24 | import java.net.URL; 25 | import java.nio.file.Files; 26 | import java.nio.file.Path; 27 | import java.nio.file.Paths; 28 | import java.nio.file.StandardCopyOption; 29 | 30 | public final class MavenWrapperDownloader 31 | { 32 | private static final String WRAPPER_VERSION = "3.2.0"; 33 | 34 | private static final boolean VERBOSE = Boolean.parseBoolean( System.getenv( "MVNW_VERBOSE" ) ); 35 | 36 | public static void main( String[] args ) 37 | { 38 | log( "Apache Maven Wrapper Downloader " + WRAPPER_VERSION ); 39 | 40 | if ( args.length != 2 ) 41 | { 42 | System.err.println( " - ERROR wrapperUrl or wrapperJarPath parameter missing" ); 43 | System.exit( 1 ); 44 | } 45 | 46 | try 47 | { 48 | log( " - Downloader started" ); 49 | final URL wrapperUrl = new URL( args[0] ); 50 | final String jarPath = args[1].replace( "..", "" ); // Sanitize path 51 | final Path wrapperJarPath = Paths.get( jarPath ).toAbsolutePath().normalize(); 52 | downloadFileFromURL( wrapperUrl, wrapperJarPath ); 53 | log( "Done" ); 54 | } 55 | catch ( IOException e ) 56 | { 57 | System.err.println( "- Error downloading: " + e.getMessage() ); 58 | if ( VERBOSE ) 59 | { 60 | e.printStackTrace(); 61 | } 62 | System.exit( 1 ); 63 | } 64 | } 65 | 66 | private static void downloadFileFromURL( URL wrapperUrl, Path wrapperJarPath ) 67 | throws IOException 68 | { 69 | log( " - Downloading to: " + wrapperJarPath ); 70 | if ( System.getenv( "MVNW_USERNAME" ) != null && System.getenv( "MVNW_PASSWORD" ) != null ) 71 | { 72 | final String username = System.getenv( "MVNW_USERNAME" ); 73 | final char[] password = System.getenv( "MVNW_PASSWORD" ).toCharArray(); 74 | Authenticator.setDefault( new Authenticator() 75 | { 76 | @Override 77 | protected PasswordAuthentication getPasswordAuthentication() 78 | { 79 | return new PasswordAuthentication( username, password ); 80 | } 81 | } ); 82 | } 83 | try ( InputStream inStream = wrapperUrl.openStream() ) 84 | { 85 | Files.copy( inStream, wrapperJarPath, StandardCopyOption.REPLACE_EXISTING ); 86 | } 87 | log( " - Downloader complete" ); 88 | } 89 | 90 | private static void log( String msg ) 91 | { 92 | if ( VERBOSE ) 93 | { 94 | System.out.println( msg ); 95 | } 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/test/kotlin/hm/binkley/veil/VeilTest.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("RedundantInnerClassModifier") // IntelliJ 2 | 3 | package hm.binkley.veil 4 | 5 | import org.junit.jupiter.api.Assertions.assertEquals 6 | import org.junit.jupiter.api.Nested 7 | import org.junit.jupiter.api.Test 8 | 9 | private const val VEILED_ROW_ONE_A = 2 10 | private const val PIERCED_ROW_ONE_A = 222 11 | private const val REAL_ROW_ONE_B = "apple" 12 | 13 | private val fakeDs = FakeBobDataSource(-1, REAL_ROW_ONE_B) 14 | 15 | internal class VeilTest { 16 | @Nested 17 | inner class Veiling { 18 | @Test 19 | fun `should veil first time, and stay veiled`() { 20 | fakeDs.rowOneA = VEILED_ROW_ONE_A 21 | 22 | val bobs = bobs().pierceable 23 | // Sequences are not restartable -- TODO: Use List? 24 | val bobOne = bobs.first() 25 | 26 | assertEquals(VEILED_ROW_ONE_A, bobOne.a) 27 | 28 | fakeDs.rowOneA = PIERCED_ROW_ONE_A // No effect 29 | 30 | assertEquals(VEILED_ROW_ONE_A, bobOne.a) 31 | } 32 | 33 | @Test 34 | fun `should pierce second time when pierceable`() { 35 | fakeDs.rowOneA = VEILED_ROW_ONE_A 36 | 37 | val bobs = bobs().pierceable 38 | // Sequences are not restartable -- TODO: Use List? 39 | val bobOne = bobs.first() 40 | 41 | fakeDs.rowOneA = PIERCED_ROW_ONE_A 42 | bobOne.b // Pierce the veil with an unveiled property 43 | 44 | assertEquals(PIERCED_ROW_ONE_A, bobOne.a) 45 | } 46 | 47 | @Test 48 | fun `should not veil when not veilable`() { 49 | val bobs = bobs().pierceable 50 | // Sequences are not restartable -- TODO: Use List? 51 | val bobOne = bobs.first() 52 | 53 | assertEquals(REAL_ROW_ONE_B, bobOne.b) 54 | } 55 | 56 | @Test 57 | fun `should not pierce second time when unpierceable`() { 58 | fakeDs.rowOneA = VEILED_ROW_ONE_A 59 | 60 | val bobs = bobs().unpierceable 61 | // Sequences are not restartable -- TODO: Use List? 62 | val bobOne = bobs.first() 63 | 64 | bobOne.b // Pierce the veil 65 | fakeDs.rowOneA = PIERCED_ROW_ONE_A 66 | 67 | assertEquals(VEILED_ROW_ONE_A, bobOne.a) 68 | } 69 | } 70 | 71 | @Nested 72 | inner class Reflecting { 73 | @Test 74 | fun `should reflect`() { 75 | val bobs = bobs().pierceable 76 | // Sequences are not restartable -- TODO: Use List? 77 | val bobOne = bobs.first() 78 | 79 | @Suppress("UNCHECKED_CAST") 80 | bobOne as Veilable 81 | 82 | assertEquals(false, bobOne.pierced) 83 | assertEquals(true, bobOne.veiled(Bob::a)) 84 | assertEquals(false, bobOne.veiled(Bob::b)) 85 | assertEquals(false, bobOne.veiled(Bob::veiled)) 86 | } 87 | 88 | /** 89 | * Key point: `veiled` is _both_ a prop on the data object, and a 90 | * reflective property (using JDK proxy logic). The object prop 91 | * should not be hidden by the reflective prop. 92 | */ 93 | @Test 94 | fun `should not hide props because of reflection`() { 95 | val bobs = bobs().unpierceable 96 | // Sequences are not restartable -- TODO: Use List? 97 | val bobOne = bobs.first() 98 | 99 | assertEquals(17, bobOne.veiled) 100 | } 101 | } 102 | } 103 | 104 | private fun bobs() = object { 105 | val pierceable: Sequence get() = bobs(true) 106 | val unpierceable: Sequence get() = bobs(false) 107 | 108 | private fun bobs(pierceable: Boolean) = 109 | veil( 110 | pierceable = pierceable, 111 | ds = fakeDs, 112 | initialData = fakeDs.fetch(SELECT_ALL_BOBS), 113 | idProp = "id", 114 | "a" 115 | ) { ds, id -> 116 | BobFromDataSource(ds, id) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Public Domain 4 | 5 | 6 | # Kotlin Veil 7 | 8 | [![build](https://github.com/binkley/kotlin-veil/workflows/build/badge.svg)](https://github.com/binkley/kotlin-veil/actions) 9 | [![pull requests](https://img.shields.io/github/issues-pr/binkley/kotlin-veil.svg)](https://github.com/binkley/kotlin-veil/pulls) 10 | [![issues](https://img.shields.io/github/issues/binkley/kotlin-veil.svg)](https://github.com/binkley/kotlin-veil/issues) 11 | [![vulnerabilities](https://snyk.io/test/github/binkley/kotlin-veil/badge.svg)](https://snyk.io/test/github/binkley/kotlin-veil) 12 | [![license](https://img.shields.io/badge/license-Public%20Domain-blue.svg)](http://unlicense.org) 13 | 14 | A Kotlin demonstration of Yegor's "veiled objects" 15 | 16 | See [_Veil Objects to Replace 17 | DTOs_](https://www.yegor256.com/2020/05/19/veil-objects.html). 18 | 19 | ## Try it 20 | 21 | ``` 22 | $ ./run.sh 23 | ``` 24 | 25 | ## Build locally 26 | 27 | ``` 28 | $ ./mvnw clean verify 29 | ``` 30 | 31 | ### Build with CI (Docker) 32 | 33 | ``` 34 | $ ./batect build 35 | ``` 36 | 37 | ## Notes 38 | 39 | As this code is a demonstration, it comes with caveats: 40 | 41 | - The code does not use an actual database, rather a "fake" data source 42 | - Implementation uses JDK proxies 43 | - Data rows representing objects each has a unique "id" key 44 | 45 | ## Features 46 | 47 | - Demonstrates "piercing the veil" and "unpierceable veils" 48 | - JDK reflection on veiledness by casting to a `Veiled`, and using the 49 | `pierced` property, or the `veiled(prop-ref)` function 50 | 51 | One might say, "Use an ORM"! The point of veiled objects is lighter-weight code 52 | without the complexity of an ORM. Contrawise, 53 | 54 | ## Demonstration 55 | 56 | Output of `main` demonstrating veiled, unveiled, and pierced: 57 | 58 | ``` 59 | NOTE: Bob has props: a, b, veiled. 60 | NOTE: Prop veiled is to show that the data value is not masked by Veilable. 61 | 62 | PIERCED 63 | ------- 64 | 65 | == Read veiled, then pierced if pierceable (true), then underlying real object 66 | VEILING -> getA=2 67 | PIERCING VEIL 68 | CALLING public abstract java.lang.String hm.binkley.veil.Bob.getB() 69 | FETCHING[1] -> SELECT b FROM Bob WHERE ID = :id 70 | CALLING public abstract int hm.binkley.veil.Bob.getVeiled() 71 | VEILED: Bob{a=2, b=apple, veiled=17} 72 | 73 | CALLING public abstract int hm.binkley.veil.Bob.getA() 74 | FETCHING[1] -> SELECT a FROM Bob WHERE ID = :id 75 | CALLING public abstract java.lang.String hm.binkley.veil.Bob.getB() 76 | FETCHING[1] -> SELECT b FROM Bob WHERE ID = :id 77 | CALLING public abstract int hm.binkley.veil.Bob.getVeiled() 78 | MAYBE-PIERCED: Bob{a=222, b=apple, veiled=17} 79 | 80 | CALLING public java.lang.String java.lang.Object.toString() 81 | FETCHING[1] -> SELECT a FROM Bob WHERE ID = :id 82 | FETCHING[1] -> SELECT b FROM Bob WHERE ID = :id 83 | REAL: RealBob(1){a=222, b=apple, veiled=17} 84 | 85 | PIERCED? true 86 | VEILED-A? false 87 | VEILED-B? false 88 | IS REFLECTIVE VEILED MASKED? false 89 | 90 | UNPIERCED 91 | --------- 92 | 93 | == Read veiled, then pierced if pierceable (false), then underlying real object 94 | VEILING -> getA=2 95 | CALLING public abstract java.lang.String hm.binkley.veil.Bob.getB() 96 | FETCHING[1] -> SELECT b FROM Bob WHERE ID = :id 97 | CALLING public abstract int hm.binkley.veil.Bob.getVeiled() 98 | VEILED: Bob{a=2, b=apple, veiled=17} 99 | 100 | VEILING -> getA=2 101 | CALLING public abstract java.lang.String hm.binkley.veil.Bob.getB() 102 | FETCHING[1] -> SELECT b FROM Bob WHERE ID = :id 103 | CALLING public abstract int hm.binkley.veil.Bob.getVeiled() 104 | MAYBE-PIERCED: Bob{a=2, b=apple, veiled=17} 105 | 106 | CALLING public java.lang.String java.lang.Object.toString() 107 | FETCHING[1] -> SELECT a FROM Bob WHERE ID = :id 108 | FETCHING[1] -> SELECT b FROM Bob WHERE ID = :id 109 | REAL: RealBob(1){a=222, b=apple, veiled=17} 110 | 111 | PIERCED? false 112 | VEILED-A? true 113 | VEILED-B? false 114 | IS REFLECTIVE VEILED MASKED? false 115 | ``` 116 | -------------------------------------------------------------------------------- /coverage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # This is free and unencumbered software released into the public domain. 5 | # 6 | # Anyone is free to copy, modify, publish, use, compile, sell, or 7 | # distribute this software, either in source code form or as a compiled 8 | # binary, for any purpose, commercial or non-commercial, and by any 9 | # means. 10 | # 11 | # In jurisdictions that recognize copyright laws, the author or authors 12 | # of this software dedicate any and all copyright interest in the 13 | # software to the public domain. We make this dedication for the benefit 14 | # of the public at large and to the detriment of our heirs and 15 | # successors. We intend this dedication to be an overt act of 16 | # relinquishment in perpetuity of all present and future rights to this 17 | # software under copyright law. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | # IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 23 | # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 24 | # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | # OTHER DEALINGS IN THE SOFTWARE. 26 | # 27 | # For more information, please refer to 28 | # 29 | 30 | export PS4='+${BASH_SOURCE}:${LINENO}:${FUNCNAME[0]:+${FUNCNAME[0]}():} ' 31 | 32 | set -e 33 | set -u 34 | set -o pipefail 35 | 36 | readonly progname="${0##*/}" 37 | 38 | function print-help() { 39 | cat <&2 84 | return 1 85 | fi 86 | } 87 | 88 | full=none 89 | mutation=false 90 | open=false 91 | run=true 92 | # shellcheck disable=SC2214 93 | while getopts :f:hmox-: opt; do 94 | [[ $opt == - ]] && opt=${OPTARG%%=*} OPTARG=${OPTARG#*=} 95 | # shellcheck disable=SC2213 96 | case $opt in 97 | f | full-coverage) 98 | case "$OPTARG" in 99 | unit) full="$OPTARG" ;; 100 | all | mutation) mutation=true full="$OPTARG" ;; 101 | *) 102 | bad-option-argument "-f" "$OPTARG" 103 | exit 2 104 | ;; 105 | esac 106 | ;; 107 | h | help) 108 | print-help 109 | exit 0 110 | ;; 111 | m | with-mutation) mutation=true ;; 112 | o | open-report) open=true ;; 113 | x | no-rerun) open=true run=false ;; 114 | \?) 115 | bad-option "$OPTARG" 116 | exit 2 117 | ;; 118 | :) 119 | bad-option-argument "$OPTARG" 120 | exit 2 121 | ;; 122 | esac 123 | done 124 | shift $((OPTIND - 1)) 125 | 126 | case "$full" in 127 | none) flags='' ;; 128 | all) 129 | flags='-Dcoverage.lines=100 -Dcoverage.branches=100 -Dcoverage.instructions=100 -Dcoverage.mutation=100' 130 | ;; 131 | mutation) 132 | flags='-Dcoverage.mutation=100' 133 | ;; 134 | unit) 135 | flags='-Dcoverage.lines=100 -Dcoverage.branches=100 -Dcoverage.instructions=100' 136 | ;; 137 | esac 138 | 139 | ((rc = 0)) || true 140 | if $run; then 141 | if $mutation; then 142 | targets='-DwithHistory clean verify' 143 | else 144 | targets='clean test jacoco:report jacoco:check' 145 | fi 146 | 147 | # shellcheck disable=SC2086 148 | ./mvnw $flags "$@" $targets || ((rc += $?)) || true 149 | else 150 | # Refresh coverage report when editing limits in pom.xml without rebuild 151 | ./mvnw $flags "$@" jacoco:report jacoco:check || ((rc += $?)) || true 152 | fi 153 | 154 | if $open; then 155 | if require-file target/site/jacoco/index.html; then 156 | (cd target/site/jacoco && open index.html) 157 | else 158 | ((++rc)) 159 | fi 160 | if $mutation && require-file target/pit-reports/index.html; then 161 | (cd target/pit-reports && open index.html) 162 | else 163 | ((++rc)) 164 | fi 165 | fi 166 | 167 | # shellcheck disable=SC2086 168 | exit $rc 169 | -------------------------------------------------------------------------------- /batect: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | { 4 | set -euo pipefail 5 | 6 | # This file is part of Batect. 7 | # Do not modify this file. It will be overwritten next time you upgrade Batect. 8 | # You should commit this file to version control alongside the rest of your project. It should not be installed globally. 9 | # For more information, visit https://github.com/batect/batect. 10 | 11 | VERSION="0.85.0" 12 | CHECKSUM="${BATECT_DOWNLOAD_CHECKSUM:-901ed73295be75d295cec1d06315f7026b36ccb1666660b8af432cfbbc7feae8}" 13 | DOWNLOAD_URL_ROOT=${BATECT_DOWNLOAD_URL_ROOT:-"https://updates.batect.dev/v1/files"} 14 | DOWNLOAD_URL=${BATECT_DOWNLOAD_URL:-"$DOWNLOAD_URL_ROOT/$VERSION/batect-$VERSION.jar"} 15 | QUIET_DOWNLOAD=${BATECT_QUIET_DOWNLOAD:-false} 16 | 17 | BATECT_WRAPPER_CACHE_DIR=${BATECT_CACHE_DIR:-"$HOME/.batect/cache"} 18 | VERSION_CACHE_DIR="$BATECT_WRAPPER_CACHE_DIR/$VERSION" 19 | JAR_PATH="$VERSION_CACHE_DIR/batect-$VERSION.jar" 20 | BATECT_WRAPPER_DID_DOWNLOAD=false 21 | 22 | SCRIPT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 23 | 24 | function main() { 25 | if ! haveVersionCachedLocally; then 26 | download 27 | BATECT_WRAPPER_DID_DOWNLOAD=true 28 | fi 29 | 30 | checkChecksum 31 | runApplication "$@" 32 | } 33 | 34 | function haveVersionCachedLocally() { 35 | [ -f "$JAR_PATH" ] 36 | } 37 | 38 | function download() { 39 | checkForCurl 40 | 41 | mkdir -p "$VERSION_CACHE_DIR" 42 | temp_file=$(mktemp) 43 | 44 | if [[ $QUIET_DOWNLOAD == 'true' ]]; then 45 | curl --silent --fail --show-error --location --output "$temp_file" --retry 3 --retry-connrefused "$DOWNLOAD_URL" 46 | else 47 | echo "Downloading Batect version $VERSION from $DOWNLOAD_URL..." 48 | curl -# --fail --show-error --location --output "$temp_file" --retry 3 --retry-connrefused "$DOWNLOAD_URL" 49 | fi 50 | 51 | mv "$temp_file" "$JAR_PATH" 52 | } 53 | 54 | function checkChecksum() { 55 | local_checksum=$(getLocalChecksum) 56 | 57 | if [[ "$local_checksum" != "$CHECKSUM" ]]; then 58 | echo "The downloaded version of Batect does not have the expected checksum. Delete '$JAR_PATH' and then re-run this script to download it again." 59 | exit 1 60 | fi 61 | } 62 | 63 | function getLocalChecksum() { 64 | if [[ "$(uname)" == "Darwin" ]]; then 65 | shasum -a 256 "$JAR_PATH" | cut -d' ' -f1 66 | else 67 | sha256sum "$JAR_PATH" | cut -d' ' -f1 68 | fi 69 | } 70 | 71 | function runApplication() { 72 | java_path=$(getPathToJava) 73 | checkForJava "$java_path" 74 | 75 | java_version_info=$(getJavaVersionInfo "$java_path") 76 | checkJavaVersion "$java_version_info" 77 | 78 | java_version=$(extractJavaVersion "$java_version_info") 79 | java_version_major=$(extractJavaMajorVersion "$java_version") 80 | 81 | if (( java_version_major >= 9 )); then 82 | JAVA_OPTS=(--add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED) 83 | else 84 | JAVA_OPTS=() 85 | fi 86 | 87 | if [[ "$(uname -o 2>&1)" == "Msys" ]] && hash winpty 2>/dev/null && [ -t /dev/stdin ]; then 88 | GIT_BASH_PTY_WORKAROUND=(winpty) 89 | else 90 | GIT_BASH_PTY_WORKAROUND=() 91 | fi 92 | 93 | BATECT_WRAPPER_SCRIPT_DIR="$SCRIPT_PATH" \ 94 | BATECT_WRAPPER_CACHE_DIR="$BATECT_WRAPPER_CACHE_DIR" \ 95 | BATECT_WRAPPER_DID_DOWNLOAD="$BATECT_WRAPPER_DID_DOWNLOAD" \ 96 | HOSTNAME="$HOSTNAME" \ 97 | exec \ 98 | ${GIT_BASH_PTY_WORKAROUND[@]+"${GIT_BASH_PTY_WORKAROUND[@]}"} \ 99 | "$java_path" \ 100 | -Djava.net.useSystemProxies=true \ 101 | ${JAVA_OPTS[@]+"${JAVA_OPTS[@]}"} \ 102 | -jar "$JAR_PATH" \ 103 | "$@" 104 | } 105 | 106 | function checkForCurl() { 107 | if ! hash curl 2>/dev/null; then 108 | echo "curl is not installed or not on your PATH. Please install it and try again." >&2 109 | exit 1 110 | fi 111 | } 112 | 113 | function getPathToJava() { 114 | if useJavaHome; then 115 | echo "$JAVA_HOME/bin/java" 116 | else 117 | echo "java" 118 | fi 119 | } 120 | 121 | function useJavaHome() { 122 | test -n "${JAVA_HOME+x}" 123 | } 124 | 125 | function checkForJava() { 126 | local java_path="$1" 127 | 128 | if ! hash "$java_path" 2>/dev/null; then 129 | showJavaNotInstalledError 130 | fi 131 | } 132 | 133 | function showJavaNotInstalledError() { 134 | if useJavaHome; then 135 | echo "JAVA_HOME is set to '$JAVA_HOME', but there is no Java executable at '$JAVA_HOME/bin/java'." >&2 136 | else 137 | echo "Java is not installed or not on your PATH. Please install it and try again." >&2 138 | fi 139 | 140 | exit 1 141 | } 142 | 143 | function checkJavaVersion() { 144 | java_version_info="$1" 145 | java_version=$(extractJavaVersion "$java_version_info") 146 | java_version_major=$(extractJavaMajorVersion "$java_version") 147 | java_version_minor=$(extractJavaMinorVersion "$java_version") 148 | 149 | if (( java_version_major < 1 || ( java_version_major == 1 && java_version_minor <= 7 ) )); then 150 | if useJavaHome; then 151 | echo "The version of Java that is available in JAVA_HOME is version $java_version, but version 1.8 or greater is required." >&2 152 | echo "If you have a newer version of Java installed, please make sure JAVA_HOME is set correctly." >&2 153 | echo "JAVA_HOME takes precedence over any versions of Java available on your PATH." >&2 154 | else 155 | echo "The version of Java that is available on your PATH is version $java_version, but version 1.8 or greater is required." >&2 156 | echo "If you have a newer version of Java installed, please make sure your PATH is set correctly." >&2 157 | fi 158 | 159 | exit 1 160 | fi 161 | 162 | if ! javaIs64Bit "$java_version_info"; then 163 | if useJavaHome; then 164 | echo "The version of Java that is available in JAVA_HOME is a 32-bit version, but Batect requires a 64-bit Java runtime." >&2 165 | echo "If you have a 64-bit version of Java installed, please make sure JAVA_HOME is set correctly." >&2 166 | echo "JAVA_HOME takes precedence over any versions of Java available on your PATH." >&2 167 | else 168 | echo "The version of Java that is available on your PATH is a 32-bit version, but Batect requires a 64-bit Java runtime." >&2 169 | echo "If you have a 64-bit version of Java installed, please make sure your PATH is set correctly." >&2 170 | fi 171 | 172 | exit 1 173 | fi 174 | } 175 | 176 | function getJavaVersionInfo() { 177 | local java_path="$1" 178 | 179 | "$java_path" -version 2>&1 || showJavaNotInstalledError 180 | } 181 | 182 | function extractJavaVersion() { 183 | echo "$1" | grep version | sed -En ';s/.* version "([0-9]+)(\.([0-9]+))?.*".*/\1.\3/p;' 184 | } 185 | 186 | function extractJavaMajorVersion() { 187 | java_version=$1 188 | 189 | echo "${java_version%.*}" 190 | } 191 | 192 | function extractJavaMinorVersion() { 193 | java_version=$1 194 | java_version_minor="${java_version#*.}" 195 | 196 | echo "${java_version_minor:-0}" 197 | } 198 | 199 | function javaIs64Bit() { 200 | echo "$1" | grep -q '64-[Bb]it' 201 | } 202 | 203 | main "$@" 204 | exit $? 205 | } 206 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Apache Maven Wrapper startup batch script, version 3.2.0 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 28 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 29 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 30 | @REM e.g. to debug Maven itself, use 31 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 32 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 33 | @REM ---------------------------------------------------------------------------- 34 | 35 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 36 | @echo off 37 | @REM set title of command window 38 | title %0 39 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 40 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 41 | 42 | @REM set %HOME% to equivalent of $HOME 43 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 44 | 45 | @REM Execute a user defined script before this one 46 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 47 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 48 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 49 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 50 | :skipRcPre 51 | 52 | @setlocal 53 | 54 | set ERROR_CODE=0 55 | 56 | @REM To isolate internal variables from possible post scripts, we use another setlocal 57 | @setlocal 58 | 59 | @REM ==== START VALIDATION ==== 60 | if not "%JAVA_HOME%" == "" goto OkJHome 61 | 62 | echo. 63 | echo Error: JAVA_HOME not found in your environment. >&2 64 | echo Please set the JAVA_HOME variable in your environment to match the >&2 65 | echo location of your Java installation. >&2 66 | echo. 67 | goto error 68 | 69 | :OkJHome 70 | if exist "%JAVA_HOME%\bin\java.exe" goto init 71 | 72 | echo. 73 | echo Error: JAVA_HOME is set to an invalid directory. >&2 74 | echo JAVA_HOME = "%JAVA_HOME%" >&2 75 | echo Please set the JAVA_HOME variable in your environment to match the >&2 76 | echo location of your Java installation. >&2 77 | echo. 78 | goto error 79 | 80 | @REM ==== END VALIDATION ==== 81 | 82 | :init 83 | 84 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 85 | @REM Fallback to current working directory if not found. 86 | 87 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 88 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 89 | 90 | set EXEC_DIR=%CD% 91 | set WDIR=%EXEC_DIR% 92 | :findBaseDir 93 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 94 | cd .. 95 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 96 | set WDIR=%CD% 97 | goto findBaseDir 98 | 99 | :baseDirFound 100 | set MAVEN_PROJECTBASEDIR=%WDIR% 101 | cd "%EXEC_DIR%" 102 | goto endDetectBaseDir 103 | 104 | :baseDirNotFound 105 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 106 | cd "%EXEC_DIR%" 107 | 108 | :endDetectBaseDir 109 | 110 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 111 | 112 | @setlocal EnableExtensions EnableDelayedExpansion 113 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 114 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 115 | 116 | :endReadAdditionalConfig 117 | 118 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 123 | 124 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 125 | IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | if "%MVNW_VERBOSE%" == "true" ( 132 | echo Found %WRAPPER_JAR% 133 | ) 134 | ) else ( 135 | if not "%MVNW_REPOURL%" == "" ( 136 | SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 137 | ) 138 | if "%MVNW_VERBOSE%" == "true" ( 139 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 140 | echo Downloading from: %WRAPPER_URL% 141 | ) 142 | 143 | powershell -Command "&{"^ 144 | "$webclient = new-object System.Net.WebClient;"^ 145 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 146 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 147 | "}"^ 148 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ 149 | "}" 150 | if "%MVNW_VERBOSE%" == "true" ( 151 | echo Finished downloading %WRAPPER_JAR% 152 | ) 153 | ) 154 | @REM End of extension 155 | 156 | @REM If specified, validate the SHA-256 sum of the Maven wrapper jar file 157 | SET WRAPPER_SHA_256_SUM="" 158 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 159 | IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B 160 | ) 161 | IF NOT %WRAPPER_SHA_256_SUM%=="" ( 162 | powershell -Command "&{"^ 163 | "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ 164 | "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ 165 | " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ 166 | " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ 167 | " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ 168 | " exit 1;"^ 169 | "}"^ 170 | "}" 171 | if ERRORLEVEL 1 goto error 172 | ) 173 | 174 | @REM Provide a "standardized" way to retrieve the CLI args that will 175 | @REM work with both Windows and non-Windows executions. 176 | set MAVEN_CMD_LINE_ARGS=%* 177 | 178 | %MAVEN_JAVA_EXE% ^ 179 | %JVM_CONFIG_MAVEN_PROPS% ^ 180 | %MAVEN_OPTS% ^ 181 | %MAVEN_DEBUG_OPTS% ^ 182 | -classpath %WRAPPER_JAR% ^ 183 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 184 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 185 | if ERRORLEVEL 1 goto error 186 | goto end 187 | 188 | :error 189 | set ERROR_CODE=1 190 | 191 | :end 192 | @endlocal & set ERROR_CODE=%ERROR_CODE% 193 | 194 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 195 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 196 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 197 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 198 | :skipRcPost 199 | 200 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 201 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 202 | 203 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 204 | 205 | cmd /C exit /B %ERROR_CODE% 206 | -------------------------------------------------------------------------------- /batect.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | rem This file is part of Batect. 3 | rem Do not modify this file. It will be overwritten next time you upgrade Batect. 4 | rem You should commit this file to version control alongside the rest of your project. It should not be installed globally. 5 | rem For more information, visit https://github.com/batect/batect. 6 | 7 | setlocal EnableDelayedExpansion 8 | 9 | set "version=0.85.0" 10 | 11 | if "%BATECT_CACHE_DIR%" == "" ( 12 | set "BATECT_CACHE_DIR=%USERPROFILE%\.batect\cache" 13 | ) 14 | 15 | set "rootCacheDir=!BATECT_CACHE_DIR!" 16 | set "cacheDir=%rootCacheDir%\%version%" 17 | set "ps1Path=%cacheDir%\batect-%version%.ps1" 18 | 19 | set script=Set-StrictMode -Version 2.0^ 20 | 21 | $ErrorActionPreference = 'Stop'^ 22 | 23 | ^ 24 | 25 | $Version='0.85.0'^ 26 | 27 | ^ 28 | 29 | function getValueOrDefault($value, $default) {^ 30 | 31 | if ($value -eq $null) {^ 32 | 33 | $default^ 34 | 35 | } else {^ 36 | 37 | $value^ 38 | 39 | }^ 40 | 41 | }^ 42 | 43 | ^ 44 | 45 | $DownloadUrlRoot = getValueOrDefault $env:BATECT_DOWNLOAD_URL_ROOT "https://updates.batect.dev/v1/files"^ 46 | 47 | $UrlEncodedVersion = [Uri]::EscapeDataString($Version)^ 48 | 49 | $DownloadUrl = getValueOrDefault $env:BATECT_DOWNLOAD_URL "$DownloadUrlRoot/$UrlEncodedVersion/batect-$UrlEncodedVersion.jar"^ 50 | 51 | $ExpectedChecksum = getValueOrDefault $env:BATECT_DOWNLOAD_CHECKSUM '901ed73295be75d295cec1d06315f7026b36ccb1666660b8af432cfbbc7feae8'^ 52 | 53 | ^ 54 | 55 | $RootCacheDir = getValueOrDefault $env:BATECT_CACHE_DIR "$env:USERPROFILE\.batect\cache"^ 56 | 57 | $VersionCacheDir = "$RootCacheDir\$Version"^ 58 | 59 | $JarPath = "$VersionCacheDir\batect-$Version.jar"^ 60 | 61 | $DidDownload = 'false'^ 62 | 63 | ^ 64 | 65 | function main() {^ 66 | 67 | if (-not (haveVersionCachedLocally)) {^ 68 | 69 | download^ 70 | 71 | $DidDownload = 'true'^ 72 | 73 | }^ 74 | 75 | ^ 76 | 77 | checkChecksum^ 78 | 79 | runApplication @args^ 80 | 81 | }^ 82 | 83 | ^ 84 | 85 | function haveVersionCachedLocally() {^ 86 | 87 | Test-Path $JarPath^ 88 | 89 | }^ 90 | 91 | ^ 92 | 93 | function download() {^ 94 | 95 | Write-Output "Downloading Batect version $Version from $DownloadUrl..."^ 96 | 97 | ^ 98 | 99 | createCacheDir^ 100 | 101 | ^ 102 | 103 | $oldProgressPreference = $ProgressPreference^ 104 | 105 | ^ 106 | 107 | try {^ 108 | 109 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12^ 110 | 111 | ^ 112 | 113 | # Turn off the progress bar to significantly reduce download times - see https://github.com/PowerShell/PowerShell/issues/2138#issuecomment-251165868^ 114 | 115 | $ProgressPreference = 'SilentlyContinue'^ 116 | 117 | ^ 118 | 119 | Invoke-WebRequest -Uri $DownloadUrl -OutFile $JarPath ^| Out-Null^ 120 | 121 | } catch {^ 122 | 123 | $Message = $_.Exception.Message^ 124 | 125 | ^ 126 | 127 | Write-Host -ForegroundColor Red "Downloading failed with error: $Message"^ 128 | 129 | exit 1^ 130 | 131 | } finally {^ 132 | 133 | $ProgressPreference = $oldProgressPreference^ 134 | 135 | }^ 136 | 137 | }^ 138 | 139 | ^ 140 | 141 | function checkChecksum() {^ 142 | 143 | $localChecksum = (Get-FileHash -Algorithm 'SHA256' $JarPath).Hash.ToLower()^ 144 | 145 | ^ 146 | 147 | if ($localChecksum -ne $expectedChecksum) {^ 148 | 149 | Write-Host -ForegroundColor Red "The downloaded version of Batect does not have the expected checksum. Delete '$JarPath' and then re-run this script to download it again."^ 150 | 151 | exit 1^ 152 | 153 | }^ 154 | 155 | }^ 156 | 157 | ^ 158 | 159 | function createCacheDir() {^ 160 | 161 | if (-not (Test-Path $VersionCacheDir)) {^ 162 | 163 | New-Item -ItemType Directory -Path $VersionCacheDir ^| Out-Null^ 164 | 165 | }^ 166 | 167 | }^ 168 | 169 | ^ 170 | 171 | function runApplication() {^ 172 | 173 | $java = findJava^ 174 | 175 | $javaVersion = checkJavaVersion $java^ 176 | 177 | ^ 178 | 179 | if ($javaVersion.Major -ge 9) {^ 180 | 181 | $javaArgs = @("--add-opens", "java.base/sun.nio.ch=ALL-UNNAMED", "--add-opens", "java.base/java.io=ALL-UNNAMED")^ 182 | 183 | } else {^ 184 | 185 | $javaArgs = @()^ 186 | 187 | }^ 188 | 189 | ^ 190 | 191 | $combinedArgs = $javaArgs + @("-Djava.net.useSystemProxies=true", "-jar", $JarPath) + $args^ 192 | 193 | $env:HOSTNAME = $env:COMPUTERNAME^ 194 | 195 | $env:BATECT_WRAPPER_CACHE_DIR = $RootCacheDir^ 196 | 197 | $env:BATECT_WRAPPER_DID_DOWNLOAD = $DidDownload^ 198 | 199 | ^ 200 | 201 | $info = New-Object System.Diagnostics.ProcessStartInfo^ 202 | 203 | $info.FileName = $java.Source^ 204 | 205 | $info.Arguments = combineArgumentsToString($combinedArgs)^ 206 | 207 | $info.RedirectStandardError = $false^ 208 | 209 | $info.RedirectStandardOutput = $false^ 210 | 211 | $info.UseShellExecute = $false^ 212 | 213 | ^ 214 | 215 | $process = New-Object System.Diagnostics.Process^ 216 | 217 | $process.StartInfo = $info^ 218 | 219 | $process.Start() ^| Out-Null^ 220 | 221 | $process.WaitForExit()^ 222 | 223 | ^ 224 | 225 | exit $process.ExitCode^ 226 | 227 | }^ 228 | 229 | ^ 230 | 231 | function useJavaHome() {^ 232 | 233 | return ($env:JAVA_HOME -ne $null)^ 234 | 235 | }^ 236 | 237 | ^ 238 | 239 | function findJava() {^ 240 | 241 | if (useJavaHome) {^ 242 | 243 | $java = Get-Command "$env:JAVA_HOME\bin\java" -ErrorAction SilentlyContinue^ 244 | 245 | ^ 246 | 247 | if ($java -eq $null) {^ 248 | 249 | Write-Host -ForegroundColor Red "JAVA_HOME is set to '$env:JAVA_HOME', but there is no Java executable at '$env:JAVA_HOME\bin\java.exe'."^ 250 | 251 | exit 1^ 252 | 253 | }^ 254 | 255 | ^ 256 | 257 | return $java^ 258 | 259 | }^ 260 | 261 | ^ 262 | 263 | $java = Get-Command "java" -ErrorAction SilentlyContinue^ 264 | 265 | ^ 266 | 267 | if ($java -eq $null) {^ 268 | 269 | Write-Host -ForegroundColor Red "Java is not installed or not on your PATH. Please install it and try again."^ 270 | 271 | exit 1^ 272 | 273 | }^ 274 | 275 | ^ 276 | 277 | return $java^ 278 | 279 | }^ 280 | 281 | ^ 282 | 283 | function checkJavaVersion([System.Management.Automation.CommandInfo]$java) {^ 284 | 285 | $versionInfo = getJavaVersionInfo $java^ 286 | 287 | $rawVersion = getJavaVersion $versionInfo^ 288 | 289 | $parsedVersion = New-Object Version -ArgumentList $rawVersion^ 290 | 291 | $minimumVersion = "1.8"^ 292 | 293 | ^ 294 | 295 | if ($parsedVersion -lt (New-Object Version -ArgumentList $minimumVersion)) {^ 296 | 297 | if (useJavaHome) {^ 298 | 299 | Write-Host -ForegroundColor Red "The version of Java that is available in JAVA_HOME is version $rawVersion, but version $minimumVersion or greater is required."^ 300 | 301 | Write-Host -ForegroundColor Red "If you have a newer version of Java installed, please make sure JAVA_HOME is set correctly."^ 302 | 303 | Write-Host -ForegroundColor Red "JAVA_HOME takes precedence over any versions of Java available on your PATH."^ 304 | 305 | } else {^ 306 | 307 | Write-Host -ForegroundColor Red "The version of Java that is available on your PATH is version $rawVersion, but version $minimumVersion or greater is required."^ 308 | 309 | Write-Host -ForegroundColor Red "If you have a newer version of Java installed, please make sure your PATH is set correctly."^ 310 | 311 | }^ 312 | 313 | ^ 314 | 315 | exit 1^ 316 | 317 | }^ 318 | 319 | ^ 320 | 321 | if (-not ($versionInfo -match "64\-[bB]it")) {^ 322 | 323 | if (useJavaHome) {^ 324 | 325 | Write-Host -ForegroundColor Red "The version of Java that is available in JAVA_HOME is a 32-bit version, but Batect requires a 64-bit Java runtime."^ 326 | 327 | Write-Host -ForegroundColor Red "If you have a 64-bit version of Java installed, please make sure JAVA_HOME is set correctly."^ 328 | 329 | Write-Host -ForegroundColor Red "JAVA_HOME takes precedence over any versions of Java available on your PATH."^ 330 | 331 | } else {^ 332 | 333 | Write-Host -ForegroundColor Red "The version of Java that is available on your PATH is a 32-bit version, but Batect requires a 64-bit Java runtime."^ 334 | 335 | Write-Host -ForegroundColor Red "If you have a 64-bit version of Java installed, please make sure your PATH is set correctly."^ 336 | 337 | }^ 338 | 339 | ^ 340 | 341 | exit 1^ 342 | 343 | }^ 344 | 345 | ^ 346 | 347 | return $parsedVersion^ 348 | 349 | }^ 350 | 351 | ^ 352 | 353 | function getJavaVersionInfo([System.Management.Automation.CommandInfo]$java) {^ 354 | 355 | $info = New-Object System.Diagnostics.ProcessStartInfo^ 356 | 357 | $info.FileName = $java.Source^ 358 | 359 | $info.Arguments = "-version"^ 360 | 361 | $info.RedirectStandardError = $true^ 362 | 363 | $info.RedirectStandardOutput = $true^ 364 | 365 | $info.UseShellExecute = $false^ 366 | 367 | ^ 368 | 369 | $process = New-Object System.Diagnostics.Process^ 370 | 371 | $process.StartInfo = $info^ 372 | 373 | $process.Start() ^| Out-Null^ 374 | 375 | $process.WaitForExit()^ 376 | 377 | ^ 378 | 379 | $stderr = $process.StandardError.ReadToEnd()^ 380 | 381 | return $stderr^ 382 | 383 | }^ 384 | 385 | ^ 386 | 387 | function getJavaVersion([String]$versionInfo) {^ 388 | 389 | $versionLine = ($versionInfo -split [Environment]::NewLine)[0]^ 390 | 391 | ^ 392 | 393 | if (-not ($versionLine -match "version `"([0-9]+)(\.([0-9]+))?.*`"")) {^ 394 | 395 | Write-Error "Java reported a version that does not match the expected format: $versionLine"^ 396 | 397 | }^ 398 | 399 | ^ 400 | 401 | $major = $Matches.1^ 402 | 403 | ^ 404 | 405 | if ($Matches.Count -ge 3) {^ 406 | 407 | $minor = $Matches.3^ 408 | 409 | } else {^ 410 | 411 | $minor = "0"^ 412 | 413 | }^ 414 | 415 | ^ 416 | 417 | return "$major.$minor"^ 418 | 419 | }^ 420 | 421 | ^ 422 | 423 | function combineArgumentsToString([Object[]]$arguments) {^ 424 | 425 | $combined = @()^ 426 | 427 | ^ 428 | 429 | $arguments ^| %% { $combined += escapeArgument($_) }^ 430 | 431 | ^ 432 | 433 | return $combined -join " "^ 434 | 435 | }^ 436 | 437 | ^ 438 | 439 | function escapeArgument([String]$argument) {^ 440 | 441 | return '"' + $argument.Replace('"', '"""') + '"'^ 442 | 443 | }^ 444 | 445 | ^ 446 | 447 | main @args^ 448 | 449 | 450 | 451 | if not exist "%cacheDir%" ( 452 | mkdir "%cacheDir%" 453 | ) 454 | 455 | echo !script! > "%ps1Path%" 456 | 457 | set BATECT_WRAPPER_SCRIPT_DIR=%~dp0 458 | 459 | rem Why do we explicitly exit? 460 | rem cmd.exe appears to read this script one line at a time and then executes it. 461 | rem If we modify the script while it is still running (eg. because we're updating it), then cmd.exe does all kinds of odd things 462 | rem because it continues execution from the next byte (which was previously the end of the line). 463 | rem By explicitly exiting on the same line as starting the application, we avoid these issues as cmd.exe has already read the entire 464 | rem line before we start the application and therefore will always exit. 465 | 466 | rem Why do we set PSModulePath? 467 | rem See issue #627 468 | set "PSModulePath=" 469 | powershell.exe -ExecutionPolicy Bypass -NoLogo -NoProfile -File "%ps1Path%" %* && exit /b 0 || exit /b !ERRORLEVEL! 470 | 471 | rem What's this for? 472 | rem This is so the tests for the wrapper has a way to ensure that the line above terminates the script correctly. 473 | echo WARNING: you should never see this, and if you do, then Batect's wrapper script has a bug 474 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.2.0 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | # e.g. to debug Maven itself, use 32 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | # ---------------------------------------------------------------------------- 35 | 36 | if [ -z "$MAVEN_SKIP_RC" ] ; then 37 | 38 | if [ -f /usr/local/etc/mavenrc ] ; then 39 | . /usr/local/etc/mavenrc 40 | fi 41 | 42 | if [ -f /etc/mavenrc ] ; then 43 | . /etc/mavenrc 44 | fi 45 | 46 | if [ -f "$HOME/.mavenrc" ] ; then 47 | . "$HOME/.mavenrc" 48 | fi 49 | 50 | fi 51 | 52 | # OS specific support. $var _must_ be set to either true or false. 53 | cygwin=false; 54 | darwin=false; 55 | mingw=false 56 | case "$(uname)" in 57 | CYGWIN*) cygwin=true ;; 58 | MINGW*) mingw=true;; 59 | Darwin*) darwin=true 60 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 61 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 62 | if [ -z "$JAVA_HOME" ]; then 63 | if [ -x "/usr/libexec/java_home" ]; then 64 | JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME 65 | else 66 | JAVA_HOME="/Library/Java/Home"; export JAVA_HOME 67 | fi 68 | fi 69 | ;; 70 | esac 71 | 72 | if [ -z "$JAVA_HOME" ] ; then 73 | if [ -r /etc/gentoo-release ] ; then 74 | JAVA_HOME=$(java-config --jre-home) 75 | fi 76 | fi 77 | 78 | # For Cygwin, ensure paths are in UNIX format before anything is touched 79 | if $cygwin ; then 80 | [ -n "$JAVA_HOME" ] && 81 | JAVA_HOME=$(cygpath --unix "$JAVA_HOME") 82 | [ -n "$CLASSPATH" ] && 83 | CLASSPATH=$(cygpath --path --unix "$CLASSPATH") 84 | fi 85 | 86 | # For Mingw, ensure paths are in UNIX format before anything is touched 87 | if $mingw ; then 88 | [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && 89 | JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" 90 | fi 91 | 92 | if [ -z "$JAVA_HOME" ]; then 93 | javaExecutable="$(which javac)" 94 | if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then 95 | # readlink(1) is not available as standard on Solaris 10. 96 | readLink=$(which readlink) 97 | if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then 98 | if $darwin ; then 99 | javaHome="$(dirname "\"$javaExecutable\"")" 100 | javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" 101 | else 102 | javaExecutable="$(readlink -f "\"$javaExecutable\"")" 103 | fi 104 | javaHome="$(dirname "\"$javaExecutable\"")" 105 | javaHome=$(expr "$javaHome" : '\(.*\)/bin') 106 | JAVA_HOME="$javaHome" 107 | export JAVA_HOME 108 | fi 109 | fi 110 | fi 111 | 112 | if [ -z "$JAVACMD" ] ; then 113 | if [ -n "$JAVA_HOME" ] ; then 114 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 115 | # IBM's JDK on AIX uses strange locations for the executables 116 | JAVACMD="$JAVA_HOME/jre/sh/java" 117 | else 118 | JAVACMD="$JAVA_HOME/bin/java" 119 | fi 120 | else 121 | JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" 122 | fi 123 | fi 124 | 125 | if [ ! -x "$JAVACMD" ] ; then 126 | echo "Error: JAVA_HOME is not defined correctly." >&2 127 | echo " We cannot execute $JAVACMD" >&2 128 | exit 1 129 | fi 130 | 131 | if [ -z "$JAVA_HOME" ] ; then 132 | echo "Warning: JAVA_HOME environment variable is not set." 133 | fi 134 | 135 | # traverses directory structure from process work directory to filesystem root 136 | # first directory with .mvn subdirectory is considered project base directory 137 | find_maven_basedir() { 138 | if [ -z "$1" ] 139 | then 140 | echo "Path not specified to find_maven_basedir" 141 | return 1 142 | fi 143 | 144 | basedir="$1" 145 | wdir="$1" 146 | while [ "$wdir" != '/' ] ; do 147 | if [ -d "$wdir"/.mvn ] ; then 148 | basedir=$wdir 149 | break 150 | fi 151 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 152 | if [ -d "${wdir}" ]; then 153 | wdir=$(cd "$wdir/.." || exit 1; pwd) 154 | fi 155 | # end of workaround 156 | done 157 | printf '%s' "$(cd "$basedir" || exit 1; pwd)" 158 | } 159 | 160 | # concatenates all lines of a file 161 | concat_lines() { 162 | if [ -f "$1" ]; then 163 | # Remove \r in case we run on Windows within Git Bash 164 | # and check out the repository with auto CRLF management 165 | # enabled. Otherwise, we may read lines that are delimited with 166 | # \r\n and produce $'-Xarg\r' rather than -Xarg due to word 167 | # splitting rules. 168 | tr -s '\r\n' ' ' < "$1" 169 | fi 170 | } 171 | 172 | log() { 173 | if [ "$MVNW_VERBOSE" = true ]; then 174 | printf '%s\n' "$1" 175 | fi 176 | } 177 | 178 | BASE_DIR=$(find_maven_basedir "$(dirname "$0")") 179 | if [ -z "$BASE_DIR" ]; then 180 | exit 1; 181 | fi 182 | 183 | MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR 184 | log "$MAVEN_PROJECTBASEDIR" 185 | 186 | ########################################################################################## 187 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 188 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 189 | ########################################################################################## 190 | wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" 191 | if [ -r "$wrapperJarPath" ]; then 192 | log "Found $wrapperJarPath" 193 | else 194 | log "Couldn't find $wrapperJarPath, downloading it ..." 195 | 196 | if [ -n "$MVNW_REPOURL" ]; then 197 | wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 198 | else 199 | wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 200 | fi 201 | while IFS="=" read -r key value; do 202 | # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) 203 | safeValue=$(echo "$value" | tr -d '\r') 204 | case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; 205 | esac 206 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 207 | log "Downloading from: $wrapperUrl" 208 | 209 | if $cygwin; then 210 | wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") 211 | fi 212 | 213 | if command -v wget > /dev/null; then 214 | log "Found wget ... using wget" 215 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" 216 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 217 | wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 218 | else 219 | wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 220 | fi 221 | elif command -v curl > /dev/null; then 222 | log "Found curl ... using curl" 223 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" 224 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 225 | curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 226 | else 227 | curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 228 | fi 229 | else 230 | log "Falling back to using Java to download" 231 | javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" 232 | javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" 233 | # For Cygwin, switch paths to Windows format before running javac 234 | if $cygwin; then 235 | javaSource=$(cygpath --path --windows "$javaSource") 236 | javaClass=$(cygpath --path --windows "$javaClass") 237 | fi 238 | if [ -e "$javaSource" ]; then 239 | if [ ! -e "$javaClass" ]; then 240 | log " - Compiling MavenWrapperDownloader.java ..." 241 | ("$JAVA_HOME/bin/javac" "$javaSource") 242 | fi 243 | if [ -e "$javaClass" ]; then 244 | log " - Running MavenWrapperDownloader.java ..." 245 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" 246 | fi 247 | fi 248 | fi 249 | fi 250 | ########################################################################################## 251 | # End of extension 252 | ########################################################################################## 253 | 254 | # If specified, validate the SHA-256 sum of the Maven wrapper jar file 255 | wrapperSha256Sum="" 256 | while IFS="=" read -r key value; do 257 | case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; 258 | esac 259 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 260 | if [ -n "$wrapperSha256Sum" ]; then 261 | wrapperSha256Result=false 262 | if command -v sha256sum > /dev/null; then 263 | if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then 264 | wrapperSha256Result=true 265 | fi 266 | elif command -v shasum > /dev/null; then 267 | if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then 268 | wrapperSha256Result=true 269 | fi 270 | else 271 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." 272 | echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." 273 | exit 1 274 | fi 275 | if [ $wrapperSha256Result = false ]; then 276 | echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 277 | echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 278 | echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 279 | exit 1 280 | fi 281 | fi 282 | 283 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 284 | 285 | # For Cygwin, switch paths to Windows format before running java 286 | if $cygwin; then 287 | [ -n "$JAVA_HOME" ] && 288 | JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") 289 | [ -n "$CLASSPATH" ] && 290 | CLASSPATH=$(cygpath --path --windows "$CLASSPATH") 291 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 292 | MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") 293 | fi 294 | 295 | # Provide a "standardized" way to retrieve the CLI args that will 296 | # work with both Windows and non-Windows executions. 297 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" 298 | export MAVEN_CMD_LINE_ARGS 299 | 300 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 301 | 302 | # shellcheck disable=SC2086 # safe args 303 | exec "$JAVACMD" \ 304 | $MAVEN_OPTS \ 305 | $MAVEN_DEBUG_OPTS \ 306 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 307 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 308 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 309 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 4.0.0 7 | 8 | hm.binkley 9 | kotlin-veil 10 | 0-SNAPSHOT 11 | 12 | kotlin-veil 13 | 14 | An experiment with veiled object on the JVM, ala Его́р Бугае́нко 15 | 16 | https://github.com/binkley/kotlin-veil 17 | 18 | 19 | Public Domain 20 | https://unlicense.org/ 21 | repo 22 | 23 | 24 | 25 | GitHub 26 | https://github.com/binkley/kotlin-veil/issues 27 | 28 | 29 | scm:git:git://github.com/binkley/kotlin-veil 30 | https://github.com/binkley/kotlin-veil 31 | 32 | 33 | 34 | 35 | 66 36 | 50 37 | 42 38 | 49 39 | 9.1.0 40 | 1.23.5 41 | 1.9.20 42 | 0.8.12 43 | UTF-8 44 | 21 45 | 5.11.0-M1 46 | official 47 | ${jdk.version} 48 | 2.0.0-RC2 49 | 3.2.0 50 | hm.binkley.veil.MainKt 51 | 3.7.1 52 | 3.4.1 53 | 3.1.1 54 | 3.5.0 55 | 3.0.1 56 | 4.0.0-M13 57 | 3.3.1 58 | 3.2.5 59 | 2.16.2 60 | 3.3.1 61 | 3.9.0 62 | 1.2.1 63 | 1.16.0 64 | ${java.charset} 65 | ${java.charset} 66 | 67 | 68 | 69 | 70 | 71 | 72 | org.junit 73 | junit-bom 74 | ${junit.version} 75 | pom 76 | import 77 | 78 | 79 | 80 | 81 | 82 | 83 | org.jetbrains.kotlin 84 | kotlin-stdlib-jdk8 85 | ${kotlin.version} 86 | 87 | 88 | org.jetbrains.kotlin 89 | kotlin-reflect 90 | ${kotlin.version} 91 | 92 | 93 | 94 | org.junit.jupiter 95 | junit-jupiter-engine 96 | test 97 | 98 | 99 | 100 | 101 | verify 102 | 103 | 104 | ${project.basedir}/src/main/kotlin 105 | ${project.basedir}/src/test/kotlin 106 | 107 | 108 | 109 | 110 | org.apache.maven.plugins 111 | maven-wrapper-plugin 112 | ${maven-wrapper-plugin.version} 113 | 114 | source 115 | ${maven.version} 116 | 117 | 118 | 119 | maven-enforcer-plugin 120 | ${maven-enforcer-plugin.version} 121 | 122 | 123 | 124 | ${maven.version} 125 | 126 | 127 | 128 | 129 | 130 | 131 | enforce 132 | 133 | 134 | 135 | 136 | 137 | org.codehaus.mojo 138 | versions-maven-plugin 139 | ${maven-versions-plugin.version} 140 | 141 | false 142 | 143 | 144 | 145 | kotlin-maven-plugin 146 | org.jetbrains.kotlin 147 | ${kotlin.version} 148 | 149 | 150 | -Werror 151 | 152 | 153 | 154 | 155 | compile 156 | 157 | compile 158 | 159 | 160 | 161 | test-compile 162 | 163 | test-compile 164 | 165 | 166 | 167 | 168 | 169 | org.jacoco 170 | jacoco-maven-plugin 171 | ${jacoco-maven-plugin.version} 172 | 173 | 174 | 175 | 176 | 177 | LINE 178 | COVEREDRATIO 179 | ${coverage.lines}% 180 | 181 | 182 | BRANCH 183 | COVEREDRATIO 184 | ${coverage.branches}% 185 | 186 | 187 | INSTRUCTION 188 | COVEREDRATIO 189 | ${coverage.instructions}% 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | prepare-coverage 198 | 199 | prepare-agent 200 | 201 | 202 | 203 | coverage-report 204 | post-integration-test 205 | 206 | report 207 | 208 | 209 | 210 | check-coverage 211 | 212 | check 213 | 214 | 215 | 216 | 217 | 218 | org.pitest 219 | pitest-maven 220 | ${pitest-maven.version} 221 | 222 | 223 | kotlin.jvm.internal 224 | 225 | 226 | ${main.class} 227 | 228 | ${coverage.mutation} 229 | 230 | 231 | 232 | mutation-testing 233 | verify 234 | 235 | mutationCoverage 236 | 237 | 238 | 239 | 240 | 241 | org.pitest 242 | pitest-junit5-plugin 243 | ${pitest-junit5-plugin.version} 244 | 245 | 246 | 247 | 248 | maven-assembly-plugin 249 | ${maven-assembly-plugin.version} 250 | 251 | 252 | 253 | ${main.class} 254 | 255 | 256 | 257 | jar-with-dependencies 258 | 259 | 260 | 261 | 262 | package 263 | 264 | single 265 | 266 | 267 | 268 | 269 | 270 | com.github.gantsign.maven 271 | ktlint-maven-plugin 272 | ${ktlint-maven-plugin.version} 273 | 274 | true 275 | 276 | 277 | 278 | 279 | format 280 | check 281 | 282 | 283 | 284 | 285 | 286 | com.github.ozsie 287 | detekt-maven-plugin 288 | ${detekt-maven-plugin.version} 289 | 290 | config/detekt.yml 291 | 292 | 293 | html:${basedir}/target/site/detekt/index.html 294 | 295 | 296 | 297 | 298 | 299 | verify 300 | 301 | check 302 | 303 | 304 | 305 | 306 | 307 | org.owasp 308 | dependency-check-maven 309 | ${dependency-check-maven.version} 310 | 311 | false 312 | 313 | 314 | 315 | 316 | check 317 | 318 | 319 | 320 | 321 | 322 | org.jetbrains.dokka 323 | dokka-maven-plugin 324 | ${dokka.version} 325 | 326 | ${jdk.version} 327 | 328 | true 329 | 330 | 331 | 332 | pre-site 333 | 334 | dokka 335 | 336 | 337 | 338 | 339 | 340 | maven-source-plugin 341 | ${maven-source-plugin.version} 342 | 343 | 344 | attach-sources 345 | verify 346 | 347 | jar-no-fork 348 | 349 | 350 | 351 | 352 | 353 | maven-release-plugin 354 | ${maven-release-plugin.version} 355 | 356 | 357 | 358 | 359 | 360 | 361 | maven-project-info-reports-plugin 362 | ${maven-project-info-reports-plugin.version} 363 | 364 | 365 | 366 | maven-site-plugin 367 | ${maven-site-plugin.version} 368 | 369 | 370 | maven-surefire-plugin 371 | ${maven-surefire-plugin.version} 372 | 373 | 374 | org.junit.jupiter 375 | junit-jupiter-engine 376 | ${junit.version} 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | com.github.gantsign.maven 388 | ktlint-maven-plugin 389 | ${ktlint-maven-plugin.version} 390 | 391 | 392 | 393 | 394 | --------------------------------------------------------------------------------