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.diagnostic.Logger;
020    import com.intellij.openapi.progress.ProcessCanceledException;
021    import com.intellij.openapi.project.Project;
022    import com.intellij.openapi.util.Comparing;
023    import com.intellij.openapi.util.SystemInfo;
024    import com.intellij.openapi.vfs.VirtualFile;
025    import com.intellij.psi.PsiClass;
026    import com.intellij.psi.PsiElement;
027    import com.intellij.psi.PsiMethod;
028    import com.intellij.psi.impl.java.stubs.PsiClassStub;
029    import com.intellij.psi.search.GlobalSearchScope;
030    import com.intellij.psi.stubs.PsiFileStub;
031    import com.intellij.psi.stubs.StubElement;
032    import com.intellij.psi.util.PsiTreeUtil;
033    import com.intellij.util.SmartList;
034    import org.jetbrains.annotations.NotNull;
035    import org.jetbrains.annotations.Nullable;
036    import org.jetbrains.jet.codegen.binding.PsiCodegenPredictor;
037    import org.jetbrains.jet.lang.psi.*;
038    import org.jetbrains.jet.lang.resolve.java.JetClsMethod;
039    import org.jetbrains.jet.lang.resolve.java.JvmAbi;
040    import org.jetbrains.jet.lang.resolve.java.JvmClassName;
041    import org.jetbrains.jet.lang.resolve.name.FqName;
042    import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
043    import org.jetbrains.jet.utils.KotlinVfsUtil;
044    
045    import java.net.MalformedURLException;
046    import java.net.URL;
047    import java.util.*;
048    
049    public class LightClassUtil {
050        private static final Logger LOG = Logger.getInstance(LightClassUtil.class);
051        private static final String DEFINITION_OF_ANY = "Any.jet";
052    
053        /**
054         * Checks whether the given file is loaded from the location where Kotlin's built-in classes are defined.
055         * As of today, this is compiler/frontend/src/jet directory and files such as Any.jet, Nothing.jet etc.
056         *
057         * Used to skip JetLightClass creation for built-ins, because built-in classes have no Java counterparts
058         */
059        public static boolean belongsToKotlinBuiltIns(@NotNull JetFile file) {
060            try {
061                String jetVfsPathUrl = KotlinVfsUtil.convertFromUrl(getBuiltInsDirResourceUrl());
062                VirtualFile virtualFile = file.getVirtualFile();
063                if (virtualFile != null) {
064                    VirtualFile parent = virtualFile.getParent();
065                    if (parent != null) {
066                        String fileDirVfsUrl = parent.getUrl() + "/" + DEFINITION_OF_ANY;
067                        if (jetVfsPathUrl.equals(fileDirVfsUrl)) {
068                            return true;
069                        }
070                    }
071                }
072            }
073            catch (MalformedURLException e) {
074                LOG.error(e);
075            }
076            // We deliberately return false on error: who knows what weird URLs we might come across out there
077            // it would be a pity if no light classes would be created in such cases
078            return false;
079        }
080    
081        @NotNull
082        public static URL getBuiltInsDirResourceUrl() {
083            String pathToAny = "/" + KotlinBuiltIns.BUILT_INS_DIR + "/" + DEFINITION_OF_ANY;
084            URL url = KotlinBuiltIns.class.getResource(pathToAny);
085            if (url == null) {
086                throw new IllegalStateException("Built-ins not found in the classpath: " + pathToAny);
087            }
088            return url;
089        }
090    
091        /*package*/ static void logErrorWithOSInfo(@Nullable Throwable cause, @NotNull FqName fqName, @Nullable VirtualFile virtualFile) {
092            String path = virtualFile == null ? "<null>" : virtualFile.getPath();
093            LOG.error(
094                    "Could not generate LightClass for " + fqName + " declared in " + path + "\n" +
095                    "built-ins dir URL is " + getBuiltInsDirResourceUrl() + "\n" +
096                    "System: " + SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION + " Java Runtime: " + SystemInfo.JAVA_RUNTIME_VERSION,
097                    cause);
098        }
099    
100        @Nullable
101        /*package*/ static PsiClass findClass(@NotNull FqName fqn, @NotNull StubElement<?> stub) {
102            if (stub instanceof PsiClassStub && Comparing.equal(fqn.asString(), ((PsiClassStub) stub).getQualifiedName())) {
103                return (PsiClass)stub.getPsi();
104            }
105    
106            if (stub instanceof PsiClassStub || stub instanceof PsiFileStub) {
107                for (StubElement child : stub.getChildrenStubs()) {
108                    PsiClass answer = findClass(fqn, child);
109                    if (answer != null) return answer;
110                }
111            }
112    
113            return null;
114        }
115    
116        @Nullable
117        public static PsiClass getPsiClass(@Nullable JetClassOrObject classOrObject) {
118            if (classOrObject == null) return null;
119            return LightClassGenerationSupport.getInstance(classOrObject.getProject()).getPsiClass(classOrObject);
120        }
121    
122        @Nullable
123        public static PsiMethod getLightClassAccessorMethod(@NotNull JetPropertyAccessor accessor) {
124            return getPsiMethodWrapper(accessor);
125        }
126    
127        @NotNull
128        public static PropertyAccessorsPsiMethods getLightClassPropertyMethods(@NotNull JetProperty property) {
129            JetPropertyAccessor getter = property.getGetter();
130            JetPropertyAccessor setter = property.getSetter();
131    
132            PsiMethod getterWrapper = getter != null ? getLightClassAccessorMethod(getter) : null;
133            PsiMethod setterWrapper = setter != null ? getLightClassAccessorMethod(setter) : null;
134    
135            return extractPropertyAccessors(property, getterWrapper, setterWrapper);
136        }
137    
138        public static PropertyAccessorsPsiMethods getLightClassPropertyMethods(@NotNull JetParameter parameter) {
139            return extractPropertyAccessors(parameter, null, null);
140        }
141    
142        @Nullable
143        public static PsiMethod getLightClassMethod(@NotNull JetNamedFunction function) {
144            return getPsiMethodWrapper(function);
145        }
146    
147        @Nullable
148        private static PsiMethod getPsiMethodWrapper(@NotNull JetDeclaration declaration) {
149            List<PsiMethod> wrappers = getPsiMethodWrappers(declaration, false);
150            return !wrappers.isEmpty() ? wrappers.get(0) : null;
151        }
152    
153        @NotNull
154        private static List<PsiMethod> getPsiMethodWrappers(@NotNull JetDeclaration declaration, boolean collectAll) {
155            PsiClass psiClass = getWrappingClass(declaration);
156            if (psiClass == null) {
157                return Collections.emptyList();
158            }
159    
160            List<PsiMethod> methods = new SmartList<PsiMethod>();
161            for (PsiMethod method : psiClass.getMethods()) {
162                try {
163                    if (method instanceof JetClsMethod && ((JetClsMethod) method).getOrigin() == declaration) {
164                        methods.add(method);
165                        if (!collectAll) {
166                            return methods;
167                        }
168                    }
169                }
170                catch (ProcessCanceledException e) {
171                    throw e;
172                }
173                catch (Throwable e) {
174                    throw new IllegalStateException(
175                            "Error while wrapping declaration " + declaration.getName() +
176                            "Context\n:" +
177                            String.format("=== In file ===\n" +
178                                          "%s\n" +
179                                          "=== On element ===\n" +
180                                          "%s\n" +
181                                          "=== WrappedElement ===\n" +
182                                          "%s\n",
183                                          declaration.getContainingFile().getText(),
184                                          declaration.getText(),
185                                          method.toString()),
186                            e
187                    );
188                }
189            }
190    
191            return methods;
192        }
193    
194        @Nullable
195        private static PsiClass getWrappingClass(@NotNull JetDeclaration declaration) {
196            if (declaration instanceof JetParameter) {
197                JetClass constructorClass = JetPsiUtil.getClassIfParameterIsProperty((JetParameter) declaration);
198                if (constructorClass != null) {
199                    return getPsiClass(constructorClass);
200                }
201            }
202    
203            if (declaration instanceof JetPropertyAccessor) {
204                PsiElement propertyParent = declaration.getParent();
205                assert propertyParent instanceof JetProperty : "JetProperty is expected to be parent of accessor";
206    
207                declaration = (JetProperty) propertyParent;
208            }
209    
210            //noinspection unchecked
211            if (PsiTreeUtil.getParentOfType(declaration, JetFunction.class, JetProperty.class) != null) {
212                // Can't get wrappers for internal declarations. Their classes are not generated during calcStub
213                // with ClassBuilderMode.SIGNATURES mode, and this produces "Class not found exception" in getDelegate()
214                return null;
215            }
216    
217            PsiElement parent = declaration.getParent();
218    
219            if (parent instanceof JetFile) {
220                // top-level declaration
221                JvmClassName jvmName = PsiCodegenPredictor.getPredefinedJvmClassName((JetFile) parent, true);
222                if (jvmName != null) {
223                    Project project = declaration.getProject();
224    
225                    String fqName = jvmName.getFqName().asString();
226                    return JavaElementFinder.getInstance(project).findClass(fqName, GlobalSearchScope.allScope(project));
227                }
228            }
229            else if (parent instanceof JetClassBody) {
230                assert parent.getParent() instanceof JetClassOrObject;
231                return getPsiClass((JetClassOrObject) parent.getParent());
232            }
233    
234            return null;
235        }
236    
237        private static PropertyAccessorsPsiMethods extractPropertyAccessors(
238                @NotNull JetDeclaration jetDeclaration,
239                @Nullable PsiMethod specialGetter, @Nullable PsiMethod specialSetter)
240        {
241            PsiMethod getterWrapper = specialGetter;
242            PsiMethod setterWrapper = specialSetter;
243    
244            if (getterWrapper == null || setterWrapper == null) {
245                // If some getter or setter isn't found yet try to get it from wrappers for general declaration
246    
247                List<PsiMethod> wrappers = getPsiMethodWrappers(jetDeclaration, true);
248                assert wrappers.size() <= 2 : "Maximum two wrappers are expected to be generated for declaration: " + jetDeclaration.getText();
249    
250                for (PsiMethod wrapper : wrappers) {
251                    if (wrapper.getName().startsWith(JvmAbi.SETTER_PREFIX)) {
252                        assert setterWrapper == null : String.format(
253                                "Setter accessor isn't expected to be reassigned (old: %s, new: %s)", setterWrapper, wrapper);
254    
255                        setterWrapper = wrapper;
256                    }
257                    else {
258                        assert getterWrapper == null : String.format(
259                                "Getter accessor isn't expected to be reassigned (old: %s, new: %s)", getterWrapper, wrapper);
260    
261                        getterWrapper = wrapper;
262                    }
263                }
264            }
265    
266            return new PropertyAccessorsPsiMethods(getterWrapper, setterWrapper);
267        }
268    
269        public static class PropertyAccessorsPsiMethods implements Iterable<PsiMethod> {
270            private final PsiMethod getter;
271            private final PsiMethod setter;
272            private final Collection<PsiMethod> accessors = new ArrayList<PsiMethod>(2);
273    
274            PropertyAccessorsPsiMethods(@Nullable PsiMethod getter, @Nullable PsiMethod setter) {
275                this.getter = getter;
276                if (getter != null) {
277                    accessors.add(getter);
278                }
279    
280                this.setter = setter;
281                if (setter != null) {
282                    accessors.add(setter);
283                }
284            }
285    
286            @Nullable
287            public PsiMethod getGetter() {
288                return getter;
289            }
290    
291            @Nullable
292            public PsiMethod getSetter() {
293                return setter;
294            }
295    
296            @NotNull
297            @Override
298            public Iterator<PsiMethod> iterator() {
299                return accessors.iterator();
300            }
301        }
302    
303        private LightClassUtil() {}
304    }