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.js.translate.utils;
018    
019    import com.intellij.psi.PsiFile;
020    import org.jetbrains.annotations.NotNull;
021    import org.jetbrains.annotations.Nullable;
022    import org.jetbrains.kotlin.descriptors.*;
023    import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
024    import org.jetbrains.kotlin.descriptors.annotations.AnnotationWithTarget;
025    import org.jetbrains.kotlin.descriptors.annotations.Annotations;
026    import org.jetbrains.kotlin.js.PredefinedAnnotation;
027    import org.jetbrains.kotlin.name.FqName;
028    import org.jetbrains.kotlin.name.FqNameUnsafe;
029    import org.jetbrains.kotlin.psi.KtAnnotationEntry;
030    import org.jetbrains.kotlin.psi.KtFile;
031    import org.jetbrains.kotlin.resolve.BindingContext;
032    import org.jetbrains.kotlin.resolve.DescriptorUtils;
033    import org.jetbrains.kotlin.resolve.constants.ConstantValue;
034    import org.jetbrains.kotlin.resolve.source.PsiSourceFile;
035    import org.jetbrains.kotlin.serialization.js.KotlinJavascriptPackageFragment;
036    
037    import java.util.ArrayList;
038    import java.util.Collections;
039    import java.util.List;
040    
041    public final class AnnotationsUtils {
042        private static final String JS_NAME = "kotlin.js.JsName";
043        public static final FqName JS_MODULE_ANNOTATION = new FqName("kotlin.js.JsModule");
044        private static final FqName JS_NON_MODULE_ANNOTATION = new FqName("kotlin.js.JsNonModule");
045        public static final FqName JS_QUALIFIER_ANNOTATION = new FqName("kotlin.js.JsQualifier");
046    
047        private AnnotationsUtils() {
048        }
049    
050        public static boolean hasAnnotation(
051                @NotNull DeclarationDescriptor descriptor,
052                @NotNull PredefinedAnnotation annotation
053        ) {
054            return getAnnotationByName(descriptor, annotation) != null;
055        }
056    
057        @Nullable
058        private static String getAnnotationStringParameter(@NotNull DeclarationDescriptor declarationDescriptor,
059                @NotNull PredefinedAnnotation annotation) {
060            AnnotationDescriptor annotationDescriptor = getAnnotationByName(declarationDescriptor, annotation);
061            assert annotationDescriptor != null;
062            //TODO: this is a quick fix for unsupported default args problem
063            if (annotationDescriptor.getAllValueArguments().isEmpty()) {
064                return null;
065            }
066            ConstantValue<?> constant = annotationDescriptor.getAllValueArguments().values().iterator().next();
067            //TODO: this is a quick fix for unsupported default args problem
068            if (constant == null) {
069                return null;
070            }
071            Object value = constant.getValue();
072            assert value instanceof String : "Native function annotation should have one String parameter";
073            return (String) value;
074        }
075    
076        @Nullable
077        public static String getNameForAnnotatedObject(@NotNull DeclarationDescriptor declarationDescriptor,
078                @NotNull PredefinedAnnotation annotation) {
079            if (!hasAnnotation(declarationDescriptor, annotation)) {
080                return null;
081            }
082            return getAnnotationStringParameter(declarationDescriptor, annotation);
083        }
084    
085        @Nullable
086        public static String getNameForAnnotatedObject(@NotNull DeclarationDescriptor descriptor) {
087            String defaultJsName = getJsName(descriptor);
088    
089            for (PredefinedAnnotation annotation : PredefinedAnnotation.Companion.getWITH_CUSTOM_NAME()) {
090                if (!hasAnnotationOrInsideAnnotatedClass(descriptor, annotation)) {
091                    continue;
092                }
093                String name = getNameForAnnotatedObject(descriptor, annotation);
094                if (name == null) {
095                    name = defaultJsName;
096                }
097                return name != null ? name : descriptor.getName().asString();
098            }
099    
100            if (defaultJsName == null && isEffectivelyExternal(descriptor)) {
101                return descriptor.getName().asString();
102            }
103    
104            return defaultJsName;
105        }
106    
107        @Nullable
108        private static AnnotationDescriptor getAnnotationByName(
109                @NotNull DeclarationDescriptor descriptor,
110                @NotNull PredefinedAnnotation annotation
111        ) {
112            return getAnnotationByName(descriptor, annotation.getFqName());
113        }
114    
115        @Nullable
116        private static AnnotationDescriptor getAnnotationByName(@NotNull DeclarationDescriptor descriptor, @NotNull FqName fqName) {
117            AnnotationWithTarget annotationWithTarget = Annotations.Companion.findAnyAnnotation(descriptor.getAnnotations(), (fqName));
118            return annotationWithTarget != null ? annotationWithTarget.getAnnotation() : null;
119        }
120    
121        public static boolean isNativeObject(@NotNull DeclarationDescriptor descriptor) {
122            if (hasAnnotationOrInsideAnnotatedClass(descriptor, PredefinedAnnotation.NATIVE) || isEffectivelyExternal(descriptor)) return true;
123    
124            if (descriptor instanceof PropertyAccessorDescriptor) {
125                PropertyAccessorDescriptor accessor = (PropertyAccessorDescriptor) descriptor;
126                return hasAnnotationOrInsideAnnotatedClass(accessor.getCorrespondingProperty(), PredefinedAnnotation.NATIVE);
127            }
128    
129            return false;
130        }
131    
132        public static boolean isNativeInterface(@NotNull DeclarationDescriptor descriptor) {
133            return isNativeObject(descriptor) && DescriptorUtils.isInterface(descriptor);
134        }
135    
136        private static boolean isEffectivelyExternal(@NotNull DeclarationDescriptor descriptor) {
137            return descriptor instanceof MemberDescriptor && DescriptorUtils.isEffectivelyExternal((MemberDescriptor) descriptor);
138        }
139    
140        public static boolean isLibraryObject(@NotNull DeclarationDescriptor descriptor) {
141            return hasAnnotationOrInsideAnnotatedClass(descriptor, PredefinedAnnotation.LIBRARY);
142        }
143    
144        @Nullable
145        public static String getJsName(@NotNull DeclarationDescriptor descriptor) {
146            AnnotationDescriptor annotation = getJsNameAnnotation(descriptor);
147            if (annotation == null || annotation.getAllValueArguments().isEmpty()) return null;
148    
149            ConstantValue<?> value = annotation.getAllValueArguments().values().iterator().next();
150            if (value == null) return null;
151    
152            Object result = value.getValue();
153            if (!(result instanceof String)) return null;
154    
155            return (String) result;
156        }
157    
158        @Nullable
159        public static AnnotationDescriptor getJsNameAnnotation(@NotNull DeclarationDescriptor descriptor) {
160            return getAnnotationByName(descriptor, new FqName(JS_NAME));
161        }
162    
163        public static boolean isPredefinedObject(@NotNull DeclarationDescriptor descriptor) {
164            if (descriptor instanceof MemberDescriptor && ((MemberDescriptor) descriptor).isHeader()) return true;
165            if (isEffectivelyExternal(descriptor)) return true;
166    
167            for (PredefinedAnnotation annotation : PredefinedAnnotation.values()) {
168                if (hasAnnotationOrInsideAnnotatedClass(descriptor, annotation)) {
169                    return true;
170                }
171            }
172    
173            return false;
174        }
175    
176        private static boolean hasAnnotationOrInsideAnnotatedClass(
177                @NotNull DeclarationDescriptor descriptor,
178                @NotNull PredefinedAnnotation annotation
179        ) {
180            return hasAnnotationOrInsideAnnotatedClass(descriptor, annotation.getFqName());
181        }
182    
183        private static boolean hasAnnotationOrInsideAnnotatedClass(@NotNull DeclarationDescriptor descriptor, @NotNull FqName fqName) {
184            if (getAnnotationByName(descriptor, fqName) != null) {
185                return true;
186            }
187            ClassDescriptor containingClass = DescriptorUtils.getContainingClass(descriptor);
188            return containingClass != null && hasAnnotationOrInsideAnnotatedClass(containingClass, fqName);
189        }
190    
191        public static boolean hasJsNameInAccessors(@NotNull PropertyDescriptor property) {
192            for (PropertyAccessorDescriptor accessor : property.getAccessors()) {
193                if (getJsName(accessor) != null) return true;
194            }
195            return false;
196        }
197    
198        @Nullable
199        public static String getModuleName(@NotNull DeclarationDescriptor declaration) {
200            AnnotationDescriptor annotation = declaration.getAnnotations().findAnnotation(JS_MODULE_ANNOTATION);
201            return annotation != null ? extractSingleStringArgument(annotation) : null;
202        }
203    
204        @Nullable
205        public static String getFileModuleName(@NotNull BindingContext bindingContext, @NotNull DeclarationDescriptor declaration) {
206            return getSingleStringAnnotationArgument(bindingContext, declaration, JS_MODULE_ANNOTATION);
207        }
208    
209        @Nullable
210        public static String getFileQualifier(@NotNull BindingContext bindingContext, @NotNull DeclarationDescriptor declaration) {
211            return getSingleStringAnnotationArgument(bindingContext, declaration, JS_QUALIFIER_ANNOTATION);
212        }
213    
214        @Nullable
215        private static String getSingleStringAnnotationArgument(
216                @NotNull BindingContext bindingContext,
217                @NotNull DeclarationDescriptor declaration,
218                @NotNull FqName annotationFqName
219        ) {
220            for (AnnotationDescriptor annotation : getContainingFileAnnotations(bindingContext, declaration)) {
221                DeclarationDescriptor annotationType = annotation.getType().getConstructor().getDeclarationDescriptor();
222                if (annotationType == null) continue;
223    
224                FqNameUnsafe fqName = DescriptorUtils.getFqName(annotation.getType().getConstructor().getDeclarationDescriptor());
225                if (fqName.equals(annotationFqName.toUnsafe())) {
226                    return extractSingleStringArgument(annotation);
227                }
228            }
229            return null;
230        }
231    
232        public static boolean isNonModule(@NotNull DeclarationDescriptor declaration) {
233            return declaration.getAnnotations().findAnnotation(JS_NON_MODULE_ANNOTATION) != null;
234        }
235    
236        public static boolean isFromNonModuleFile(@NotNull BindingContext bindingContext, @NotNull DeclarationDescriptor declaration) {
237            for (AnnotationDescriptor annotation : getContainingFileAnnotations(bindingContext, declaration)) {
238                DeclarationDescriptor annotationType = annotation.getType().getConstructor().getDeclarationDescriptor();
239                if (annotationType == null) continue;
240    
241                DeclarationDescriptor annotationTypeDescriptor = annotation.getType().getConstructor().getDeclarationDescriptor();
242                assert annotationTypeDescriptor != null : "Annotation type should have descriptor: " + annotation.getType();
243    
244                FqNameUnsafe fqName = DescriptorUtils.getFqName(annotationTypeDescriptor);
245                if (fqName.equals(JS_NON_MODULE_ANNOTATION.toUnsafe())) {
246                    return true;
247                }
248            }
249    
250            return false;
251        }
252    
253        @Nullable
254        private static String extractSingleStringArgument(@NotNull AnnotationDescriptor annotation) {
255            if (annotation.getAllValueArguments().isEmpty()) return null;
256    
257            ConstantValue<?> importValue = annotation.getAllValueArguments().values().iterator().next();
258            if (importValue == null) return null;
259    
260            if (!(importValue.getValue() instanceof String)) return null;
261            return (String) importValue.getValue();
262        }
263    
264        @NotNull
265        public static List<AnnotationDescriptor> getContainingFileAnnotations(
266                @NotNull BindingContext bindingContext,
267                @NotNull DeclarationDescriptor descriptor
268        ) {
269            PackageFragmentDescriptor containingPackage = DescriptorUtils.getParentOfType(descriptor, PackageFragmentDescriptor.class, false);
270            if (containingPackage instanceof KotlinJavascriptPackageFragment) {
271                return ((KotlinJavascriptPackageFragment) containingPackage).getContainingFileAnnotations(descriptor);
272            }
273    
274            KtFile kotlinFile = getFile(descriptor);
275            if (kotlinFile != null) {
276                List<AnnotationDescriptor> annotations = new ArrayList<AnnotationDescriptor>();
277                for (KtAnnotationEntry psiAnnotation : kotlinFile.getAnnotationEntries()) {
278                    AnnotationDescriptor annotation = bindingContext.get(BindingContext.ANNOTATION, psiAnnotation);
279                    if (annotation != null) {
280                        annotations.add(annotation);
281                    }
282                }
283                return annotations;
284            }
285    
286            return Collections.emptyList();
287        }
288    
289        @Nullable
290        private static KtFile getFile(DeclarationDescriptor descriptor) {
291            if (!(descriptor instanceof DeclarationDescriptorWithSource)) return null;
292            SourceFile file = ((DeclarationDescriptorWithSource) descriptor).getSource().getContainingFile();
293            if (!(file instanceof PsiSourceFile)) return null;
294    
295            PsiFile psiFile = ((PsiSourceFile) file).getPsiFile();
296            if (!(psiFile instanceof KtFile)) return null;
297    
298            return (KtFile) psiFile;
299        }
300    }