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