FlowException can be thrown from within UntrustworthyData.unwrap for better Java interop, and more checked exception annotations relating to flows

This commit is contained in:
Shams Asari 2017-02-01 11:28:50 +00:00
parent 1e5dd1843d
commit 657cfa5d96
26 changed files with 55 additions and 22 deletions

2
.gitignore vendored
View File

@ -11,7 +11,6 @@ tags
.gradle .gradle
local.properties local.properties
/docs/build/doctrees
# General build files # General build files
**/build/* **/build/*
@ -34,7 +33,6 @@ lib/dokka.jar
.idea/libraries .idea/libraries
.idea/shelf .idea/shelf
.idea/dataSources .idea/dataSources
.idea/modules.xml
# if you remove the above rule, at least ignore the following: # if you remove the above rule, at least ignore the following:

View File

@ -17,6 +17,7 @@ sealed class TransactionType {
* *
* Note: Presence of _signatures_ is not checked, only the public keys to be signed for. * Note: Presence of _signatures_ is not checked, only the public keys to be signed for.
*/ */
@Throws(TransactionVerificationException::class)
fun verify(tx: LedgerTransaction) { fun verify(tx: LedgerTransaction) {
require(tx.notary != null || tx.timestamp == null) { "Transactions with timestamps must be notarised." } require(tx.notary != null || tx.timestamp == null) { "Transactions with timestamps must be notarised." }
val duplicates = detectDuplicateInputs(tx) val duplicates = detectDuplicateInputs(tx)

View File

@ -3,6 +3,7 @@ package net.corda.core.contracts
import net.corda.core.crypto.CompositeKey import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Party import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowException
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
import java.util.* import java.util.*
@ -85,30 +86,35 @@ data class TransactionForContract(val inputs: List<ContractState>,
data class InOutGroup<out T : ContractState, out K : Any>(val inputs: List<T>, val outputs: List<T>, val groupingKey: K) data class InOutGroup<out T : ContractState, out K : Any>(val inputs: List<T>, val outputs: List<T>, val groupingKey: K)
} }
class TransactionResolutionException(val hash: SecureHash) : Exception() { class TransactionResolutionException(val hash: SecureHash) : FlowException() {
override fun toString() = "Transaction resolution failure for $hash" override fun toString(): String = "Transaction resolution failure for $hash"
}
class AttachmentResolutionException(val hash : SecureHash) : FlowException() {
override fun toString(): String = "Attachment resolution failure for $hash"
} }
class TransactionConflictException(val conflictRef: StateRef, val tx1: LedgerTransaction, val tx2: LedgerTransaction) : Exception() class TransactionConflictException(val conflictRef: StateRef, val tx1: LedgerTransaction, val tx2: LedgerTransaction) : Exception()
sealed class TransactionVerificationException(val tx: LedgerTransaction, cause: Throwable?) : Exception(cause) { sealed class TransactionVerificationException(val tx: LedgerTransaction, cause: Throwable?) : FlowException(cause) {
class ContractRejection(tx: LedgerTransaction, val contract: Contract, cause: Throwable?) : TransactionVerificationException(tx, cause) class ContractRejection(tx: LedgerTransaction, val contract: Contract, cause: Throwable?) : TransactionVerificationException(tx, cause)
class MoreThanOneNotary(tx: LedgerTransaction) : TransactionVerificationException(tx, null) class MoreThanOneNotary(tx: LedgerTransaction) : TransactionVerificationException(tx, null)
class SignersMissing(tx: LedgerTransaction, val missing: List<CompositeKey>) : TransactionVerificationException(tx, null) { class SignersMissing(tx: LedgerTransaction, val missing: List<CompositeKey>) : TransactionVerificationException(tx, null) {
override fun toString() = "Signers missing: ${missing.joinToString()}" override fun toString(): String = "Signers missing: ${missing.joinToString()}"
} }
class DuplicateInputStates(tx: LedgerTransaction, val duplicates: Set<StateRef>) : TransactionVerificationException(tx, null) { class DuplicateInputStates(tx: LedgerTransaction, val duplicates: Set<StateRef>) : TransactionVerificationException(tx, null) {
override fun toString() = "Duplicate inputs: ${duplicates.joinToString()}" override fun toString(): String = "Duplicate inputs: ${duplicates.joinToString()}"
} }
class InvalidNotaryChange(tx: LedgerTransaction) : TransactionVerificationException(tx, null) class InvalidNotaryChange(tx: LedgerTransaction) : TransactionVerificationException(tx, null)
class NotaryChangeInWrongTransactionType(tx: LedgerTransaction, val outputNotary: Party) : TransactionVerificationException(tx, null) { class NotaryChangeInWrongTransactionType(tx: LedgerTransaction, val outputNotary: Party) : TransactionVerificationException(tx, null) {
override fun toString(): String = "Found unexpected notary change in transaction. Tx notary: ${tx.notary}, found: ${outputNotary}" override fun toString(): String {
return "Found unexpected notary change in transaction. Tx notary: ${tx.notary}, found: $outputNotary"
}
} }
class TransactionMissingEncumbranceException(tx: LedgerTransaction, val missing: Int, val inOut: Direction) : TransactionVerificationException(tx, null) { class TransactionMissingEncumbranceException(tx: LedgerTransaction, val missing: Int, val inOut: Direction) : TransactionVerificationException(tx, null) {
override val message: String? override val message: String get() = "Missing required encumbrance $missing in $inOut"
get() = "Missing required encumbrance ${missing} in ${inOut}"
} }
enum class Direction { enum class Direction {
INPUT, INPUT,

View File

@ -9,5 +9,8 @@ package net.corda.core.flows
* [FlowException] (or a subclass) can be a valid expected response from a flow, particularly ones which act as a service. * [FlowException] (or a subclass) can be a valid expected response from a flow, particularly ones which act as a service.
* It is recommended a [FlowLogic] document the [FlowException] types it can throw. * It is recommended a [FlowLogic] document the [FlowException] types it can throw.
*/ */
open class FlowException @JvmOverloads constructor(message: String? = null, cause: Throwable? = null) open class FlowException(override val message: String?, override val cause: Throwable?) : Exception() {
: Exception(message, cause) constructor(message: String?) : this(message, null)
constructor(cause: Throwable?) : this(cause?.toString(), cause)
constructor() : this(null, null)
}

View File

@ -54,6 +54,7 @@ class LedgerTransaction(
* *
* @throws TransactionVerificationException if anything goes wrong. * @throws TransactionVerificationException if anything goes wrong.
*/ */
@Throws(TransactionVerificationException::class)
fun verify() = type.verify(this) fun verify() = type.verify(this)
// TODO: When we upgrade to Kotlin 1.1 we can make this a data class again and have the compiler generate these. // TODO: When we upgrade to Kotlin 1.1 we can make this a data class again and have the compiler generate these.

View File

@ -1,5 +1,6 @@
package net.corda.core.transactions package net.corda.core.transactions
import net.corda.core.contracts.AttachmentResolutionException
import net.corda.core.contracts.NamedByHash import net.corda.core.contracts.NamedByHash
import net.corda.core.contracts.TransactionResolutionException import net.corda.core.contracts.TransactionResolutionException
import net.corda.core.crypto.CompositeKey import net.corda.core.crypto.CompositeKey
@ -8,7 +9,6 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.signWithECDSA import net.corda.core.crypto.signWithECDSA
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
import java.io.FileNotFoundException
import java.security.KeyPair import java.security.KeyPair
import java.security.SignatureException import java.security.SignatureException
import java.util.* import java.util.*
@ -127,12 +127,12 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
* [WireTransaction.toLedgerTransaction] with the passed in [ServiceHub] to resolve the dependencies, * [WireTransaction.toLedgerTransaction] with the passed in [ServiceHub] to resolve the dependencies,
* returning an unverified LedgerTransaction. * returning an unverified LedgerTransaction.
* *
* @throws FileNotFoundException if a required attachment was not found in storage. * @throws AttachmentResolutionException if a required attachment was not found in storage.
* @throws TransactionResolutionException if an input points to a transaction not found in storage. * @throws TransactionResolutionException if an input points to a transaction not found in storage.
* @throws SignatureException if any signatures were invalid or unrecognised * @throws SignatureException if any signatures were invalid or unrecognised
* @throws SignaturesMissingException if any signatures that should have been present are missing. * @throws SignaturesMissingException if any signatures that should have been present are missing.
*/ */
@Throws(FileNotFoundException::class, TransactionResolutionException::class, SignaturesMissingException::class) @Throws(AttachmentResolutionException::class, TransactionResolutionException::class, SignatureException::class)
fun toLedgerTransaction(services: ServiceHub) = verifySignatures().toLedgerTransaction(services) fun toLedgerTransaction(services: ServiceHub) = verifySignatures().toLedgerTransaction(services)
/** /**

View File

@ -12,7 +12,6 @@ import net.corda.core.serialization.THREAD_LOCAL_KRYO
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.utilities.Emoji import net.corda.core.utilities.Emoji
import java.io.FileNotFoundException
import java.security.PublicKey import java.security.PublicKey
/** /**
@ -66,10 +65,10 @@ class WireTransaction(
* Looks up identities and attachments from storage to generate a [LedgerTransaction]. A transaction is expected to * Looks up identities and attachments from storage to generate a [LedgerTransaction]. A transaction is expected to
* have been fully resolved using the resolution flow by this point. * have been fully resolved using the resolution flow by this point.
* *
* @throws FileNotFoundException if a required attachment was not found in storage. * @throws AttachmentResolutionException if a required attachment was not found in storage.
* @throws TransactionResolutionException if an input points to a transaction not found in storage. * @throws TransactionResolutionException if an input points to a transaction not found in storage.
*/ */
@Throws(FileNotFoundException::class, TransactionResolutionException::class) @Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
fun toLedgerTransaction(services: ServiceHub): LedgerTransaction { fun toLedgerTransaction(services: ServiceHub): LedgerTransaction {
// Look up public keys to authenticated identities. This is just a stub placeholder and will all change in future. // Look up public keys to authenticated identities. This is just a stub placeholder and will all change in future.
val authenticatedArgs = commands.map { val authenticatedArgs = commands.map {
@ -78,7 +77,7 @@ class WireTransaction(
} }
// Open attachments specified in this transaction. If we haven't downloaded them, we fail. // Open attachments specified in this transaction. If we haven't downloaded them, we fail.
val attachments = attachments.map { val attachments = attachments.map {
services.storageService.attachments.openAttachment(it) ?: throw FileNotFoundException(it.toString()) services.storageService.attachments.openAttachment(it) ?: throw AttachmentResolutionException(it)
} }
val resolvedInputs = inputs.map { StateAndRef(services.loadState(it), it) } val resolvedInputs = inputs.map { StateAndRef(services.loadState(it), it) }
return LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, mustSign, timestamp, type) return LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, mustSign, timestamp, type)

View File

@ -18,11 +18,18 @@ class UntrustworthyData<out T>(private val fromUntrustedWorld: T) {
@Deprecated("Accessing the untrustworthy data directly without validating it first is a bad idea") @Deprecated("Accessing the untrustworthy data directly without validating it first is a bad idea")
get() = fromUntrustedWorld get() = fromUntrustedWorld
@Suppress("DEPRECATION")
@Throws(FlowException::class) @Throws(FlowException::class)
inline fun <R> unwrap(validator: (T) -> R) = validator(data) fun <R> unwrap(validator: Validator<T, R>) = validator.validate(fromUntrustedWorld)
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@Deprecated("This old name was confusing, use unwrap instead", replaceWith = ReplaceWith("unwrap")) @Deprecated("This old name was confusing, use unwrap instead", replaceWith = ReplaceWith("unwrap"))
inline fun <R> validate(validator: (T) -> R) = validator(data) inline fun <R> validate(validator: (T) -> R) = validator(data)
interface Validator<in T, out R> {
@Throws(FlowException::class)
fun validate(data: T): R
}
} }
@Suppress("DEPRECATION")
inline fun <T, R> UntrustworthyData<T>.unwrap(validator: (T) -> R): R = validator(data)

View File

@ -15,6 +15,7 @@ import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.UntrustworthyData import net.corda.core.utilities.UntrustworthyData
import net.corda.core.utilities.unwrap
import net.corda.flows.AbstractStateReplacementFlow.Acceptor import net.corda.flows.AbstractStateReplacementFlow.Acceptor
import net.corda.flows.AbstractStateReplacementFlow.Instigator import net.corda.flows.AbstractStateReplacementFlow.Instigator

View File

@ -7,6 +7,7 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowException import net.corda.core.flows.FlowException
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.utilities.UntrustworthyData import net.corda.core.utilities.UntrustworthyData
import net.corda.core.utilities.unwrap
import net.corda.flows.FetchDataFlow.DownloadedVsRequestedDataMismatch import net.corda.flows.FetchDataFlow.DownloadedVsRequestedDataMismatch
import net.corda.flows.FetchDataFlow.HashNotFound import net.corda.flows.FetchDataFlow.HashNotFound
import java.util.* import java.util.*

View File

@ -5,8 +5,8 @@ import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.Party import net.corda.core.crypto.Party
import net.corda.core.crypto.SignedData import net.corda.core.crypto.SignedData
import net.corda.core.crypto.signWithECDSA import net.corda.core.crypto.signWithECDSA
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowException import net.corda.core.flows.FlowException
import net.corda.core.flows.FlowLogic
import net.corda.core.node.services.TimestampChecker import net.corda.core.node.services.TimestampChecker
import net.corda.core.node.services.UniquenessException import net.corda.core.node.services.UniquenessException
import net.corda.core.node.services.UniquenessProvider import net.corda.core.node.services.UniquenessProvider
@ -14,6 +14,7 @@ import net.corda.core.serialization.serialize
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.unwrap
object NotaryFlow { object NotaryFlow {
/** /**

View File

@ -16,6 +16,7 @@ import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.UntrustworthyData import net.corda.core.utilities.UntrustworthyData
import net.corda.core.utilities.trace import net.corda.core.utilities.trace
import net.corda.core.utilities.unwrap
import java.security.KeyPair import java.security.KeyPair
/** /**

View File

@ -14,6 +14,7 @@ import net.corda.core.flows.FlowLogic
import net.corda.core.node.PluginServiceHub import net.corda.core.node.PluginServiceHub
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.unwrap
import net.corda.flows.FinalityFlow import net.corda.flows.FinalityFlow
import net.corda.flows.ResolveTransactionsFlow import net.corda.flows.ResolveTransactionsFlow
import java.util.* import java.util.*

View File

@ -8,6 +8,7 @@ import net.corda.core.node.PluginServiceHub
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.services.linearHeadsOfType import net.corda.core.node.services.linearHeadsOfType
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.unwrap
import net.corda.flows.FinalityFlow import net.corda.flows.FinalityFlow
import java.security.PublicKey import java.security.PublicKey
import java.time.Duration import java.time.Duration

View File

@ -9,6 +9,7 @@ import net.corda.core.node.PluginServiceHub
import net.corda.core.serialization.OpaqueBytes import net.corda.core.serialization.OpaqueBytes
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.unwrap
import java.util.* import java.util.*
/** /**

View File

@ -13,6 +13,7 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.trace import net.corda.core.utilities.trace
import net.corda.core.utilities.unwrap
import java.security.KeyPair import java.security.KeyPair
import java.util.* import java.util.*

View File

@ -10,6 +10,7 @@ import net.corda.core.getOrThrow
import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.CordaRPCOps
import net.corda.core.random63BitValue import net.corda.core.random63BitValue
import net.corda.core.seconds import net.corda.core.seconds
import net.corda.core.utilities.unwrap
import net.corda.node.internal.Node import net.corda.node.internal.Node
import net.corda.node.services.User import net.corda.node.services.User
import net.corda.node.services.config.SSLConfiguration import net.corda.node.services.config.SSLConfiguration

View File

@ -10,6 +10,7 @@ import net.corda.core.node.PluginServiceHub
import net.corda.core.node.recordTransactions import net.corda.core.node.recordTransactions
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.unwrap
import net.corda.flows.* import net.corda.flows.*
import java.util.function.Function import java.util.function.Function
import javax.annotation.concurrent.ThreadSafe import javax.annotation.concurrent.ThreadSafe

View File

@ -21,6 +21,7 @@ import net.corda.core.random63BitValue
import net.corda.core.rootCause import net.corda.core.rootCause
import net.corda.core.serialization.OpaqueBytes import net.corda.core.serialization.OpaqueBytes
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.utilities.unwrap
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.flows.CashCommand import net.corda.flows.CashCommand

View File

@ -14,6 +14,7 @@ import net.corda.core.node.services.ServiceType
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.transactions.FilteredTransaction import net.corda.core.transactions.FilteredTransaction
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.unwrap
import net.corda.irs.flows.FixingFlow import net.corda.irs.flows.FixingFlow
import net.corda.irs.flows.RatesFixFlow import net.corda.irs.flows.RatesFixFlow
import net.corda.node.services.api.AcceptsFileUpload import net.corda.node.services.api.AcceptsFileUpload

View File

@ -7,6 +7,7 @@ import net.corda.core.flows.FlowLogic
import net.corda.core.node.CordaPluginRegistry import net.corda.core.node.CordaPluginRegistry
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.node.PluginServiceHub import net.corda.core.node.PluginServiceHub
import net.corda.core.utilities.unwrap
import net.corda.testing.node.MockNetworkMapCache import net.corda.testing.node.MockNetworkMapCache
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.function.Function import java.util.function.Function

View File

@ -10,6 +10,7 @@ import net.corda.core.flows.FlowLogic
import net.corda.core.transactions.FilteredTransaction import net.corda.core.transactions.FilteredTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.unwrap
import net.corda.irs.flows.RatesFixFlow.FixOutOfRange import net.corda.irs.flows.RatesFixFlow.FixOutOfRange
import net.corda.irs.utilities.suggestInterestRateAnnouncementTimeWindow import net.corda.irs.utilities.suggestInterestRateAnnouncementTimeWindow
import java.math.BigDecimal import java.math.BigDecimal

View File

@ -7,6 +7,7 @@ import net.corda.core.node.CordaPluginRegistry
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.node.PluginServiceHub import net.corda.core.node.PluginServiceHub
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.unwrap
import net.corda.node.utilities.TestClock import net.corda.node.utilities.TestClock
import net.corda.testing.node.MockNetworkMapCache import net.corda.testing.node.MockNetworkMapCache
import java.time.LocalDate import java.time.LocalDate

View File

@ -5,6 +5,7 @@ import net.corda.core.crypto.Party
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.node.PluginServiceHub import net.corda.core.node.PluginServiceHub
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.unwrap
import net.corda.flows.TwoPartyDealFlow import net.corda.flows.TwoPartyDealFlow
import net.corda.vega.contracts.IRSState import net.corda.vega.contracts.IRSState
import net.corda.vega.contracts.OGTrade import net.corda.vega.contracts.OGTrade

View File

@ -18,6 +18,7 @@ import net.corda.core.messaging.Ack
import net.corda.core.node.PluginServiceHub import net.corda.core.node.PluginServiceHub
import net.corda.core.node.services.dealsWith import net.corda.core.node.services.dealsWith
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.unwrap
import net.corda.flows.AbstractStateReplacementFlow.Proposal import net.corda.flows.AbstractStateReplacementFlow.Proposal
import net.corda.flows.StateReplacementException import net.corda.flows.StateReplacementException
import net.corda.flows.TwoPartyDealFlow import net.corda.flows.TwoPartyDealFlow

View File

@ -12,6 +12,7 @@ import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.Emoji import net.corda.core.utilities.Emoji
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.unwrap
import net.corda.flows.TwoPartyTradeFlow import net.corda.flows.TwoPartyTradeFlow
import net.corda.node.services.persistence.NodeAttachmentService import net.corda.node.services.persistence.NodeAttachmentService
import java.nio.file.Path import java.nio.file.Path