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 }