Introduction
When working on large projects I regularly use snippets of code I find online and sometimes port classes from other languages (e.g. Java) to use in my VB.NET project. Over time I've tried to figure out the best way to keep track of the different pieces of code I've used from all the online source along with their license, where I found the code, the original author, etc. so that I can give proper credit to the original author.
Then the idea of using a custom attribute came to me. By using a custom attribute I can mark individual functions, modules, classes, and even assemblies (if I had to) write in the code. This would also give me the ability to use a small utility that through reflection could compile a list of all the different author's code that may be in my project along with the proper license.
Using the code
The first thing needed before creating the attribute is a list of licenses. For this I used the list of licenses on Code Project (http://www.codeproject.com/info/Licenses.aspx) and TLDR Legal's Most Popular licenses list (https://tldrlegal.com/).
You'll notice that I have added a Description
attribute to each of the enum members that includes the full name of the license. The Licenses
class we will shortly will use this attribute to get the full name of a license based on the enum member. To use the Description
attribute you must import the System.ComponentModel
namespace.
Imports System.ComponentModel
Public Enum LicenseType
<Description("The Apache License, Version 2.0")> Apache_2_0
<Description("The BSD 2-Clause License (FreeBSD/Simplified)")> BSD_2_Clause
<Description("The BSD 3-Clause License (BSD New/BSD Modified)")> BSD_3_Clause
<Description("The Creative Commons Attribution 3.0 Unported License")> CCA_3_0_Unported
<Description("The Creative Commons Attribution-NoDerivatives 3.0 Unported License")> CCA_ND_3_0
<Description("The Creative Commons Attribution-ShareAlike 2.5 License")> CCA_SA_2_5
<Description("The Creative Commons Attribution-ShareAlike 3.0 Unported License")> CCA_SA_3_0_Unported
<Description("The Creative Commons Attribution 4.0 International")> CCA_4_International
<Description("The Common Development and Distribution License 1.0")> CDDL_1_0
<Description("The Common Public License Version 1.0")> CPL_1_0
<Description("The Code Project Open License 1.02")> CPOL_1_02
<Description("The Eclipse Public License 1.0")> EPL_1_0
<Description("The GNU General Public License v2")> GPLv2
<Description("The GNU General Public License v3")> GPLv3
<Description("The GNU Lesser General Public License v2.1")> LGPLv2_1
<Description("Teh GNU Lesser General Public License v3")> LGPLv3
<Description("The MIT License")> MIT
<Description("The Mozilla Public License 1.1")> MPL_1_1
<Description("The Mozilla Public License 2.0")> MPL_2_0
<Description("Microsoft Reciprocal License")> Ms_RL
<Description("The Microsoft Public License")> Ms_PL
<Description("The zlib/libpng License")> zlib_libpng
End Enum
The next thing needed is the Licenses
class itself. This class has three properties:
FullName
- This is the full name of the license that will be taken from the description attribute
Abbreviation
- An abbreviation of the license name (In this project I just used the Enum.ToString
method but you can use whatever you like)
LicenseSummaryUrl
- This is a link to a summary of the license for quick reference
FullLicenseUrl
- This is a link to the full text of the license. I tried to use the official link when I could find it.
Private _fullName As String
Private _abbreviation As String
Private _licenseSummaryUrl As String
Private _fullLicenseUrl As String
Public ReadOnly Property FullName As String
Get
Return Me._fullName
End Get
End Property
Public ReadOnly Property Abbreviation As String
Get
Return Me._abbreviation
End Get
End Property
Public ReadOnly Property LicenseSummaryUrl As String
Get
Return Me._licenseSummaryUrl
End Get
End Property
Public ReadOnly Property FullLicenseUrl As String
Get
Return Me._fullLicenseUrl
End Get
End Property
Public Sub New(fullName As String, abbreviation As String, licenseSummaryUrl As String, fullLicenseUrl As String)
Me._fullName = fullName
Me._abbreviation = abbreviation
Me._licenseSummaryUrl = licenseSummaryUrl
Me._fullLicenseUrl = fullLicenseUrl
End Sub
To have the classes for each version of the license act like an Enum
(so I could reference a license like Licenses.Apache20
and then access the additional license information) I created a public, shared, readonly variable for each of the license types. To keep the text short I have only included a few of definitions as an example; please see the source code for all of them.
Public Shared ReadOnly CPOL102 As Licenses = New Licenses(LicenseType.CPOL_1_02.GetDescription, LicenseType.CPOL_1_02.ToString, "http://www.codeproject.com/info/cpol10.aspx", "http://www.codeproject.com/info/cpol10.aspx")
Public Shared ReadOnly EPL10 As Licenses = New Licenses(LicenseType.EPL_1_0.GetDescription, LicenseType.EPL_1_0.ToString, "https://tldrlegal.com/license/eclipse-public-license-1.0-(epl-1.0)#summary", "https://www.eclipse.org/legal/epl-v10.html")
Public Shared ReadOnly GPLv2 As Licenses = New Licenses(LicenseType.GPLv2.GetDescription, LicenseType.GPLv2.ToString, "https://tldrlegal.com/license/gnu-general-public-license-v2#summary", "http://www.gnu.org/licenses/gpl-2.0.html")
Public Shared ReadOnly GPLv3 As Licenses = New Licenses(LicenseType.GPLv3.GetDescription, LicenseType.GPLv3.ToString, "https://tldrlegal.com/license/gnu-general-public-license-v3-(gpl-3)#summary", "http://www.gnu.org/licenses/gpl-3.0.html")
You'll notice that a method called GetDescription
is called to pass a string to the FullName
parameter of the Licenses
constructor. This is where the Description
attribute is used; GetDescription
is an extension method to get the Description
atttribute text.
Imports System.ComponentModel
Imports System.Reflection
Imports System.Runtime.CompilerServices
Public Module Extensions
<Extension>
Public Function GetDescription(currentEnum As [Enum]) As String
Dim description As String = String.Empty
Dim da As DescriptionAttribute
Dim fi As FieldInfo = currentEnum.GetType.GetField(currentEnum.ToString)
da = DirectCast(Attribute.GetCustomAttribute(fi, GetType(DescriptionAttribute)), DescriptionAttribute)
If da IsNot Nothing Then
description = da.Description
Else
description = currentEnum.ToString
End If
Return description
End Function
End Module
By having this extension method a Description
attribute can be added to any Enum
and the text easily retrived by calling GetDescription
on any of the Enum
members. If there is no Description
attribute on the Enum
, the Enum.ToString
text is returned. You can read more about extension methods http://msdn.microsoft.com/en-us/library/bb384936.aspx
I created a Dictionary
object which maps a LicenseType
to a Licenses
class. This dictionary is used by GetLicense
to return the proper Licenses
class.
Private Shared ReadOnly lics As New Dictionary(Of LicenseType, Licenses) From {{LicenseType.Apache_2_0, Licenses.Apache20}, {LicenseType.BSD_2_Clause, Licenses.BSD2}, {LicenseType.BSD_3_Clause, Licenses.BSD3}, {LicenseType.CCA_3_0_Unported, Licenses.CCA3}, {LicenseType.CCA_ND_3_0, Licenses.CCAND30}, {LicenseType.CCA_SA_2_5, Licenses.CCASA25}, {LicenseType.CCA_SA_3_0_Unported, Licenses.CCASA30}, {LicenseType.CCA_4_International, Licenses.CCA4International}, {LicenseType.CDDL_1_0, Licenses.CDDL10}, {LicenseType.CPL_1_0, Licenses.CPL10}, {LicenseType.CPOL_1_02, Licenses.CPOL102}, {LicenseType.EPL_1_0, Licenses.EPL10}, {LicenseType.GPLv2, Licenses.GPLv2}, {LicenseType.GPLv3, Licenses.GPLv3}, {LicenseType.LGPLv2_1, Licenses.LGPL21}, {LicenseType.LGPLv3, Licenses.LGPLv3}, {LicenseType.MIT, Licenses.MIT}, {LicenseType.MPL_1_1, Licenses.MPL11}, {LicenseType.MPL_2_0, Licenses.MPL20}, {LicenseType.Ms_RL, Licenses.MsPL}, {LicenseType.zlib_libpng, Licenses.zlib_libpng}}
Public Shared Function GetLicense(license As LicenseType) As Licenses
Return lics.Item(license)
End Function
Now that the licenses class is created we can work on creating the custom attribute. I wanted the attribute to be able to be applied to anything (class, method, etc.) since the code snippets can be any of these. Also the attribute should not be able to be applied multiple times to the same object and it should not be inherited. So I created a class called OpenSourcedAttribute
which inherits System.Attribute
and applied an AttributeUsage
attribute to it.
<AttributeUsage(AttributeTargets.All, AllowMultiple:=False, Inherited:=False)>
Public Class OpenSourcedAttribute
Inherits Attribute
End Class
I felt the most important pieces of information to record were the original author's name, the original project name that the code came from, the date I accessed the code, the original code's URL, and the obviously the code's license. The properties are all public read/write to allow for name parameters (instead of just positional parameters) when the attribute is applied.
Private _author As String
Private _originalProjectName As String
Private _dateAttributed As String
Private _codeUrl As String
Private _codeLicense As Licenses
Public Property Author As String
Get
Return Me._author
End Get
Set(value As String)
Me._author = value
End Set
End Property
Public Property DateAttributed As String
Get
Return Me._dateAttributed
End Get
Set(value As String)
Me._dateAttributed = value
End Set
End Property
Public Property OriginalProjectName As String
Get
Return Me._originalProjectName
End Get
Set(value As String)
Me._originalProjectName = value
End Set
End Property
Public Property CodeUrl As String
Get
Return Me._codeUrl
End Get
Set(value As String)
Me._codeUrl = value
End Set
End Property
Public Property CodeLicense As Licenses
Get
Return Me._codeLicense
End Get
Set(value As Licenses)
Me._codeLicense = value
End Set
End Property
The constructor only has one required parameter and that is the license
. The other parameters can be included but using one of the overloaded constructors
Public Sub New(license As LicenseType)
Me.New(license, String.Empty, String.Empty, String.Empty)
End Sub
Public Sub New(license As LicenseType, author As String)
Me.New(license, author, String.Empty, String.Empty)
End Sub
Public Sub New(license As LicenseType, author As String, originalProjectName As String)
Me.New(license, author, originalProjectName, String.Empty)
End Sub
Public Sub New(license As LicenseType, author As String, originalProjectName As String, dateAttributed As String)
Me.New(license, author, originalProjectName, dateAttributed, String.Empty)
End Sub
Public Sub New(license As LicenseType, Optional author As String = "", Optional originalProjectName As String = "", Optional dateAttributed As String = "", Optional codeUrl As String = "")
Me._codeLicense = Licenses.GetLicense(license)
Me._author = author
Me._originalProjectName = originalProjectName
Me._codeUrl = codeUrl
End Sub
Now with all that in place we can actually use the attribute. Add a console applicate to the solution. In the default module (Module1
) apply the new attribute:
<OpenSourced(LicenseType.Apache_2_0, "Dominick", "http://www.google.com", "8/8/2014", "Open Sourced License Attribute")>
Module Module1
End Module
To get the information from the attribute we first must get a reference to the attribute. This is done by first defining a new OpenSourcedAttribute
and using the static GetCustomAttribute
method of the System.Attribute
class
Dim osAttrib As OpenSourcedAttribute
osAttrib = CType(Attribute.GetCustomAttribute(GetType(Module1), GetType(OpenSourcedAttribute)), OpenSourcedAttribute)
Now the you can get all the information from the attribute by using the osAttrib
object:
With osAttrib
Dim author As String = .Author
Dim dateAttributed As String = .DateAttributed
Dim projectName As String = .OriginalProjectName
Dim codeUrl As String = .CodeUrl
Dim codeLink As Uri = .CodeLink
End With
Dim lic As Licenses = osAttrib.CodeLicense
With lic
Dim fullName As String = .FullName
Dim abbrev As String = .Abbreviation
Dim summaryUrl As String = .LicenseSummaryUrl
Dim summaryLink As Uri = .LicenseSummaryLink
Dim fullUrl As String = .FullLicenseUrl
Dim fullLink As Uri = .FullLicenseLink
Dim i As Integer = 0
End With
With this custom attribute I can now easily record open source snippets of code (or classes) that I use in my projects and the code is directly attributed. By using these tags in all my assemblies I can even get the license information of a method in assembly A from assembly B; for me it is also much neater.
I hope others find and get as must use out of this custom attribute as I do.
Points of Interest
To read more about creating and using custom attributes please visit http://msdn.microsoft.com/en-us/library/5x6cd29c(v=vs.110).aspx
History
1.0 - Initial post
1.1 - Minor formatting corrections