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