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