441 lines
14 KiB
Kotlin
441 lines
14 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 java.io.IOException
|
|
import java.io.ObjectInputStream
|
|
import java.io.ObjectOutputStream
|
|
import java.time.LocalDate
|
|
import java.util.*
|
|
|
|
|
|
open class LicenseData(var name: String, var license: License) : java.io.Serializable, Comparable<LicenseData> {
|
|
// This is only for internal use when checking if specific license information needs to be suppressed under certain circumstances
|
|
internal var mavenId = ""
|
|
|
|
/**
|
|
* Description/title
|
|
*/
|
|
var description = ""
|
|
|
|
/**
|
|
* If not specified, will be blank after the name
|
|
*/
|
|
fun description(description: String) {
|
|
this.description = description
|
|
}
|
|
|
|
/**
|
|
* The Berne Convention leaves "implementation details" up to individual countries.
|
|
*
|
|
* In the US (which is the relevant country for FSF documents), copyright notices are defined by Title 17, Chapter 4;
|
|
* for "visually perceptive copies" (which includes software). http://www.copyright.gov/title17/92chap4.html
|
|
*
|
|
* These take the form :
|
|
*
|
|
* © year name
|
|
*
|
|
* where
|
|
*
|
|
* `©` is specifically that symbol (not "(c)", which has no legal value), and can be replaced by "Copyright" or "Copr." (§ 401(b)(1))
|
|
*
|
|
* `year` is the year of first publication of the work, or the year of first publication of the compilation or derivative work if relevant
|
|
*
|
|
* `name` is the name of the owner of the copyright.
|
|
*
|
|
* What counts is years of publication; for software this is generally considered years in which the software is released.
|
|
*
|
|
* So if you release a piece of software in 2014, and release it again in 2016 without making changes in 2015, the years of publication
|
|
* would be 2014 and 2016, and the copyright notices would be "© 2014" in the first release and "© 2016" in the second release.
|
|
*
|
|
*
|
|
* When scanning for license copyright dates, we use the date of publication (the date of the manifest file, within the published jar)
|
|
*/
|
|
internal var copyright = LocalDate.now().year
|
|
|
|
|
|
/**
|
|
* 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: ExtraLicenseData.() -> Unit) {
|
|
val licenseData = ExtraLicenseData(name, license)
|
|
licenseAction(licenseData)
|
|
extras.add(licenseData)
|
|
}
|
|
|
|
/**
|
|
* ignore case when sorting these
|
|
*/
|
|
override operator fun compareTo(other: LicenseData): Int {
|
|
return this.name.lowercase(Locale.US).compareTo(other.name.lowercase(Locale.US))
|
|
}
|
|
|
|
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 (copyright != other.copyright) 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 + copyright
|
|
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) {
|
|
// as STUPID as this is, the first value in version '0' is a UTF8 string.
|
|
//
|
|
// Without horrifically breaking how the license information is read/written, we must ALSO write the
|
|
// version integer as a string, then read + parse it later (so we can still parse version 0 files).
|
|
//
|
|
// If we don't do this, then we cannot read license information for any older jars, because this
|
|
// feature wasn't added from the start :(
|
|
s.writeUTF(version.toString())
|
|
|
|
s.writeUTF(name)
|
|
s.writeUTF(license.name)
|
|
s.writeUTF(description)
|
|
|
|
s.writeInt(copyright)
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
// this is used to load license data from supported dependencies
|
|
@Suppress("DuplicatedCode")
|
|
@Throws(IOException::class)
|
|
fun readObject(stream: ObjectInputStream) {
|
|
// as STUPID as this is, the first value in version '0' is a UTF8 string.
|
|
//
|
|
// Without horrifically breaking how the license information is read/written, we must ALSO write the
|
|
// version integer as a string, then read + parse it later (so we can still parse version 0 files).
|
|
//
|
|
// If we don't do this, then we cannot read license information for any older jars, because this
|
|
// feature wasn't added from the start :(
|
|
|
|
|
|
// first we check for a version number, if there is an error parsing info, then this is version 0.
|
|
val versionOrName = stream.readUTF()
|
|
|
|
val version = try {
|
|
Integer.parseInt(versionOrName)
|
|
} catch (e: Exception) {
|
|
0
|
|
}
|
|
|
|
when (version) {
|
|
0 -> {
|
|
name = versionOrName
|
|
|
|
val licenseName = stream.readUTF()
|
|
license = License.valueOf(licenseName)
|
|
description = stream.readUTF()
|
|
|
|
copyright = stream.readInt()
|
|
|
|
val urlsSize = stream.readInt()
|
|
for (i in 1..urlsSize) {
|
|
urls.add(stream.readUTF())
|
|
}
|
|
|
|
val notesSize = stream.readInt()
|
|
for (i in 1..notesSize) {
|
|
notes.add(stream.readUTF())
|
|
}
|
|
|
|
val authorsSize = stream.readInt()
|
|
for (i in 1..authorsSize) {
|
|
authors.add(stream.readUTF())
|
|
}
|
|
|
|
val extrasSize = stream.readInt()
|
|
if (extrasSize > 0) {
|
|
for (i in 1..extrasSize) {
|
|
val dep = ExtraLicenseData("", License.CUSTOM)
|
|
dep.readObject(stream) // can recursively create objects
|
|
extras.add(dep)
|
|
}
|
|
}
|
|
}
|
|
1 -> {
|
|
// this the same as version 0, but with the version information header
|
|
name = stream.readUTF()
|
|
|
|
val licenseName = stream.readUTF()
|
|
license = License.valueOf(licenseName)
|
|
description = stream.readUTF()
|
|
|
|
copyright = stream.readInt()
|
|
|
|
val urlsSize = stream.readInt()
|
|
for (i in 1..urlsSize) {
|
|
urls.add(stream.readUTF())
|
|
}
|
|
|
|
val notesSize = stream.readInt()
|
|
for (i in 1..notesSize) {
|
|
notes.add(stream.readUTF())
|
|
}
|
|
|
|
val authorsSize = stream.readInt()
|
|
for (i in 1..authorsSize) {
|
|
authors.add(stream.readUTF())
|
|
}
|
|
|
|
val extrasSize = stream.readInt()
|
|
if (extrasSize > 0) {
|
|
for (i in 1..extrasSize) {
|
|
val dep = ExtraLicenseData("", License.CUSTOM)
|
|
dep.readObject(stream) // can recursively create objects
|
|
extras.add(dep)
|
|
}
|
|
}
|
|
}
|
|
else -> {
|
|
// no other versions yet!
|
|
}
|
|
}
|
|
}
|
|
|
|
companion object {
|
|
private const val version = 1
|
|
private const val serialVersionUID = version.toLong()
|
|
|
|
// NOTE: we ALWAYS use unix line endings!
|
|
private const val NL = "\n"
|
|
private const val HEADER1 = " - "
|
|
private const val HEADER4 = " ---- "
|
|
private const val SPACER3 = " "
|
|
|
|
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, HEADER1, license.name, " - ", license.description)
|
|
|
|
line(prefixOffset, b, SPACER3, "[", license.license.preferredName, "]")
|
|
|
|
license.urls.forEach {
|
|
line(prefixOffset, b, SPACER3, it)
|
|
}
|
|
|
|
// as per US Code Title 17, Chapter 4; for "visually perceptive copies" (which includes software).
|
|
// http://www.copyright.gov/title17/92chap4.html
|
|
// it is ONLY... © year name (or Copyright year name)
|
|
// authors go on a separate line
|
|
if (license is ExtraLicenseData) {
|
|
if (license.copyright != 0) {
|
|
// we only want to have the copyright info IF we specified it on the extra license info (otherwise, they are under
|
|
// the same copyright date as the parent)
|
|
line(prefixOffset, b, SPACER3, "Copyright ", license.copyright)
|
|
}
|
|
} else {
|
|
line(prefixOffset, b, SPACER3, "Copyright ", license.copyright)
|
|
}
|
|
|
|
license.authors.forEach {
|
|
line(prefixOffset, b, SPACER3, " ", it)
|
|
}
|
|
|
|
if (license.license === License.CUSTOM) {
|
|
line(prefixOffset, b, HEADER4)
|
|
}
|
|
|
|
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
|
|
|
|
b.append(NL)
|
|
line(prefixOffset, b, SPACER3, "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)
|
|
}
|
|
}
|
|
}
|