/*
* Copyright 2010 the original author or authors.
*
* 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 org.gradle.foundation.output;
import org.gradle.foundation.output.definitions.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This class holds on FileLinkDefinitions used for searching output.
*
* @author mhunsicker
*/
public class FileLinkDefinitionLord {
private List<String> extensions = new ArrayList<String>( );
//these are definitions where the file is between known tokens.
private Map<Pattern,FileLinkDefinition> complexFileLinkDefinitions = new LinkedHashMap<Pattern,FileLinkDefinition>();
//these are definitions where we only try to match based on the file extension.
private Map<Pattern,FileLinkDefinition> extensionFileLinkDefinitions = new LinkedHashMap<Pattern,FileLinkDefinition>();
private Pattern combinedSearchPattern; //search pattern consisting of all of our sub search patterns
public FileLinkDefinitionLord() {
//this is where we define what files we find.
//add all the file extension definitions
addFileExtension( ".java", ":" ); //the colon handles compile errors with line numbers
addFileExtension( ".groovy", ":" );
addFileExtension( ".gradle", ":" );
addFileExtension( ".xml", ":" ); //I don't think I've ever seen an xml or html file specified with a line number delimiter, but we'll try it anyway
addFileExtension( ".html", ":" );
addFileExtension( ".htm", ":" );
//now add the more complex ones
addPrefixedFileLink( "Ant Compiler Error", "[ant:javac]", ".java", ":" ); //handles java compiler errors
addPrefixedFileLink( "Compiler Warning", "Note:", ".java", null ); //handles java compiler warnings such as deprecated APIs
addCustomComplexFileLink( new OptionalLineNumberFileLinkDefinition( "Build File Errors", "Build file '",".gradle", "line:" ) ); //handles errors in a gradle build file
addPrefixedFileLink( "Ant Checkstyle Error/Warning", "[ant:checkstyle]", ".java", ":" );//handles checkstyle errors/warnings
addPrefixedFileLink( "Checkstyle Error (report xml)", "See the report at", ".xml", null ); //handles checkstyle errors. Links to the report xml file.
addPrefixedFileLink( "Codenarc Error", "See the report at", ".html", null ); //handles Codenarc errors. Links to the report file.
addCustomComplexFileLink( new TestReportFileLinkDefinition() );
}
/**
Call this to add file extensions to look for in the output. This assumes the file path
is the first thing on the line.
@param extension the file extension
@param lineNumberDelimiter optional delimiter text for line number. Whatever is after
this will be assumed to be a line number. We'll only parse the numbers after
this so there can be other stuff after the line number. Pass in null to ignore.
*/
public void addFileExtension( String extension, String lineNumberDelimiter )
{
if( !extension.startsWith( "." ) ) {
extension = "." + extension;
}
extension = extension.toLowerCase();
if( extensions.contains( extension ) ) //don't add extensions already added
{
return;
}
extensions.add( extension );
String name = extension + " Files";
ExtensionFileLinkDefinition linkDefinition = new ExtensionFileLinkDefinition(name, extension, lineNumberDelimiter);
addToMap(extensionFileLinkDefinitions, linkDefinition );
}
/**
Creates a file link definition to find file paths in the output that have a known prefix and extension.
It also allows for an optional line number after a delimiter. This is useful if you know a certain
message always precedes a file path.
@param name the name of this file link definition. Used by tests mostly.
@param prefix the text that is before the file path. It should be enough to make it fairly unique
@param extension the expected file extension. If we don't find this extension, we do not consider
the text a file's path. If there are multiple extensions, you'll have to add multiples of these.
@param lineNumberDelimiter optional delimiter text for line number. Whatever is after
this will be assumed to be a line number. We'll only parse the numbers after
this so there can be other stuff after the line number. Pass in null to ignore.
*/
public void addPrefixedFileLink( String name, String prefix, String extension, String lineNumberDelimiter )
{
PrefixedFileLinkDefinition linkDefinition = new PrefixedFileLinkDefinition(name, prefix, extension, lineNumberDelimiter);
addToMap( complexFileLinkDefinitions, linkDefinition );
}
private void addCustomComplexFileLink( FileLinkDefinition fileLinkDefinition )
{
addToMap( complexFileLinkDefinitions, fileLinkDefinition );
}
private void addToMap( Map<Pattern,FileLinkDefinition> destinationMap, FileLinkDefinition fileLinkDefinition )
{
//if you change anything, we'll destroy our combined search pattern. This will recreate it with the
//latest settings when its next asked for.
combinedSearchPattern = null;
String searchExpression = fileLinkDefinition.getSearchExpression();
Pattern pattern = Pattern.compile( searchExpression, getSearchPatternFlags() );
destinationMap.put( pattern, fileLinkDefinition);
}
/**
@return a list of known file extensions that are searched for in the output.
*/
public List<String> getFileExtensions() { return Collections.unmodifiableList( extensions ); }
/**
@return a list of our FileLinkDefinitions
*/
public List<FileLinkDefinition> getFileLinkDefinitions()
{
List<FileLinkDefinition> fileLinkDefinitions = new ArrayList<FileLinkDefinition>();
fileLinkDefinitions.addAll( complexFileLinkDefinitions.values() );
fileLinkDefinitions.addAll( extensionFileLinkDefinitions.values() );
return Collections.unmodifiableList( fileLinkDefinitions );
}
private int getSearchPatternFlags() { return Pattern.CASE_INSENSITIVE; }
/**
* This returns the FileLinkDefinition whose search pattern 'matches' (as in 'finds', not 'equals') the
* specified text. The tricky thing here is that multiple FileLinkDefinitions can match the text. To
* assist this we've done two things: we first try to match it with the complex patterns (the ones that
* try to match prefixed and suffixed text around a file's path), then if we don't find one, we'll match
* it with a simple extension FileLinkDefinitions. The other thing is that we search the definitions in
* order. This means the order in which the FileLinkDefinitions are added can be important. Add the more
* definitive ones first.
* @param text the text to use to find a match.
* @return a FileLinkDefinition that matches the text
*/
public FileLinkDefinition getMatchingFileLinkDefinition( String text )
{
FileLinkDefinition fileLinkDefinition = getMatchingFileLinkDefinition( text, complexFileLinkDefinitions );
if( fileLinkDefinition == null ) {
fileLinkDefinition = getMatchingFileLinkDefinition( text, extensionFileLinkDefinitions );
}
return fileLinkDefinition;
}
private static FileLinkDefinition getMatchingFileLinkDefinition( String text, Map<Pattern,FileLinkDefinition> map )
{
Iterator<Pattern> iterator = map.keySet().iterator();
while( iterator.hasNext() )
{
Pattern pattern = iterator.next();
Matcher matcher = pattern.matcher( text );
if( matcher.find( 0 ) )
{
return map.get( pattern );
}
}
return null;
}
public Pattern getSearchPattern()
{
if( combinedSearchPattern == null ) //only build it if we need to.
{
combinedSearchPattern = buildSearchPattern();
}
return combinedSearchPattern;
}
/**
This iterates through all the FileLinkDefinitions and builds one giant single RegEx search pattern. This is more
efficient than using multiple search patterns.
*/
private Pattern buildSearchPattern()
{
StringBuilder criteria = new StringBuilder();
Iterator<FileLinkDefinition> iterator = getFileLinkDefinitions().iterator();
while( iterator.hasNext() )
{
FileLinkDefinition fileLinkDefinition = iterator.next();
String searchExpression = fileLinkDefinition.getSearchExpression();
criteria.append( "(" ).append( searchExpression ).append( ")" );
if( iterator.hasNext() )
{
criteria.append( "|" );
}
}
return Pattern.compile( criteria.toString(), getSearchPatternFlags() );
}
}