package org.jboss.windup.rules.files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.jboss.forge.furnace.util.Assert;
import org.jboss.windup.config.GraphRewrite;
import org.jboss.windup.config.GraphRule;
import org.jboss.windup.config.PreRulesetEvaluation;
import org.jboss.windup.config.phase.ArchiveMetadataExtractionPhase;
import org.jboss.windup.graph.model.WindupVertexFrame;
import org.jboss.windup.graph.model.resource.FileModel;
import org.jboss.windup.graph.model.resource.FileModel.OnParseError;
import org.jboss.windup.graph.service.FileService;
import org.jboss.windup.graph.service.GraphService;
import org.jboss.windup.util.Logging;
import org.ocpsoft.rewrite.context.EvaluationContext;
/**
* Maps file extensions to {@link WindupVertexFrame} types. Mappings are always applied during the
* {@link ArchiveMetadataExtractionPhase} phase, no matter where this rule appears in the pipeline. The following example
* demonstrates how to match files ending with *.xml to a frame type:
*
* <pre>
* {@link FileMapping}.from(".*\\.xml").to({@link XmlFileModel}.class)
* </pre>
*
* @author <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a>
*
*/
public class FileMapping extends GraphRule implements PreRulesetEvaluation, FileMappingFrom, FileMappingTo, FileMappingWithID
{
private static final Logger LOG = Logging.get(FileMapping.class);
private final Pattern pattern;
private final List<Class<? extends WindupVertexFrame>> types = new ArrayList<>();
private OnParseError onParseError = OnParseError.WARN;
private String id;
private FileMapping(Pattern pattern)
{
this.pattern = pattern;
String normalizedPattern = StringUtils.replacePattern(pattern.pattern(), "\\s", "_");
normalizedPattern = StringUtils.substring(normalizedPattern, 0, 10);
this.id = this.getClass().getSimpleName()+ "_" + normalizedPattern
+ "_" + RandomStringUtils.randomAlphanumeric(2);
}
@Override
public FileMappingWithID withId(String ruleID)
{
this.id = ruleID;
return this;
}
/**
* Create a new {@link FileMapping}
*/
public static FileMappingFrom from(String regex)
{
return new FileMapping(Pattern.compile(regex));
}
@Override
public FileMappingTo to(Class<? extends WindupVertexFrame> type)
{
Assert.notNull(type, "Model type must not be null.");
this.types.add(type);
return this;
}
@Override
@SuppressWarnings("unchecked")
public FileMappingTo to(Class<? extends WindupVertexFrame>... types)
{
Assert.notNull(types, "Model type list must not be null.");
this.types.addAll(Arrays.asList(types));
return this;
}
@Override
public FileMappingTo onParseError(OnParseError onParseError)
{
this.onParseError = onParseError;
return this;
}
@Override
public void preRulesetEvaluation(GraphRewrite event)
{
LOG.info("Added " + toString());
for (Class<? extends WindupVertexFrame> type : types)
{
addMapping(event, pattern.pattern(), type);
}
/*
* Handle mapping any files that were directly inserted in the graph before this executed.
*/
Map<String, List<Class<? extends WindupVertexFrame>>> mappings = FileMapping
.getMappings(event);
FileService fileService = new FileService(event.getGraphContext());
for (Entry<String, List<Class<? extends WindupVertexFrame>>> entry : mappings.entrySet())
{
String pattern = entry.getKey();
List<Class<? extends WindupVertexFrame>> types = entry.getValue();
Iterable<FileModel> models = fileService.findAllByPropertyMatchingRegex(FileModel.FILE_PATH, pattern);
for (FileModel model : models)
{
if (model.isDirectory())
continue;
for (Class<? extends WindupVertexFrame> type : types)
GraphService.addTypeToModel(event.getGraphContext(), model, type);
if (this.onParseError == OnParseError.IGNORE)
model.setOnParseError(OnParseError.IGNORE);
LOG.info("Mapped file [" + model.getFilePath() + "] matching pattern [" + pattern + "] to the following [" + types.size()
+ "] types: " + types);
}
}
}
/**
* Get the list of pattern to {@link WindupVertexFrame} type mappings for the {@link GraphRewrite} event.
*/
@SuppressWarnings("unchecked")
public static Map<String, List<Class<? extends WindupVertexFrame>>> getMappings(GraphRewrite event)
{
Map<String, List<Class<? extends WindupVertexFrame>>> map = (Map<String, List<Class<? extends WindupVertexFrame>>>) event
.getRewriteContext().get(FileMapping.class);
if (map == null)
{
map = new HashMap<>();
event.getRewriteContext().put(FileMapping.class, map);
}
return map;
}
/**
* Get the list of {@link WindupVertexFrame} type mappings for the given pattern and {@link GraphRewrite} event.
*/
public static List<Class<? extends WindupVertexFrame>> getMappings(GraphRewrite event, String pattern)
{
Map<String, List<Class<? extends WindupVertexFrame>>> mappings = getMappings(event);
List<Class<? extends WindupVertexFrame>> result = mappings.get(pattern);
if (result == null)
{
result = new ArrayList<>();
mappings.put(pattern, result);
}
return result;
}
/**
* Add a {@link WindupVertexFrame} type to the list of mappings for the given pattern and {@link GraphRewrite}
* event.
*/
public static void addMapping(GraphRewrite event, String pattern, Class<? extends WindupVertexFrame> type)
{
getMappings(event, pattern).add(type);
}
@Override
public boolean evaluate(GraphRewrite event, EvaluationContext context)
{
return true;
}
@Override
public void perform(GraphRewrite event, EvaluationContext context)
{
// No-op
}
@Override
public String getId()
{
return this.id;
}
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append(getClass().getName());
builder.append(".from(").append(pattern).append(")");
builder.append(".to(").append(types).append(")");
return builder.toString();
}
}