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() + '}'; } }