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    }