001package de.monochromata.anaphors.ast.strategy;
002
003import static java.util.Arrays.asList;
004
005import java.util.ArrayList;
006import java.util.List;
007import java.util.Optional;
008
009import de.monochromata.anaphors.ast.ASTBasedAnaphora;
010import de.monochromata.anaphors.ast.AnaphorPart;
011import de.monochromata.anaphors.ast.DefaultAnaphorPart;
012import de.monochromata.anaphors.ast.DefaultRelatedExpressionPart;
013import de.monochromata.anaphors.ast.RelatedExpressionPart;
014import de.monochromata.anaphors.ast.reference.AbstractReferent;
015import de.monochromata.anaphors.ast.reference.Referent;
016import de.monochromata.anaphors.ast.reference.strategy.ReferentializationStrategy;
017import de.monochromata.anaphors.ast.relatedexp.RelatedExpression;
018import de.monochromata.anaphors.ast.relatedexp.strategy.ClassInstanceCreationStrategy;
019import de.monochromata.anaphors.ast.relatedexp.strategy.LocalVariableDeclarationStrategy;
020import de.monochromata.anaphors.ast.relatedexp.strategy.ParameterDeclarationStrategy;
021import de.monochromata.anaphors.ast.relatedexp.strategy.RelatedExpressionStrategy;
022import de.monochromata.anaphors.ast.spi.AnaphoraResolutionSpi;
023import de.monochromata.anaphors.ast.spi.AnaphorsSpi;
024import de.monochromata.anaphors.ast.spi.RelatedExpressionsSpi;
025import de.monochromata.anaphors.perspectivation.Perspectivation;
026import de.monochromata.anaphors.perspectivation.Perspectivation.Hide;
027import de.monochromata.anaphors.perspectivation.Perspectivation.ToLowerCase;
028
029/**
030 * An indirect-anaphora resolution strategy that anchors anaphors in a field
031 * declared by the anchor.
032 *
033 * @param <N>  The node type in the AST
034 * @param <E>  The expression type
035 * @param <T>  The type type
036 * @param <B>  The binding type
037 * @param <VB> The variable binding type
038 * @param <FB> The field binding type
039 * @param <MB> The method binding type
040 * @param <TB> The type binding type
041 * @param <S>  The scope type (optional)
042 * @param <I>  The type used to represent identifiers
043 * @param <QI> The type used to represent qualified identifiers
044 * @param <EV> The type of the event contained in the condition that is
045 *             evaluated to check when the perspectivations shall be applied.
046 * @param <PP> The type used for positions that carry perspectivations
047 * @param <R>  The sub-type of related expression to use
048 * @param <A>  The sub-type of AST-based anaphora to use
049 */
050public class IA2FStrategy<N, E, T, B, VB extends B, FB extends B, MB extends B, TB extends B, S, I, QI, EV, PP, R extends RelatedExpression<N, T, B, TB, S, QI, R>, A extends ASTBasedAnaphora<N, E, T, B, TB, S, I, QI, R, A>>
051        extends AbstractAnchoringStrategy<N, E, T, B, VB, FB, MB, TB, S, I, QI, EV, PP, R, A>
052        implements StoresAnchorInLocalTempVariable<N, E, T, B, TB, S, I, QI, R, A> {
053
054    public static final List<Class<? extends RelatedExpressionStrategy>> IA2F_SUPPORTED_RELATED_EXPRESSION_STRATEGIES = asList(
055            ClassInstanceCreationStrategy.class, ParameterDeclarationStrategy.class,
056            LocalVariableDeclarationStrategy.class);
057
058    /**
059     * Used in contract testing.
060     */
061    @SuppressWarnings("unused")
062    protected IA2FStrategy() {
063    }
064
065    public IA2FStrategy(final AnaphorsSpi<N, E, TB, S, I, QI, EV, PP> anaphorsSpi,
066            final RelatedExpressionsSpi<N, E, T, B, MB, TB, S, I, QI, EV, PP, R> relatedExpressionsSpi,
067            final AnaphoraResolutionSpi<N, E, T, B, VB, FB, MB, TB, S, I, QI, R, A> anaphoraResolutionSpi) {
068        super(IA2F_SUPPORTED_RELATED_EXPRESSION_STRATEGIES, anaphorsSpi, relatedExpressionsSpi, anaphoraResolutionSpi);
069    }
070
071    @Override
072    protected List<Referent<TB, S, I, QI>> createPotentialReferents(final S scope, final R potentialRelatedExpression) {
073        // TODO: Maybe use existing type binding, if it has been resolved
074        // previously
075        return createPotentialReferents(potentialRelatedExpression, scope,
076                potentialRelatedExpression.resolveType(scope));
077    }
078
079    protected List<Referent<TB, S, I, QI>> createPotentialReferents(final R relatedExpression, final S scope,
080            final TB typeBinding) {
081        final List<Referent<TB, S, I, QI>> potentialReferents = new ArrayList<>();
082        final List<FB> fieldBindings = anaphoraResolutionSpi.getAccessibleFields(typeBinding, scope);
083        for (final FB fieldBinding : fieldBindings) {
084            final I fieldName = anaphoraResolutionSpi.getFieldName(fieldBinding);
085            final QI qualifiedFieldName = relatedExpressionsSpi.toQualifiedIdentifier(fieldName);
086            final String fieldDescription = anaphoraResolutionSpi.getFieldDescription(fieldBinding);
087            potentialReferents.add(new IA2FReferent<>(relatedExpression, fieldBinding, qualifiedFieldName,
088                    fieldDescription, relatedExpressionsSpi, anaphoraResolutionSpi));
089        }
090        return potentialReferents;
091    }
092
093    @Override
094    public Referent<TB, S, I, QI> createReferent(final S scope, final R relatedExpression, final Object memento) {
095        return createReferent(relatedExpression, scope, relatedExpression.resolveType(scope), memento);
096    }
097
098    protected Referent<TB, S, I, QI> createReferent(final R relatedExpression, final S scope, final TB typeBinding,
099            final Object memento) {
100        final List<FB> fieldBindings = anaphoraResolutionSpi.getAccessibleFields(typeBinding, scope);
101        for (final FB fieldBinding : fieldBindings) {
102            final I fieldName = anaphoraResolutionSpi.getFieldName(fieldBinding);
103            final QI qualifiedFieldName = relatedExpressionsSpi.toQualifiedIdentifier(fieldName);
104            final String fieldDescription = anaphoraResolutionSpi.getFieldDescription(fieldBinding);
105            if (fieldName.equals(memento)) {
106                return new IA2FReferent<>(relatedExpression, fieldBinding, qualifiedFieldName, fieldDescription,
107                        relatedExpressionsSpi, anaphoraResolutionSpi);
108            }
109        }
110        throw new IllegalArgumentException("Could not re-create referent from memento " + memento);
111    }
112
113    @SuppressWarnings("unchecked")
114    @Override
115    public A createAnaphora(final S scope, final String anaphor, final E anaphorExpression,
116            final R potentialRelatedExpression, final Referent<TB, S, I, QI> potentialReferent,
117            final ReferentializationStrategy<E, TB, S, I, QI> refStrategy) {
118        final DefaultRelatedExpressionPart<N, E, T, B, VB, TB, S, I, QI, R> relatedExpressionPart = new DefaultRelatedExpressionPart<>(
119                potentialRelatedExpression);
120        final DefaultAnaphorPart<N, E, T, B, VB, TB, S, I, QI, R, A> anaphorPart = new DefaultAnaphorPart<>(anaphor,
121                anaphorExpression, potentialReferent, this, refStrategy,
122                ((IA2FReferent<N, E, T, B, VB, FB, MB, TB, S, I, QI, EV, PP, R, A>) potentialReferent)
123                        .getFieldBinding());
124        final String underspecifiedRelation = anaphoraResolutionSpi.getIA2FUnderspecifiedRelation(
125                potentialRelatedExpression,
126                ((IA2FReferent<N, E, T, B, VB, FB, MB, TB, S, I, QI, EV, PP, R, A>) potentialReferent)
127                        .getFieldBinding(),
128                scope);
129        return anaphoraResolutionSpi.createIndirectAnaphora(relatedExpressionPart, anaphorPart, underspecifiedRelation,
130                false);
131    }
132
133    @Override
134    public E realize(final RelatedExpressionPart<N, E, T, B, TB, S, I, QI, R> relatedExpressionPart,
135            final AnaphorPart<N, E, T, B, TB, S, I, QI, R, A> anaphorPart, final E replacee,
136            final Optional<I> guessedTempName, final Object... support) {
137        return anaphoraResolutionSpi.realizeIA2F(relatedExpressionPart, anaphorPart, replacee, guessedTempName,
138                support);
139    }
140
141    @Override
142    public String getKind() {
143        return "IA2F";
144    }
145
146    @Override
147    public List<Perspectivation> underspecifyAnaphor(final R relatedExpression, final String anaphor,
148            final E anaphorExpression, final Referent<TB, S, I, QI> referent, final S scope) {
149        return underspecifyAnaphorForIA2F(relatedExpression, anaphor, anaphorExpression,
150                (IA2FReferent<N, E, T, B, VB, FB, MB, TB, S, I, QI, EV, PP, R, A>) referent, scope);
151    }
152
153    protected List<Perspectivation> underspecifyAnaphorForIA2F(final R relatedExpression, final String anaphor,
154            final E anaphorExpression, final IA2FReferent<N, E, T, B, VB, FB, MB, TB, S, I, QI, EV, PP, R, A> referent,
155            final S scope) {
156        final QI qualifier = getQualifierForIA(relatedExpression, anaphor, scope);
157        final int qualifierLength = anaphorsSpi.getLength(qualifier) + ".".length();
158        final I fieldName = anaphoraResolutionSpi.getFieldName(referent.getFieldBinding());
159        final boolean fieldNameStartsUpperCase = anaphoraResolutionSpi.identifierStartsUpperCase(fieldName);
160        return underspecifyAnaphorForIA2F(qualifierLength, fieldNameStartsUpperCase);
161    }
162
163    /**
164     * Only public to ease testing. Should be moved to a mixin interface and be
165     * hidden/separated using Java modules.
166     */
167    public List<Perspectivation> underspecifyAnaphorForIA2F(final int qualifierLength,
168            final boolean fieldNameStartsUpperCase) {
169        final List<Perspectivation> perspectivations = new ArrayList<>();
170        perspectivations.add(new Hide(0, qualifierLength));
171        if (fieldNameStartsUpperCase) {
172            perspectivations.add(new ToLowerCase(qualifierLength));
173        }
174        return perspectivations;
175    }
176
177    @Override
178    public String getAnaphorToBeRealized(final RelatedExpressionPart<N, E, T, B, TB, S, I, QI, R> relatedExpressionPart,
179            final List<AnaphorPart<N, E, T, B, TB, S, I, QI, R, A>> allAnaphorPartsRelatedToTheRelatedExpression,
180            final AnaphorPart<N, E, T, B, TB, S, I, QI, R, A> anaphorPart, final S scope) {
181        final IA2FReferent<N, E, T, B, VB, FB, MB, TB, S, I, QI, EV, PP, R, A> referent = ((IA2FReferent<N, E, T, B, VB, FB, MB, TB, S, I, QI, EV, PP, R, A>) anaphorPart
182                .getReferent());
183        final I fieldName = anaphoraResolutionSpi.getFieldName(referent.getFieldBinding());
184        return getAnaphorToBeRealized(fieldName);
185    }
186
187    protected String getAnaphorToBeRealized(final I fieldName) {
188        final String rawAnaphor = relatedExpressionsSpi.identifierToString(fieldName);
189        return getAnaphorToBeRealized(rawAnaphor);
190    }
191
192    protected String getAnaphorToBeRealized(final String rawAnaphor) {
193        if (rawAnaphor.isEmpty()) {
194            return rawAnaphor;
195        }
196        final String prefix = Character.toLowerCase(rawAnaphor.charAt(0)) + "";
197        final String suffix = rawAnaphor.length() > 1 ? rawAnaphor.substring(1) : "";
198        return prefix + suffix;
199    }
200
201    public static class IA2FReferent<N, E, T, B, VB extends B, FB extends B, MB extends B, TB extends B, S, I, QI, EV, PP, R extends RelatedExpression<N, T, B, TB, S, QI, R>, A extends ASTBasedAnaphora<N, E, T, B, TB, S, I, QI, R, A>>
202            extends AbstractReferent<N, E, T, B, VB, FB, MB, TB, S, I, QI, R, A> {
203
204        final FB fieldBinding;
205        final QI fieldName;
206
207        /**
208         * Used in contract testing.
209         */
210        @SuppressWarnings("unused")
211        protected IA2FReferent() {
212            fieldBinding = null;
213            fieldName = null;
214        }
215
216        IA2FReferent(final R relatedExpression, final FB fieldBinding, final QI fieldName,
217                final String fieldDescription,
218                final RelatedExpressionsSpi<N, E, T, B, MB, TB, S, I, QI, EV, PP, R> relatedExpressionsSpi,
219                final AnaphoraResolutionSpi<N, E, T, B, VB, FB, MB, TB, S, I, QI, R, A> anaphorResolutionSpi) {
220            super(relatedExpression, fieldDescription, relatedExpressionsSpi, anaphorResolutionSpi);
221            this.fieldBinding = fieldBinding;
222            this.fieldName = fieldName;
223            // TODO: Add field name or binding as feature?
224        }
225
226        public FB getFieldBinding() {
227            return this.fieldBinding;
228        }
229
230        @Override
231        public boolean canBeUsedInsteadOf(final Referent<TB, S, I, QI> other) {
232            // TODO: implement additional alias analysis
233            return false;
234        }
235
236        @Override
237        public boolean hasName() {
238            return true;
239        }
240
241        @Override
242        public QI getName() {
243            return this.fieldName;
244        }
245
246        @Override
247        public boolean hasMethodName() {
248            return false;
249        }
250
251        @Override
252        public I getMethodName() {
253            throw new UnsupportedOperationException("IA2FReferent has no method name");
254        }
255
256        @Override
257        public TB resolveType(final S scope) {
258            return anaphorResolutionSpi.resolveType(fieldBinding, scope);
259        }
260
261        @Override
262        public Object getMemento() {
263            return this.fieldName;
264        }
265
266        @Override
267        public int hashCode() {
268            final int prime = 31;
269            int result = super.hashCode();
270            result = prime * result + ((this.fieldBinding == null) ? 0 : this.fieldBinding.hashCode());
271            return result;
272        }
273
274        @SuppressWarnings("unchecked")
275        @Override
276        public boolean equals(final Object obj) {
277            if (this == obj) {
278                return true;
279            }
280            if (!super.equals(obj)) {
281                return false;
282            }
283            if (getClass() != obj.getClass()) {
284                return false;
285            }
286            final IA2FReferent<N, E, T, B, VB, FB, MB, TB, S, I, QI, EV, PP, R, A> other = (IA2FReferent<N, E, T, B, VB, FB, MB, TB, S, I, QI, EV, PP, R, A>) obj;
287            if (this.fieldBinding == null) {
288                if (other.fieldBinding != null) {
289                    return false;
290                }
291            } else if (!this.fieldBinding.equals(other.fieldBinding)) {
292                return false;
293            }
294            return true;
295        }
296
297        @Override
298        public String toString() {
299            // The field binding is not output because it can be quite long.
300            return "IA2FReferent [fieldName=" + fieldName + ", getDescription()=" + getDescription()
301                    + ", getFeatures()=" + getFeatures() + "]";
302        }
303
304    }
305
306}