001    /*
002     * Copyright 2010-2015 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.kotlin.codegen;
018    
019    import com.google.common.collect.Lists;
020    import com.intellij.openapi.vfs.VirtualFile;
021    import com.intellij.psi.PsiFile;
022    import com.intellij.util.Function;
023    import com.intellij.util.containers.ContainerUtil;
024    import com.intellij.util.io.DataOutputStream;
025    import kotlin.KotlinPackage;
026    import org.jetbrains.annotations.NotNull;
027    import org.jetbrains.annotations.Nullable;
028    import org.jetbrains.annotations.TestOnly;
029    import org.jetbrains.kotlin.backend.common.output.OutputFile;
030    import org.jetbrains.kotlin.backend.common.output.OutputFileCollection;
031    import org.jetbrains.kotlin.codegen.state.GenerationState;
032    import org.jetbrains.kotlin.load.java.JvmAbi;
033    import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils;
034    import org.jetbrains.kotlin.load.kotlin.PackageParts;
035    import org.jetbrains.kotlin.name.FqName;
036    import org.jetbrains.kotlin.psi.JetFile;
037    import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin;
038    import org.jetbrains.kotlin.serialization.jvm.JvmPackageTable;
039    import org.jetbrains.org.objectweb.asm.Type;
040    
041    import java.io.ByteArrayOutputStream;
042    import java.io.File;
043    import java.io.IOException;
044    import java.io.UnsupportedEncodingException;
045    import java.util.*;
046    
047    import static org.jetbrains.kotlin.codegen.JvmCodegenUtil.getMappingFileName;
048    
049    public class ClassFileFactory implements OutputFileCollection {
050        private final GenerationState state;
051        private final ClassBuilderFactory builderFactory;
052        private final Map<FqName, PackageCodegen> package2codegen = new HashMap<FqName, PackageCodegen>();
053        private final Map<FqName, MultifileClassCodegen> multifileClass2codegen = new HashMap<FqName, MultifileClassCodegen>();
054        private final Map<String, OutAndSourceFileList> generators = new LinkedHashMap<String, OutAndSourceFileList>();
055    
056        private boolean isDone = false;
057    
058        public ClassFileFactory(@NotNull GenerationState state, @NotNull ClassBuilderFactory builderFactory) {
059            this.state = state;
060            this.builderFactory = builderFactory;
061        }
062    
063        @NotNull
064        public ClassBuilder newVisitor(
065                @NotNull JvmDeclarationOrigin origin,
066                @NotNull Type asmType,
067                @NotNull PsiFile sourceFile) {
068            return newVisitor(origin, asmType, Collections.singletonList(sourceFile));
069        }
070    
071        @NotNull
072        public ClassBuilder newVisitor(
073                @NotNull JvmDeclarationOrigin origin,
074                @NotNull Type asmType,
075                @NotNull Collection<? extends PsiFile> sourceFiles) {
076            String outputFilePath = asmType.getInternalName() + ".class";
077            List<File> ioSourceFiles = toIoFilesIgnoringNonPhysical(sourceFiles);
078            state.getProgress().reportOutput(ioSourceFiles, new File(outputFilePath));
079            ClassBuilder answer = builderFactory.newClassBuilder(origin);
080            generators.put(outputFilePath, new ClassBuilderAndSourceFileList(answer, ioSourceFiles));
081            return answer;
082        }
083    
084        void done() {
085            if (!isDone) {
086                isDone = true;
087                Collection<PackageCodegen> packageCodegens = package2codegen.values();
088                for (PackageCodegen codegen : packageCodegens) {
089                    codegen.done();
090                }
091                Collection<MultifileClassCodegen> multifileClassCodegens = multifileClass2codegen.values();
092                for (MultifileClassCodegen codegen : multifileClassCodegens) {
093                    codegen.done();
094                }
095                writeModuleMappings(packageCodegens, multifileClassCodegens);
096            }
097        }
098    
099        private void writeModuleMappings(
100                @NotNull Collection<PackageCodegen> packageCodegens,
101                @NotNull Collection<MultifileClassCodegen> multifileClassCodegens
102        ) {
103            final JvmPackageTable.PackageTable.Builder builder = JvmPackageTable.PackageTable.newBuilder();
104            String outputFilePath = getMappingFileName(state.getModuleName());
105    
106            List<PackageParts> parts = collectGeneratedPackageParts(packageCodegens, multifileClassCodegens);
107    
108            Set<File> sourceFiles = new HashSet<File>();
109            // TODO extract common logic
110            for (PackageCodegen codegen : packageCodegens) {
111                sourceFiles.addAll(toIoFilesIgnoringNonPhysical(PackagePartClassUtils.getFilesWithCallables(codegen.getFiles())));
112            }
113            for (MultifileClassCodegen codegen : multifileClassCodegens) {
114                sourceFiles.addAll(toIoFilesIgnoringNonPhysical(PackagePartClassUtils.getFilesWithCallables(codegen.getFiles())));
115            }
116    
117            for (PackageParts part : CodegenPackage.addCompiledPartsAndSort(parts, state)) {
118                PackageParts.Companion.serialize(part, builder);
119            }
120    
121            if (builder.getPackagePartsCount() != 0) {
122                state.getProgress().reportOutput(sourceFiles, new File(outputFilePath));
123                generators.put(outputFilePath, new OutAndSourceFileList(KotlinPackage.toList(sourceFiles)) {
124                    @Override
125                    public byte[] asBytes(ClassBuilderFactory factory) {
126                        try {
127                            ByteArrayOutputStream moduleMapping = new ByteArrayOutputStream(4096);
128                            DataOutputStream dataOutStream = new DataOutputStream(moduleMapping);
129                            int[] version = JvmAbi.VERSION.toArray();
130                            dataOutStream.writeInt(version.length);
131                            for (int number : version) {
132                                dataOutStream.writeInt(number);
133                            }
134                            builder.build().writeTo(dataOutStream);
135                            dataOutStream.flush();
136                            return moduleMapping.toByteArray();
137                        }
138                        catch (UnsupportedEncodingException e) {
139                            throw new RuntimeException(e);
140                        }
141                        catch (IOException e) {
142                            throw new RuntimeException(e);
143                        }
144                    }
145    
146                    @Override
147                    public String asText(ClassBuilderFactory factory) {
148                        try {
149                            return new String(asBytes(factory), "UTF-8");
150                        }
151                        catch (UnsupportedEncodingException e) {
152                            throw new RuntimeException(e);
153                        }
154                    }
155                });
156            }
157        }
158    
159        private static List<PackageParts> collectGeneratedPackageParts(
160                @NotNull Collection<PackageCodegen> packageCodegens,
161                @NotNull Collection<MultifileClassCodegen> multifileClassCodegens
162        ) {
163            Map<String, PackageParts> mergedPartsByPackageName = new LinkedHashMap<String, PackageParts>();
164    
165            for (PackageCodegen packageCodegen : packageCodegens) {
166                PackageParts generatedParts = packageCodegen.getPackageParts();
167                PackageParts premergedParts = new PackageParts(generatedParts.getPackageFqName());
168                mergedPartsByPackageName.put(generatedParts.getPackageFqName(), premergedParts);
169                premergedParts.getParts().addAll(generatedParts.getParts());
170            }
171    
172            for (MultifileClassCodegen multifileClassCodegen : multifileClassCodegens) {
173                PackageParts multifileClassParts = multifileClassCodegen.getPackageParts();
174                PackageParts premergedParts = mergedPartsByPackageName.get(multifileClassParts.getPackageFqName());
175                if (premergedParts == null) {
176                    premergedParts = new PackageParts(multifileClassParts.getPackageFqName());
177                    mergedPartsByPackageName.put(multifileClassParts.getPackageFqName(), premergedParts);
178                }
179                premergedParts.getParts().addAll(multifileClassParts.getParts());
180            }
181    
182            List<PackageParts> result = new ArrayList<PackageParts>();
183            result.addAll(mergedPartsByPackageName.values());
184            return result;
185        }
186    
187        @NotNull
188        @Override
189        public List<OutputFile> asList() {
190            done();
191            return ContainerUtil.map(generators.keySet(), new Function<String, OutputFile>() {
192                @Override
193                public OutputFile fun(String relativeClassFilePath) {
194                    return new OutputClassFile(relativeClassFilePath);
195                }
196            });
197        }
198    
199        @Override
200        @Nullable
201        public OutputFile get(@NotNull String relativePath) {
202            return generators.containsKey(relativePath) ? new OutputClassFile(relativePath) : null;
203        }
204    
205        @NotNull
206        @TestOnly
207        public String createText() {
208            StringBuilder answer = new StringBuilder();
209    
210            for (OutputFile file : asList()) {
211                answer.append("@").append(file.getRelativePath()).append('\n');
212                answer.append(file.asText());
213            }
214    
215            return answer.toString();
216        }
217    
218        @NotNull
219        @TestOnly
220        public Map<String, String> createTextForEachFile() {
221            Map<String, String> answer = new LinkedHashMap<String, String>();
222            for (OutputFile file : asList()) {
223                answer.put(file.getRelativePath(), file.asText());
224            }
225            return answer;
226        }
227    
228        @NotNull
229        public PackageCodegen forPackage(@NotNull FqName fqName, @NotNull Collection<JetFile> files) {
230            assert !isDone : "Already done!";
231            PackageCodegen codegen = package2codegen.get(fqName);
232            if (codegen == null) {
233                codegen = new PackageCodegen(state, files, fqName);
234                package2codegen.put(fqName, codegen);
235            }
236    
237            return codegen;
238        }
239    
240        @NotNull
241        public MultifileClassCodegen forMultifileClass(@NotNull FqName facadeFqName, @NotNull Collection<JetFile> files) {
242            assert !isDone : "Already done!";
243            MultifileClassCodegen codegen = multifileClass2codegen.get(facadeFqName);
244            if (codegen == null) {
245                codegen = new MultifileClassCodegen(state, files, facadeFqName);
246                multifileClass2codegen.put(facadeFqName, codegen);
247            }
248            return codegen;
249        }
250    
251        @NotNull
252        private static List<File> toIoFilesIgnoringNonPhysical(@NotNull Collection<? extends PsiFile> psiFiles) {
253            List<File> result = Lists.newArrayList();
254            for (PsiFile psiFile : psiFiles) {
255                VirtualFile virtualFile = psiFile.getVirtualFile();
256                // We ignore non-physical files here, because this code is needed to tell the make what inputs affect which outputs
257                // a non-physical file cannot be processed by make
258                if (virtualFile != null) {
259                    result.add(new File(virtualFile.getPath()));
260                }
261            }
262            return result;
263        }
264    
265        private class OutputClassFile implements OutputFile {
266            private final String relativeClassFilePath;
267    
268            public OutputClassFile(String relativeClassFilePath) {
269                this.relativeClassFilePath = relativeClassFilePath;
270            }
271    
272            @NotNull
273            @Override
274            public String getRelativePath() {
275                return relativeClassFilePath;
276            }
277    
278            @NotNull
279            @Override
280            public List<File> getSourceFiles() {
281                OutAndSourceFileList pair = generators.get(relativeClassFilePath);
282                if (pair == null) {
283                    throw new IllegalStateException("No record for binary file " + relativeClassFilePath);
284                }
285    
286                return pair.sourceFiles;
287            }
288    
289            @NotNull
290            @Override
291            public byte[] asByteArray() {
292                return generators.get(relativeClassFilePath).asBytes(builderFactory);
293            }
294    
295            @NotNull
296            @Override
297            public String asText() {
298                return generators.get(relativeClassFilePath).asText(builderFactory);
299            }
300    
301            @NotNull
302            @Override
303            public String toString() {
304                return getRelativePath() + " (compiled from " + getSourceFiles() + ")";
305            }
306        }
307    
308        private static final class ClassBuilderAndSourceFileList extends OutAndSourceFileList {
309            private final ClassBuilder classBuilder;
310    
311            private ClassBuilderAndSourceFileList(ClassBuilder classBuilder, List<File> sourceFiles) {
312                super(sourceFiles);
313                this.classBuilder = classBuilder;
314            }
315    
316            @Override
317            public byte[] asBytes(ClassBuilderFactory factory) {
318                return factory.asBytes(classBuilder);
319            }
320    
321            @Override
322            public String asText(ClassBuilderFactory factory) {
323                return factory.asText(classBuilder);
324            }
325        }
326    
327        private static abstract class OutAndSourceFileList {
328    
329            protected final List<File> sourceFiles;
330    
331            private OutAndSourceFileList(List<File> sourceFiles) {
332                this.sourceFiles = sourceFiles;
333            }
334    
335            public abstract byte[] asBytes(ClassBuilderFactory factory);
336    
337            public abstract String asText(ClassBuilderFactory factory);
338        }
339    
340        public void removeInlinedClasses(Set<String> classNamesToRemove) {
341            for (String classInternalName : classNamesToRemove) {
342                generators.remove(classInternalName + ".class");
343            }
344        }
345    
346        @TestOnly
347        public List<JetFile> getInputFiles() {
348            return state.getFiles();
349        }
350    }