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.BitEncoding;
035    import org.jetbrains.jet.descriptors.serialization.DescriptorSerializer;
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 : BitEncoding.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            writeKotlinPackageFragmentAnnotation(builder);
198    
199            FieldOwnerContext nameSpaceContext = CodegenContext.STATIC.intoNamespace(descriptor);
200    
201            FieldOwnerContext nameSpacePart = CodegenContext.STATIC.intoNamespacePart(className, descriptor);
202    
203            for (JetDeclaration declaration : file.getDeclarations()) {
204                if (declaration instanceof JetNamedFunction || declaration instanceof JetProperty) {
205                    genFunctionOrProperty(nameSpaceContext, (JetTypeParameterListOwner) declaration, builder);
206                    genFunctionOrProperty(nameSpacePart, (JetTypeParameterListOwner) declaration, v.getClassBuilder());
207                }
208            }
209    
210            generateStaticInitializers(builder, file, nameSpaceContext);
211    
212            builder.done();
213    
214            return builder;
215        }
216    
217        private static void writeKotlinPackageFragmentAnnotation(@NotNull ClassBuilder builder) {
218            AnnotationVisitor av = builder.newAnnotation(JvmAnnotationNames.KOTLIN_PACKAGE_FRAGMENT.getDescriptor(), true);
219            av.visit(JvmAnnotationNames.ABI_VERSION_FIELD_NAME, JvmAbi.VERSION);
220            av.visitEnd();
221        }
222    
223        public void generateClassOrObject(@NotNull JetClassOrObject classOrObject) {
224            CodegenContext context = CodegenContext.STATIC.intoNamespace(descriptor);
225            genClassOrObject(context, classOrObject);
226        }
227    
228        /**
229         * @param namespaceFiles all files should have same package name
230         * @return
231         */
232        public static boolean shouldGenerateNSClass(Collection<JetFile> namespaceFiles) {
233            checkAllFilesHaveSameNamespace(namespaceFiles);
234    
235            for (JetFile file : namespaceFiles) {
236                for (JetDeclaration declaration : file.getDeclarations()) {
237                    if (declaration instanceof JetProperty ||
238                        declaration instanceof JetNamedFunction ||
239                        declaration instanceof JetObjectDeclaration) {
240                        return true;
241                    }
242                }
243            }
244    
245            return false;
246        }
247    
248        private static void checkAllFilesHaveSameNamespace(Collection<JetFile> namespaceFiles) {
249            FqName commonFqName = null;
250            for (JetFile file : namespaceFiles) {
251                FqName fqName = JetPsiUtil.getFQName(file);
252                if (commonFqName != null) {
253                    if (!commonFqName.equals(fqName)) {
254                        throw new IllegalArgumentException("All files should have same package name");
255                    }
256                }
257                else {
258                    commonFqName = JetPsiUtil.getFQName(file);
259                }
260            }
261        }
262    
263        private void generateStaticInitializers(@NotNull ClassBuilder builder, @NotNull JetFile file, @NotNull FieldOwnerContext context) {
264            List<JetProperty> properties = collectPropertiesToInitialize(file);
265            if (properties.isEmpty()) return;
266    
267            MethodVisitor mv = builder.newMethod(file, ACC_STATIC, "<clinit>", "()V", null, null);
268            if (state.getClassBuilderMode() == ClassBuilderMode.FULL) {
269                mv.visitCode();
270    
271                FrameMap frameMap = new FrameMap();
272    
273                SimpleFunctionDescriptorImpl clInit =
274                        new SimpleFunctionDescriptorImpl(descriptor, Collections.<AnnotationDescriptor>emptyList(),
275                                                         Name.special("<clinit>"),
276                                                         CallableMemberDescriptor.Kind.SYNTHESIZED);
277                clInit.initialize(null, null, Collections.<TypeParameterDescriptor>emptyList(),
278                                  Collections.<ValueParameterDescriptor>emptyList(), null, null, Visibilities.PRIVATE, false);
279    
280                ExpressionCodegen codegen = new ExpressionCodegen(mv, frameMap, Type.VOID_TYPE, context.intoFunction(clInit), state);
281    
282                for (JetDeclaration declaration : properties) {
283                    ImplementationBodyCodegen.
284                            initializeProperty(codegen, state.getBindingContext(), (JetProperty) declaration);
285                }
286    
287                mv.visitInsn(RETURN);
288                FunctionCodegen.endVisit(mv, "static initializer for namespace", file);
289                mv.visitEnd();
290            }
291        }
292    
293        @NotNull
294        private List<JetProperty> collectPropertiesToInitialize(@NotNull JetFile file) {
295            List<JetProperty> result = Lists.newArrayList();
296            for (JetDeclaration declaration : file.getDeclarations()) {
297                if (declaration instanceof JetProperty &&
298                    ImplementationBodyCodegen.shouldInitializeProperty((JetProperty) declaration, typeMapper)) {
299                    result.add((JetProperty) declaration);
300                }
301            }
302            return result;
303        }
304    
305        public void done() {
306            v.done();
307        }
308    
309        @NotNull
310        public static JvmClassName getJVMClassNameForKotlinNs(@NotNull FqName fqName) {
311            String packageClassName = PackageClassUtils.getPackageClassName(fqName);
312            if (fqName.isRoot()) {
313                return JvmClassName.byInternalName(packageClassName);
314            }
315    
316            return JvmClassName.byFqNameWithoutInnerClasses(fqName.child(Name.identifier(packageClassName)));
317        }
318    
319        @NotNull
320        private static JvmClassName getMultiFileNamespaceInternalName(@NotNull FqName facadeFqName, @NotNull PsiFile file) {
321            String fileName = FileUtil.getNameWithoutExtension(PathUtil.getFileName(file.getName()));
322    
323            // path hashCode to prevent same name / different path collision
324            String srcName = facadeFqName.shortName().asString() + "-" + replaceSpecialSymbols(fileName) + "-" + Integer.toHexString(
325                    CodegenUtil.getPathHashCode(file));
326    
327            FqName srcFqName = facadeFqName.parent().child(Name.identifier(srcName));
328    
329            return JvmClassName.byFqNameWithoutInnerClasses(srcFqName);
330        }
331    
332        @NotNull
333        private static String replaceSpecialSymbols(@NotNull String str) {
334            return str.replace('.', '_');
335        }
336    
337        @NotNull
338        public static String getNamespacePartInternalName(@NotNull JetFile file) {
339            FqName fqName = JetPsiUtil.getFQName(file);
340            JvmClassName namespaceJvmClassName = getJVMClassNameForKotlinNs(fqName);
341            return getMultiFileNamespaceInternalName(namespaceJvmClassName.getFqName(), file).getInternalName();
342        }
343    }