org.jetbrains.kotlin.codegen.ClassFileFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kotlin-compiler-embeddable Show documentation
Show all versions of kotlin-compiler-embeddable Show documentation
the Kotlin compiler embeddable
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen;
import com.google.common.collect.Lists;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.util.Function;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.io.DataOutputStream;
import kotlin.collections.CollectionsKt;
import kotlin.collections.MapsKt;
import kotlin.jvm.functions.Function0;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import org.jetbrains.kotlin.backend.common.output.OutputFile;
import org.jetbrains.kotlin.backend.common.output.OutputFileCollection;
import org.jetbrains.kotlin.codegen.state.GenerationState;
import org.jetbrains.kotlin.load.kotlin.JvmMetadataVersion;
import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils;
import org.jetbrains.kotlin.load.kotlin.PackageParts;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin;
import org.jetbrains.kotlin.serialization.jvm.JvmPackageTable;
import org.jetbrains.org.objectweb.asm.Type;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.*;
import static org.jetbrains.kotlin.codegen.JvmCodegenUtil.getMappingFileName;
public class ClassFileFactory implements OutputFileCollection {
private final GenerationState state;
private final ClassBuilderFactory builderFactory;
private final Map generators = new LinkedHashMap();
private boolean isDone = false;
private final Set packagePartSourceFiles = new HashSet();
private final Map partsGroupedByPackage = new LinkedHashMap();
public ClassFileFactory(@NotNull GenerationState state, @NotNull ClassBuilderFactory builderFactory) {
this.state = state;
this.builderFactory = builderFactory;
}
@NotNull
public ClassBuilder newVisitor(
@NotNull JvmDeclarationOrigin origin,
@NotNull Type asmType,
@NotNull PsiFile sourceFile) {
return newVisitor(origin, asmType, Collections.singletonList(sourceFile));
}
@NotNull
public ClassBuilder newVisitor(
@NotNull JvmDeclarationOrigin origin,
@NotNull Type asmType,
@NotNull Collection extends PsiFile> sourceFiles) {
String outputFilePath = asmType.getInternalName() + ".class";
List ioSourceFiles = toIoFilesIgnoringNonPhysical(sourceFiles);
state.getProgress().reportOutput(ioSourceFiles, new File(outputFilePath));
ClassBuilder answer = builderFactory.newClassBuilder(origin);
generators.put(outputFilePath, new ClassBuilderAndSourceFileList(answer, ioSourceFiles));
return answer;
}
void done() {
if (!isDone) {
isDone = true;
writeModuleMappings();
}
}
public void releaseGeneratedOutput() {
generators.clear();
}
private void writeModuleMappings() {
final JvmPackageTable.PackageTable.Builder builder = JvmPackageTable.PackageTable.newBuilder();
String outputFilePath = getMappingFileName(state.getModuleName());
List parts = new SmartList(partsGroupedByPackage.values());
for (PackageParts part : ClassFileUtilsKt.addCompiledPartsAndSort(parts, state)) {
PackageParts.Companion.serialize(part, builder);
}
if (builder.getPackagePartsCount() != 0) {
state.getProgress().reportOutput(packagePartSourceFiles, new File(outputFilePath));
generators.put(outputFilePath, new OutAndSourceFileList(CollectionsKt.toList(packagePartSourceFiles)) {
@Override
public byte[] asBytes(ClassBuilderFactory factory) {
try {
ByteArrayOutputStream moduleMapping = new ByteArrayOutputStream(4096);
DataOutputStream dataOutStream = new DataOutputStream(moduleMapping);
int[] version = JvmMetadataVersion.INSTANCE.toArray();
dataOutStream.writeInt(version.length);
for (int number : version) {
dataOutStream.writeInt(number);
}
builder.build().writeTo(dataOutStream);
dataOutStream.flush();
return moduleMapping.toByteArray();
}
catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public String asText(ClassBuilderFactory factory) {
try {
return new String(asBytes(factory), "UTF-8");
}
catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
});
}
}
@NotNull
@Override
public List asList() {
done();
return getCurrentOutput();
}
@NotNull
public List getCurrentOutput() {
return ContainerUtil.map(generators.keySet(), new Function() {
@Override
public OutputFile fun(String relativeClassFilePath) {
return new OutputClassFile(relativeClassFilePath);
}
});
}
@Override
@Nullable
public OutputFile get(@NotNull String relativePath) {
return generators.containsKey(relativePath) ? new OutputClassFile(relativePath) : null;
}
@NotNull
@TestOnly
public String createText() {
StringBuilder answer = new StringBuilder();
for (OutputFile file : asList()) {
answer.append("@").append(file.getRelativePath()).append('\n');
answer.append(file.asText());
}
return answer.toString();
}
@NotNull
@TestOnly
public Map createTextForEachFile() {
Map answer = new LinkedHashMap();
for (OutputFile file : asList()) {
answer.put(file.getRelativePath(), file.asText());
}
return answer;
}
@NotNull
public PackageCodegen forPackage(@NotNull FqName fqName, @NotNull Collection files) {
assert !isDone : "Already done!";
registerPackagePartSourceFiles(files);
return new PackageCodegen(state, files, fqName, buildNewPackagePartRegistry(fqName));
}
@NotNull
public MultifileClassCodegen forMultifileClass(@NotNull FqName facadeFqName, @NotNull Collection files) {
assert !isDone : "Already done!";
registerPackagePartSourceFiles(files);
return new MultifileClassCodegen(state, files, facadeFqName, buildNewPackagePartRegistry(facadeFqName.parent()));
}
private PackagePartRegistry buildNewPackagePartRegistry(@NotNull FqName packageFqName) {
final String packageFqNameAsString = packageFqName.asString();
return new PackagePartRegistry() {
@Override
public void addPart(@NotNull String partShortName) {
MapsKt.getOrPut(partsGroupedByPackage, packageFqNameAsString, new Function0() {
@Override
public PackageParts invoke() {
return new PackageParts(packageFqNameAsString);
}
}).getParts().add(partShortName);
}
};
}
public void registerPackagePartSourceFiles(Collection files) {
packagePartSourceFiles.addAll(toIoFilesIgnoringNonPhysical(PackagePartClassUtils.getFilesWithCallables(files)));
}
@NotNull
private static List toIoFilesIgnoringNonPhysical(@NotNull Collection extends PsiFile> psiFiles) {
List result = Lists.newArrayList();
for (PsiFile psiFile : psiFiles) {
VirtualFile virtualFile = psiFile.getVirtualFile();
// We ignore non-physical files here, because this code is needed to tell the make what inputs affect which outputs
// a non-physical file cannot be processed by make
if (virtualFile != null) {
result.add(new File(virtualFile.getPath()));
}
}
return result;
}
private class OutputClassFile implements OutputFile {
private final String relativeClassFilePath;
public OutputClassFile(String relativeClassFilePath) {
this.relativeClassFilePath = relativeClassFilePath;
}
@NotNull
@Override
public String getRelativePath() {
return relativeClassFilePath;
}
@NotNull
@Override
public List getSourceFiles() {
OutAndSourceFileList pair = generators.get(relativeClassFilePath);
if (pair == null) {
throw new IllegalStateException("No record for binary file " + relativeClassFilePath);
}
return pair.sourceFiles;
}
@NotNull
@Override
public byte[] asByteArray() {
try {
return generators.get(relativeClassFilePath).asBytes(builderFactory);
}
catch (RuntimeException e) {
throw new RuntimeException("Error generating class file " + this.toString() + ": " + e.getMessage(), e);
}
}
@NotNull
@Override
public String asText() {
try {
return generators.get(relativeClassFilePath).asText(builderFactory);
}
catch (RuntimeException e) {
throw new RuntimeException("Error generating class file " + this.toString() + ": " + e.getMessage(), e);
}
}
@NotNull
@Override
public String toString() {
return getRelativePath() + " (compiled from " + getSourceFiles() + ")";
}
}
private static final class ClassBuilderAndSourceFileList extends OutAndSourceFileList {
private final ClassBuilder classBuilder;
private ClassBuilderAndSourceFileList(ClassBuilder classBuilder, List sourceFiles) {
super(sourceFiles);
this.classBuilder = classBuilder;
}
@Override
public byte[] asBytes(ClassBuilderFactory factory) {
return factory.asBytes(classBuilder);
}
@Override
public String asText(ClassBuilderFactory factory) {
return factory.asText(classBuilder);
}
}
private static abstract class OutAndSourceFileList {
protected final List sourceFiles;
private OutAndSourceFileList(List sourceFiles) {
this.sourceFiles = sourceFiles;
}
public abstract byte[] asBytes(ClassBuilderFactory factory);
public abstract String asText(ClassBuilderFactory factory);
}
public void removeClasses(Set classNamesToRemove) {
for (String classInternalName : classNamesToRemove) {
generators.remove(classInternalName + ".class");
}
}
@TestOnly
public List getInputFiles() {
return state.getFiles();
}
}