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 }