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