Licensing/src/dorkbox/license/LicenseData.kt

391 lines
11 KiB
Kotlin

/*
* Copyright 2012 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.license
import License
import org.gradle.api.Action
import org.gradle.api.GradleException
import java.io.IOException
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.time.LocalDate
class LicenseData(var name: String, var license: License) : java.io.Serializable, Comparable<LicenseData> {
/**
* Description/title
*/
var description = ""
/**
* If not specified, will be blank after the name
*/
fun description(description: String) {
this.description = description
}
/**
* Copyright
*/
val copyrights = mutableListOf<Int>()
/**
* If not specified, will use the current year
*/
fun copyright(copyright: Int = LocalDate.now().year): CopyrightRange {
copyrights.add(copyright)
return CopyrightRange(copyright, copyrights)
}
/**
* URL
*/
val urls = mutableListOf<String>()
/**
* Specifies the URLs this project is located at
*/
fun url(url: String) {
urls.add(url)
}
/**
* Notes
*/
val notes = mutableListOf<String>()
/**
* Specifies any extra notes (or copyright info) as needed
*/
fun note(note: String) {
notes.add(note)
}
/**
* AUTHOR
*/
val authors = mutableListOf<String>()
/**
* Specifies the authors of this project
*/
fun author(author: String) {
authors.add(author)
}
/**
* Extra License information
*/
val extras = mutableListOf<LicenseData>()
/**
* Specifies the extra license information for this project
*/
fun extra(name: String, license: License, licenseAction: Action<LicenseData>) {
val licenseData = LicenseData(name, license)
licenseAction.execute(licenseData)
extras.add(licenseData)
}
/**
* Specifies the extra license information for this project
*/
fun extra(name: String, license: License, licenseAction: (LicenseData) -> Unit) {
val licenseData = LicenseData(name, license)
licenseAction(licenseData)
extras.add(licenseData)
}
/**
* ignore case when sorting these
*/
override operator fun compareTo(other: LicenseData): Int {
return this.name.toLowerCase().compareTo(other.name.toLowerCase())
}
override fun toString(): String {
return this.name
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as LicenseData
if (name != other.name) return false
if (license != other.license) return false
if (description != other.description) return false
if (copyrights != other.copyrights) return false
if (urls != other.urls) return false
if (notes != other.notes) return false
if (authors != other.authors) return false
if (extras != other.extras) return false
return true
}
override fun hashCode(): Int {
var result = name.hashCode()
result = 31 * result + license.hashCode()
result = 31 * result + description.hashCode()
result = 31 * result + copyrights.hashCode()
result = 31 * result + urls.hashCode()
result = 31 * result + notes.hashCode()
result = 31 * result + authors.hashCode()
result = 31 * result + extras.hashCode()
return result
}
@Throws(IOException::class)
fun writeObject(s: ObjectOutputStream) {
s.writeUTF(name)
s.writeUTF(license.name)
s.writeUTF(description)
s.writeInt(copyrights.size)
copyrights.forEach {
s.writeInt(it)
}
s.writeInt(urls.size)
urls.forEach {
s.writeUTF(it)
}
s.writeInt(notes.size)
notes.forEach {
s.writeUTF(it)
}
s.writeInt(authors.size)
authors.forEach {
s.writeUTF(it)
}
s.writeInt(extras.size)
if (extras.size > 0) {
extras.forEach {
it.writeObject(s)
}
}
}
// Gradle only needs to serialize objects, so this isn't strictly needed
@Throws(IOException::class)
fun readObject(s: ObjectInputStream) {
name = s.readUTF()
license = License.valueOfLicenseName(s.readUTF())
description = s.readUTF()
val copyrightsSize = s.readInt()
for (i in 1..copyrightsSize) {
copyrights.add(s.readInt())
}
val urlsSize = s.readInt()
for (i in 1..urlsSize) {
urls.add(s.readUTF())
}
val notesSize = s.readInt()
for (i in 1..notesSize) {
notes.add(s.readUTF())
}
val authorsSize = s.readInt()
for (i in 1..authorsSize) {
authors.add(s.readUTF())
}
val extrasSize = s.readInt()
for (i in 1..extrasSize) {
val dep = LicenseData("", License.CUSTOM)
dep.readObject(s) // can recursively create objects
extras.add(dep)
}
}
companion object {
private const val serialVersionUID = 1L
// NOTE: we ALWAYS use unix line endings!
private const val NL = "\n"
private const val HEADER = " - "
private const val HEADR4 = " ---- "
private const val SPACER3 = " "
private const val SPACER4 = " "
private fun prefix(prefix: Int, builder: StringBuilder): StringBuilder {
if (prefix == 0) {
builder.append("")
} else {
for (i in 0 until prefix) {
builder.append(" ")
}
}
return builder
}
private fun line(prefix: Int, builder: StringBuilder, vararg strings: Any) {
prefix(prefix, builder)
strings.forEach {
builder.append(it.toString())
}
builder.append(NL)
}
/**
* Returns the LICENSE text file, as a combo of the listed licenses. Duplicates are removed.
*/
fun buildString(licenses: MutableList<LicenseData>, prefixOffset: Int = 0): String {
val b = StringBuilder(256)
sortAndClean(licenses)
var first = true
licenses.forEach { license ->
// append a new line AFTER the first entry
if (first) {
first = false
}
else {
b.append(NL).append(NL)
}
buildLicenseString(b, license, prefixOffset)
}
return b.toString()
}
// NOTE: we ALWAYS use unix line endings!
private fun buildLicenseString(b: StringBuilder, license: LicenseData, prefixOffset: Int) {
line(prefixOffset, b, HEADER, license.name, " - ", license.description)
license.urls.forEach {
line(prefixOffset, b, SPACER3, it)
}
prefix(prefixOffset, b).append(SPACER3).append("Copyright")
if (license.copyrights.isEmpty()) {
// append the current year
b.append(" ").append(LocalDate.now().year)
}
else if (license.copyrights.size == 1) {
// this is 2001
b.append(" ").append(license.copyrights.first())
} else {
// is this 2001,2002,2004,2014 <OR> 2001-2014
val sumA = license.copyrights.sum()
val sumB = license.copyrights.first().rangeTo(license.copyrights.last()).sum()
if (sumA == sumB) {
// this is 2001-2004
b.append(" ").append(license.copyrights.first()).append("-").append(license.copyrights.last())
} else {
// this is 2001,2002,2004
license.copyrights.forEach {
b.append(" ").append(it).append(",")
}
b.deleteCharAt(b.length-1)
}
}
b.append(HEADER).append(license.license.preferedName).append(NL)
license.authors.forEach {
line(prefixOffset, b, SPACER4, it)
}
if (license.license === License.CUSTOM) {
line(prefixOffset, b, HEADR4)
}
license.notes.forEach {
line(prefixOffset, b, SPACER3, it)
}
// now add the DEPENDENCY license information. This info is nested (and CAN contain duplicates from elsewhere!)
if (license.extras.isNotEmpty()) {
var isFirstExtra = true
line(prefixOffset, b, SPACER3, NL, "Extra license information")
license.extras.forEach { extraLicense ->
if (isFirstExtra) {
isFirstExtra = false
} else {
b.append(NL)
}
buildLicenseString(b, extraLicense, prefixOffset + 4)
}
}
}
/**
* Sorts and remove dupes for the list of licenses.
*/
private fun sortAndClean(licenses: MutableList<LicenseData>) {
if (licenses.isEmpty()) {
return
}
// The FIRST one is always FIRST! (the rest are alphabetical by name)
val firstLicense = licenses[0]
// remove dupes
val dedupe = HashSet(licenses)
val copy = ArrayList(dedupe)
copy.sort()
// figure out where the first one went
for (i in copy.indices) {
if (firstLicense === copy[i]) {
copy.removeAt(i)
break
}
}
licenses.clear()
licenses.add(firstLicense)
licenses.addAll(copy)
}
}
}
class CopyrightRange internal constructor(private val start: Int, private val copyrights: MutableList<Int>) {
fun to(copyRight: Int) {
if (start >= copyRight) {
throw GradleException("Cannot have a start copyright date that is equal or greater than the `to` copyright date")
}
val newStart = start+1
if (newStart < copyRight) {
// increment by 1, since the first part of the range is already added
copyrights.addAll((newStart).rangeTo(copyRight))
}
}
fun toNow() {
to(LocalDate.now().year)
}
}