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.codegen;
018    
019    import com.google.common.collect.Lists;
020    import com.intellij.openapi.application.ApplicationManager;
021    import com.intellij.openapi.progress.ProcessCanceledException;
022    import com.intellij.openapi.util.io.FileUtil;
023    import com.intellij.openapi.vfs.VirtualFile;
024    import com.intellij.psi.PsiFile;
025    import com.intellij.util.PathUtil;
026    import org.jetbrains.annotations.NotNull;
027    import org.jetbrains.annotations.Nullable;
028    import org.jetbrains.asm4.AnnotationVisitor;
029    import org.jetbrains.asm4.MethodVisitor;
030    import org.jetbrains.asm4.Type;
031    import org.jetbrains.jet.codegen.context.CodegenContext;
032    import org.jetbrains.jet.codegen.context.FieldOwnerContext;
033    import org.jetbrains.jet.codegen.state.GenerationState;
034    import org.jetbrains.jet.descriptors.serialization.DescriptorSerializer;
035    import org.jetbrains.jet.descriptors.serialization.JavaProtoBufUtil;
036    import org.jetbrains.jet.descriptors.serialization.PackageData;
037    import org.jetbrains.jet.descriptors.serialization.ProtoBuf;
038    import org.jetbrains.jet.lang.descriptors.*;
039    import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptor;
040    import org.jetbrains.jet.lang.descriptors.impl.SimpleFunctionDescriptorImpl;
041    import org.jetbrains.jet.lang.diagnostics.DiagnosticUtils;
042    import org.jetbrains.jet.lang.psi.*;
043    import org.jetbrains.jet.lang.resolve.BindingContext;
044    import org.jetbrains.jet.lang.resolve.java.JvmAbi;
045    import org.jetbrains.jet.lang.resolve.java.JvmAnnotationNames;
046    import org.jetbrains.jet.lang.resolve.java.JvmClassName;
047    import org.jetbrains.jet.lang.resolve.java.PackageClassUtils;
048    import org.jetbrains.jet.lang.resolve.name.FqName;
049    import org.jetbrains.jet.lang.resolve.name.Name;
050    
051    import java.util.ArrayList;
052    import java.util.Collection;
053    import java.util.Collections;
054    import java.util.List;
055    
056    import static org.jetbrains.asm4.Opcodes.*;
057    import static org.jetbrains.jet.descriptors.serialization.NameSerializationUtil.createNameResolver;
058    
059    public class NamespaceCodegen extends MemberCodegen {
060        @NotNull
061        private final ClassBuilderOnDemand v;
062        @NotNull private final FqName name;
063        private final Collection<JetFile> files;
064        private final NamespaceDescriptor descriptor;
065    
066        public NamespaceCodegen(
067                @NotNull ClassBuilderOnDemand v,
068                @NotNull final FqName fqName,
069                GenerationState state,
070                Collection<JetFile> namespaceFiles
071        ) {
072            super(state, null);
073            checkAllFilesHaveSameNamespace(namespaceFiles);
074    
075            this.v = v;
076            name = fqName;
077            this.files = namespaceFiles;
078    
079            descriptor = state.getBindingContext().get(BindingContext.FQNAME_TO_NAMESPACE_DESCRIPTOR, name);
080            assert descriptor != null : "No namespace found for FQ name " + name;
081    
082            final PsiFile sourceFile = namespaceFiles.size() == 1 ? namespaceFiles.iterator().next().getContainingFile() : null;
083    
084            v.addOptionalDeclaration(new ClassBuilderOnDemand.ClassBuilderCallback() {
085                @Override
086                public void doSomething(@NotNull ClassBuilder v) {
087                    v.defineClass(sourceFile, V1_6,
088                                  ACC_PUBLIC | ACC_FINAL,
089                                  getJVMClassNameForKotlinNs(fqName).getInternalName(),
090                                  null,
091                                  //"jet/lang/Namespace",
092                                  "java/lang/Object",
093                                  new String[0]
094                    );
095                    //We don't generate any source information for namespace with multiple files
096                    if (sourceFile != null) {
097                        v.visitSource(sourceFile.getName(), null);
098                    }
099                }
100            });
101        }
102    
103        public void generate(@NotNull CompilationErrorHandler errorHandler) {
104            List<MemberMap> namespaceMembers = new ArrayList<MemberMap>(files.size() + 1);
105            boolean shouldGeneratePackageClass = shouldGenerateNSClass(files);
106            if (shouldGeneratePackageClass) {
107                namespaceMembers.add(v.getClassBuilder().getMemberMap());
108            }
109    
110            for (JetFile file : files) {
111                try {
112                    ClassBuilder builder = generate(file);
113                    if (builder != null) {
114                        namespaceMembers.add(builder.getMemberMap());
115                    }
116                }
117                catch (ProcessCanceledException e) {
118                    throw e;
119                }
120                catch (Throwable e) {
121                    VirtualFile vFile = file.getVirtualFile();
122                    errorHandler.reportException(e, vFile == null ? "no file" : vFile.getUrl());
123                    DiagnosticUtils.throwIfRunningOnServer(e);
124                    if (ApplicationManager.getApplication().isInternal()) {
125                        //noinspection CallToPrintStackTrace
126                        e.printStackTrace();
127                    }
128                }
129            }
130    
131            if (shouldGeneratePackageClass) {
132                writeKotlinPackageAnnotationIfNeeded(MemberMap.union(namespaceMembers));
133            }
134    
135            assert v.isActivated() == shouldGeneratePackageClass :
136                    "Different algorithms for generating namespace class and for heuristics for: " + name.asString();
137        }
138    
139        private void writeKotlinPackageAnnotationIfNeeded(@NotNull MemberMap members) {
140            for (JetFile file : files) {
141                if (file.isScript()) return;
142            }
143    
144            DescriptorSerializer serializer = new DescriptorSerializer(new JavaSerializerExtension(members));
145            ProtoBuf.Package packageProto = serializer.packageProto(descriptor).build();
146    
147            if (packageProto.getMemberCount() == 0) return;
148    
149            PackageData data = new PackageData(createNameResolver(serializer.getNameTable()), packageProto);
150    
151            AnnotationVisitor av = v.getClassBuilder().newAnnotation(JvmAnnotationNames.KOTLIN_PACKAGE.getDescriptor(), true);
152            av.visit(JvmAnnotationNames.ABI_VERSION_FIELD_NAME, JvmAbi.VERSION);
153            AnnotationVisitor array = av.visitArray(JvmAnnotationNames.DATA_FIELD_NAME);
154            for (String string : JavaProtoBufUtil.encodeBytes(data.toBytes())) {
155                array.visit(null, string);
156            }
157            array.visitEnd();
158            av.visitEnd();
159        }
160    
161        @Nullable
162        private ClassBuilder generate(@NotNull JetFile file) {
163            boolean generateSrcClass = false;
164            for (JetDeclaration declaration : file.getDeclarations()) {
165                if (declaration instanceof JetProperty || declaration instanceof JetNamedFunction) {
166                    generateSrcClass = true;
167                }
168                else if (declaration instanceof JetClassOrObject) {
169                    if (state.isGenerateDeclaredClasses()) {
170                        generateClassOrObject((JetClassOrObject) declaration);
171                    }
172                }
173                else if (declaration instanceof JetScript) {
174                    state.getScriptCodegen().generate((JetScript) declaration);
175                }
176            }
177    
178            if (!generateSrcClass) return null;
179    
180            JvmClassName className = getMultiFileNamespaceInternalName(PackageClassUtils.getPackageClassFqName(name), file);
181            ClassBuilder builder = state.getFactory().forNamespacePart(className, file);
182    
183            builder.defineClass(file, V1_6,
184                                ACC_PUBLIC | ACC_FINAL,
185                                className.getInternalName(),
186                                null,
187                                //"jet/lang/Namespace",
188                                "java/lang/Object",
189                                new String[0]
190            );
191            builder.visitSource(file.getName(), null);
192    
193            FieldOwnerContext nameSpaceContext = CodegenContext.STATIC.intoNamespace(descriptor);
194    
195            FieldOwnerContext nameSpacePart = CodegenContext.STATIC.intoNamespacePart(className, descriptor);
196    
197            for (JetDeclaration declaration : file.getDeclarations()) {
198                if (declaration instanceof JetNamedFunction || declaration instanceof JetProperty) {
199                    genFunctionOrProperty(nameSpaceContext, (JetTypeParameterListOwner) declaration, builder);
200                    genFunctionOrProperty(nameSpacePart, (JetTypeParameterListOwner) declaration, v.getClassBuilder());
201                }
202            }
203    
204            generateStaticInitializers(builder, file, nameSpaceContext);
205    
206            builder.done();
207    
208            return builder;
209        }
210    
211        public void generateClassOrObject(@NotNull JetClassOrObject classOrObject) {
212            CodegenContext context = CodegenContext.STATIC.intoNamespace(descriptor);
213            genClassOrObject(context, classOrObject);
214        }
215    
216        /**
217         * @param namespaceFiles all files should have same package name
218         * @return
219         */
220        public static boolean shouldGenerateNSClass(Collection<JetFile> namespaceFiles) {
221            checkAllFilesHaveSameNamespace(namespaceFiles);
222    
223            for (JetFile file : namespaceFiles) {
224                for (JetDeclaration declaration : file.getDeclarations()) {
225                    if (declaration instanceof JetProperty ||
226                        declaration instanceof JetNamedFunction ||
227                        declaration instanceof JetObjectDeclaration) {
228                        return true;
229                    }
230                }
231            }
232    
233            return false;
234        }
235    
236        private static void checkAllFilesHaveSameNamespace(Collection<JetFile> namespaceFiles) {
237            FqName commonFqName = null;
238            for (JetFile file : namespaceFiles) {
239                FqName fqName = JetPsiUtil.getFQName(file);
240                if (commonFqName != null) {
241                    if (!commonFqName.equals(fqName)) {
242                        throw new IllegalArgumentException("All files should have same package name");
243                    }
244                }
245                else {
246                    commonFqName = JetPsiUtil.getFQName(file);
247                }
248            }
249        }
250    
251        private void generateStaticInitializers(@NotNull ClassBuilder builder, @NotNull JetFile file, @NotNull FieldOwnerContext context) {
252            List<JetProperty> properties = collectPropertiesToInitialize(file);
253            if (properties.isEmpty()) return;
254    
255            MethodVisitor mv = builder.newMethod(file, ACC_STATIC, "<clinit>", "()V", null, null);
256            if (state.getClassBuilderMode() == ClassBuilderMode.FULL) {
257                mv.visitCode();
258    
259                FrameMap frameMap = new FrameMap();
260    
261                SimpleFunctionDescriptorImpl clInit =
262                        new SimpleFunctionDescriptorImpl(descriptor, Collections.<AnnotationDescriptor>emptyList(),
263                                                         Name.special("<clinit>"),
264                                                         CallableMemberDescriptor.Kind.SYNTHESIZED);
265                clInit.initialize(null, null, Collections.<TypeParameterDescriptor>emptyList(),
266                                  Collections.<ValueParameterDescriptor>emptyList(), null, null, Visibilities.PRIVATE, false);
267    
268                ExpressionCodegen codegen = new ExpressionCodegen(mv, frameMap, Type.VOID_TYPE, context.intoFunction(clInit), state);
269    
270                for (JetDeclaration declaration : properties) {
271                    ImplementationBodyCodegen.
272                            initializeProperty(codegen, state.getBindingContext(), (JetProperty) declaration);
273                }
274    
275                mv.visitInsn(RETURN);
276                FunctionCodegen.endVisit(mv, "static initializer for namespace", file);
277                mv.visitEnd();
278            }
279        }
280    
281        @NotNull
282        private List<JetProperty> collectPropertiesToInitialize(@NotNull JetFile file) {
283            List<JetProperty> result = Lists.newArrayList();
284            for (JetDeclaration declaration : file.getDeclarations()) {
285                if (declaration instanceof JetProperty &&
286                    ImplementationBodyCodegen.shouldInitializeProperty((JetProperty) declaration, typeMapper)) {
287                    result.add((JetProperty) declaration);
288                }
289            }
290            return result;
291        }
292    
293        public void done() {
294            v.done();
295        }
296    
297        @NotNull
298        public static JvmClassName getJVMClassNameForKotlinNs(@NotNull FqName fqName) {
299            String packageClassName = PackageClassUtils.getPackageClassName(fqName);
300            if (fqName.isRoot()) {
301                return JvmClassName.byInternalName(packageClassName);
302            }
303    
304            return JvmClassName.byFqNameWithoutInnerClasses(fqName.child(Name.identifier(packageClassName)));
305        }
306    
307        @NotNull
308        private static JvmClassName getMultiFileNamespaceInternalName(@NotNull FqName facadeFqName, @NotNull PsiFile file) {
309            String fileName = FileUtil.getNameWithoutExtension(PathUtil.getFileName(file.getName()));
310    
311            // path hashCode to prevent same name / different path collision
312            String srcName = facadeFqName.shortName().asString() + "$src$" + replaceSpecialSymbols(fileName) + "$" + Integer.toHexString(
313                    CodegenUtil.getPathHashCode(file));
314    
315            FqName srcFqName = facadeFqName.parent().child(Name.identifier(srcName));
316    
317            return JvmClassName.byFqNameWithoutInnerClasses(srcFqName);
318        }
319    
320        @NotNull
321        private static String replaceSpecialSymbols(@NotNull String str) {
322            return str.replace('.', '_');
323        }
324    
325        @NotNull
326        public static String getNamespacePartInternalName(@NotNull JetFile file) {
327            FqName fqName = JetPsiUtil.getFQName(file);
328            JvmClassName namespaceJvmClassName = getJVMClassNameForKotlinNs(fqName);
329            return getMultiFileNamespaceInternalName(namespaceJvmClassName.getFqName(), file).getInternalName();
330        }
331    }