1/*
2 * Copyright (C) 2007-2010 Júlio Vilmar Gesser.
3 * Copyright (C) 2011, 2013-2016 The JavaParser Team.
4 *
5 * This file is part of JavaParser.
6 *
7 * JavaParser can be used either under the terms of
8 * a) the GNU Lesser General Public License as published by
9 *     the Free Software Foundation, either version 3 of the License, or
10 *     (at your option) any later version.
11 * b) the terms of the Apache License
12 *
13 * You should have received a copy of both licenses in LICENCE.LGPL and
14 * LICENCE.APACHE. Please refer to those files for details.
15 *
16 * JavaParser is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 * GNU Lesser General Public License for more details.
20 */
21
22package com.github.javaparser.resolution.types;
23
24import com.github.javaparser.resolution.MethodUsage;
25import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration;
26import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration;
27import com.github.javaparser.resolution.types.parametrization.ResolvedTypeParameterValueProvider;
28import com.github.javaparser.resolution.types.parametrization.ResolvedTypeParametersMap;
29import com.github.javaparser.resolution.types.parametrization.ResolvedTypeParametrized;
30import com.github.javaparser.utils.Pair;
31
32import java.util.*;
33import java.util.stream.Collectors;
34
35/**
36 * A ReferenceType like a class, an interface or an enum. Note that this type can contain also the values
37 * specified for the type parameters.
38 *
39 * @author Federico Tomassetti
40 */
41public abstract class ResolvedReferenceType implements ResolvedType,
42        ResolvedTypeParametrized, ResolvedTypeParameterValueProvider {
43
44    //
45    // Fields
46    //
47
48    protected ResolvedReferenceTypeDeclaration typeDeclaration;
49    protected ResolvedTypeParametersMap typeParametersMap;
50
51    //
52    // Constructors
53    //
54
55    public ResolvedReferenceType(ResolvedReferenceTypeDeclaration typeDeclaration) {
56        this(typeDeclaration, deriveParams(typeDeclaration));
57    }
58
59    public ResolvedReferenceType(ResolvedReferenceTypeDeclaration typeDeclaration, List<ResolvedType> typeArguments) {
60        if (typeDeclaration.isTypeParameter()) {
61            throw new IllegalArgumentException("You should use only Classes, Interfaces and enums");
62        }
63        if (typeArguments.size() > 0 && typeArguments.size() != typeDeclaration.getTypeParameters().size()) {
64            throw new IllegalArgumentException(String.format(
65                    "expected either zero type arguments or has many as defined in the declaration (%d). Found %d",
66                    typeDeclaration.getTypeParameters().size(), typeArguments.size()));
67        }
68        ResolvedTypeParametersMap.Builder typeParametersMapBuilder = new ResolvedTypeParametersMap.Builder();
69        for (int i = 0; i < typeArguments.size(); i++) {
70            typeParametersMapBuilder.setValue(typeDeclaration.getTypeParameters().get(i), typeArguments.get(i));
71        }
72        this.typeParametersMap = typeParametersMapBuilder.build();
73        this.typeDeclaration = typeDeclaration;
74    }
75
76    //
77    // Public Object methods
78    //
79
80    @Override
81    public boolean equals(Object o) {
82        if (this == o) return true;
83        if (o == null || getClass() != o.getClass()) return false;
84
85        ResolvedReferenceType that = (ResolvedReferenceType) o;
86
87        if (!typeDeclaration.equals(that.typeDeclaration)) return false;
88        if (!typeParametersMap.equals(that.typeParametersMap)) return false;
89
90        return true;
91    }
92
93    @Override
94    public int hashCode() {
95        int result = typeDeclaration.hashCode();
96        result = 31 * result + typeParametersMap.hashCode();
97        return result;
98    }
99
100    @Override
101    public String toString() {
102        return "ReferenceType{" + getQualifiedName() +
103                ", typeParametersMap=" + typeParametersMap +
104                '}';
105    }
106
107    ///
108    /// Relation with other types
109    ///
110
111    @Override
112    public final boolean isReferenceType() {
113        return true;
114    }
115
116    ///
117    /// Downcasting
118    ///
119
120    @Override
121    public ResolvedReferenceType asReferenceType() {
122        return this;
123    }
124
125    ///
126    /// Naming
127    ///
128
129    @Override
130    public String describe() {
131        StringBuilder sb = new StringBuilder();
132        if (hasName()) {
133            sb.append(typeDeclaration.getQualifiedName());
134        } else {
135            sb.append("<anonymous class>");
136        }
137        if (!typeParametersMap().isEmpty()) {
138            sb.append("<");
139            sb.append(String.join(", ", typeDeclaration.getTypeParameters().stream()
140                    .map(tp -> typeParametersMap().getValue(tp).describe())
141                    .collect(Collectors.toList())));
142            sb.append(">");
143        }
144        return sb.toString();
145    }
146
147    ///
148    /// TypeParameters
149    ///
150
151    /**
152     * Execute a transformation on all the type parameters of this element.
153     */
154    public abstract ResolvedType transformTypeParameters(ResolvedTypeTransformer transformer);
155
156    @Override
157    public ResolvedType replaceTypeVariables(ResolvedTypeParameterDeclaration tpToReplace, ResolvedType replaced,
158                                     Map<ResolvedTypeParameterDeclaration, ResolvedType> inferredTypes) {
159        if (replaced == null) {
160            throw new IllegalArgumentException();
161        }
162
163        ResolvedReferenceType result = this;
164        int i = 0;
165        for (ResolvedType tp : this.typeParametersValues()) {
166            ResolvedType transformedTp = tp.replaceTypeVariables(tpToReplace, replaced, inferredTypes);
167            // Identity comparison on purpose
168            if (tp.isTypeVariable() && tp.asTypeVariable().describe().equals(tpToReplace.getName())) {
169                inferredTypes.put(tp.asTypeParameter(), replaced);
170            }
171            // FIXME
172            if (true) {
173                List<ResolvedType> typeParametersCorrected = result.asReferenceType().typeParametersValues();
174                typeParametersCorrected.set(i, transformedTp);
175                result = create(typeDeclaration, typeParametersCorrected);
176            }
177            i++;
178        }
179
180        List<ResolvedType> values = result.typeParametersValues();
181        // FIXME
182        if(values.contains(tpToReplace)){
183            int index = values.indexOf(tpToReplace);
184            values.set(index, replaced);
185            return create(result.getTypeDeclaration(), values);
186        }
187
188        return result;
189    }
190
191    ///
192    /// Assignability
193    ///
194
195    /**
196     * This method checks if ThisType t = new OtherType() would compile.
197     */
198    @Override
199    public abstract boolean isAssignableBy(ResolvedType other);
200
201    ///
202    /// Ancestors
203    ///
204
205    /**
206     * Return all ancestors, that means all superclasses and interfaces.
207     * This list should always include Object (unless this is a reference to Object).
208     * The type typeParametersValues should be expressed in terms of this type typeParametersValues.
209     * <p>
210     * For example, given:
211     * <p>
212     * class Foo&lt;A, B&gt; {}
213     * class Bar&lt;C&gt; extends Foo&lt;C, String&gt; {}
214     * <p>
215     * a call to getAllAncestors on a reference to Bar having type parameter Boolean should include
216     * Foo&lt;Boolean, String&gt;.
217     */
218    public abstract List<ResolvedReferenceType> getAllAncestors();
219
220    public final List<ResolvedReferenceType> getAllInterfacesAncestors() {
221        return getAllAncestors().stream()
222                .filter(it -> it.getTypeDeclaration().isInterface())
223                .collect(Collectors.toList());
224    }
225
226    public final List<ResolvedReferenceType> getAllClassesAncestors() {
227        return getAllAncestors().stream()
228                .filter(it -> it.getTypeDeclaration().isClass())
229                .collect(Collectors.toList());
230    }
231
232    ///
233    /// Type parameters
234    ///
235
236    /**
237     * Get the type associated with the type parameter with the given name.
238     * It returns Optional.empty unless the type declaration declares a type parameter with the given name.
239     */
240    public Optional<ResolvedType> getGenericParameterByName(String name) {
241        for (ResolvedTypeParameterDeclaration tp : typeDeclaration.getTypeParameters()) {
242            if (tp.getName().equals(name)) {
243                return Optional.of(this.typeParametersMap().getValue(tp));
244            }
245        }
246        return Optional.empty();
247    }
248
249    /**
250     * Get the values for all type parameters declared on this type.
251     * The list can be empty for raw types.
252     */
253    public List<ResolvedType> typeParametersValues() {
254        return this.typeParametersMap.isEmpty() ? Collections.emptyList() : typeDeclaration.getTypeParameters().stream().map(tp -> typeParametersMap.getValue(tp)).collect(Collectors.toList());
255    }
256
257    /**
258     * Get the values for all type parameters declared on this type.
259     * In case of raw types the values correspond to TypeVariables.
260     */
261    public List<Pair<ResolvedTypeParameterDeclaration, ResolvedType>> getTypeParametersMap() {
262        List<Pair<ResolvedTypeParameterDeclaration, ResolvedType>> typeParametersMap = new ArrayList<>();
263        if (!isRawType()) {
264	        for (int i = 0; i < typeDeclaration.getTypeParameters().size(); i++) {
265	            typeParametersMap.add(new Pair<>(typeDeclaration.getTypeParameters().get(0), typeParametersValues().get(i)));
266	        }
267        }
268        return typeParametersMap;
269    }
270
271    @Override
272    public ResolvedTypeParametersMap typeParametersMap() {
273        return typeParametersMap;
274    }
275
276    ///
277    /// Other methods introduced by ReferenceType
278    ///
279
280    /**
281     * Corresponding TypeDeclaration
282     */
283    public final ResolvedReferenceTypeDeclaration getTypeDeclaration() {
284        return typeDeclaration;
285    }
286
287    /**
288     * The type of the field could be different from the one in the corresponding FieldDeclaration because
289     * type variables would be solved.
290     */
291    public Optional<ResolvedType> getFieldType(String name) {
292        if (!typeDeclaration.hasField(name)) {
293            return Optional.empty();
294        }
295        ResolvedType type = typeDeclaration.getField(name).getType();
296        type = useThisTypeParametersOnTheGivenType(type);
297        return Optional.of(type);
298    }
299
300    /**
301     * Has the TypeDeclaration a name? Anonymous classes do not have one.
302     */
303    public boolean hasName() {
304        return typeDeclaration.hasName();
305    }
306
307    /**
308     * Qualified name of the declaration.
309     */
310    public String getQualifiedName() {
311        return typeDeclaration.getQualifiedName();
312    }
313
314    /**
315     * Id of the declaration. It corresponds to the qualified name, unless for local classes.
316     */
317    public String getId() {
318        return typeDeclaration.getId();
319    }
320
321    /**
322     * Methods declared on this type.
323     */
324    public abstract Set<MethodUsage> getDeclaredMethods();
325
326    public boolean isRawType() {
327        if (!typeDeclaration.getTypeParameters().isEmpty()) {
328            if (typeParametersMap().isEmpty()) {
329                return true;
330            }
331            for (String name : typeParametersMap().getNames()) {
332                Optional<ResolvedType> value = typeParametersMap().getValueBySignature(name);
333                if (value.isPresent() && value.get().isTypeVariable() && value.get().asTypeVariable().qualifiedName().equals(name)) {
334                    // nothing to do
335                } else {
336                    return false;
337                }
338            }
339            return true;
340        }
341        return false;
342    }
343
344    public Optional<ResolvedType> typeParamValue(ResolvedTypeParameterDeclaration typeParameterDeclaration) {
345        if (typeParameterDeclaration.declaredOnMethod()) {
346            throw new IllegalArgumentException();
347        }
348        String typeId = this.getTypeDeclaration().getId();
349        if (typeId.equals(typeParameterDeclaration.getContainerId())) {
350            return Optional.of(this.typeParametersMap().getValue(typeParameterDeclaration));
351        }
352        for (ResolvedReferenceType ancestor : this.getAllAncestors()) {
353            if (ancestor.getId().equals(typeParameterDeclaration.getContainerId())) {
354                return Optional.of(ancestor.typeParametersMap().getValue(typeParameterDeclaration));
355            }
356        }
357        return Optional.empty();
358    }
359
360    public abstract ResolvedType toRawType();
361
362    //
363    // Protected methods
364    //
365
366    protected abstract ResolvedReferenceType create(ResolvedReferenceTypeDeclaration typeDeclaration, List<ResolvedType> typeParameters);
367
368    protected ResolvedReferenceType create(ResolvedReferenceTypeDeclaration typeDeclaration, ResolvedTypeParametersMap typeParametersMap) {
369        return create(typeDeclaration, typeDeclaration.getTypeParameters().stream()
370                .map(typeParametersMap::getValue)
371                .collect(Collectors.toList()));
372    }
373
374    protected abstract ResolvedReferenceType create(ResolvedReferenceTypeDeclaration typeDeclaration);
375
376    protected boolean isCorrespondingBoxingType(String typeName) {
377        switch (typeName) {
378            case "boolean":
379                return getQualifiedName().equals(Boolean.class.getCanonicalName());
380            case "char":
381                return getQualifiedName().equals(Character.class.getCanonicalName());
382            case "byte":
383                return getQualifiedName().equals(Byte.class.getCanonicalName());
384            case "short":
385                return getQualifiedName().equals(Short.class.getCanonicalName());
386            case "int":
387                return getQualifiedName().equals(Integer.class.getCanonicalName());
388            case "long":
389                return getQualifiedName().equals(Long.class.getCanonicalName());
390            case "float":
391                return getQualifiedName().equals(Float.class.getCanonicalName());
392            case "double":
393                return getQualifiedName().equals(Double.class.getCanonicalName());
394            default:
395                throw new UnsupportedOperationException(typeName);
396        }
397    }
398
399    protected boolean compareConsideringTypeParameters(ResolvedReferenceType other) {
400        if (other.equals(this)) {
401            return true;
402        }
403        if (this.getQualifiedName().equals(other.getQualifiedName())) {
404            if (this.isRawType() || other.isRawType()) {
405                return true;
406            }
407            if (this.typeParametersValues().size() != other.typeParametersValues().size()) {
408                throw new IllegalStateException();
409            }
410            for (int i = 0; i < typeParametersValues().size(); i++) {
411                ResolvedType thisParam = typeParametersValues().get(i);
412                ResolvedType otherParam = other.typeParametersValues().get(i);
413                if (!thisParam.equals(otherParam)) {
414                    if (thisParam instanceof ResolvedWildcard) {
415                        ResolvedWildcard thisParamAsWildcard = (ResolvedWildcard) thisParam;
416                        if (thisParamAsWildcard.isSuper() && otherParam.isAssignableBy(thisParamAsWildcard.getBoundedType())) {
417                            // ok
418                        } else if (thisParamAsWildcard.isExtends() && thisParamAsWildcard.getBoundedType().isAssignableBy(otherParam)) {
419                            // ok
420                        } else if (!thisParamAsWildcard.isBounded()) {
421                            // ok
422                        } else {
423                            return false;
424                        }
425                    } else {
426                        if (thisParam instanceof ResolvedTypeVariable && otherParam instanceof ResolvedTypeVariable) {
427                            List<ResolvedType> thisBounds = thisParam.asTypeVariable().asTypeParameter().getBounds().stream().map(bound -> bound.getType()).collect(Collectors.toList());
428                            List<ResolvedType> otherBounds = otherParam.asTypeVariable().asTypeParameter().getBounds().stream().map(bound -> bound.getType()).collect(Collectors.toList());
429                            if (thisBounds.size() == otherBounds.size() && otherBounds.containsAll(thisBounds)) {
430                                return true;
431                            }
432                        }
433                        return false;
434                    }
435                }
436            }
437            return true;
438        }
439        return false;
440    }
441
442    //
443    // Private methods
444    //
445
446    private static List<ResolvedType> deriveParams(ResolvedReferenceTypeDeclaration typeDeclaration) {
447        return typeDeclaration.getTypeParameters().stream().map((tp) -> new ResolvedTypeVariable(tp)).collect(Collectors.toList());
448    }
449
450    public abstract ResolvedReferenceType deriveTypeParameters(ResolvedTypeParametersMap typeParametersMap);
451}
452