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 }