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.Sets;
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.ArrayUtil;
026    import com.intellij.util.PathUtil;
027    import org.jetbrains.annotations.NotNull;
028    import org.jetbrains.annotations.Nullable;
029    import org.jetbrains.asm4.AnnotationVisitor;
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.diagnostics.DiagnosticUtils;
040    import org.jetbrains.jet.lang.psi.*;
041    import org.jetbrains.jet.lang.resolve.BindingContext;
042    import org.jetbrains.jet.lang.resolve.java.JvmAbi;
043    import org.jetbrains.jet.lang.resolve.java.JvmAnnotationNames;
044    import org.jetbrains.jet.lang.resolve.java.JvmClassName;
045    import org.jetbrains.jet.lang.resolve.name.FqName;
046    import org.jetbrains.jet.lang.resolve.name.Name;
047    
048    import java.util.*;
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        private final ClassBuilderOnDemand v;
058    
059        @NotNull
060        private final FqName name;
061    
062        @NotNull
063        private final Collection<JetFile> files;
064        private final Set<PackageFragmentDescriptor> packageFragments;
065    
066        public NamespaceCodegen(
067                @NotNull ClassBuilderOnDemand v,
068                @NotNull final FqName fqName,
069                @NotNull GenerationState state,
070                @NotNull 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            packageFragments = Sets.newHashSet();
080            for (JetFile file : namespaceFiles) {
081                packageFragments.add(getPackageFragment(file));
082            }
083    
084            final PsiFile sourceFile = namespaceFiles.size() == 1 ? namespaceFiles.iterator().next().getContainingFile() : null;
085    
086            v.addOptionalDeclaration(new ClassBuilderOnDemand.ClassBuilderCallback() {
087                @Override
088                public void doSomething(@NotNull ClassBuilder v) {
089                    v.defineClass(sourceFile, V1_6,
090                                  ACC_PUBLIC | ACC_FINAL,
091                                  JvmClassName.byFqNameWithoutInnerClasses(getPackageClassFqName(fqName)).getInternalName(),
092                                  null,
093                                  "java/lang/Object",
094                                  ArrayUtil.EMPTY_STRING_ARRAY
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(packageFragments).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 packagePartContext = CodegenContext.STATIC.intoPackagePart(getPackageFragment(file));
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, packagePartContext).generate();
183                }
184            }
185    
186            if (!generateSrcClass) return null;
187    
188            Type packagePartType = getNamespacePartType(getPackageClassFqName(name), file.getVirtualFile());
189            ClassBuilder builder = state.getFactory().forPackageFragment(packagePartType, file);
190    
191            new NamespacePartCodegen(builder, file, packagePartType, packagePartContext, state).generate();
192    
193            FieldOwnerContext namespaceFacade = CodegenContext.STATIC.intoPackageFacade(packagePartType, getPackageFragment(file));
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        @NotNull
205        private PackageFragmentDescriptor getPackageFragment(@NotNull JetFile file) {
206            PackageFragmentDescriptor packageFragment = bindingContext.get(BindingContext.FILE_TO_PACKAGE_FRAGMENT, file);
207            assert packageFragment != null : "package fragment is null for " + file;
208            return packageFragment;
209        }
210    
211        public void generateClassOrObject(@NotNull JetClassOrObject classOrObject) {
212            CodegenContext context = CodegenContext.STATIC.intoPackagePart(getPackageFragment((JetFile) classOrObject.getContainingFile()));
213            genClassOrObject(context, classOrObject);
214        }
215    
216        /**
217         * @param namespaceFiles all files should have same package name
218         * @return
219         */
220        public static boolean shouldGenerateNSClass(@NotNull Collection<JetFile> namespaceFiles) {
221            checkAllFilesHaveSameNamespace(namespaceFiles);
222    
223            for (JetFile file : namespaceFiles) {
224                for (JetDeclaration declaration : file.getDeclarations()) {
225                    if (declaration instanceof JetProperty || declaration instanceof JetNamedFunction) {
226                        return true;
227                    }
228                }
229            }
230    
231            return false;
232        }
233    
234        private static void checkAllFilesHaveSameNamespace(Collection<JetFile> namespaceFiles) {
235            FqName commonFqName = null;
236            for (JetFile file : namespaceFiles) {
237                FqName fqName = JetPsiUtil.getFQName(file);
238                if (commonFqName != null) {
239                    if (!commonFqName.equals(fqName)) {
240                        throw new IllegalArgumentException("All files should have same package name");
241                    }
242                }
243                else {
244                    commonFqName = JetPsiUtil.getFQName(file);
245                }
246            }
247        }
248    
249        public void done() {
250            v.done();
251        }
252    
253        @NotNull
254        public static Type getNamespacePartType(@NotNull FqName facadeFqName, @NotNull VirtualFile file) {
255            String fileName = FileUtil.getNameWithoutExtension(PathUtil.getFileName(file.getName()));
256    
257            // path hashCode to prevent same name / different path collision
258            String srcName = facadeFqName.shortName().asString() + "-" + replaceSpecialSymbols(fileName) + "-" + Integer.toHexString(
259                    CodegenUtil.getPathHashCode(file));
260    
261            FqName srcFqName = facadeFqName.parent().child(Name.identifier(srcName));
262    
263            return asmTypeByFqNameWithoutInnerClasses(srcFqName);
264        }
265    
266        @NotNull
267        private static String replaceSpecialSymbols(@NotNull String str) {
268            return str.replace('.', '_');
269        }
270    
271        @NotNull
272        public static String getNamespacePartInternalName(@NotNull JetFile file) {
273            FqName packageFqName = JetPsiUtil.getFQName(file);
274            return getNamespacePartType(getPackageClassFqName(packageFqName), file.getVirtualFile()).getInternalName();
275        }
276    }