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