blob: 38987db55e0f80043f3ddd1a4c79774e5ed42087 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 23.08.2011 Aaron Digulla.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Aaron Digulla - initial API and implementation and/or initial documentation
*******************************************************************************/
package m4e
import groovy.xml.MarkupBuilder;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class AnalyzeCmd extends AbstractCommand {
static final String DESCRIPTION = '''\
repository [ ignore ]
- Check a converted Maven 2 repository for various problems
'''
void doRun( String... args ) {
if( args.size() <= 1 ) {
throw new UserError( 'Missing path to repository to analyze' )
}
File repo = new File( args[1] ).absoluteFile
if( !repo.exists() ) {
throw new UserError( "Directory ${repo} doesn't exist" )
}
File ignoreList = null
if( args.size() >= 3 ) {
ignoreList = new File( args[2] ).absoluteFile
if( !ignoreList.exists() ) {
throw new UserError( "File with ignore options ${ignoreList} doesn't exist" )
}
}
def tool = new Analyzer( repo, Calendar.getInstance() )
if( ignoreList ) {
tool.loadIgnores( ignoreList )
}
tool.run()
}
}
class Analyzer implements CommonConstants {
static final Logger log = LoggerFactory.getLogger( Analyzer )
File repo
File reportFile
Calendar timestamp
Set<Glob> ignores = new HashSet()
Set<Glob> ignoreMissingSources = new HashSet()
Analyzer( File repo, Calendar timestamp ) {
this.repo = repo.canonicalFile
this.timestamp = timestamp
SimpleDateFormat formatter = new SimpleDateFormat( 'yyyyMMdd-HHmmss' )
formatter.setTimeZone( timestamp.getTimeZone() )
String now = formatter.format( timestamp.getTime() )
reportFile = new File( repo.absolutePath + "-analysis-${now}.html" )
}
void loadIgnores( File file ) {
String manyRegexp = '[^ :]*'
file.eachLine {
String line = it.substringBefore( '#' ).trim()
if( !line ) {
return
}
line = line.replaceAll( '\\s+', ' ' )
if( line.startsWith( 'MissingSources ' ) ) {
line = line.substringAfter( ' ' )
ignoreMissingSources << new Glob( line, manyRegexp )
} else {
ignores << new Glob( line, manyRegexp )
}
}
}
void run() {
log.info( 'Analyzing {}...', repo )
MavenRepositoryTools.eachPom( repo ) {
analyzePom( it )
}
loadXmlLogs()
sortEverything()
if( missingSource ) {
def l = missingSource.findResults {
def key = it.key()
for( Glob g : ignoreMissingSources ) {
if( g.matches( key ) ) {
return null
}
}
return it
}
if( l ) {
problems << new MissingSources( l )
}
}
log.info( 'Found {} POM files. Looking for problems...', poms.size() )
validate()
log.info( 'Found {} problems. Generating report...', problemCount )
report()
}
void loadXmlLogs() {
File folder = new File( repo, MT4E_FOLDER + '/logs' )
loadXmlLog( folder )
}
void loadXmlLog( File file ) {
if( file.isDirectory() ) {
file.eachFile {
loadXmlLog( it )
}
return
}
try {
log.debug( 'Loading {}', file.absolutePath )
def root = new XmlSlurper().parse( file )
String path = null
String command = null
for( def node : root.depthFirst() ) {
String name = node.name()
switch( name ) {
case 'source':
path = node.'@file'
break
case 'mt4e-log':
command = node.'@command'
break
case 'warning':
case 'error':
xmlToProblem( command, path, node )
break
case 'merged':
break
default: log.warn( "Unexpected node '${name}'" )
}
}
} catch( Exception e ) {
throw new RuntimeException( "Error loading XML from ${file.absolutePath}", e )
}
}
void xmlToProblem( String command, String path, node ) {
String code = node.'@code'
def e
if( 'W' == code[0] ) {
e = Warning.fromCode( code )
} else if( 'E' == code[0] ) {
e = Error.fromCode( code )
} else {
throw new RuntimeException( "Unsupported code ${code}" )
}
def problem
switch( e ) {
case Error.TWO_VERSIONS:
problem = TwoVersionsProblem.create( node )
break
case Error.MISSING_MANIFEST:
problem = MissingManifest.create( node )
break
case Error.IMPORT_ERROR:
problem = ImportError.create( node )
break
case Warning.MULTIPLE_NESTED_JARS:
problem = MultipleNestedJarsProblem.create( node )
break
default:
log.warn( "Unsupported code ${code} ${e}" )
return
}
problem.command = command
problem.logFile = path
problem.code = code
if( ! problem.message ) {
problem.message = node.text()
}
problems << problem
}
void report() {
// textReport()
htmlReport()
}
void textReport() {
println "Found ${poms.size()} POM files" // text report
println "Found ${problems.size()} problems" // text report
for( def p in problems ) {
println p // text report
}
}
void htmlReport() {
log.info( 'Writing HTML report to {}', reportFile )
reportFile.withWriter('utf-8') { writer ->
htmlReport( writer )
}
}
void htmlReport( Writer writer ) {
MarkupBuilder builder = new MarkupBuilder( writer )
SimpleDateFormat formatter = new SimpleDateFormat( 'yyyy.MM.dd HH:mm:ss' )
formatter.setTimeZone( timestamp.getTimeZone() )
String now = formatter.format( timestamp.getTime() )
String titleText = "Analysis of ${repo} (${now})"
builder.html {
head {
title titleText
style( type: 'text/css', '''
html, body { background: white; }
.pom { font-weight: bold; color: #7F0055; font-family: monospace; }
.dependency { font-weight: bold; color: #55007F; font-family: monospace; }
.version { font-weight: bold; color: #007F55; font-family: monospace; }
.file { font-weight: bold; color: #00557F; font-family: monospace; }
.files { font-style: italic; }
.padLeft { padding-left: 10px; }
tr:hover { background-color: #D0E0FF; }
.hidden { color: white; }
.error { font-weight: bold; color: red; }
.problem { border-left: 3px solid white; border-bottom: 1px solid #ccc; padding-left: 3px; }
.problem:hover { border-left-color: #cccccc; }
.ignoreKey { color: #ccc; }
'''
)
}
body {
h1 titleText
p "Found ${poms.size()} POM files"
p "Found ${problems.size()} problems"
renderProblemsAsHtml( builder )
renderRepoAsHtml( builder )
// Add some empty space below the page to make sure anchors can always scroll to the top
div( style: 'height: 20em;' ) {
yield( '&nbsp;', false )
}
}
}
}
Map<String, String> renderToc( MarkupBuilder builder, List<ProblemType> keys, Map<ProblemType, List<Problem>> map ) {
Map<ProblemType, String> problemTitle2Anchor = [:]
builder.h2 'Table of Contents'
int index = 1
builder.ul( 'class': 'toc' ) {
for( def key in keys ) {
String anchor = "toc${index}"
index ++
problemTitle2Anchor[key] = anchor
int count = 0
map[key].each { count += it.problemCount() }
li {
a( href: "#${anchor}", "${key.title} (${count})" )
}
}
li {
a( href: "#poms", "${poms.size()} POMs in the repository" )
}
}
return problemTitle2Anchor
}
void renderProblemsAsHtml( MarkupBuilder builder ) {
Map<ProblemType, List<Problem>> map = [:]
for( def p in problems ) {
ProblemType type = ProblemType.byClass( p.class )
def list = map.get( type, [] )
list << p
}
List<ProblemType> keys = new ArrayList( map.keySet() )
keys.sort()
def problemTitle2Anchor = renderToc( builder, keys, map )
for( def key in keys ) {
def list = map[key]
builder.h2( id: problemTitle2Anchor[key], key.title )
if( key.description ) {
builder.p key.description
}
int count = 0
list.each { count += it.problemCount() }
String s = count == 1 ? '' : 's'
builder.p "${count} time${s}"
for( def p in list ) {
p.render( builder )
}
}
}
void renderRepoAsHtml( MarkupBuilder builder ) {
builder.h2( id: 'poms', "${poms.size()} POMs in the repository" )
def pomShortKeys = new ArrayList( pomByShortKey.keySet() )
pomShortKeys.sort()
builder.table( border: '0', cellspacing: '0', cellpadding: '0' ) {
String currentLabel = ''
tr {
th 'Group ID'
th 'Artifact ID + Version'
th 'Files'
}
for( def shortKey in pomShortKeys ) {
String label = shortKey.substringBefore( ':' )
if( label == currentLabel ) {
label = ''
} else {
currentLabel = label
}
tr {
td {
builder.yield( label, true )
if( label ) {
builder.yield( '''<span class='hidden'>:</span>''', false )
}
}
def pom = pomByShortKey[shortKey]
def artifactId = pom.value( Pom.ARTIFACT_ID )
def version = pom.version()
td {
span( 'class': 'pom', artifactId )
builder.yield( '''<span class='hidden'>:</span>''', false )
span( 'class': 'version', version )
}
td( 'class': 'padLeft' ) {
def files = pom.files()
if( files ) {
span( 'class': 'files' ) {
builder.yield( ' ', true )
builder.yield( pom.files().join( ' ' ), true )
}
} else {
span( 'class': 'error', 'No files found; check problems above' )
}
}
}
}
}
}
void validate() {
checkDifferentVersions()
checkMissingDependencies()
postProcessProblemSameKeyDifferentVersion()
applyIgnores()
countProblems()
}
int problemCount = 0
void countProblems() {
problems.each { problemCount += it.problemCount() }
}
void applyIgnores() {
Set<Glob> unused = new HashSet( ignores )
problems = problems.findResults {
String key = it.key()
for( Glob g : ignores ) {
if( g.matches( key ) ) {
unused.remove( g )
return null
}
}
return it
}
if( unused ) {
log.warn( "Not all ignores were necessary:" )
unused.each {
log.warn( ' {}', it )
}
}
}
void postProcessProblemSameKeyDifferentVersion() {
for( def p in problems ) {
if( !(p instanceof ProblemSameKeyDifferentVersion ) ) {
continue
}
Set<Pom> set = new HashSet<Pom>( nullToEmpty( dependencyUsage[p.pom.shortKey()] ) )
set.addAll( ( nullToEmpty( dependencyUsage[p.other.shortKey()] ) ) )
List<Pom> list = new ArrayList( set )
list.sort() { it.key() }
p.usedIn = list
}
}
List nullToEmpty( def list ) {
return null == list ? [] : list
}
void checkDifferentVersions() {
for( def entry in versionBackRefsMap.entrySet() ) {
// println "${entry.key} -> ${entry.value.keySet()}"
if( entry.value.size() <= 1 ) {
continue
}
problems << new ProblemDifferentVersions( entry.key, entry.value )
}
}
void checkMissingDependencies() {
List<String> keys = new ArrayList( dependencyUsage.keySet() )
keys.sort()
for( def key in keys ) {
def pom = pomByShortKey[key]
if( !pom ) {
problems << new MissingDependency( key, dependencyUsage[key] )
}
}
}
/** List of all POMs in the repo */
List<Pom> poms = []
/** shortKey -> POM */
Map<String, Pom> pomByShortKey = [:]
/** shortKey or dependency -> list of POMs in which it is used */
Map<String, List<Pom>> dependencyUsage = [:]
/** All found problems */
List<Problem> problems = []
/** All versions of a dependency */
Map<String, Set<String>> versions = [:]
/** short key -> versions -> poms */
Map<String, Map<String, List<Pom>>> versionBackRefsMap = [:]
/** List of artifacts without source */
List<Pom> missingSource = []
void sortEverything() {
poms.sort() { it.key() }
for( def item in dependencyUsage ) {
item.value.sort() { it.key() }
}
problems.sort() { it.class.name + ':' + it.sortKey() }
for( def backRefs in versionBackRefsMap ) {
for( def backRef in backRefs.value ) {
backRef.value.sort() { it.key() }
}
}
missingSource.sort() { it.key() }
}
void analyzePom( File path ) {
def pom = Pom.load( path )
poms << pom
log.debug( 'Analyzing {} {}', path, pom.key() )
File pomPath = MavenRepositoryTools.buildPath( repo, pom.key(), 'pom' ).canonicalFile
if( path != pomPath ) {
problems << new PathProblem( pom, pomPath, path )
}
String shortKey = pom.shortKey()
Pom other = pomByShortKey[shortKey]
if( other ) {
def poms = [ pom, other ].sort { it.key() }
problems << new ProblemSameKeyDifferentVersion( poms[0], poms[1] )
}
String version = pom.version()
if( version && version.endsWith( '-SNAPSHOT' ) ) {
problems << new ProblemSnaphotVersion( pom )
}
pomByShortKey[shortKey] = pom
def files = pom.files()
if( files && !files.contains( 'sources' ) ) {
missingSource << pom
}
for( def d in pom.dependencies ) {
if( 'true' == d.value( Dependency.OPTIONAL ) ) {
continue
}
def depKey = d.shortKey()
def list = dependencyUsage.get( depKey, [] )
list << pom
version = d.value( Dependency.VERSION )
if( !version ) {
problems << new DependencyWithoutVersion( pom, d )
} else if( '[0,)' == version ) {
// Ignore
} else if( isVersionRange( version ) ) {
// This is no longer a problem because the version ranges are overwritten by dependency management
// problems << new ProblemVersionRange( pom, d )
} else if( version && version.endsWith( '-SNAPSHOT' ) ) {
problems << new ProblemSnaphotVersion( pom, d )
}
def set = versions.get( depKey, new HashSet<String>() )
set << version
def versionToPoms = versionBackRefsMap.get( depKey, [:] )
def backRefs = versionToPoms.get( version, [] )
backRefs << pom
}
}
boolean isVersionRange( String version ) {
return version && (
version.startsWith( '[' )
|| version.startsWith( '(' )
)
}
}
class Problem {
Pom pom
String message
Problem( Pom pom, String message ) {
this.pom = pom
this.message = message
}
@Override
public String toString() {
return "POM ${pom?.key()}: ${message}"
}
void render( MarkupBuilder builder ) {
builder.div( 'class': 'problem' ) {
yield( 'POM ', true )
span( 'class': 'pom', pom.key() )
yield( ' ', true )
span( 'class': 'message', message )
}
}
String key() {
return "${getClass().simpleName} ${pom?.key()}"
}
String sortKey() {
return pom.key()
}
int problemCount() {
return 1
}
}
class CommandProblem extends Problem {
String logFile
String command
String code
String key
CommandProblem() {
super( null, null )
}
@Override
public String toString() {
return message
}
void render( MarkupBuilder builder ) {
builder.div( 'class': 'problem' ) {
if( key ) {
div( 'class': 'ignoreKey', key() )
}
span( 'class': 'message', message )
}
}
String key() {
return "${getClass().simpleName} ${key}"
}
String sortKey() {
return key
}
}
class MultipleNestedJarsProblem extends CommandProblem {
String jar
String nestedJarPath
String relPath
static MultipleNestedJarsProblem create( node ) {
String jar = PathUtils.normalize( node.'@jar'.toString() )
String relPath = jar.substringAfterLast( '/m2repo/' )
String nestedJarPath = node.'@nestedJarPath'
String key = "${node.'@code'} ${relPath} ${nestedJarPath}"
String message = "Found multiple nested JARs in ${jar}"
def result = new MultipleNestedJarsProblem( jar: jar, nestedJarPath: nestedJarPath, key: key, relPath : relPath, message: message )
return result
}
}
class TwoVersionsProblem extends CommandProblem {
static TwoVersionsProblem create( node ) {
String shortKey = node.'@shortKey'
String version1 = node.'@version1'
String version2 = node.'@version2'
String key = "${node.'@code'} ${shortKey} ${version1} ${version2}"
String message = "The artifact ${shortKey} exists with several versions: ${version1} ${version2}"
def result = new TwoVersionsProblem( key: key, message: message )
return result
}
}
class MissingManifest extends CommandProblem {
String jar
String nestedJarPath
String relPath
static MissingManifest create( node ) {
String jar = PathUtils.normalize( node.'@jar'.toString() )
jar = jar.removeEnd( '/META-INF/MANIFEST.MF' )
String key = "${node.'@code'} ${jar.substringAfterLast( '/' )}"
String message = "Couldn't find MANIFEST.MF in ${jar}"
def result = new MissingManifest( jar: jar, key: key, message: message )
return result
}
}
class ImportError extends CommandProblem {
static ImportError create( node ) {
return new ImportError()
}
}
class ProblemVersionRange extends Problem {
Dependency dependency
ProblemVersionRange( Pom pom, Dependency dependency ) {
super( pom, "The dependency ${dependency.key()} in POM ${pom.key()} uses a version range" )
this.dependency = dependency
}
@Override
public String toString() {
return "POM ${pom.key()}: ${message}";
}
@Override
String key() {
return "${super.key()} ${dependency.key()}"
}
void render( MarkupBuilder builder ) {
builder.div( 'class': 'problem' ) {
yield( 'The dependency ', true )
span( 'class': 'dependency', dependency.key() )
yield( ' in POM ', true )
span( 'class': 'pom', pom.key() )
yield( ' uses a version range', true )
}
}
}
class MissingSources extends Problem {
List<Pom> poms
MissingSources( List<Pom> poms ) {
super( null, "${poms.size} artifacts are without sources" )
this.poms = poms
}
@Override
public String sortKey() {
return "MissingSources";
}
@Override
public String toString() {
StringBuilder buffer = new StringBuilder()
buffer << message
buffer << ':\n'
poms.each() {
buffer << " ${it.key()}\n"
}
return buffer;
}
void render( MarkupBuilder builder ) {
builder.div( 'class': 'problem' ) {
p "Missing sources for ${poms.size()} artifacts"
ul {
for( def pom in poms ) {
li {
span( 'class': 'ignoreKey', 'MissingSources ' )
span( 'class': 'pom', pom.key() )
}
}
}
}
}
int problemCount() {
return poms.size()
}
}
class PathProblem extends Problem {
File expected
File actual
PathProblem( Pom pom, File expected, File actual ) {
super( pom, "The path for the POM ${pom.key()} should [${expected}] but it is ${actual} " )
this.expected = expected
this.actual = actual
}
@Override
public String toString() {
return "POM ${pom.key()}: ${message}";
}
void render( MarkupBuilder builder ) {
builder.div( 'class': 'problem' ) {
yield( 'The path for the POM ', true )
span( 'class': 'pom', pom.key() )
yield( ' should be', true )
span( 'class': 'file', expected )
yield( ' but was ', true )
span( 'class': 'file', actual )
}
}
}
class ProblemSnaphotVersion extends Problem {
Dependency dependency
ProblemSnaphotVersion( Pom pom ) {
super( pom, "The POM ${pom.key()} is a snapshot version" )
}
ProblemSnaphotVersion( Pom pom, Dependency dependency ) {
super( pom, "The dependency ${dependency.key()} in POM ${pom.key()} uses a snapshot version" )
this.dependency = dependency
}
@Override
public String toString() {
return "${message}";
}
void render( MarkupBuilder builder ) {
if( dependency ) {
builder.div( 'class': 'problem', 'The dependency ' ) {
span( 'class': 'dependency', dependency.key() )
yield( ' in POM ', true )
span( 'class': 'pom', pom.key() )
yield( ' uses a snapshot version', true )
}
} else {
builder.div( 'class': 'problem', 'The POM ' ) {
span( 'class': 'pom', pom.key() )
yield( ' uses a snapshot version', true )
}
}
}
}
class ProblemSameKeyDifferentVersion extends Problem {
Pom other
List<Pom> usedIn = []
ProblemSameKeyDifferentVersion( Pom pom, Pom other ) {
super( pom, 'There is another POM with the same ID but a different version' )
this.other = other
}
@Override
String key() {
return "${super.key()} ${other.key()}"
}
@Override
public String toString() {
return "POM ${pom.key()}: ${message}: ${other.key()}"
}
void render( MarkupBuilder builder ) {
builder.div( 'class': 'problem' ) {
div( 'class': 'ignoreKey' ) {
yield( key(), true )
}
yield( 'There are two POMs with the same ID but different version:', true )
ul {
li {
span( 'class': 'pom', pom.key() )
}
li {
span( 'class': 'pom', other.key() )
}
}
if( usedIn ) {
yield( 'These POMs are used in:', true )
ul {
for( Pom pom in usedIn ) {
li {
span( 'class': 'pom', pom.key() )
}
}
}
}
}
}
}
class DependencyWithoutVersion extends Problem {
Dependency dependency
DependencyWithoutVersion( Pom pom, Dependency dependency ) {
super( pom, 'Missing version in dependency' )
this.dependency = dependency
}
@Override
String key() {
return "${super.key()} ${dependency.key()}"
}
@Override
public String toString() {
return "POM ${pom.key()}: ${message} ${dependency.key()}";
}
void render( MarkupBuilder builder ) {
builder.div( 'class': 'problem' ) {
yield( 'POM ', true )
span( 'class': 'pom', pom.key() )
yield( ' ', true )
span( 'class': 'message', message )
yield( ' ', true )
span( 'class': 'dependency', dependency.key() )
}
}
}
class ProblemDifferentVersions extends Problem {
Map<String, List<Pom>> versionBackRefs
String dependency
ProblemDifferentVersions( String dependency, Map<String, List<Pom>> versionBackRefs ) {
super( null, 'This dependency is referenced with different versions' )
this.dependency = dependency
this.versionBackRefs = versionBackRefs
}
@Override
String key() {
return "${super.key()} ${dependency}"
}
@Override
String sortKey() {
return dependency
}
@Override
public String toString() {
def versions = new ArrayList( versionBackRefs.keySet() )
Collections.sort( versions )
StringBuilder buffer = new StringBuilder()
buffer.append( "The dependency ${pom.key()} is referenced with ${versions.size()} different versions:\n" )
for( String version in versions ) {
buffer.append( " Version ${version} is used in:\n" )
def backRefs = versionBackRefs[version]
for( def pom in backRefs ) {
buffer.append( " ${pom.key()}" )
}
}
return buffer
}
void render( MarkupBuilder builder ) {
def versions = new ArrayList( versionBackRefs.keySet() )
Collections.sort( versions )
builder.div( 'class': 'problem' ) {
div( 'class': 'ignoreKey' ) {
yield( key(), true )
}
yield( 'The dependency ', true )
span( 'class': 'dependency', dependency )
yield( " is referenced with ${versions.size()} different versions:", true )
ul() {
for( String version in versions ) {
renderVersion( builder, version )
}
}
}
}
void renderVersion( MarkupBuilder builder, String version ) {
def backRefs = versionBackRefs[version]
backRefs.sort(true) {
it.key()
}
builder.li() {
yield( 'Version "', true )
span('class': 'version', version)
yield( '" is used in:', true )
ul() {
for( def pom in backRefs ) {
def parts = pom.key().split(':', -1)
li {
span('class': 'pom', "${parts[0]}:${parts[1]}")
yield( ':', true )
span('class': 'version', "${parts[2]}")
}
}
}
}
}
}
class MissingDependency extends Problem {
String key
List<Pom> poms
MissingDependency( String key, List<Pom> poms ) {
super( poms[0], 'Missing dependencies' )
this.key = key
this.poms = poms
}
@Override
String sortKey() {
return key
}
@Override
String key() {
return "${getClass().simpleName} ${key}"
}
@Override
public String toString() {
StringBuilder buffer = new StringBuilder()
buffer << "The dependency ${key} is used in ${poms.size} POMs but I can't find it in this M2 repo:\n"
for( def pom in poms ) {
buffer << " ${pom.key()}"
}
return buffer
}
void render(MarkupBuilder builder) {
builder.div( 'class': 'problem' ) {
div( 'class': 'ignoreKey' ) {
yield( key(), true )
}
yield( 'The dependency ', true )
span( 'class':'dependency', key )
yield( " is used in ${poms.size} POMs:", true )
ul {
for( def pom in poms ) {
li {
span( 'class': 'pom', pom.key() )
}
}
}
}
}
}
enum ProblemType {
ImportError( 'Plug-ins Which Couldn\'t be Imported' ),
MissingManifest( 'Plug-ins Without Manifest' ),
Problem( 'Generic Problems', null),
ProblemSameKeyDifferentVersion( 'POMs with same ID but different version', null),
DependencyWithoutVersion( 'Problems With Dependencies', null),
ProblemDifferentVersions( 'Dependencies With Different Versions', null),
MissingDependency( 'Missing Dependencies', "The following dependencies are used in POMs in the repository but they couldn't be found in it." ),
ProblemVersionRange( 'Dependencies With Version Ranges', 'Dependencies should not use version ranges.' ),
ProblemSnaphotVersion( 'Snapshot Versions', 'Release Repositories should not contain SNAPSHOTs' ),
PathProblem( 'Path Problems', 'These POMs are not where they should be' ),
TwoVersionsProblem( 'Artifacts With Several Versions' ),
MultipleNestedJarsProblem( 'Multiple Nested JARs' ),
MissingSources( 'Missing Sources' )
final String title
final String description
private ProblemType( String title, String description = null ) {
this.title = title
this.description = description
}
public static ProblemType byClass( Class type ) {
ProblemType result = valueOf( type.simpleName )
if( null == result ) {
throw new RuntimeException( "Unknown type ${type.name}" )
}
return result
}
}