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