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    
017    package org.jetbrains.jet.lang.resolve.java.provider;
018    
019    import com.google.common.collect.HashMultimap;
020    import com.google.common.collect.ImmutableSet;
021    import com.google.common.collect.Multimap;
022    import com.intellij.openapi.util.Ref;
023    import com.intellij.psi.*;
024    import com.intellij.psi.util.MethodSignature;
025    import com.intellij.psi.util.MethodSignatureBackedByPsiMethod;
026    import com.intellij.psi.util.PsiFormatUtil;
027    import com.intellij.util.ArrayUtil;
028    import org.jetbrains.annotations.NotNull;
029    import org.jetbrains.annotations.Nullable;
030    import org.jetbrains.jet.lang.resolve.java.*;
031    import org.jetbrains.jet.lang.resolve.java.kt.JetClassAnnotation;
032    import org.jetbrains.jet.lang.resolve.java.prop.PropertyNameUtils;
033    import org.jetbrains.jet.lang.resolve.java.prop.PropertyParseResult;
034    import org.jetbrains.jet.lang.resolve.java.wrapper.*;
035    import org.jetbrains.jet.lang.resolve.name.Name;
036    import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
037    
038    import java.util.Collection;
039    import java.util.HashMap;
040    import java.util.List;
041    import java.util.Map;
042    
043    import static com.intellij.psi.util.MethodSignatureUtil.areSignaturesErasureEqual;
044    import static com.intellij.psi.util.PsiFormatUtilBase.*;
045    
046    public 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    }