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}