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.Function1;
037    import kotlin.KotlinPackage;
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        @NotNull
153        public static PropertyAccessorsPsiMethods getLightClassPropertyMethods(@NotNull JetProperty property) {
154            JetPropertyAccessor getter = property.getGetter();
155            JetPropertyAccessor setter = property.getSetter();
156    
157            PsiMethod getterWrapper = getter != null ? getLightClassAccessorMethod(getter) : null;
158            PsiMethod setterWrapper = setter != null ? getLightClassAccessorMethod(setter) : null;
159    
160            return extractPropertyAccessors(property, getterWrapper, setterWrapper);
161        }
162    
163        @NotNull
164        public static PropertyAccessorsPsiMethods getLightClassPropertyMethods(@NotNull JetParameter parameter) {
165            return extractPropertyAccessors(parameter, null, null);
166        }
167    
168        @Nullable
169        public static PsiMethod getLightClassMethod(@NotNull JetNamedFunction function) {
170            return getPsiMethodWrapper(function);
171        }
172    
173        @Nullable
174        private static PsiMethod getPsiMethodWrapper(@NotNull JetDeclaration declaration) {
175            List<PsiMethod> wrappers = getPsiMethodWrappers(declaration, false);
176            return !wrappers.isEmpty() ? wrappers.get(0) : null;
177        }
178    
179        @NotNull
180        private static List<PsiMethod> getPsiMethodWrappers(@NotNull JetDeclaration declaration, boolean collectAll) {
181            PsiClass psiClass = getWrappingClass(declaration);
182            if (psiClass == null) {
183                return Collections.emptyList();
184            }
185    
186            List<PsiMethod> methods = new SmartList<PsiMethod>();
187            for (PsiMethod method : psiClass.getMethods()) {
188                try {
189                    if (method instanceof KotlinLightMethod && ((KotlinLightMethod) method).getOrigin() == declaration) {
190                        methods.add(method);
191                        if (!collectAll) {
192                            return methods;
193                        }
194                    }
195                }
196                catch (ProcessCanceledException e) {
197                    throw e;
198                }
199                catch (Throwable e) {
200                    throw new IllegalStateException(
201                            "Error while wrapping declaration " + declaration.getName() +
202                            "Context\n:" +
203                            String.format("=== In file ===\n" +
204                                          "%s\n" +
205                                          "=== On element ===\n" +
206                                          "%s\n" +
207                                          "=== WrappedElement ===\n" +
208                                          "%s\n",
209                                          declaration.getContainingFile().getText(),
210                                          declaration.getText(),
211                                          method.toString()),
212                            e
213                    );
214                }
215            }
216    
217            return methods;
218        }
219    
220        @Nullable
221        private static PsiClass getWrappingClass(@NotNull JetDeclaration declaration) {
222            if (declaration instanceof JetParameter) {
223                JetClass constructorClass = JetPsiUtil.getClassIfParameterIsProperty((JetParameter) declaration);
224                if (constructorClass != null) {
225                    return getPsiClass(constructorClass);
226                }
227            }
228    
229            if (declaration instanceof JetPropertyAccessor) {
230                PsiElement propertyParent = declaration.getParent();
231                assert propertyParent instanceof JetProperty : "JetProperty is expected to be parent of accessor";
232    
233                declaration = (JetProperty) propertyParent;
234            }
235    
236            //noinspection unchecked
237            if (PsiTreeUtil.getParentOfType(declaration, JetFunction.class, JetProperty.class) != null) {
238                // Can't get wrappers for internal declarations. Their classes are not generated during calcStub
239                // with ClassBuilderMode.LIGHT_CLASSES mode, and this produces "Class not found exception" in getDelegate()
240                return null;
241            }
242    
243            PsiElement parent = declaration.getParent();
244    
245            if (parent instanceof JetFile) {
246                // top-level declaration
247                FqName fqName = PackageClassUtils.getPackageClassFqName(((JetFile) parent).getPackageFqName());
248                Project project = declaration.getProject();
249                return JavaElementFinder.getInstance(project).findClass(fqName.asString(), GlobalSearchScope.allScope(project));
250            }
251            else if (parent instanceof JetClassBody) {
252                assert parent.getParent() instanceof JetClassOrObject;
253                return getPsiClass((JetClassOrObject) parent.getParent());
254            }
255    
256            return null;
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 = KotlinPackage.filter(
271                        getPsiMethodWrappers(jetDeclaration, true),
272                        new Function1<PsiMethod, Boolean>() {
273                            @Override
274                            public Boolean invoke(PsiMethod method) {
275                                return JvmAbi.isAccessorName(method.getName());
276                            }
277                        }
278                );
279                assert wrappers.size() <= 2 : "Maximum two wrappers are expected to be generated for declaration: " + jetDeclaration.getText();
280    
281                for (PsiMethod wrapper : wrappers) {
282                    if (wrapper.getName().startsWith(JvmAbi.SETTER_PREFIX)) {
283                        assert setterWrapper == null : String.format(
284                                "Setter accessor isn't expected to be reassigned (old: %s, new: %s)", setterWrapper, wrapper);
285    
286                        setterWrapper = wrapper;
287                    }
288                    else {
289                        assert getterWrapper == null : String.format(
290                                "Getter accessor isn't expected to be reassigned (old: %s, new: %s)", getterWrapper, wrapper);
291    
292                        getterWrapper = wrapper;
293                    }
294                }
295            }
296    
297            return new PropertyAccessorsPsiMethods(getterWrapper, setterWrapper);
298        }
299    
300        @NotNull
301        public static PsiTypeParameterList buildLightTypeParameterList(
302                PsiTypeParameterListOwner owner,
303                JetDeclaration declaration) {
304            LightTypeParameterListBuilder builder = new KotlinLightTypeParameterListBuilder(owner.getManager());
305            if (declaration instanceof JetTypeParameterListOwner) {
306                JetTypeParameterListOwner typeParameterListOwner = (JetTypeParameterListOwner) declaration;
307                List<JetTypeParameter> parameters = typeParameterListOwner.getTypeParameters();
308                for (int i = 0; i < parameters.size(); i++) {
309                    JetTypeParameter jetTypeParameter = parameters.get(i);
310                    String name = jetTypeParameter.getName();
311                    String safeName = name == null ? "__no_name__" : name;
312                    builder.addParameter(new KotlinLightTypeParameter(owner, i, safeName));
313                }
314            }
315            return builder;
316        }
317    
318        public static class PropertyAccessorsPsiMethods implements Iterable<PsiMethod> {
319            private final PsiMethod getter;
320            private final PsiMethod setter;
321            private final Collection<PsiMethod> accessors = new ArrayList<PsiMethod>(2);
322    
323            PropertyAccessorsPsiMethods(@Nullable PsiMethod getter, @Nullable PsiMethod setter) {
324                this.getter = getter;
325                if (getter != null) {
326                    accessors.add(getter);
327                }
328    
329                this.setter = setter;
330                if (setter != null) {
331                    accessors.add(setter);
332                }
333            }
334    
335            @Nullable
336            public PsiMethod getGetter() {
337                return getter;
338            }
339    
340            @Nullable
341            public PsiMethod getSetter() {
342                return setter;
343            }
344    
345            @NotNull
346            @Override
347            public Iterator<PsiMethod> iterator() {
348                return accessors.iterator();
349            }
350        }
351    
352        private LightClassUtil() {
353        }
354    }