001    /*
002     * Copyright 2010-2014 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 PackageCodegen 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 PackageCodegen(
067                @NotNull ClassBuilderOnDemand v,
068                @NotNull final FqName fqName,
069                @NotNull GenerationState state,
070                @NotNull Collection<JetFile> packageFiles
071        ) {
072            super(state, null);
073            checkAllFilesHaveSamePackage(packageFiles);
074    
075            this.v = v;
076            name = fqName;
077            this.files = packageFiles;
078    
079            packageFragments = Sets.newHashSet();
080            for (JetFile file : packageFiles) {
081                packageFragments.add(getPackageFragment(file));
082            }
083    
084            final PsiFile sourceFile = packageFiles.size() == 1 ? packageFiles.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 package 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 = shouldGeneratePackageClass(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 package 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                    JetClassOrObject classOrObject = (JetClassOrObject) declaration;
178                    if (state.getGenerateDeclaredClassFilter().shouldProcess(classOrObject)) {
179                        generateClassOrObject(classOrObject);
180                    }
181                }
182                else if (declaration instanceof JetScript) {
183                   ScriptCodegen.createScriptCodegen((JetScript) declaration, state, packagePartContext).generate();
184                }
185            }
186    
187            if (!generateSrcClass) return null;
188    
189            Type packagePartType = getPackagePartType(getPackageClassFqName(name), file.getVirtualFile());
190            ClassBuilder builder = state.getFactory().forPackagePart(packagePartType, file);
191    
192            new PackagePartCodegen(builder, file, packagePartType, packagePartContext, state).generate();
193    
194            FieldOwnerContext packageFacade = CodegenContext.STATIC.intoPackageFacade(packagePartType, getPackageFragment(file));
195    
196            for (JetDeclaration declaration : file.getDeclarations()) {
197                if (declaration instanceof JetNamedFunction || declaration instanceof JetProperty) {
198                    genFunctionOrProperty(packageFacade, (JetTypeParameterListOwner) declaration, v.getClassBuilder());
199                }
200            }
201    
202            return builder;
203        }
204    
205        @NotNull
206        private PackageFragmentDescriptor getPackageFragment(@NotNull JetFile file) {
207            PackageFragmentDescriptor packageFragment = bindingContext.get(BindingContext.FILE_TO_PACKAGE_FRAGMENT, file);
208            assert packageFragment != null : "package fragment is null for " + file;
209            return packageFragment;
210        }
211    
212        public void generateClassOrObject(@NotNull JetClassOrObject classOrObject) {
213            CodegenContext context = CodegenContext.STATIC.intoPackagePart(getPackageFragment((JetFile) classOrObject.getContainingFile()));
214            genClassOrObject(context, classOrObject);
215        }
216    
217        /**
218         * @param packageFiles all files should have same package name
219         * @return
220         */
221        public static boolean shouldGeneratePackageClass(@NotNull Collection<JetFile> packageFiles) {
222            checkAllFilesHaveSamePackage(packageFiles);
223    
224            for (JetFile file : packageFiles) {
225                for (JetDeclaration declaration : file.getDeclarations()) {
226                    if (declaration instanceof JetProperty || declaration instanceof JetNamedFunction) {
227                        return true;
228                    }
229                }
230            }
231    
232            return false;
233        }
234    
235        private static void checkAllFilesHaveSamePackage(Collection<JetFile> packageFiles) {
236            FqName commonFqName = null;
237            for (JetFile file : packageFiles) {
238                FqName fqName = JetPsiUtil.getFQName(file);
239                if (commonFqName != null) {
240                    if (!commonFqName.equals(fqName)) {
241                        throw new IllegalArgumentException("All files should have same package name");
242                    }
243                }
244                else {
245                    commonFqName = JetPsiUtil.getFQName(file);
246                }
247            }
248        }
249    
250        public void done() {
251            v.done();
252        }
253    
254        @NotNull
255        public static Type getPackagePartType(@NotNull FqName facadeFqName, @NotNull VirtualFile file) {
256            String fileName = FileUtil.getNameWithoutExtension(PathUtil.getFileName(file.getName()));
257    
258            // path hashCode to prevent same name / different path collision
259            String srcName = facadeFqName.shortName().asString() + "-" + replaceSpecialSymbols(fileName) + "-" + Integer.toHexString(
260                    CodegenUtil.getPathHashCode(file));
261    
262            FqName srcFqName = facadeFqName.parent().child(Name.identifier(srcName));
263    
264            return asmTypeByFqNameWithoutInnerClasses(srcFqName);
265        }
266    
267        @NotNull
268        private static String replaceSpecialSymbols(@NotNull String str) {
269            return str.replace('.', '_');
270        }
271    
272        @NotNull
273        public static String getPackagePartInternalName(@NotNull JetFile file) {
274            FqName packageFqName = JetPsiUtil.getFQName(file);
275            return getPackagePartType(getPackageClassFqName(packageFqName), file.getVirtualFile()).getInternalName();
276        }
277    }