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.intellij.openapi.application.ApplicationManager;
020    import com.intellij.openapi.progress.ProcessCanceledException;
021    import com.intellij.openapi.util.io.FileUtil;
022    import com.intellij.openapi.vfs.VirtualFile;
023    import com.intellij.psi.PsiFile;
024    import com.intellij.util.PathUtil;
025    import org.jetbrains.annotations.NotNull;
026    import org.jetbrains.annotations.Nullable;
027    import org.jetbrains.asm4.AnnotationVisitor;
028    import org.jetbrains.asm4.Type;
029    import org.jetbrains.jet.codegen.context.CodegenContext;
030    import org.jetbrains.jet.codegen.context.FieldOwnerContext;
031    import org.jetbrains.jet.codegen.state.GenerationState;
032    import org.jetbrains.jet.descriptors.serialization.BitEncoding;
033    import org.jetbrains.jet.descriptors.serialization.DescriptorSerializer;
034    import org.jetbrains.jet.descriptors.serialization.PackageData;
035    import org.jetbrains.jet.descriptors.serialization.ProtoBuf;
036    import org.jetbrains.jet.lang.descriptors.NamespaceDescriptor;
037    import org.jetbrains.jet.lang.diagnostics.DiagnosticUtils;
038    import org.jetbrains.jet.lang.psi.*;
039    import org.jetbrains.jet.lang.resolve.BindingContext;
040    import org.jetbrains.jet.lang.resolve.java.JvmAbi;
041    import org.jetbrains.jet.lang.resolve.java.JvmAnnotationNames;
042    import org.jetbrains.jet.lang.resolve.java.JvmClassName;
043    import org.jetbrains.jet.lang.resolve.name.FqName;
044    import org.jetbrains.jet.lang.resolve.name.Name;
045    
046    import java.util.ArrayList;
047    import java.util.Collection;
048    import java.util.List;
049    
050    import static org.jetbrains.asm4.Opcodes.*;
051    import static org.jetbrains.jet.codegen.AsmUtil.asmDescByFqNameWithoutInnerClasses;
052    import static org.jetbrains.jet.codegen.AsmUtil.asmTypeByFqNameWithoutInnerClasses;
053    import static org.jetbrains.jet.descriptors.serialization.NameSerializationUtil.createNameResolver;
054    import static org.jetbrains.jet.lang.resolve.java.PackageClassUtils.getPackageClassFqName;
055    
056    public class NamespaceCodegen extends MemberCodegen {
057        @NotNull
058        private final ClassBuilderOnDemand v;
059    
060        @NotNull
061        private final FqName name;
062    
063        @NotNull
064        private final Collection<JetFile> files;
065        private final NamespaceDescriptor descriptor;
066    
067        public NamespaceCodegen(
068                @NotNull ClassBuilderOnDemand v,
069                @NotNull final FqName fqName,
070                @NotNull GenerationState state,
071                @NotNull Collection<JetFile> namespaceFiles
072        ) {
073            super(state, null);
074            checkAllFilesHaveSameNamespace(namespaceFiles);
075    
076            this.v = v;
077            name = fqName;
078            this.files = namespaceFiles;
079    
080            descriptor = state.getBindingContext().get(BindingContext.FQNAME_TO_NAMESPACE_DESCRIPTOR, name);
081            assert descriptor != null : "No namespace found for FQ name " + name;
082    
083            final PsiFile sourceFile = namespaceFiles.size() == 1 ? namespaceFiles.iterator().next().getContainingFile() : null;
084    
085            v.addOptionalDeclaration(new ClassBuilderOnDemand.ClassBuilderCallback() {
086                @Override
087                public void doSomething(@NotNull ClassBuilder v) {
088                    v.defineClass(sourceFile, V1_6,
089                                  ACC_PUBLIC | ACC_FINAL,
090                                  JvmClassName.byFqNameWithoutInnerClasses(getPackageClassFqName(fqName)).getInternalName(),
091                                  null,
092                                  //"jet/lang/Namespace",
093                                  "java/lang/Object",
094                                  new String[0]
095                    );
096                    //We don't generate any source information for namespace with multiple files
097                    if (sourceFile != null) {
098                        v.visitSource(sourceFile.getName(), null);
099                    }
100                }
101            });
102        }
103    
104        public void generate(@NotNull CompilationErrorHandler errorHandler) {
105            List<JvmSerializationBindings> bindings = new ArrayList<JvmSerializationBindings>(files.size() + 1);
106            boolean shouldGeneratePackageClass = shouldGenerateNSClass(files);
107            if (shouldGeneratePackageClass) {
108                bindings.add(v.getClassBuilder().getSerializationBindings());
109            }
110    
111            for (JetFile file : files) {
112                try {
113                    ClassBuilder builder = generate(file);
114                    if (builder != null) {
115                        bindings.add(builder.getSerializationBindings());
116                    }
117                }
118                catch (ProcessCanceledException e) {
119                    throw e;
120                }
121                catch (Throwable e) {
122                    VirtualFile vFile = file.getVirtualFile();
123                    errorHandler.reportException(e, vFile == null ? "no file" : vFile.getUrl());
124                    DiagnosticUtils.throwIfRunningOnServer(e);
125                    if (ApplicationManager.getApplication().isInternal()) {
126                        //noinspection CallToPrintStackTrace
127                        e.printStackTrace();
128                    }
129                }
130            }
131    
132            if (shouldGeneratePackageClass) {
133                writeKotlinPackageAnnotationIfNeeded(JvmSerializationBindings.union(bindings));
134            }
135    
136            assert v.isActivated() == shouldGeneratePackageClass :
137                    "Different algorithms for generating namespace class and for heuristics for: " + name.asString();
138        }
139    
140        private void writeKotlinPackageAnnotationIfNeeded(@NotNull JvmSerializationBindings bindings) {
141            if (state.getClassBuilderMode() != ClassBuilderMode.FULL) {
142                return;
143            }
144    
145            for (JetFile file : files) {
146                if (file.isScript()) return;
147            }
148    
149            DescriptorSerializer serializer = new DescriptorSerializer(new JavaSerializerExtension(bindings));
150            ProtoBuf.Package packageProto = serializer.packageProto(descriptor).build();
151    
152            if (packageProto.getMemberCount() == 0) return;
153    
154            PackageData data = new PackageData(createNameResolver(serializer.getNameTable()), packageProto);
155    
156            AnnotationVisitor av =
157                    v.getClassBuilder().newAnnotation(asmDescByFqNameWithoutInnerClasses(JvmAnnotationNames.KOTLIN_PACKAGE), true);
158            av.visit(JvmAnnotationNames.ABI_VERSION_FIELD_NAME, JvmAbi.VERSION);
159            AnnotationVisitor array = av.visitArray(JvmAnnotationNames.DATA_FIELD_NAME);
160            for (String string : BitEncoding.encodeBytes(data.toBytes())) {
161                array.visit(null, string);
162            }
163            array.visitEnd();
164            av.visitEnd();
165        }
166    
167        @Nullable
168        private ClassBuilder generate(@NotNull JetFile file) {
169            boolean generateSrcClass = false;
170            FieldOwnerContext packageFragmentContext = CodegenContext.STATIC.intoNamespace(descriptor);
171    
172            for (JetDeclaration declaration : file.getDeclarations()) {
173                if (declaration instanceof JetProperty || declaration instanceof JetNamedFunction) {
174                    generateSrcClass = true;
175                }
176                else if (declaration instanceof JetClassOrObject) {
177                    if (state.isGenerateDeclaredClasses()) {
178                        generateClassOrObject((JetClassOrObject) declaration);
179                    }
180                }
181                else if (declaration instanceof JetScript) {
182                   ScriptCodegen.createScriptCodegen((JetScript) declaration, state, packageFragmentContext).generate();
183                }
184            }
185    
186            if (!generateSrcClass) return null;
187    
188            Type packageFragmentType = getNamespacePartType(getPackageClassFqName(name), file.getVirtualFile());
189            ClassBuilder builder = state.getFactory().forPackageFragment(packageFragmentType, file);
190    
191            new NamespacePartCodegen(builder, file, packageFragmentType, packageFragmentContext, state).generate();
192    
193            FieldOwnerContext namespaceFacade = CodegenContext.STATIC.intoNamespaceFacade(packageFragmentType, descriptor);
194    
195            for (JetDeclaration declaration : file.getDeclarations()) {
196                if (declaration instanceof JetNamedFunction || declaration instanceof JetProperty) {
197                    genFunctionOrProperty(namespaceFacade, (JetTypeParameterListOwner) declaration, v.getClassBuilder());
198                }
199            }
200    
201            return builder;
202        }
203    
204        public void generateClassOrObject(@NotNull JetClassOrObject classOrObject) {
205            CodegenContext context = CodegenContext.STATIC.intoNamespace(descriptor);
206            genClassOrObject(context, classOrObject);
207        }
208    
209        /**
210         * @param namespaceFiles all files should have same package name
211         * @return
212         */
213        public static boolean shouldGenerateNSClass(Collection<JetFile> namespaceFiles) {
214            checkAllFilesHaveSameNamespace(namespaceFiles);
215    
216            for (JetFile file : namespaceFiles) {
217                for (JetDeclaration declaration : file.getDeclarations()) {
218                    if (declaration instanceof JetProperty ||
219                        declaration instanceof JetNamedFunction ||
220                        declaration instanceof JetObjectDeclaration) {
221                        return true;
222                    }
223                }
224            }
225    
226            return false;
227        }
228    
229        private static void checkAllFilesHaveSameNamespace(Collection<JetFile> namespaceFiles) {
230            FqName commonFqName = null;
231            for (JetFile file : namespaceFiles) {
232                FqName fqName = JetPsiUtil.getFQName(file);
233                if (commonFqName != null) {
234                    if (!commonFqName.equals(fqName)) {
235                        throw new IllegalArgumentException("All files should have same package name");
236                    }
237                }
238                else {
239                    commonFqName = JetPsiUtil.getFQName(file);
240                }
241            }
242        }
243    
244        public void done() {
245            v.done();
246        }
247    
248        @NotNull
249        public static Type getNamespacePartType(@NotNull FqName facadeFqName, @NotNull VirtualFile file) {
250            String fileName = FileUtil.getNameWithoutExtension(PathUtil.getFileName(file.getName()));
251    
252            // path hashCode to prevent same name / different path collision
253            String srcName = facadeFqName.shortName().asString() + "-" + replaceSpecialSymbols(fileName) + "-" + Integer.toHexString(
254                    CodegenUtil.getPathHashCode(file));
255    
256            FqName srcFqName = facadeFqName.parent().child(Name.identifier(srcName));
257    
258            return asmTypeByFqNameWithoutInnerClasses(srcFqName);
259        }
260    
261        @NotNull
262        private static String replaceSpecialSymbols(@NotNull String str) {
263            return str.replace('.', '_');
264        }
265    
266        @NotNull
267        public static String getNamespacePartInternalName(@NotNull JetFile file) {
268            FqName packageFqName = JetPsiUtil.getFQName(file);
269            return getNamespacePartType(getPackageClassFqName(packageFqName), file.getVirtualFile()).getInternalName();
270        }
271    }