001/*
002 * Copyright 2010-2013 JetBrains s.r.o.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.jetbrains.jet.lang.resolve.java.provider;
018
019import com.google.common.collect.HashMultimap;
020import com.google.common.collect.ImmutableSet;
021import com.google.common.collect.Multimap;
022import com.intellij.openapi.util.Ref;
023import com.intellij.psi.*;
024import com.intellij.psi.util.MethodSignature;
025import com.intellij.psi.util.MethodSignatureBackedByPsiMethod;
026import com.intellij.psi.util.PsiFormatUtil;
027import com.intellij.util.ArrayUtil;
028import org.jetbrains.annotations.NotNull;
029import org.jetbrains.annotations.Nullable;
030import org.jetbrains.jet.lang.resolve.java.*;
031import org.jetbrains.jet.lang.resolve.java.kt.JetClassAnnotation;
032import org.jetbrains.jet.lang.resolve.java.prop.PropertyNameUtils;
033import org.jetbrains.jet.lang.resolve.java.prop.PropertyParseResult;
034import org.jetbrains.jet.lang.resolve.java.wrapper.*;
035import org.jetbrains.jet.lang.resolve.name.Name;
036import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
037
038import java.util.Collection;
039import java.util.HashMap;
040import java.util.List;
041import java.util.Map;
042
043import static com.intellij.psi.util.MethodSignatureUtil.areSignaturesErasureEqual;
044import static com.intellij.psi.util.PsiFormatUtilBase.*;
045
046public final class MembersCache {
047    private static final ImmutableSet<String> OBJECT_METHODS = ImmutableSet.of("hashCode()", "equals(java.lang.Object)", "toString()");
048
049    private final Multimap<Name, Runnable> memberProcessingTasks = HashMultimap.create();
050    private final Map<Name, NamedMembers> namedMembersMap = new HashMap<Name, NamedMembers>();
051
052    @Nullable
053    public NamedMembers get(@NotNull Name name) {
054        runTasksByName(name);
055        return namedMembersMap.get(name);
056    }
057
058    @NotNull
059    public Collection<NamedMembers> allMembers() {
060        runAllTasks();
061        memberProcessingTasks.clear();
062        return namedMembersMap.values();
063    }
064
065    @NotNull
066    private NamedMembers getOrCreateEmpty(@NotNull Name name) {
067        NamedMembers r = namedMembersMap.get(name);
068        if (r == null) {
069            r = new NamedMembers(name);
070            namedMembersMap.put(name, r);
071        }
072        return r;
073    }
074
075    private void addTask(@NotNull PsiMember member, @NotNull RunOnce task) {
076        addTask(member.getName(), task);
077    }
078
079    private void addTask(@Nullable String name, @NotNull RunOnce task) {
080        if (name == null) {
081            return;
082        }
083        memberProcessingTasks.put(Name.identifier(name), task);
084    }
085
086    private void runTasksByName(Name name) {
087        if (!memberProcessingTasks.containsKey(name)) return;
088        Collection<Runnable> tasks = memberProcessingTasks.get(name);
089        for (Runnable task : tasks) {
090            task.run();
091        }
092        // Delete tasks
093        tasks.clear();
094    }
095
096    private void runAllTasks() {
097        for (Runnable task : memberProcessingTasks.values()) {
098            task.run();
099        }
100    }
101
102    @NotNull
103    public static MembersCache buildMembersByNameCache(
104            @NotNull MembersCache membersCache,
105            @NotNull PsiClassFinder finder,
106            @Nullable PsiClass psiClass,
107            @Nullable PsiPackage psiPackage,
108            boolean staticMembers,
109            boolean isKotlin
110    ) {
111        if (psiClass != null) {
112            membersCache.new ClassMemberProcessor(new PsiClassWrapper(psiClass), staticMembers, isKotlin).process();
113        }
114
115        //TODO:
116        List<PsiClass> classes = psiPackage != null ? finder.findPsiClasses(psiPackage) : finder.findInnerPsiClasses(psiClass);
117        membersCache.new ExtraPackageMembersProcessor(classes).process();
118        return membersCache;
119    }
120
121    private class ExtraPackageMembersProcessor { // 'extra' means that PSI elements for these members are not just top-level classes
122        @NotNull
123        private final List<PsiClass> psiClasses;
124
125        private ExtraPackageMembersProcessor(@NotNull List<PsiClass> classes) {
126            psiClasses = classes;
127        }
128
129        private void process() {
130            for (PsiClass psiClass : psiClasses) {
131                if (!(psiClass instanceof JetJavaMirrorMarker)) { // to filter out JetLightClasses
132                    if (JetClassAnnotation.get(psiClass).kind() == JvmStdlibNames.FLAG_CLASS_KIND_OBJECT) {
133                        processObjectClass(psiClass);
134                    }
135                    if (isSamInterface(psiClass)) {
136                        processSamInterface(psiClass);
137                    }
138                }
139            }
140        }
141
142        private void processObjectClass(@NotNull PsiClass psiClass) {
143            PsiField instanceField = psiClass.findFieldByName(JvmAbi.INSTANCE_FIELD, false);
144            if (instanceField != null) {
145                NamedMembers namedMembers = getOrCreateEmpty(Name.identifier(psiClass.getName()));
146
147                TypeSource type = new TypeSource("", instanceField.getType(), instanceField);
148                namedMembers.addPropertyAccessor(new PropertyPsiDataElement(new PsiFieldWrapper(instanceField), type, null));
149            }
150        }
151
152        private void processSamInterface(@NotNull PsiClass psiClass) {
153            NamedMembers namedMembers = getOrCreateEmpty(Name.identifier(psiClass.getName()));
154            namedMembers.setSamInterface(psiClass);
155        }
156    }
157
158    private class ClassMemberProcessor {
159        @NotNull
160        private final PsiClassWrapper psiClass;
161        private final boolean staticMembers;
162        private final boolean kotlin;
163
164        private ClassMemberProcessor(@NotNull PsiClassWrapper psiClass, boolean staticMembers, boolean kotlin) {
165            this.psiClass = psiClass;
166            this.staticMembers = staticMembers;
167            this.kotlin = kotlin;
168        }
169
170        public void process() {
171            processFields();
172            processMethods();
173            processNestedClasses();
174        }
175
176        private void processFields() {
177            // Hack to load static members for enum class loaded from class file
178            if (kotlin && !psiClass.getPsiClass().isEnum()) {
179                return;
180            }
181            for (final PsiField field : psiClass.getPsiClass().getAllFields()) {
182                addTask(field, new RunOnce() {
183                    @Override
184                    public void doRun() {
185                        processField(field);
186                    }
187                });
188            }
189        }
190
191        private void processMethods() {
192            parseAllMethodsAsProperties();
193            processOwnMethods();
194        }
195
196        private void processOwnMethods() {
197            for (final PsiMethod method : psiClass.getPsiClass().getMethods()) {
198                RunOnce task = new RunOnce() {
199                    @Override
200                    public void doRun() {
201                        processOwnMethod(method);
202                    }
203                };
204                addTask(method, task);
205
206                PropertyParseResult propertyParseResult = PropertyNameUtils.parseMethodToProperty(method.getName());
207                if (propertyParseResult != null) {
208                    addTask(propertyParseResult.getPropertyName(), task);
209                }
210            }
211        }
212
213        private void parseAllMethodsAsProperties() {
214            for (PsiMethod method : psiClass.getPsiClass().getAllMethods()) {
215                createEmptyEntry(Name.identifier(method.getName()));
216
217                PropertyParseResult propertyParseResult = PropertyNameUtils.parseMethodToProperty(method.getName());
218                if (propertyParseResult != null) {
219                    createEmptyEntry(Name.identifier(propertyParseResult.getPropertyName()));
220                }
221            }
222        }
223
224        private void processNestedClasses() {
225            if (!staticMembers) {
226                return;
227            }
228            for (final PsiClass nested : psiClass.getPsiClass().getInnerClasses()) {
229                addTask(nested, new RunOnce() {
230                    @Override
231                    public void doRun() {
232                        processNestedClass(nested);
233                    }
234                });
235            }
236        }
237
238        private boolean includeMember(PsiMemberWrapper member) {
239            if (psiClass.getPsiClass().isEnum() && staticMembers) {
240                return member.isStatic();
241            }
242
243            if (member.isStatic() != staticMembers) {
244                return false;
245            }
246
247            if (member.getPsiMember().getContainingClass() != psiClass.getPsiClass()) {
248                return false;
249            }
250
251            //process private accessors
252            if (member.isPrivate()
253                && !(member instanceof PsiMethodWrapper && ((PsiMethodWrapper)member).getJetMethodAnnotation().hasPropertyFlag())) {
254                return false;
255            }
256
257            if (isObjectMethodInInterface(member.getPsiMember())) {
258                return false;
259            }
260
261            return true;
262        }
263
264        private void processField(PsiField field) {
265            PsiFieldWrapper fieldWrapper = new PsiFieldWrapper(field);
266
267            // group must be created even for excluded field
268            NamedMembers namedMembers = getOrCreateEmpty(Name.identifier(fieldWrapper.getName()));
269
270            if (!includeMember(fieldWrapper)) {
271                return;
272            }
273
274            TypeSource type = new TypeSource("", fieldWrapper.getType(), field);
275            namedMembers.addPropertyAccessor(new PropertyPsiDataElement(fieldWrapper, type, null));
276        }
277
278        private void processOwnMethod(PsiMethod ownMethod) {
279            PsiMethodWrapper method = new PsiMethodWrapper(ownMethod);
280
281            if (!includeMember(method)) {
282                return;
283            }
284
285            PropertyParseResult propertyParseResult = PropertyNameUtils.parseMethodToProperty(method.getName());
286
287            // TODO: remove getJavaClass
288            if (propertyParseResult != null && propertyParseResult.isGetter()) {
289                processGetter(ownMethod, method, propertyParseResult);
290            }
291            else if (propertyParseResult != null && !propertyParseResult.isGetter()) {
292                processSetter(method, propertyParseResult);
293            }
294
295            if (!method.getJetMethodAnnotation().hasPropertyFlag()) {
296                NamedMembers namedMembers = getOrCreateEmpty(Name.identifier(method.getName()));
297                namedMembers.addMethod(method);
298            }
299        }
300
301        private void processSetter(PsiMethodWrapper method, PropertyParseResult propertyParseResult) {
302            String propertyName = propertyParseResult.getPropertyName();
303            NamedMembers members = getOrCreateEmpty(Name.identifier(propertyName));
304
305            if (method.getJetMethodAnnotation().hasPropertyFlag()) {
306                if (method.getParameters().size() == 0) {
307                    // TODO: report error properly
308                    throw new IllegalStateException();
309                }
310
311                int i = 0;
312
313                TypeSource receiverType = null;
314                PsiParameterWrapper p1 = method.getParameter(0);
315                if (p1.getJetValueParameter().receiver()) {
316                    receiverType = new TypeSource(p1.getJetValueParameter().type(), p1.getPsiParameter().getType(), p1.getPsiParameter());
317                    ++i;
318                }
319
320                while (i < method.getParameters().size() && method.getParameter(i).getJetTypeParameter().isDefined()) {
321                    ++i;
322                }
323
324                if (i + 1 != method.getParameters().size()) {
325                    throw new IllegalStateException();
326                }
327
328                PsiParameterWrapper propertyTypeParameter = method.getParameter(i);
329                TypeSource propertyType =
330                        new TypeSource(method.getJetMethodAnnotation().propertyType(), propertyTypeParameter.getPsiParameter().getType(),
331                                       propertyTypeParameter.getPsiParameter());
332
333                members.addPropertyAccessor(new PropertyPsiDataElement(method, false, propertyType, receiverType));
334            }
335        }
336
337        private void processGetter(PsiMethod ownMethod, PsiMethodWrapper method, PropertyParseResult propertyParseResult) {
338            String propertyName = propertyParseResult.getPropertyName();
339            NamedMembers members = getOrCreateEmpty(Name.identifier(propertyName));
340
341            // TODO: some java properties too
342            if (method.getJetMethodAnnotation().hasPropertyFlag()) {
343
344                int i = 0;
345
346                TypeSource receiverType;
347                if (i < method.getParameters().size() && method.getParameter(i).getJetValueParameter().receiver()) {
348                    PsiParameterWrapper receiverParameter = method.getParameter(i);
349                    receiverType =
350                            new TypeSource(receiverParameter.getJetValueParameter().type(), receiverParameter.getPsiParameter().getType(),
351                                           receiverParameter.getPsiParameter());
352                    ++i;
353                }
354                else {
355                    receiverType = null;
356                }
357
358                while (i < method.getParameters().size() && method.getParameter(i).getJetTypeParameter().isDefined()) {
359                    // TODO: store is reified
360                    ++i;
361                }
362
363                if (i != method.getParameters().size()) {
364                    // TODO: report error properly
365                    throw new IllegalStateException("something is wrong with method " + ownMethod);
366                }
367
368                // TODO: what if returnType == null?
369                PsiType returnType = method.getReturnType();
370                assert returnType != null;
371                TypeSource propertyType = new TypeSource(method.getJetMethodAnnotation().propertyType(), returnType, method.getPsiMethod());
372
373                members.addPropertyAccessor(new PropertyPsiDataElement(method, true, propertyType, receiverType));
374            }
375        }
376
377        private void createEmptyEntry(@NotNull Name identifier) {
378            getOrCreateEmpty(identifier);
379        }
380
381        private void processNestedClass(PsiClass nested) {
382            if (isSamInterface(nested)) {
383                NamedMembers namedMembers = getOrCreateEmpty(Name.identifier(nested.getName()));
384                namedMembers.setSamInterface(nested);
385            }
386        }
387    }
388
389    public static boolean isObjectMethodInInterface(@NotNull PsiMember member) {
390        if (!(member instanceof PsiMethod)) {
391            return false;
392        }
393        PsiClass containingClass = member.getContainingClass();
394        assert containingClass != null : "containing class is null for " + member;
395
396        if (!containingClass.isInterface()) {
397            return false;
398        }
399
400        return isObjectMethod((PsiMethod) member);
401    }
402
403    private static boolean isObjectMethod(PsiMethod method) {
404        String formattedMethod = PsiFormatUtil.formatMethod(
405                method, PsiSubstitutor.EMPTY, SHOW_NAME | SHOW_PARAMETERS, SHOW_TYPE | SHOW_FQ_CLASS_NAMES);
406        return OBJECT_METHODS.contains(formattedMethod);
407    }
408
409    public static boolean isSamInterface(@NotNull PsiClass psiClass) {
410        return getSamInterfaceMethod(psiClass) != null;
411    }
412
413    // Returns null if not SAM interface
414    @Nullable
415    public static PsiMethod getSamInterfaceMethod(@NotNull PsiClass psiClass) {
416        if (DescriptorResolverUtils.isKotlinClass(psiClass)) {
417            return null;
418        }
419        String qualifiedName = psiClass.getQualifiedName();
420        if (qualifiedName == null || qualifiedName.startsWith(KotlinBuiltIns.BUILT_INS_PACKAGE_FQ_NAME.asString() + ".")) {
421            return null;
422        }
423        if (!psiClass.isInterface() || psiClass.isAnnotationType()) {
424            return null;
425        }
426
427        return findOnlyAbstractMethod(psiClass);
428    }
429
430    @Nullable
431    private static PsiMethod findOnlyAbstractMethod(@NotNull PsiClass psiClass) {
432        PsiClassType classType = JavaPsiFacade.getElementFactory(psiClass.getProject()).createType(psiClass);
433
434        OnlyAbstractMethodFinder finder = new OnlyAbstractMethodFinder();
435        if (finder.find(classType)) {
436            return finder.getFoundMethod();
437        }
438        return null;
439    }
440
441    private static boolean isVarargMethod(@NotNull PsiMethod method) {
442        PsiParameter lastParameter = ArrayUtil.getLastElement(method.getParameterList().getParameters());
443        return lastParameter != null && lastParameter.getType() instanceof PsiEllipsisType;
444    }
445
446    private static abstract class RunOnce implements Runnable {
447        private boolean hasRun = false;
448
449        @Override
450        public final void run() {
451            if (hasRun) return;
452            hasRun = true;
453            doRun();
454        }
455
456        protected abstract void doRun();
457    }
458
459    private static class OnlyAbstractMethodFinder {
460        private MethodSignatureBackedByPsiMethod found;
461
462        private boolean find(@NotNull PsiClassType classType) {
463            PsiClassType.ClassResolveResult classResolveResult = classType.resolveGenerics();
464            PsiSubstitutor classSubstitutor = classResolveResult.getSubstitutor();
465            PsiClass psiClass = classResolveResult.getElement();
466            if (psiClass == null) {
467                return false; // can't resolve class -> not a SAM interface
468            }
469            if (CommonClassNames.JAVA_LANG_OBJECT.equals(psiClass.getQualifiedName())) {
470                return true;
471            }
472            for (PsiMethod method : psiClass.getMethods()) {
473                if (isObjectMethod(method)) { // e.g., ignore toString() declared in interface
474                    continue;
475                }
476                if (method.hasTypeParameters()) {
477                    return false; // if interface has generic methods, it is not a SAM interface
478                }
479
480                if (found == null) {
481                    found = (MethodSignatureBackedByPsiMethod) method.getSignature(classSubstitutor);
482                    continue;
483                }
484                if (!found.getName().equals(method.getName())) {
485                    return false; // optimizing heuristic
486                }
487                MethodSignatureBackedByPsiMethod current = (MethodSignatureBackedByPsiMethod) method.getSignature(classSubstitutor);
488                if (!areSignaturesErasureEqual(current, found) || isVarargMethod(method) != isVarargMethod(found.getMethod())) {
489                    return false; // different signatures
490                }
491            }
492
493            for (PsiType t : classType.getSuperTypes()) {
494                if (!find((PsiClassType) t)) {
495                    return false;
496                }
497            }
498
499            return true;
500        }
501
502        @Nullable
503        PsiMethod getFoundMethod() {
504            return found == null ? null : found.getMethod();
505        }
506    }
507}