package org.jboss.windup.config.tags;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
import javax.inject.Singleton;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.commons.lang3.StringEscapeUtils;
import org.xml.sax.SAXException;
/**
* Manages the relations between Windup tags and provides API to query these relations.
*
* @author Ondrej Zizka, ozizka at redhat.com
*/
@Singleton
public class TagService
{
private final ConcurrentMap<String, Tag> definedTags = new ConcurrentHashMap<>();
/**
* Read the tag structure from the provided stream.
*/
public void readTags(InputStream tagsXML)
{
SAXParserFactory factory = SAXParserFactory.newInstance();
try
{
SAXParser saxParser = factory.newSAXParser();
saxParser.parse(tagsXML, new TagsSaxHandler(this));
}
catch (ParserConfigurationException | SAXException | IOException ex)
{
throw new RuntimeException("Failed parsing the tags definition: " + ex.getMessage(), ex);
}
}
/**
* Gets all tags that are "root" tags.
*/
public List<Tag> getRootTags() {
return this.definedTags.values().stream()
.filter(Tag::isRoot)
.collect(Collectors.toList());
}
/**
* Returns the {@link Tag} with the provided name.
*/
public Tag getTag(String tagName)
{
return definedTags.get(tagName);
}
/**
* Gets the {@link Tag} with the given name or creates a new {@link Tag} if one does not already exist.
*/
public Tag getOrCreateTag(String tagName)
{
synchronized (this.definedTags)
{
if (definedTags.containsKey(tagName))
return definedTags.get(tagName);
else
{
final Tag tag = new Tag(tagName);
definedTags.put(tagName, tag);
return tag;
}
}
}
/**
* Convenience method, calls this.isUnderTag(Tag superTag, Tag subTag).
*/
public boolean isUnderTag(String superTagName, String subTagName)
{
if (superTagName == null || subTagName == null)
return false;
if (superTagName.equals(subTagName))
return false;
final Tag superTag = this.getTag(superTagName);
final Tag subTag = this.getTag(subTagName);
return this.isUnderTag(superTag, subTag);
}
/**
* @return true if the subTag is contained directly or indirectly in the superTag.
*/
public boolean isUnderTag(Tag superTag, Tag subTag)
{
if (superTag == null || subTag == null)
return false;
if (superTag.getName().equals(subTag.getName()))
return false;
Set<Tag> walkedSet = new LinkedHashSet<>();
Set<Tag> currentSet = new LinkedHashSet<>();
currentSet.add(subTag);
do
{
walkedSet.addAll(currentSet);
Set<Tag> nextSet = new LinkedHashSet<>();
for (Tag currentTag : currentSet)
{
Set<Tag> parentTags = currentTag.getParentTags();
if (parentTags.contains(superTag))
return true;
nextSet.addAll(currentTag.getParentTags());
}
// Prevent infinite loops - detect graph cycles.
Iterator<Tag> it = walkedSet.iterator();
while (it.hasNext())
{
Tag walkedTag = it.next();
if (nextSet.contains(walkedTag))
nextSet.remove(walkedTag);
}
currentSet = nextSet;
}
while (!currentSet.isEmpty());
return false;
}
/**
* Writes the JavaScript code describing the tags as Tag classes to given writer.
*/
public void writeTagsToJavaScript(Writer writer) throws IOException
{
writer.append("function fillTagService(tagService) {\n");
writer.append("\t// (name, isRoot, isPseudo, color), [parent tags]\n");
for (Tag tag : definedTags.values())
{
writer.append("\ttagService.registerTag(new Tag(");
escapeOrNull(tag.getName(), writer);
writer.append(", ");
escapeOrNull(tag.getTitle(), writer);
writer.append(", ").append("" + tag.isRoot())
.append(", ").append("" + tag.isPseudo())
.append(", ");
escapeOrNull(tag.getColor(), writer);
writer.append(")").append(", [");
// We only have strings, not references, so we're letting registerTag() getOrCreate() the tag.
for (Tag parentTag : tag.getParentTags())
{
writer.append("'").append(StringEscapeUtils.escapeEcmaScript(parentTag.getName())).append("',");
}
writer.append("]);\n");
}
writer.append("}\n");
}
private void escapeOrNull(final String string, Writer writer) throws IOException
{
if (string == null)
writer.append("null");
else
writer.append('"').append(StringEscapeUtils.escapeEcmaScript(string)).append('"');
}
@Override
public String toString()
{
return "TagService{ definedTags: " + definedTags.size() + '}';
}
}