package org.jboss.windup.rules.files.condition;
import java.io.FileReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.jboss.forge.furnace.util.Assert;
import org.jboss.windup.config.GraphRewrite;
import org.jboss.windup.config.Variables;
import org.jboss.windup.config.condition.EvaluationStrategy;
import org.jboss.windup.config.condition.NoopEvaluationStrategy;
import org.jboss.windup.config.parameters.FrameContext;
import org.jboss.windup.config.parameters.FrameCreationContext;
import org.jboss.windup.config.parameters.ParameterizedGraphCondition;
import org.jboss.windup.graph.model.FileLocationModel;
import org.jboss.windup.graph.model.FileReferenceModel;
import org.jboss.windup.graph.model.WindupVertexFrame;
import org.jboss.windup.graph.model.resource.FileModel;
import org.jboss.windup.graph.service.FileService;
import org.jboss.windup.graph.service.GraphService;
import org.jboss.windup.rules.files.condition.regex.StreamRegexMatchListener;
import org.jboss.windup.rules.files.condition.regex.StreamRegexMatchedEvent;
import org.jboss.windup.rules.files.condition.regex.StreamRegexMatcher;
import org.jboss.windup.util.Logging;
import org.ocpsoft.rewrite.config.ConditionBuilder;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.ocpsoft.rewrite.param.DefaultParameterStore;
import org.ocpsoft.rewrite.param.ParameterStore;
import org.ocpsoft.rewrite.param.ParameterizedPatternResult;
import org.ocpsoft.rewrite.param.RegexParameterizedPatternParser;
import org.ocpsoft.rewrite.util.Maps;
import com.github.rwitzel.streamflyer.core.Modifier;
import com.github.rwitzel.streamflyer.core.ModifyingReader;
/**
* Matches on file contents based upon parameterization.
* <p/>
* Example:
* <p/>
* <pre>
* {@link FileContent}.matches("Some example {text}").inFilesNamed("{filename}")
* </pre>
*
* @author <a href="mailto:jesse.sightler@gmail.com">Jesse Sightler</a>
*/
public class FileContent extends ParameterizedGraphCondition implements FileContentMatches, FileContentFileName
{
private static Logger LOG = Logging.get(FileContent.class);
private RegexParameterizedPatternParser contentPattern;
private RegexParameterizedPatternParser filenamePattern;
public FileContent()
{
}
public FileContent(String contentPattern)
{
this.contentPattern = new RegexParameterizedPatternParser(contentPattern);
}
public static FileContentFrom from(String from)
{
FileContentFromImpl f = new FileContentFromImpl(from);
return f;
}
/**
* Match file contents against the provided parameterized string.
*/
public static FileContentMatches matches(String contentPattern)
{
return new FileContent(contentPattern);
}
/**
* Optionally specify the variable name to use for the output of this condition
*/
public ConditionBuilder as(String variable)
{
Assert.notNull(variable, "Variable name must not be null.");
this.setOutputVariablesName(variable);
return this;
}
/**
* Match filenames against the provided parameterized string.
*/
public FileContentFileName inFileNamed(String filenamePattern)
{
if (filenamePattern != null && !filenamePattern.isEmpty())
{
this.filenamePattern = new RegexParameterizedPatternParser(filenamePattern);
}
return this;
}
@Override
public Set<String> getRequiredParameterNames()
{
Set<String> result = new HashSet<>();
if (contentPattern != null)
{
result.addAll(contentPattern.getRequiredParameterNames());
}
if (filenamePattern != null)
result.addAll(filenamePattern.getRequiredParameterNames());
return result;
}
@Override
public void setParameterStore(ParameterStore store)
{
if (contentPattern != null)
contentPattern.setParameterStore(store);
if (filenamePattern != null)
filenamePattern.setParameterStore(store);
}
@Override
protected String getVarname()
{
return getOutputVariablesName();
}
@Override
@SuppressWarnings("unchecked")
protected boolean evaluateAndPopulateValueStores(GraphRewrite event, EvaluationContext context, final FrameCreationContext frameCreationContext)
{
return evaluate(event, context, new EvaluationStrategy()
{
private LinkedHashMap<String, List<WindupVertexFrame>> variables;
@Override
@SuppressWarnings("rawtypes")
public void modelMatched()
{
this.variables = new LinkedHashMap<>();
frameCreationContext.beginNew((Map) variables);
}
@Override
public void modelSubmitted(WindupVertexFrame model)
{
Maps.addListValue(this.variables, getVarname(), model);
}
@Override
public void modelSubmissionRejected()
{
frameCreationContext.rollback();
}
});
}
@Override
protected boolean evaluateWithValueStore(GraphRewrite event, EvaluationContext context, final FrameContext frameContext)
{
boolean result = evaluate(event, context, new NoopEvaluationStrategy());
if (result == false)
frameContext.reject();
return result;
}
private boolean evaluate(final GraphRewrite event, final EvaluationContext context,
final EvaluationStrategy evaluationStrategy)
{
final ParameterStore store = DefaultParameterStore.getInstance(context);
final GraphService<FileLocationModel> fileLocationService = new GraphService<>(event.getGraphContext(), FileLocationModel.class);
// initialize the input
List<FileModel> fileModels = new ArrayList<>();
fromInput(fileModels, event);
fileNameInput(fileModels, event, store);
allInput(fileModels, event, store);
final List<FileLocationModel> results = new ArrayList<>();
for (final FileModel fileModel : fileModels)
{
if (fileModel.isDirectory())
continue;
ParameterizedPatternResult parsedFileNamePattern = null;
if (filenamePattern != null)
{
parsedFileNamePattern = filenamePattern.parse(fileModel.getFileName());
if (!parsedFileNamePattern.matches())
{
continue;
}
}
//because it is used in the internal method definition
final ParameterizedPatternResult parsedFileNamePattern2 = parsedFileNamePattern;
try
{
Reader reader = new FileReader(fileModel.asFile());
Pattern fileContentsRegex = contentPattern.getCompiledPattern(store);
StreamRegexMatchListener matchListener = new StreamRegexMatchListener()
{
@Override
public void regexMatched(StreamRegexMatchedEvent matchEvent)
{
String matchedStr = matchEvent.getMatch();
boolean passed = true;
ParameterizedPatternResult contentPatternResult = null;
if (contentPattern != null)
{
contentPatternResult = contentPattern.parse(matchedStr);
passed = passed && contentPatternResult.matches();
}
if (passed)
{
evaluationStrategy.modelMatched();
if (parsedFileNamePattern2 == null || (parsedFileNamePattern2.submit(event, context)
&& (contentPattern == null || contentPatternResult.submit(event, context))))
{
FileLocationModel fileLocationModel = fileLocationService.create();
fileLocationModel.setFile(fileModel);
fileLocationModel.setColumnNumber((int) matchEvent.getColumnNumber());
// increment by one, as the source is 0-based, but the model is 1-based
int lineNumber = (int) (matchEvent.getLineNumber() + 1);
fileLocationModel.setLineNumber(lineNumber);
fileLocationModel.setLength(matchedStr.length());
fileLocationModel.setSourceSnippit(matchedStr);
results.add(fileLocationModel);
evaluationStrategy.modelSubmitted(fileLocationModel);
}
else
{
evaluationStrategy.modelSubmissionRejected();
}
}
}
};
Modifier regexModifier = StreamRegexMatcher.create(fileContentsRegex.pattern(), matchListener);
try (ModifyingReader modifyingReader = new ModifyingReader(reader, regexModifier))
{
char[] buffer = new char[32768];
while (modifyingReader.read(buffer) != -1)
; // consume the stream
}
}
catch (Exception e)
{
LOG.log(Level.WARNING, "Error loading and matching contents for file: " + fileModel.getFilePath() + " due to: " + e.getMessage(),
e);
}
}
setResults(event, getVarname(), results);
return !results.isEmpty();
}
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append(this.getClass().getSimpleName());
builder.append(".from(").append(getInputVariablesName()).append(")");
builder.append(".matches(").append(contentPattern).append(")");
builder.append(".inFilesNamed(").append(filenamePattern).append(")");
builder.append(".as(").append(getVarname()).append(")");
return builder.toString();
}
/**
* Generating the input vertices is quite complex. Therefore there are multiple methods that handles the input vertices based on the attribute
* specified in specific order. This method handles the {@link FileContent#from(String)} attribute.
*/
private void fromInput(List<FileModel> vertices, GraphRewrite event)
{
if (vertices.isEmpty() && StringUtils.isNotBlank(getInputVariablesName()))
{
for (WindupVertexFrame windupVertexFrame : Variables.instance(event).findVariable(getInputVariablesName()))
{
if (windupVertexFrame instanceof FileModel)
vertices.add((FileModel) windupVertexFrame);
if (windupVertexFrame instanceof FileReferenceModel)
vertices.add(((FileReferenceModel) windupVertexFrame).getFile());
}
}
}
/**
* Generating the input vertices is quite complex. Therefore there are multiple methods that handles the input vertices based on the attribute
* specified in specific order. This method handles the {@link FileContent#inFileNamed(String)} attribute.
*/
private void fileNameInput(List<FileModel> vertices, GraphRewrite event, ParameterStore store)
{
if (this.filenamePattern != null)
{
Pattern filenameRegex = filenamePattern.getCompiledPattern(store);
//in case the filename is the first operation generating result, we can use the graph regex index. That's why we distinguish that in this clause
if (vertices.isEmpty() && StringUtils.isBlank(getInputVariablesName()))
{
FileService fileService = new FileService(event.getGraphContext());
for (FileModel fileModel : fileService.findByFilenameRegex(filenameRegex.pattern()))
{
vertices.add(fileModel);
}
}
else
{
for (FileModel vertex : vertices)
{
if (!filenameRegex.matcher(vertex.getFileName()).matches())
{
vertices.remove(vertex);
}
}
}
}
}
/**
* Generating the input vertices is quite complex. Therefore there are multiple methods that handles the input vertices
* based on the attribute specified in specific order. This method generates all the vertices if there is no other way how to handle
* the input.
*/
private void allInput(List<FileModel> vertices, GraphRewrite event, ParameterStore store)
{
if (StringUtils.isBlank(getInputVariablesName()) && this.filenamePattern == null)
{
FileService fileModelService = new FileService(event.getGraphContext());
for (FileModel fileModel : fileModelService.findAll())
{
vertices.add(fileModel);
}
}
}
}