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.asJava; 018 019 import com.intellij.openapi.application.ApplicationManager; 020 import com.intellij.openapi.diagnostic.Logger; 021 import com.intellij.openapi.progress.ProcessCanceledException; 022 import com.intellij.openapi.project.Project; 023 import com.intellij.openapi.util.Comparing; 024 import com.intellij.openapi.util.io.FileUtil; 025 import com.intellij.openapi.vfs.StandardFileSystems; 026 import com.intellij.openapi.vfs.VirtualFile; 027 import com.intellij.psi.PsiClass; 028 import com.intellij.psi.PsiElement; 029 import com.intellij.psi.PsiMethod; 030 import com.intellij.psi.impl.java.stubs.PsiClassStub; 031 import com.intellij.psi.search.GlobalSearchScope; 032 import com.intellij.psi.stubs.PsiFileStub; 033 import com.intellij.psi.stubs.StubElement; 034 import com.intellij.psi.util.PsiTreeUtil; 035 import com.intellij.util.PathUtil; 036 import com.intellij.util.SmartList; 037 import org.jetbrains.annotations.NotNull; 038 import org.jetbrains.annotations.Nullable; 039 import org.jetbrains.jet.lang.psi.*; 040 import org.jetbrains.jet.lang.resolve.java.JvmAbi; 041 import org.jetbrains.jet.lang.resolve.java.PackageClassUtils; 042 import org.jetbrains.jet.lang.resolve.java.jetAsJava.KotlinLightMethod; 043 import org.jetbrains.jet.lang.resolve.name.FqName; 044 import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns; 045 import org.jetbrains.jet.utils.ExceptionUtils; 046 import org.jetbrains.jet.utils.KotlinVfsUtil; 047 048 import java.io.File; 049 import java.net.MalformedURLException; 050 import java.net.URL; 051 import java.util.*; 052 053 public class LightClassUtil { 054 private static final Logger LOG = Logger.getInstance(LightClassUtil.class); 055 056 public static final File BUILT_INS_SRC_DIR = new File("idea/builtinsSrc", KotlinBuiltIns.BUILT_INS_PACKAGE_NAME_STRING); 057 058 /** 059 * Checks whether the given file is loaded from the location where Kotlin's built-in classes are defined. 060 * As of today, this is idea/builtinsSrc/jet directory and files such as Any.jet, Nothing.jet etc. 061 * 062 * Used to skip JetLightClass creation for built-ins, because built-in classes have no Java counterparts 063 */ 064 public static boolean belongsToKotlinBuiltIns(@NotNull JetFile file) { 065 VirtualFile virtualFile = file.getVirtualFile(); 066 if (virtualFile != null) { 067 VirtualFile parent = virtualFile.getParent(); 068 if (parent != null) { 069 try { 070 String jetVfsPathUrl = KotlinVfsUtil.convertFromUrl(getBuiltInsDirUrl()); 071 String fileDirVfsUrl = parent.getUrl(); 072 if (jetVfsPathUrl.equals(fileDirVfsUrl)) { 073 return true; 074 } 075 } 076 catch (MalformedURLException e) { 077 LOG.error(e); 078 } 079 } 080 } 081 082 // We deliberately return false on error: who knows what weird URLs we might come across out there 083 // it would be a pity if no light classes would be created in such cases 084 return false; 085 } 086 087 @NotNull 088 public static URL getBuiltInsDirUrl() { 089 String builtInFilePath = "/" + KotlinBuiltIns.BUILT_INS_PACKAGE_NAME_STRING + "/Library.kt"; 090 091 URL url = KotlinBuiltIns.class.getResource(builtInFilePath); 092 093 if (url == null) { 094 if (ApplicationManager.getApplication().isUnitTestMode()) { 095 // HACK: Temp code. Get built-in files from the sources when running from test. 096 try { 097 return new URL(StandardFileSystems.FILE_PROTOCOL, "", 098 FileUtil.toSystemIndependentName(BUILT_INS_SRC_DIR.getAbsolutePath())); 099 } 100 catch (MalformedURLException e) { 101 throw ExceptionUtils.rethrow(e); 102 } 103 } 104 105 throw new IllegalStateException("Built-ins file wasn't found at url: " + builtInFilePath); 106 } 107 108 try { 109 return new URL(url.getProtocol(), url.getHost(), PathUtil.getParentPath(url.getFile())); 110 } 111 catch (MalformedURLException e) { 112 throw new AssertionError(e); 113 } 114 } 115 116 @Nullable 117 /*package*/ static PsiClass findClass(@NotNull FqName fqn, @NotNull StubElement<?> stub) { 118 if (stub instanceof PsiClassStub && Comparing.equal(fqn.asString(), ((PsiClassStub) stub).getQualifiedName())) { 119 return (PsiClass) stub.getPsi(); 120 } 121 122 if (stub instanceof PsiClassStub || stub instanceof PsiFileStub) { 123 for (StubElement child : stub.getChildrenStubs()) { 124 PsiClass answer = findClass(fqn, child); 125 if (answer != null) return answer; 126 } 127 } 128 129 return null; 130 } 131 132 @Nullable 133 public static PsiClass getPsiClass(@Nullable JetClassOrObject classOrObject) { 134 if (classOrObject == null) return null; 135 return LightClassGenerationSupport.getInstance(classOrObject.getProject()).getPsiClass(classOrObject); 136 } 137 138 @Nullable 139 public static PsiMethod getLightClassAccessorMethod(@NotNull JetPropertyAccessor accessor) { 140 return getPsiMethodWrapper(accessor); 141 } 142 143 @NotNull 144 public static PropertyAccessorsPsiMethods getLightClassPropertyMethods(@NotNull JetProperty property) { 145 JetPropertyAccessor getter = property.getGetter(); 146 JetPropertyAccessor setter = property.getSetter(); 147 148 PsiMethod getterWrapper = getter != null ? getLightClassAccessorMethod(getter) : null; 149 PsiMethod setterWrapper = setter != null ? getLightClassAccessorMethod(setter) : null; 150 151 return extractPropertyAccessors(property, getterWrapper, setterWrapper); 152 } 153 154 @NotNull 155 public static PropertyAccessorsPsiMethods getLightClassPropertyMethods(@NotNull JetParameter parameter) { 156 return extractPropertyAccessors(parameter, null, null); 157 } 158 159 @Nullable 160 public static PsiMethod getLightClassMethod(@NotNull JetNamedFunction function) { 161 return getPsiMethodWrapper(function); 162 } 163 164 @Nullable 165 private static PsiMethod getPsiMethodWrapper(@NotNull JetDeclaration declaration) { 166 List<PsiMethod> wrappers = getPsiMethodWrappers(declaration, false); 167 return !wrappers.isEmpty() ? wrappers.get(0) : null; 168 } 169 170 @NotNull 171 private static List<PsiMethod> getPsiMethodWrappers(@NotNull JetDeclaration declaration, boolean collectAll) { 172 PsiClass psiClass = getWrappingClass(declaration); 173 if (psiClass == null) { 174 return Collections.emptyList(); 175 } 176 177 List<PsiMethod> methods = new SmartList<PsiMethod>(); 178 for (PsiMethod method : psiClass.getMethods()) { 179 try { 180 if (method instanceof KotlinLightMethod && ((KotlinLightMethod) method).getOrigin() == declaration) { 181 methods.add(method); 182 if (!collectAll) { 183 return methods; 184 } 185 } 186 } 187 catch (ProcessCanceledException e) { 188 throw e; 189 } 190 catch (Throwable e) { 191 throw new IllegalStateException( 192 "Error while wrapping declaration " + declaration.getName() + 193 "Context\n:" + 194 String.format("=== In file ===\n" + 195 "%s\n" + 196 "=== On element ===\n" + 197 "%s\n" + 198 "=== WrappedElement ===\n" + 199 "%s\n", 200 declaration.getContainingFile().getText(), 201 declaration.getText(), 202 method.toString()), 203 e 204 ); 205 } 206 } 207 208 return methods; 209 } 210 211 @Nullable 212 private static PsiClass getWrappingClass(@NotNull JetDeclaration declaration) { 213 if (declaration instanceof JetParameter) { 214 JetClass constructorClass = JetPsiUtil.getClassIfParameterIsProperty((JetParameter) declaration); 215 if (constructorClass != null) { 216 return getPsiClass(constructorClass); 217 } 218 } 219 220 if (declaration instanceof JetPropertyAccessor) { 221 PsiElement propertyParent = declaration.getParent(); 222 assert propertyParent instanceof JetProperty : "JetProperty is expected to be parent of accessor"; 223 224 declaration = (JetProperty) propertyParent; 225 } 226 227 //noinspection unchecked 228 if (PsiTreeUtil.getParentOfType(declaration, JetFunction.class, JetProperty.class) != null) { 229 // Can't get wrappers for internal declarations. Their classes are not generated during calcStub 230 // with ClassBuilderMode.LIGHT_CLASSES mode, and this produces "Class not found exception" in getDelegate() 231 return null; 232 } 233 234 PsiElement parent = declaration.getParent(); 235 236 if (parent instanceof JetFile) { 237 // top-level declaration 238 FqName fqName = getPackageClassNameForFile((JetFile) parent); 239 if (fqName != null) { 240 Project project = declaration.getProject(); 241 242 return JavaElementFinder.getInstance(project).findClass(fqName.asString(), GlobalSearchScope.allScope(project)); 243 } 244 } 245 else if (parent instanceof JetClassBody) { 246 assert parent.getParent() instanceof JetClassOrObject; 247 return getPsiClass((JetClassOrObject) parent.getParent()); 248 } 249 250 return null; 251 } 252 253 @Nullable 254 private static FqName getPackageClassNameForFile(@NotNull JetFile jetFile) { 255 String packageName = jetFile.getPackageName(); 256 return packageName == null ? null : PackageClassUtils.getPackageClassFqName(new FqName(packageName)); 257 } 258 259 @NotNull 260 private static PropertyAccessorsPsiMethods extractPropertyAccessors( 261 @NotNull JetDeclaration jetDeclaration, 262 @Nullable PsiMethod specialGetter, @Nullable PsiMethod specialSetter 263 ) { 264 PsiMethod getterWrapper = specialGetter; 265 PsiMethod setterWrapper = specialSetter; 266 267 if (getterWrapper == null || setterWrapper == null) { 268 // If some getter or setter isn't found yet try to get it from wrappers for general declaration 269 270 List<PsiMethod> wrappers = getPsiMethodWrappers(jetDeclaration, true); 271 assert wrappers.size() <= 2 : "Maximum two wrappers are expected to be generated for declaration: " + jetDeclaration.getText(); 272 273 for (PsiMethod wrapper : wrappers) { 274 if (wrapper.getName().startsWith(JvmAbi.SETTER_PREFIX)) { 275 assert setterWrapper == null : String.format( 276 "Setter accessor isn't expected to be reassigned (old: %s, new: %s)", setterWrapper, wrapper); 277 278 setterWrapper = wrapper; 279 } 280 else { 281 assert getterWrapper == null : String.format( 282 "Getter accessor isn't expected to be reassigned (old: %s, new: %s)", getterWrapper, wrapper); 283 284 getterWrapper = wrapper; 285 } 286 } 287 } 288 289 return new PropertyAccessorsPsiMethods(getterWrapper, setterWrapper); 290 } 291 292 public static class PropertyAccessorsPsiMethods implements Iterable<PsiMethod> { 293 private final PsiMethod getter; 294 private final PsiMethod setter; 295 private final Collection<PsiMethod> accessors = new ArrayList<PsiMethod>(2); 296 297 PropertyAccessorsPsiMethods(@Nullable PsiMethod getter, @Nullable PsiMethod setter) { 298 this.getter = getter; 299 if (getter != null) { 300 accessors.add(getter); 301 } 302 303 this.setter = setter; 304 if (setter != null) { 305 accessors.add(setter); 306 } 307 } 308 309 @Nullable 310 public PsiMethod getGetter() { 311 return getter; 312 } 313 314 @Nullable 315 public PsiMethod getSetter() { 316 return setter; 317 } 318 319 @NotNull 320 @Override 321 public Iterator<PsiMethod> iterator() { 322 return accessors.iterator(); 323 } 324 } 325 326 private LightClassUtil() { 327 } 328 }