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.asJava;
018
019import com.intellij.openapi.diagnostic.Logger;
020import com.intellij.openapi.progress.ProcessCanceledException;
021import com.intellij.openapi.project.Project;
022import com.intellij.openapi.util.Comparing;
023import com.intellij.openapi.util.SystemInfo;
024import com.intellij.openapi.vfs.VirtualFile;
025import com.intellij.psi.PsiClass;
026import com.intellij.psi.PsiElement;
027import com.intellij.psi.PsiMethod;
028import com.intellij.psi.impl.java.stubs.PsiClassStub;
029import com.intellij.psi.search.GlobalSearchScope;
030import com.intellij.psi.stubs.PsiFileStub;
031import com.intellij.psi.stubs.StubElement;
032import com.intellij.psi.util.PsiTreeUtil;
033import com.intellij.util.SmartList;
034import org.jetbrains.annotations.NotNull;
035import org.jetbrains.annotations.Nullable;
036import org.jetbrains.jet.codegen.binding.PsiCodegenPredictor;
037import org.jetbrains.jet.lang.psi.*;
038import org.jetbrains.jet.lang.resolve.java.JetClsMethod;
039import org.jetbrains.jet.lang.resolve.java.JvmAbi;
040import org.jetbrains.jet.lang.resolve.java.JvmClassName;
041import org.jetbrains.jet.lang.resolve.name.FqName;
042import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
043import org.jetbrains.jet.utils.KotlinVfsUtil;
044
045import java.net.MalformedURLException;
046import java.net.URL;
047import java.util.*;
048
049public 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}