001package de.monochromata.anaphors.ast;
002
003import static de.monochromata.anaphors.ast.unify.DirectAnaphoraPrecedesIndirectAnaphora.preferDirectOverIndirectAnaphora;
004import static de.monochromata.anaphors.ast.unify.HyponymyPrecedesFauxHyponymy.preferHynonymyOverFauxHyponymy;
005import static de.monochromata.anaphors.ast.unify.TypeRecurrencePrecedesHyponymy.preferTypeRecurrenceOverHynonymy;
006import static java.util.Collections.singletonList;
007
008import java.util.ArrayList;
009import java.util.List;
010import java.util.function.BiPredicate;
011import java.util.function.Supplier;
012
013import de.monochromata.anaphors.ast.reference.strategy.ReferentializationStrategy;
014import de.monochromata.anaphors.ast.reference.strategy.concept.NameRecurrence;
015import de.monochromata.anaphors.ast.relatedexp.RelatedExpression;
016import de.monochromata.anaphors.ast.relatedexp.RelatedExpressionsCollector;
017import de.monochromata.anaphors.ast.relatedexp.strategy.LocalTempVariableIntroducingStrategy;
018import de.monochromata.anaphors.ast.relatedexp.strategy.RelatedExpressionStrategy;
019import de.monochromata.anaphors.ast.strategy.AnaphorResolutionStrategy;
020
021/**
022 * A generic interface to anaphora resolution. Note that invocations of the
023 * interface need to be aware of a specific AST implementation to correctly
024 * invoke the interface.
025 *
026 * @param <N>  The node type in the AST
027 * @param <E>  The expression type
028 * @param <T>  The type type
029 * @param <B>  The binding type
030 * @param <TB> The type binding type
031 * @param <S>  The scope type (optional)
032 * @param <I>  The type used to represent identifiers
033 * @param <QI> The type used to represent qualified identifiers
034 * @param <R>  The sub-type of related expression to use
035 * @param <A>  The sub-type of AST-based anaphora to use
036 */
037public class ASTBasedAnaphorResolution<N, E, T, B, TB extends B, S, I, QI, R extends RelatedExpression<N, T, B, TB, S, QI, R>, A extends ASTBasedAnaphora<N, E, T, B, TB, S, I, QI, R, A>> {
038
039    private final List<RelatedExpressionStrategy<N, T, B, TB, S, QI, R>> relatedExpressionStrategies;
040    private final List<AnaphorResolutionStrategy<N, E, T, B, TB, S, I, QI, R, A>> resolutionStrategies;
041    private final List<ReferentializationStrategy<E, TB, S, I, QI>> referentializationStrategies;
042
043    private final Supplier<RelatedExpressionsCollector<N, E, T, B, TB, S, QI, R>> relatedExpressionsCollectorSupplier;
044
045    /**
046     * Used in contract testing.
047     */
048    @SuppressWarnings("unused")
049    protected ASTBasedAnaphorResolution() {
050        relatedExpressionStrategies = null;
051        resolutionStrategies = null;
052        referentializationStrategies = null;
053        relatedExpressionsCollectorSupplier = null;
054    }
055
056    public ASTBasedAnaphorResolution(
057            final List<RelatedExpressionStrategy<N, T, B, TB, S, QI, R>> relatedExpressionStrategies,
058            final List<AnaphorResolutionStrategy<N, E, T, B, TB, S, I, QI, R, A>> resolutionStrategies,
059            final List<ReferentializationStrategy<E, TB, S, I, QI>> referentializationStrategies,
060            final Supplier<RelatedExpressionsCollector<N, E, T, B, TB, S, QI, R>> relatedExpressionsCollectorSupplier) {
061        this.relatedExpressionStrategies = relatedExpressionStrategies;
062        this.resolutionStrategies = resolutionStrategies;
063        this.referentializationStrategies = validate(referentializationStrategies);
064        this.relatedExpressionsCollectorSupplier = relatedExpressionsCollectorSupplier;
065    }
066
067    protected List<ReferentializationStrategy<E, TB, S, I, QI>> validate(
068            final List<ReferentializationStrategy<E, TB, S, I, QI>> referentializationStrategies) {
069        if (referentializationStrategies.isEmpty()) {
070            throw new IllegalArgumentException("No referentialization strategies provided. At least "
071                    + NameRecurrence.class.getSimpleName() + " needs to be provided because "
072                    + LocalTempVariableIntroducingStrategy.class.getSimpleName()
073                    + " instances use it for re-resolution.");
074        } else if (!(referentializationStrategies.get(0) instanceof NameRecurrence)) {
075            throw new IllegalArgumentException("No referentialization strategies provided. At least "
076                    + NameRecurrence.class.getSimpleName() + " needs to be provided because "
077                    + LocalTempVariableIntroducingStrategy.class.getSimpleName()
078                    + " instances use it for re-resolution.");
079        }
080        return referentializationStrategies;
081    }
082
083    /**
084     * Perform anaphora resolution on the AST implementation configured via the
085     * service provider interfaces.
086     *
087     * @param anaphor            The anaphor that is to be (re-)resolved.
088     * @param definiteExpression The expression that may function as anaphor in the
089     *                           anaphora relation to be generated by this method.
090     *                           If the anaphora relation is to be re-resolved, this
091     *                           can be a non-trivial expression. If the anaphora
092     *                           relation is to be resolved for the first time, this
093     *                           is typically a simple name and might as well be
094     *                           called a definite expression at this point.
095     * @param scope              May be null if not required by the AST
096     *                           implementation configured via the service provider
097     *                           interfaces.
098     * @return A list of anaphors that the given definite expression can function
099     *         as. The list is empty is the anaphor could not be resolved and will
100     *         contain more than one element if the anaphor is ambiguous.
101     */
102    public List<A> resolveAnaphor(final String anaphor, final E definiteExpression, final S scope) {
103
104        // Get potential related expressions
105        final var reCollector = relatedExpressionsCollectorSupplier.get();
106        final List<R> potentialRelatedExpressions = reCollector.traverse(definiteExpression, scope);
107
108        return resolveAnaphor(potentialRelatedExpressions, anaphor, definiteExpression, scope);
109    }
110
111    public List<A> resolveAnaphor(final R potentialRelatedExpression, final String anaphor, final E definiteExpression,
112            final S scope) {
113        return resolveAnaphor(singletonList(potentialRelatedExpression), anaphor, definiteExpression, scope);
114    }
115
116    public List<A> resolveAnaphor(final List<R> potentialRelatedExpressions, final String anaphor,
117            final E definiteExpression, final S scope) {
118        // Unify co-referential related expressions
119        final List<R> unifiedPotentialRelatedExpressions = unifyCoReferentialRelatedExpressions(
120                potentialRelatedExpressions);
121
122        // Get potential anaphora relations
123        List<A> potentialAnaphoraRelations = new ArrayList<>();
124        for (final AnaphorResolutionStrategy<N, E, T, B, TB, S, I, QI, R, A> resolutionStrat : this.resolutionStrategies) {
125            potentialAnaphoraRelations.addAll(resolutionStrat.generatePotentialAnaphora(scope, anaphor,
126                    definiteExpression, unifiedPotentialRelatedExpressions, this.referentializationStrategies));
127        }
128
129        // Unify co-referential referents
130        potentialAnaphoraRelations = unifyCoReferentialReferents(potentialAnaphoraRelations);
131
132        // Apply precedence rule
133        potentialAnaphoraRelations = preferTypeRecurrenceOverHynonymy(potentialAnaphoraRelations);
134        potentialAnaphoraRelations = preferHynonymyOverFauxHyponymy(potentialAnaphoraRelations);
135        potentialAnaphoraRelations = preferDirectOverIndirectAnaphora(potentialAnaphoraRelations);
136
137        return potentialAnaphoraRelations;
138    }
139
140    /**
141     * @return a potential {@link RelatedExpression} representing the given node, or
142     *         {@literal null}, if the node cannot function as related expression.
143     */
144    public R getRelatedExpression(final N node) {
145        throw new UnsupportedOperationException("Creation of related expressions should be moved to d.m.e.a entirely");
146        /*
147         * for (final RelatedExpressionStrategy<N, T, B, TB, S, QI, R> strategy :
148         * relatedExpressionStrategies) { if
149         * (strategy.isPotentialRelatedExpression(node)) { return
150         * strategy.createRelatedExpression(node); } } return null;
151         */
152    }
153
154    protected List<R> unifyCoReferentialRelatedExpressions(final List<R> potentialRelatedExpressions) {
155        return unifyListElements(potentialRelatedExpressions, (re1, re2) -> re1.canBeUsedInsteadOf(re2));
156    }
157
158    protected List<A> unifyCoReferentialReferents(final List<A> potentialAnaphoraRelations) {
159        return unifyListElements(potentialAnaphoraRelations,
160                (anaphora1, anaphora2) -> anaphora1.getReferent().canBeUsedInsteadOf(anaphora2.getReferent()));
161    }
162
163    protected static <T> List<T> unifyListElements(final List<T> elements, final BiPredicate<T, T> check) {
164        final List<T> retainedElements = new ArrayList<>();
165        final List<T> omittedElements = new ArrayList<>();
166        for (final T toCheck : elements) {
167            if (!omittedElements.contains(toCheck)) {
168                boolean retainedInsteadOfAnotherOne = false;
169                for (final T other : elements) {
170                    if (toCheck != other && check.test(toCheck, other)) {
171                        if (!retainedInsteadOfAnotherOne) {
172                            retainedElements.add(toCheck);
173                            retainedInsteadOfAnotherOne = true;
174                        }
175                        System.err.println("Omitting " + other + " in favour of " + toCheck);
176                        omittedElements.add(other);
177                        retainedElements.remove(other);
178                    }
179                }
180                if (!retainedInsteadOfAnotherOne) {
181                    retainedElements.add(toCheck);
182                }
183            }
184        }
185        return retainedElements;
186    }
187}