public enum Relation {

    UNKNOWN,

    // Coordinations
    UNKNOWN_COORDINATION, // the default for coordination
    CONTRAST,
    CAUSE_C,
    RESULT_C,
    LIST,
    DISJUNCTION,
    TEMPORAL_AFTER_C,
    TEMPORAL_BEFORE_C,

    // Subordinations
    UNKNOWN_SUBORDINATION, // the default for subordination
    ATTRIBUTION,
    BACKGROUND,
    CAUSE,
    RESULT,
    CONDITION,
    ELABORATION,
    PURPOSE,
    TEMPORAL_AFTER,
    TEMPORAL_BEFORE,

    // for sentence simplification
    NOUN_BASED,
    SPATIAL,
    TEMPORAL,
    TEMPORAL_TIME, // indicating a particular instance on a time scale (e.g. “Next Sunday 2 pm”).
    TEMPORAL_DURATION, // the amount of time between the two end-points of a time interval (e.g. “2 weeks").
    TEMPORAL_DATE, // particular date (e.g. “On 7 April 2013”).
    TEMPORAL_SET, IDENTIFYING_DEFINITION, DESCRIBING_DEFINITION; // periodic temporal sets representing times that occur with some frequency (“Every Tuesday”).

    static {
        UNKNOWN_COORDINATION.coordination = true;
        CONTRAST.coordination = true;
        CAUSE_C.coordination = true;
        RESULT_C.coordination = true;
        LIST.coordination = true;
        DISJUNCTION.coordination = true;
        TEMPORAL_AFTER_C.coordination = true;
        TEMPORAL_BEFORE_C.coordination = true;

        CAUSE.coordinateVersion = CAUSE_C;
        RESULT.coordinateVersion = RESULT_C;
        TEMPORAL_AFTER.coordinateVersion = TEMPORAL_AFTER_C;
        TEMPORAL_BEFORE.coordinateVersion = TEMPORAL_BEFORE_C;

        CAUSE_C.subordinateVersion = CAUSE;
        RESULT_C.subordinateVersion = RESULT;
        TEMPORAL_AFTER_C.subordinateVersion = TEMPORAL_AFTER;
        TEMPORAL_BEFORE_C.subordinateVersion = TEMPORAL_BEFORE;

        CAUSE_C.inverse = RESULT_C;
        RESULT_C.inverse = CAUSE_C;
        TEMPORAL_AFTER_C.inverse = TEMPORAL_BEFORE_C;
        TEMPORAL_BEFORE_C.inverse = TEMPORAL_AFTER_C;
        CAUSE.inverse = RESULT;
        RESULT.inverse = CAUSE;
        TEMPORAL_AFTER.inverse = TEMPORAL_BEFORE;
        TEMPORAL_BEFORE.inverse = TEMPORAL_AFTER;
    }

    private boolean coordination;
    private Relation regular; // class of context span (in subordination) or right span (coordination)
    private Relation inverse; // class of core span (in subordination) or left span (coordination)
    private Relation coordinateVersion; // optional
    private Relation subordinateVersion; // optional

    Relation() {
        this.coordination = false;
        this.regular = this;
        this.inverse = this; // only used in coordinations
        this.coordinateVersion = null;
        this.subordinateVersion = null;
    }

    public boolean isCoordination() {
        return coordination;
    }

    public Relation getRegulatRelation() {
        return regular;
    }

    public Relation getInverseRelation() {
        return inverse;
    }

    public Optional<Relation> getCoordinateVersion() {
        return Optional.ofNullable(coordinateVersion);
    }

    public Optional<Relation> getSubordinateVersion() {
        return Optional.ofNullable(subordinateVersion);
    }
} public class Extraction {
    private String extractionRule;
    private boolean referring;
    private String cuePhrase; // optional
    private Relation relation;
    private boolean contextRight; // only for subordinate relations
    private List<Leaf> constituents;

    public Extraction(String extractionRule, boolean referring, List<Word> cuePhraseWords, Relation relation, boolean contextRight, List<Leaf> constituents) {
        if ((referring) && (constituents.size() != 1)) {
            throw new AssertionError("Referring relations should have one constituent");
        }

        if ((!referring) && (!relation.isCoordination()) && (constituents.size() != 2)) {
            throw new AssertionError("(Non-referring) subordinate relations rules should have two constituents");
        }

        this.extractionRule = extractionRule;
        this.referring = referring;
        this.cuePhrase = (cuePhraseWords == null)? null : WordsUtils.wordsToString(cuePhraseWords);
        this.relation = relation;
        this.contextRight = contextRight;
        this.constituents = constituents;
    }

    public Optional<DiscourseTree> generate(Leaf currChild) {

        if (relation.isCoordination()) {
            if (referring) {

                // find previous node to use as a reference
                Optional<DiscourseTree> prevNode = currChild.getPreviousNode();
                if ((prevNode.isPresent()) && (prevNode.get().usableAsReference())) {

                    // use prev node as a reference
                    prevNode.get().useAsReference();

                    Coordination res = new Coordination(
                        extractionRule,
                        relation,
                        cuePhrase,
                        Collections.emptyList()
                    );
                    res.addCoordination(prevNode.get()); // set prev node as a reference
                    res.addCoordination(constituents.get(0));

                    return Optional.of(res);
                }
            } else {
                return Optional.of(new Coordination(
                    extractionRule,
                    relation,
                    cuePhrase,
                    constituents.stream().collect(Collectors.toList())
                ));
            }
        } else {
            if (referring) {

                // find previous node to use as a reference
                Optional<DiscourseTree> prevNode = currChild.getPreviousNode();
                if ((prevNode.isPresent()) && (prevNode.get().usableAsReference())) {

                    // use prev node as a reference
                    prevNode.get().useAsReference();

                    Subordination res = new Subordination(
                        extractionRule,
                        relation,
                        cuePhrase,
                        new Leaf(), // tmp
                        constituents.get(0),
                        contextRight
                    );
                    res.replaceLeftConstituent(prevNode.get()); // set prev node as a reference

                    return Optional.of(res);
                }
            } else {
                return Optional.of(new Subordination(
                    extractionRule,
                    relation,
                    cuePhrase,
                    constituents.get(0),
                    constituents.get(1),
                    contextRight
                ));
            }
        }

        return Optional.empty();
    }
} public abstract class ExtractionRule {
    protected static final Logger LOGGER = LoggerFactory.getLogger(ExtractionRule.class);

    protected enum Tense {
        PRESENT,
        PAST
    }

    protected enum Number {
        SINGULAR,
        PLURAL
    }

    protected CuePhraseClassifier classifer;

    public ExtractionRule() {
    }

    public void setConfig(Config config) {
        this.classifer = new CuePhraseClassifier(config);
    }

    public abstract Optional<Extraction> extract(Leaf leaf) throws ParseTreeException;

    protected static List<Tree> getSiblings(Tree parseTree, List<String> tags) {
        return parseTree.getChildrenAsList().stream().filter(c -> tags.contains(c.value())).collect(Collectors.toList());
    }

    protected static Number getNumber(Tree np) {
        Number res = Number.SINGULAR;

        // find plural forms
        TregexPattern p = TregexPattern.compile("NNS|NNPS");
        TregexMatcher matcher = p.matcher(np);
        if (matcher.find()) {
            res = Number.PLURAL;
        }

        // find and
        p = TregexPattern.compile("CC <<: and");
        matcher = p.matcher(np);
        if (matcher.find()) {
            res = Number.PLURAL;
        }

        return res;
    }

    protected static Tense getTense(Tree vp) {
        Tense res = Tense.PRESENT;

        // find past tense
        TregexPattern p = TregexPattern.compile("VBD|VBN");
        TregexMatcher matcher = p.matcher(vp);

        if (matcher.find()) {
            res = Tense.PAST;
        }

        return res;
    }

    protected static Optional<Word> getHeadVerb(Tree vp) {
        TregexPattern pattern = TregexPattern.compile(vp.value() + " [ <+(VP) (VP=lowestvp !< VP < /V../=v) | ==(VP=lowestvp !< VP < /V../=v) ]");
        TregexMatcher matcher = pattern.matcher(vp);
        while (matcher.findAt(vp)) {
            return Optional.of(ParseTreeExtractionUtils.getContainingWords(matcher.getNode("v")).get(0));
        }
        return Optional.empty();
    }

    private static List<Word> appendWordsFromTree(List<Word> words, Tree tree) {
        List<Word> res = new ArrayList<Word>();
        res.addAll(words);

        TregexPattern p = TregexPattern.compile(tree.value() + " <<, NNP|NNPS");
        TregexMatcher matcher = p.matcher(tree);

        boolean isFirst = true;
        for (Word word : tree.yieldWords()) {
            if ((isFirst) && (!matcher.findAt(tree))) {
                res.add(WordsUtils.lowercaseWord(word));
            } else {
                res.add(word);
            }
            isFirst = false;
        }

        return res;
    }

    // pp is optional
    protected static List<Word> rephraseIntraSententialAttribution(List<Word> words) {
        try {
            List<Word> res = new ArrayList<>();

            Tree parseTree = ParseTreeParser.parse(WordsUtils.wordsToProperSentenceString(words));

            TregexPattern p = TregexPattern.compile("ROOT << (S !> S < (NP=np ?$,, PP=pp $.. VP=vp))");
            TregexMatcher matcher = p.matcher(parseTree);
            if (matcher.findAt(parseTree)) {
                Tree pp = matcher.getNode("pp"); // optional
                Tree np = matcher.getNode("np");
                Tree vp = matcher.getNode("vp");

                Tense tense = getTense(vp);
                if (tense.equals(Tense.PRESENT)) {
                    res.add(new Word("This"));
                    res.add(new Word("is"));
                    res.add(new Word("what"));
                } else {
                    res.add(new Word("This"));
                    res.add(new Word("was"));
                    res.add(new Word("what"));
                }
                res = appendWordsFromTree(res, np);
                res = appendWordsFromTree(res, vp);
                if (pp != null) {
                    res = appendWordsFromTree(res, pp);
                }
            }

            return res;
        } catch (ParseTreeException e) {
            return words;
        }
    }

    protected static List<Word> rephraseEnablement(Tree s, Tree vp) {
        List<Word> res = new ArrayList<>();

        Tense tense = getTense(vp);
        if (tense.equals(Tense.PRESENT)) {
            res.add(new Word("This"));
            res.add(new Word("is"));
        } else {
            res.add(new Word("This"));
            res.add(new Word("was"));
        }
        res = appendWordsFromTree(res, s);

        return res;
    }

    
    protected static String rephraseApposition(Tree vp, String np) {
        String res = "";

        Tense tense = getTense(vp);
        //Number number = getNumber(np);
        if (tense.equals(Tense.PRESENT)) {
        	if (np.equals("NN") || np.equals("NNP")) {
        		res = " is ";
        	} else {
        		res = " are ";
        	}
        } else {
        	if (np.equals("NN") || np.equals("NNP")) {
        		res = " was ";
        	} else {
        		res = " were ";
        	}
        }
        
        return res;
    }
    
    protected static List<Word> rephraseAppositionNonRes(Tree vp, Tree np, Tree np2) {
        List<Word> res = new ArrayList<>();

        Tense tense = getTense(vp);
        Number number = getNumber(np);
        if (tense.equals(Tense.PRESENT)) {
        	if (number.equals(Number.SINGULAR)) {
        		 res.add(new Word("is"));
        	} else {
        		 res.add(new Word("are"));
        	}
        } else {
        	if (number.equals(Number.SINGULAR)) {
        		 res.add(new Word("was"));
        	} else {
        		 res.add(new Word("were"));
        	}
        }
        res = appendWordsFromTree(res, np2);
        
        return res;
    }


    protected static List<Word> getRephrasedParticipalS(Tree np, Tree vp, Tree s, Tree vbgn) {
        Number number = getNumber(np);
        Tense tense = getTense(vp);

        TregexPattern p = TregexPattern.compile(vbgn.value() + " <<: (having . (been . VBN=vbn))");
        TregexPattern p2 = TregexPattern.compile(vbgn.value() + " <<: (having . VBN=vbn)");
        TregexPattern p3 = TregexPattern.compile(vbgn.value() + " <<: (being . VBN=vbn)");

        TregexMatcher matcher = p.matcher(s);
        if (matcher.findAt(s)) {
            List<Word> res = new ArrayList<>();

            res.add(new Word((number.equals(Number.SINGULAR))? "has" : "have"));
            res.add(new Word("been"));
            List<Word> next = ParseTreeExtractionUtils.getFollowingWords(s, matcher.getNode("vbn"), true);
            if (next.size() > 0) {
                next.set(0, WordsUtils.lowercaseWord(next.get(0)));
            }
            res.addAll(next);

            return res;
        }

        matcher = p2.matcher(s);
        if (matcher.findAt(s)) {
            List<Word> res = new ArrayList<>();

            res.add(new Word((number.equals(Number.SINGULAR))? "has" : "have"));
            List<Word> next = ParseTreeExtractionUtils.getFollowingWords(s, matcher.getNode("vbn"), true);
            if (next.size() > 0) {
                next.set(0, WordsUtils.lowercaseWord(next.get(0)));
            }
            res.addAll(next);

            return res;
        }

        matcher = p3.matcher(s);
        if (matcher.findAt(s)) {
            List<Word> res = new ArrayList<>();
            if (tense.equals(Tense.PRESENT)) {
                res.add(new Word((number.equals(Number.SINGULAR)) ? "is" : "are"));
            } else {
                res.add(new Word((number.equals(Number.SINGULAR)) ? "was" : "were"));
            }
            List<Word> next = ParseTreeExtractionUtils.getFollowingWords(s, matcher.getNode("vbn"), true);
            if (next.size() > 0) {
                next.set(0, WordsUtils.lowercaseWord(next.get(0)));
            }
            res.addAll(next);

            return res;
        }

        // default
        List<Word> res = new ArrayList<>();
        if (tense.equals(Tense.PRESENT)) {
            res.add(new Word((number.equals(Number.SINGULAR)) ? "is" : "are"));
        } else {
            res.add(new Word((number.equals(Number.SINGULAR)) ? "was" : "were"));
        }
        List<Word> next = ParseTreeExtractionUtils.getFollowingWords(s, vbgn, true);
        if (next.size() > 0) {
            next.set(0, WordsUtils.lowercaseWord(next.get(0)));
        }
        res.addAll(next);

        return res;
    }

} public class Leaf extends DiscourseTree {
    private Tree parseTree;
    private boolean allowSplit; // true, if extraction-rules will be applied on the text
    private boolean toSimpleContext;

    public Leaf() {
        super("UNKNOWN");
    }

    public Leaf(String extractionRule, Tree parseTree) {
        super(extractionRule);
        this.parseTree = parseTree;
        this.allowSplit = true;
        this.toSimpleContext = false;
    }

    // not efficient -> prefer to use constructor with tree
    public Leaf(String extractionRule, String text) throws ParseTreeException {
        this(extractionRule, ParseTreeParser.parse(text));
    }

    public void dontAllowSplit() {
        this.allowSplit = false;
    }

    public Tree getParseTree() {
        return parseTree;
    }

    public void setParseTree(Tree parseTree) {
        this.parseTree = parseTree;
    }

    public String getText() {
        return WordsUtils.wordsToString(ParseTreeExtractionUtils.getContainingWords(parseTree));
    }

    public void setToSimpleContext(boolean toSimpleContext) {
        this.toSimpleContext = toSimpleContext;
    }

    public boolean isAllowSplit() {
        return allowSplit;
    }

    public boolean isToSimpleContext() {
        return toSimpleContext;
    }

    // VISUALIZATION ///////////////////////////////////////////////////////////////////////////////////////////////////

    @Override
    public List<String> getPTPCaption() {
        return Collections.singletonList("'" + getText() + "'");
    }

    @Override
    public List<PrettyTreePrinter.Edge> getPTPEdges() {
        return new ArrayList<>();
    }
} public class NERStringParseException extends Exception {

    public NERStringParseException(String msg) {
        super(msg);
    }
} public class NERStringParser {

    private static final AbstractSequenceClassifier NER_CLASSIFIER = CRFClassifier.getClassifierNoExceptions("edu/stanford/nlp/models/ner/english.all.3class.distsim.crf.ser.gz");

    public static NERString parse(String text) {
        List<NERToken> tokens = new ArrayList<>();

        String nerString = NER_CLASSIFIER.classifyToString(text);
        String[] nerTokens = nerString.split(" ");

        int idx = 0;
        for (String nerToken : nerTokens) {
            int sep_idx = nerToken.lastIndexOf("/");

            // create text
            String txt = nerToken.substring(0, sep_idx);
            String category = nerToken.substring(sep_idx + 1);
            NERToken token = new NERToken(idx, txt, category);
            tokens.add(token);

            ++idx;
        }

        return new NERString(tokens);
    }

    public static TNERString parse(Tree parseTree) throws NERStringParseException {
        List<TNERToken> tokens = new ArrayList<>();

        List<Integer> parseTreeLeafNumbers = ParseTreeExtractionUtils.getLeafNumbers(parseTree, parseTree);
        String nerString = NER_CLASSIFIER.classifyToString(WordsUtils.wordsToString(parseTree.yieldWords()));
        String[] nerTokens = nerString.split(" ");

        if (parseTreeLeafNumbers.size() != nerTokens.length) {
            throw new NERStringParseException("Could not map NER string to parseTree");
        }

        int idx = 0;
        for (String nerToken : nerTokens) {
            int sep_idx = nerToken.lastIndexOf("/");

            // create token
            String text = nerToken.substring(0, sep_idx);
            String category = nerToken.substring(sep_idx + 1);
            TNERToken token = new TNERToken(idx, text, category, parseTree.getNodeNumber(parseTreeLeafNumbers.get(idx)));
            tokens.add(token);

            ++idx;
        }

        return new TNERString(tokens, parseTree);
    }
} public class TNERString extends NERString {
    private final Tree parseTree;

    public TNERString(List<TNERToken> tokens, Tree parseTree) {
        super(new ArrayList<>(tokens));
        this.parseTree = parseTree;
        this.tokens.forEach(t -> ((TNERToken) t).setNerString(this));
    }

    public Tree getParseTree() {
        return parseTree;
    }
} public class ParseTreeException extends Exception {

    public ParseTreeException(String text) {
        super("Failed to parse text: \"" + text + "\"");
    }
} public class ParseTreeExtractionUtils {

    public interface INodeChecker {
        boolean check(Tree anchorTree, Tree node);
    }


    public static List<Integer> getLeafNumbers(Tree anchorTree, Tree node) {
        List<Integer> res = new ArrayList<>();
        for (Tree leaf : node.getLeaves()) {
            res.add(leaf.nodeNumber(anchorTree));
        }
        return res;
    }

    private static IndexRange getLeafIndexRange(Tree anchorTree, Tree node) {
        int fromIdx = -1;
        int toIdx = -1;

        List<Integer> leafNumbers = getLeafNumbers(anchorTree, anchorTree);
        List<Integer> nodeLeafNumbers = getLeafNumbers(anchorTree, node);
        int fromNumber = nodeLeafNumbers.get(0);
        int toNumber = nodeLeafNumbers.get(nodeLeafNumbers.size() - 1);

        int idx = 0;
        for (int leafNumber : leafNumbers) {
            if (leafNumber == fromNumber) {
                fromIdx = idx;
            }
            if (leafNumber == toNumber) {
                toIdx = idx;
            }
            ++idx;
        }

        if ((fromIdx >= 0) && (toIdx >= 0)) {
            return new IndexRange(fromIdx, toIdx);
        } else {
            throw new IllegalArgumentException("node should be a subtree of anchorTree.");
        }
    }

    // returns True, if the model of node would not check/divide a NER group, else False
    public static boolean isNERSafeExtraction(Tree anchorTree, NERString anchorNERString, Tree node) {
        IndexRange leafIdxRange = getLeafIndexRange(anchorTree, node);
        List<IndexRange> nerIdxRanges = NERExtractionUtils.getNERIndexRanges(anchorNERString);

        for (IndexRange nerIdxRange : nerIdxRanges) {
            if (((nerIdxRange.getFromIdx() < leafIdxRange.getFromIdx()) && (leafIdxRange.getFromIdx() <= nerIdxRange.getToIdx()))
                    || ((nerIdxRange.getFromIdx() <= leafIdxRange.getToIdx()) && (leafIdxRange.getToIdx() < nerIdxRange.getToIdx()))) {
                return false;
            }
        }

        return true;
    }




    public static List<Word> leavesToWords(List<Tree> leaves) {
        return leaves.stream().map(l -> l.yieldWords().get(0)).collect(Collectors.toList());
    }

    public static List<List<Tree>> splitLeaves(Tree anchorTree, List<Tree> leaves, INodeChecker leafChecker, boolean removeEmpty) {
        List<List<Tree>> res = new ArrayList<>();
        List<Tree> currElement = new ArrayList<>();
        for (Tree leaf : leaves) {
            if (leafChecker.check(anchorTree, leaf)) {
                if ((currElement.size() > 0) || (!removeEmpty))
                    res.add(currElement);
                currElement = new ArrayList<>();
            } else {
                currElement.add(leaf);
            }
        }
        if ((currElement.size() > 0) || (!removeEmpty))
            res.add(currElement);

        return res;
    }

    public static List<Tree> findLeaves(Tree anchorTree, List<Tree> leaves, INodeChecker leafChecker, boolean reversed) {
        List<Tree> res = leaves.stream().filter(l -> leafChecker.check(anchorTree, l)).collect(Collectors.toList());
        if (reversed) {
            Collections.reverse(res);
        }
        return res;
    }

    public static Tree getFirstLeaf(Tree tree) {
        if (tree.isLeaf()) {
            return tree;
        } else {
            return getFirstLeaf(tree.firstChild());
        }
    }

    public static Tree getLastLeaf(Tree tree) {
        if (tree.isLeaf()) {
            return tree;
        } else {
            return getLastLeaf(tree.lastChild());
        }
    }

    public static List<Tree> getLeavesInBetween(Tree anchorTree, Tree leftNode, Tree rightNode, boolean includeLeft, boolean includeRight) {
        List<Tree> res = new ArrayList<>();

        if (leftNode == null) {
            leftNode = getFirstLeaf(anchorTree);
        }
        if (rightNode == null) {
            rightNode = getLastLeaf(anchorTree);
        }

        int startLeafNumber = (includeLeft) ? getFirstLeaf(leftNode).nodeNumber(anchorTree) : getLastLeaf(leftNode).nodeNumber(anchorTree) + 1;
        int endLeafNumber = (includeRight) ? getLastLeaf(rightNode).nodeNumber(anchorTree) : getFirstLeaf(rightNode).nodeNumber(anchorTree) - 1;
        if ((startLeafNumber < 0) || (endLeafNumber < 0)) {
            return res;
        }

        for (int i = startLeafNumber; i <= endLeafNumber; ++i) {
            Tree node = anchorTree.getNodeNumber(i);
            if (node.isLeaf()) {
                res.addAll(node);
            }
        }

        return res;
    }

    public static List<Tree> getPrecedingLeaves(Tree anchorTree, Tree node, boolean include) {
        return getLeavesInBetween(anchorTree, getFirstLeaf(anchorTree), node, true, include);
    }

    public static List<Tree> getFollowingLeaves(Tree anchorTree, Tree node, boolean include) {
        return getLeavesInBetween(anchorTree, node, getLastLeaf(anchorTree), include, true);
    }

    public static List<Tree> getContainingLeaves(Tree node) {
        return getLeavesInBetween(node, getFirstLeaf(node), getLastLeaf(node), true, true);
    }

    public static List<Word> getWordsInBetween(Tree anchorTree, Tree leftNode, Tree rightNode, boolean includeLeft, boolean includeRight) {
        return leavesToWords(getLeavesInBetween(anchorTree, leftNode, rightNode, includeLeft, includeRight));
    }

    public static List<Word> getPrecedingWords(Tree anchorTree, Tree node, boolean include) {
        return leavesToWords(getPrecedingLeaves(anchorTree, node, include));
    }

    public static List<Word> getFollowingWords(Tree anchorTree, Tree node, boolean include) {
        return leavesToWords(getFollowingLeaves(anchorTree, node, include));
    }

    public static List<Word> getContainingWords(Tree node) {
        return leavesToWords(getContainingLeaves(node));
    }

    public static Optional<Tree> findSpanningTree(Tree anchorTree, Tree firstLeaf, Tree lastLeaf) {
        return findSpanningTreeRec(anchorTree, anchorTree, firstLeaf, lastLeaf);
    }

    private static Optional<Tree> findSpanningTreeRec(Tree anchorTree, Tree currTree, Tree firstLeaf, Tree lastLeaf) {
        int firstNumber = firstLeaf.nodeNumber(anchorTree);
        int lastNumber = lastLeaf.nodeNumber(anchorTree);
        int currFirstNumber = getFirstLeaf(currTree).nodeNumber(anchorTree);
        int currLastNumber = getLastLeaf(currTree).nodeNumber(anchorTree);
        if (((currFirstNumber <= firstNumber) && (firstNumber <= currLastNumber)) && ((currFirstNumber <= lastNumber) && (lastNumber <= currLastNumber))) {
            if ((currFirstNumber == firstNumber) && (lastNumber == currLastNumber)) {
                return Optional.of(currTree);
            } else {
                // recursion
                for (Tree child : currTree.getChildrenAsList()) {
                    Optional<Tree> cr = findSpanningTreeRec(anchorTree, child, firstLeaf, lastLeaf);
                    if (cr.isPresent()) {
                        return Optional.of(cr.get());
                    }
                }
            }
        }

        return Optional.empty();
    }
} public class POSTagger {
    private static final MaxentTagger TAGGER = new MaxentTagger("edu/stanford/nlp/models/pos-tagger/english-left3words/english-left3words-distsim.tagger");

    public static List<POSToken> parse(String text) {
        List<POSToken> tokens = new ArrayList<>();

        String posString = TAGGER.tagString(text);

        String[] posTokens = posString.split(" ");

        int idx = 0;
        for (String posToken : posTokens) {
            int sep_idx = posToken.lastIndexOf("_");

            // create text
            String txt = posToken.substring(0, sep_idx);
            String pos = posToken.substring(sep_idx + 1);
            POSToken token = new POSToken(idx, txt, pos);
            tokens.add(token);

            ++idx;
        }

        return tokens;
    }
} public class POSToken {
    private final int index;
    private final String text;
    private final String pos;

    public POSToken(int index, String text, String pos) {
        this.index = index;
        this.text = text;
        this.pos = pos;
    }

    public int getIndex() {
        return index;
    }

    public String getText() {
        return text;
    }

    public String getPos() {
        return pos;
    }

    public String toString() {
        return "(" + index + ": " + pos + ", '" + text + "')";
    }
} public class WordsUtils {

    public static Word lemmatize(Word word) {
        Sentence sentence = new Sentence(word.value());
        return new Word(sentence.lemma(0));
    }

    public static List<Word> splitIntoWords(String sentence) {
        PTBTokenizer<CoreLabel> ptbt = new PTBTokenizer<>(new StringReader(sentence), new CoreLabelTokenFactory(), "");
        List<Word> words = new ArrayList<>();

        while (ptbt.hasNext()) {
            CoreLabel label = ptbt.next();
            words.add(new Word(label));
        }

        return words;
    }

    public static String wordsToString(List<Word> words) {
        return SentenceUtils.listToString(words);
    }

    public static String wordsToProperSentenceString(List<Word> words) {
        return wordsToString(wordsToProperSentence(words));
    }

    private static Word capitalizeWord(Word word) {
        String s = word.value();
        if (s.length() > 0) {
            s = s.substring(0, 1).toUpperCase() + s.substring(1);
        }

        return new Word(s);
    }

    public static Word lowercaseWord(Word word) {
        return new Word(word.value().toLowerCase());
    }

    private static List<Word> wordsToProperSentence(List<Word> words) {
        List<Word> res = new ArrayList<>();
        res.addAll(words);

        // trim '.' and ',' at beginning and the end and remove multiple, consecutive occurrences
        for (String c : Arrays.asList(".", ",")) {
            Word prev = null;
            Iterator<Word> it = res.iterator();
            while (it.hasNext()) {
                Word word = it.next();
                if (word.value().equals(c)) {
                    if (prev == null || prev.value().equals(word.value())) {
                        it.remove();
                    }
                }
                prev = word;
            }
            if ((!res.isEmpty()) && (res.get(res.size() - 1).value().equals(c))) {
                res.remove(res.size() - 1);
            }
        }

        // add a '.' at the end
        res.add(new Word("."));

        // capitalize first word
        if (!res.isEmpty()) {
            res.set(0, capitalizeWord(res.get(0)));
        }

        return res;
    }
}
import edu.stanford.nlp.ling.Word;
import edu.stanford.nlp.trees.Tree;
import edu.stanford.nlp.trees.tregex.TregexMatcher;
import edu.stanford.nlp.trees.tregex.TregexPattern;
import org.lambda3.text.simplification.discourse.runner.discourse_tree.Relation;
import org.lambda3.text.simplification.discourse.runner.discourse_tree.extraction.Extraction;
import org.lambda3.text.simplification.discourse.runner.discourse_tree.extraction.ExtractionRule;
import org.lambda3.text.simplification.discourse.runner.discourse_tree.model.Leaf;
import org.lambda3.text.simplification.discourse.utils.ner.NERStringParseException;
import org.lambda3.text.simplification.discourse.utils.ner.NERStringParser;
import org.lambda3.text.simplification.discourse.utils.ner.tner.TNERString;
import org.lambda3.text.simplification.discourse.utils.parseTree.ParseTreeException;
import org.lambda3.text.simplification.discourse.utils.parseTree.ParseTreeExtractionUtils;
import org.lambda3.text.simplification.discourse.utils.pos.POSTagger;
import org.lambda3.text.simplification.discourse.utils.pos.POSToken;
import org.lambda3.text.simplification.discourse.utils.words.WordsUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
/*
 * ==========================License-Start=============================
 * DiscourseSimplification : SubordinationPostExtractor
 *
 * Copyright © 2017 Lambda³
 *
 * GNU General Public License 3
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses/.
 * ==========================License-End==============================
 */

package org.lambda3.text.simplification.discourse.runner.discourse_tree.extraction.rules;



/**
 *
 */
public class PrepositionalAttachedtoVPExtractor extends ExtractionRule {

	@Override
    public Optional<Extraction> extract(Leaf leaf) throws ParseTreeException {