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            if (state.getClassBuilderMode() != ClassBuilderMode.FULL) {
141                return;
142            }
143    
144            for (JetFile file : files) {
145                if (file.isScript()) return;
146            }
147    
148            DescriptorSerializer serializer = new DescriptorSerializer(new JavaSerializerExtension(members));
149            ProtoBuf.Package packageProto = serializer.packageProto(descriptor).build();
150    
151            if (packageProto.getMemberCount() == 0) return;
152    
153            PackageData data = new PackageData(createNameResolver(serializer.getNameTable()), packageProto);
154    
155            AnnotationVisitor av = v.getClassBuilder().newAnnotation(JvmAnnotationNames.KOTLIN_PACKAGE.getDescriptor(), true);
156            av.visit(JvmAnnotationNames.ABI_VERSION_FIELD_NAME, JvmAbi.VERSION);
157            AnnotationVisitor array = av.visitArray(JvmAnnotationNames.DATA_FIELD_NAME);
158            for (String string : JavaProtoBufUtil.encodeBytes(data.toBytes())) {
159                array.visit(null, string);
160            }
161            array.visitEnd();
162            av.visitEnd();
163        }
164    
165        @Nullable
166        private ClassBuilder generate(@NotNull JetFile file) {
167            boolean generateSrcClass = false;
168            for (JetDeclaration declaration : file.getDeclarations()) {
169                if (declaration instanceof JetProperty || declaration instanceof JetNamedFunction) {
170                    generateSrcClass = true;
171                }
172                else if (declaration instanceof JetClassOrObject) {
173                    if (state.isGenerateDeclaredClasses()) {
174                        generateClassOrObject((JetClassOrObject) declaration);
175                    }
176                }
177                else if (declaration instanceof JetScript) {
178                    state.getScriptCodegen().generate((JetScript) declaration);
179                }
180            }
181    
182            if (!generateSrcClass) return null;
183    
184            JvmClassName className = getMultiFileNamespaceInternalName(PackageClassUtils.getPackageClassFqName(name), file);
185            ClassBuilder builder = state.getFactory().forNamespacePart(className, file);
186    
187            builder.defineClass(file, V1_6,
188                                ACC_PUBLIC | ACC_FINAL,
189                                className.getInternalName(),
190                                null,
191                                //"jet/lang/Namespace",
192                                "java/lang/Object",
193                                new String[0]
194            );
195            builder.visitSource(file.getName(), null);
196    
197            FieldOwnerContext nameSpaceContext = CodegenContext.STATIC.intoNamespace(descriptor);
198    
199            FieldOwnerContext nameSpacePart = CodegenContext.STATIC.intoNamespacePart(className, descriptor);
200    
201            for (JetDeclaration declaration : file.getDeclarations()) {
202                if (declaration instanceof JetNamedFunction || declaration instanceof JetProperty) {
203                    genFunctionOrProperty(nameSpaceContext, (JetTypeParameterListOwner) declaration, builder);
204                    genFunctionOrProperty(nameSpacePart, (JetTypeParameterListOwner) declaration, v.getClassBuilder());
205                }
206            }
207    
208            generateStaticInitializers(builder, file, nameSpaceContext);
209    
210            builder.done();
211    
212            return builder;
213        }
214    
215        public void generateClassOrObject(@NotNull JetClassOrObject classOrObject) {
216            CodegenContext context = CodegenContext.STATIC.intoNamespace(descriptor);
217            genClassOrObject(context, classOrObject);
218        }
219    
220        /**
221         * @param namespaceFiles all files should have same package name
222         * @return
223         */
224        public static boolean shouldGenerateNSClass(Collection<JetFile> namespaceFiles) {
225            checkAllFilesHaveSameNamespace(namespaceFiles);
226    
227            for (JetFile file : namespaceFiles) {
228                for (JetDeclaration declaration : file.getDeclarations()) {
229                    if (declaration instanceof JetProperty ||
230                        declaration instanceof JetNamedFunction ||
231                        declaration instanceof JetObjectDeclaration) {
232                        return true;
233                    }
234                }
235            }
236    
237            return false;
238        }
239    
240        private static void checkAllFilesHaveSameNamespace(Collection<JetFile> namespaceFiles) {
241            FqName commonFqName = null;
242            for (JetFile file : namespaceFiles) {
243                FqName fqName = JetPsiUtil.getFQName(file);
244                if (commonFqName != null) {
245                    if (!commonFqName.equals(fqName)) {
246                        throw new IllegalArgumentException("All files should have same package name");
247                    }
248                }
249                else {
250                    commonFqName = JetPsiUtil.getFQName(file);
251                }
252            }
253        }
254    
255        private void generateStaticInitializers(@NotNull ClassBuilder builder, @NotNull JetFile file, @NotNull FieldOwnerContext context) {
256            List<JetProperty> properties = collectPropertiesToInitialize(file);
257            if (properties.isEmpty()) return;
258    
259            MethodVisitor mv = builder.newMethod(file, ACC_STATIC, "<clinit>", "()V", null, null);
260            if (state.getClassBuilderMode() == ClassBuilderMode.FULL) {
261                mv.visitCode();
262    
263                FrameMap frameMap = new FrameMap();
264    
265                SimpleFunctionDescriptorImpl clInit =
266                        new SimpleFunctionDescriptorImpl(descriptor, Collections.<AnnotationDescriptor>emptyList(),
267                                                         Name.special("<clinit>"),
268                                                         CallableMemberDescriptor.Kind.SYNTHESIZED);
269                clInit.initialize(null, null, Collections.<TypeParameterDescriptor>emptyList(),
270                                  Collections.<ValueParameterDescriptor>emptyList(), null, null, Visibilities.PRIVATE, false);
271    
272                ExpressionCodegen codegen = new ExpressionCodegen(mv, frameMap, Type.VOID_TYPE, context.intoFunction(clInit), state);
273    
274                for (JetDeclaration declaration : properties) {
275                    ImplementationBodyCodegen.
276                            initializeProperty(codegen, state.getBindingContext(), (JetProperty) declaration);
277                }
278    
279                mv.visitInsn(RETURN);
280                FunctionCodegen.endVisit(mv, "static initializer for namespace", file);
281                mv.visitEnd();
282            }
283        }
284    
285        @NotNull
286        private List<JetProperty> collectPropertiesToInitialize(@NotNull JetFile file) {
287            List<JetProperty> result = Lists.newArrayList();
288            for (JetDeclaration declaration : file.getDeclarations()) {
289                if (declaration instanceof JetProperty &&
290                    ImplementationBodyCodegen.shouldInitializeProperty((JetProperty) declaration, typeMapper)) {
291                    result.add((JetProperty) declaration);
292                }
293            }
294            return result;
295        }
296    
297        public void done() {
298            v.done();
299        }
300    
301        @NotNull
302        public static JvmClassName getJVMClassNameForKotlinNs(@NotNull FqName fqName) {
303            String packageClassName = PackageClassUtils.getPackageClassName(fqName);
304            if (fqName.isRoot()) {
305                return JvmClassName.byInternalName(packageClassName);
306            }
307    
308            return JvmClassName.byFqNameWithoutInnerClasses(fqName.child(Name.identifier(packageClassName)));
309        }
310    
311        @NotNull
312        private static JvmClassName getMultiFileNamespaceInternalName(@NotNull FqName facadeFqName, @NotNull PsiFile file) {
313            String fileName = FileUtil.getNameWithoutExtension(PathUtil.getFileName(file.getName()));
314    
315            // path hashCode to prevent same name / different path collision
316            String srcName = facadeFqName.shortName().asString() + "$src$" + replaceSpecialSymbols(fileName) + "$" + Integer.toHexString(
317                    CodegenUtil.getPathHashCode(file));
318    
319            FqName srcFqName = facadeFqName.parent().child(Name.identifier(srcName));
320    
321            return JvmClassName.byFqNameWithoutInnerClasses(srcFqName);
322        }
323    
324        @NotNull
325        private static String replaceSpecialSymbols(@NotNull String str) {
326            return str.replace('.', '_');
327        }
328    
329        @NotNull
330        public static String getNamespacePartInternalName(@NotNull JetFile file) {
331            FqName fqName = JetPsiUtil.getFQName(file);
332            JvmClassName namespaceJvmClassName = getJVMClassNameForKotlinNs(fqName);
333            return getMultiFileNamespaceInternalName(namespaceJvmClassName.getFqName(), file).getInternalName();
334        }
335    }