All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlin.konan.properties.Properties.kt Maven / Gradle / Ivy

There is a newer version: 2.1.0-RC
Show newest version
/*
 * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package org.jetbrains.kotlin.konan.properties

import org.jetbrains.kotlin.konan.file.*
import org.jetbrains.kotlin.util.parseSpaceSeparatedArgs
import java.io.ByteArrayOutputStream
import java.io.OutputStream
import java.io.StringWriter

typealias Properties = java.util.Properties

fun File.loadProperties(): Properties {
    val properties = java.util.Properties()
    this.bufferedReader().use { reader ->
        properties.load(reader)
    }
    return properties
}

fun loadProperties(path: String): Properties = File(path).loadProperties()

/**
 * Standard properties writer has two issues, which prevents build reproducibility
 *
 * 1. The order of lines is not defined
 * 2. It uses platform-specific end-of-lines
 *
 * This function deals with both issues
 */
fun File.saveProperties(properties: Properties) {
    val rawData = StringWriter().apply {
        properties.store(this, null)
    }.toString()

    val lines = rawData
        .split(System.lineSeparator())
        .filterNot { it.isEmpty() || it.startsWith("#") }
        .sorted()

    outputStream().use {
        it.write(lines.joinToString("\n", postfix = "\n").toByteArray())
    }
}

fun Properties.saveToFile(file: File) = file.saveProperties(this)

fun Properties.propertyString(key: String, suffix: String? = null): String? = getProperty(key.suffix(suffix)) ?: this.getProperty(key)

/**
 * TODO: this method working with suffixes should be replaced with
 *  functionality borrowed from def file parser and unified for interop tool
 *  and kotlin compiler.
 */
fun Properties.propertyList(key: String, suffix: String? = null, escapeInQuotes: Boolean = false): List {
    val value: String? = (getProperty(key.suffix(suffix)) ?: getProperty(key))?.trim(Char::isWhitespace)
    return when {
        value.isNullOrEmpty() -> emptyList()
        escapeInQuotes -> parseSpaceSeparatedArgs(value)
        else -> value.split(Regex("\\s+"))
    }
}

fun Properties.hasProperty(key: String, suffix: String? = null): Boolean
        = this.getProperty(key.suffix(suffix)) != null

fun String.suffix(suf: String?): String =
    if (suf == null) this
    else "${this}.$suf"

fun Properties.keepOnlyDefaultProfiles() {
    val DEPENDENCY_PROFILES_KEY = "dependencyProfiles"
    val dependencyProfiles = this.getProperty(DEPENDENCY_PROFILES_KEY)
    if (dependencyProfiles != "default alt")
        error("unexpected $DEPENDENCY_PROFILES_KEY value: expected 'default alt', got '$dependencyProfiles'")

    // Force build to use only 'default' profile:
    this.setProperty(DEPENDENCY_PROFILES_KEY, "default")
    // TODO: it actually affects only resolution made in :dependencies,
    // that's why we assume that 'default' profile comes first (and check this above).
}


/**
 * Wraps [propertyList] with resolving mechanism. See [String.resolveValue].
 */
fun Properties.resolvablePropertyList(
    key: String, suffix: String? = null, escapeInQuotes: Boolean = false,
    visitedProperties: MutableSet = mutableSetOf()
): List = propertyList(key, suffix, escapeInQuotes).flatMap {
    // We need to create a copy of a visitedProperties to avoid collisions
    // between different elements of the list.
    it.resolveValue(this, visitedProperties.toMutableSet())
}

/**
 * Wraps [propertyString] with resolving mechanism. See [String.resolveValue].
 */
fun Properties.resolvablePropertyString(
    key: String, suffix: String? = null,
    visitedProperties: MutableSet = mutableSetOf()
): String? = propertyString(key, suffix)
    ?.split(' ')
    ?.flatMap { it.resolveValue(this, visitedProperties) }
    ?.joinToString(" ")

/**
 * Adds trivial symbol resolving mechanism to properties files.
 *
 * Given the following properties file:
 *
 *      key0 = value1 value2
 *      key1 = value3 $key0
 *      key2 = $key1
 *
 * "$key1".resolveValue(properties) will return List("value3", "value1", "value2")
 */
private fun String.resolveValue(properties: Properties, visitedProperties: MutableSet = mutableSetOf()): List =
    when {
        contains("$") -> {
            val prefix = this.substringBefore('$', missingDelimiterValue = "")
            val withoutSigil = this.substringAfter('$')
            val property = withoutSigil.substringBefore('/')
            val relative = withoutSigil.substringAfter('/', missingDelimiterValue = "")
            // Keep track of visited properties to avoid running in circles.
            if (!visitedProperties.add(property)) {
                error("Circular dependency: ${visitedProperties.joinToString()}")
            }
            val substitutionResult = properties.resolvablePropertyList(property, visitedProperties = visitedProperties)
            when {
                substitutionResult.size > 1 -> when {
                    relative.isNotEmpty() ->
                        error("Cannot append `/$relative` to multiple values: ${substitutionResult.joinToString()}")
                    prefix.isNotEmpty() ->
                        error("Cannot add prefix `$prefix` to multiple values: ${substitutionResult.joinToString()}")
                    else -> substitutionResult
                }
                else -> substitutionResult.map {
                    // Avoid repeated '/' at the end.
                    if (relative.isNotEmpty()) {
                        "$prefix${it.dropLastWhile { it == '/' }}/$relative"
                    } else {
                        "$prefix$it"
                    }
                }
            }
        }
        else -> listOf(this)
    }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy