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.asm4.AnnotationVisitor;
028    import org.jetbrains.asm4.MethodVisitor;
029    import org.jetbrains.asm4.Type;
030    import org.jetbrains.jet.codegen.context.CodegenContext;
031    import org.jetbrains.jet.codegen.context.FieldOwnerContext;
032    import org.jetbrains.jet.codegen.state.GenerationState;
033    import org.jetbrains.jet.lang.descriptors.*;
034    import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptor;
035    import org.jetbrains.jet.lang.descriptors.impl.SimpleFunctionDescriptorImpl;
036    import org.jetbrains.jet.lang.diagnostics.DiagnosticUtils;
037    import org.jetbrains.jet.lang.psi.*;
038    import org.jetbrains.jet.lang.resolve.BindingContext;
039    import org.jetbrains.jet.lang.resolve.java.JvmAbi;
040    import org.jetbrains.jet.lang.resolve.java.JvmClassName;
041    import org.jetbrains.jet.lang.resolve.java.JvmStdlibNames;
042    import org.jetbrains.jet.lang.resolve.java.PackageClassUtils;
043    import org.jetbrains.jet.lang.resolve.name.FqName;
044    import org.jetbrains.jet.lang.resolve.name.Name;
045    
046    import java.util.Collection;
047    import java.util.Collections;
048    import java.util.List;
049    
050    import static org.jetbrains.asm4.Opcodes.*;
051    
052    public 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    }