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