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 }