001    /*
002     * Copyright 2010-2013 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.jet.codegen;
018    
019    import com.google.common.collect.Lists;
020    import com.intellij.openapi.vfs.VfsUtilCore;
021    import com.intellij.openapi.vfs.VirtualFile;
022    import com.intellij.psi.PsiFile;
023    import com.intellij.util.Function;
024    import com.intellij.util.containers.ContainerUtil;
025    import org.jetbrains.annotations.NotNull;
026    import org.jetbrains.annotations.Nullable;
027    import org.jetbrains.annotations.TestOnly;
028    import org.jetbrains.jet.OutputFile;
029    import org.jetbrains.jet.OutputFileCollection;
030    import org.jetbrains.jet.codegen.state.GenerationState;
031    import org.jetbrains.jet.lang.psi.JetFile;
032    import org.jetbrains.jet.lang.resolve.name.FqName;
033    import org.jetbrains.org.objectweb.asm.Type;
034    
035    import java.io.File;
036    import java.util.*;
037    
038    public class ClassFileFactory implements OutputFileCollection {
039        private final GenerationState state;
040        private final ClassBuilderFactory builderFactory;
041        private final Map<FqName, PackageCodegen> package2codegen = new HashMap<FqName, PackageCodegen>();
042        private final Map<String, ClassBuilderAndSourceFileList> generators = new LinkedHashMap<String, ClassBuilderAndSourceFileList>();
043    
044        private boolean isDone = false;
045    
046        public ClassFileFactory(@NotNull GenerationState state, @NotNull ClassBuilderFactory builderFactory) {
047            this.state = state;
048            this.builderFactory = builderFactory;
049        }
050    
051        @NotNull
052        public ClassBuilder newVisitor(@NotNull Type asmType, @NotNull PsiFile sourceFile) {
053            return newVisitor(asmType, Collections.singletonList(sourceFile));
054        }
055    
056        @NotNull
057        public ClassBuilder newVisitor(@NotNull Type asmType, @NotNull Collection<? extends PsiFile> sourceFiles) {
058            String outputFilePath = asmType.getInternalName() + ".class";
059            state.getProgress().reportOutput(toIoFilesIgnoringNonPhysical(sourceFiles), new File(outputFilePath));
060            ClassBuilder answer = builderFactory.newClassBuilder();
061            generators.put(outputFilePath, new ClassBuilderAndSourceFileList(answer, sourceFiles));
062            return answer;
063        }
064    
065        void done() {
066            if (!isDone) {
067                isDone = true;
068                for (PackageCodegen codegen : package2codegen.values()) {
069                    codegen.done();
070                }
071            }
072        }
073    
074        @NotNull
075        @Override
076        public List<OutputFile> asList() {
077            done();
078            return ContainerUtil.map(generators.keySet(), new Function<String, OutputFile>() {
079                @Override
080                public OutputFile fun(String relativeClassFilePath) {
081                    return new OutputClassFile(relativeClassFilePath);
082                }
083            });
084        }
085    
086        @Override
087        @Nullable
088        public OutputFile get(@NotNull String relativePath) {
089            return generators.containsKey(relativePath) ? new OutputClassFile(relativePath) : null;
090        }
091    
092        @NotNull
093        @TestOnly
094        public String createText() {
095            StringBuilder answer = new StringBuilder();
096    
097            for (OutputFile file : asList()) {
098                answer.append("@").append(file.getRelativePath()).append('\n');
099                answer.append(file.asText());
100            }
101    
102            return answer.toString();
103        }
104    
105        @NotNull
106        public PackageCodegen forPackage(@NotNull FqName fqName, @NotNull Collection<JetFile> files) {
107            assert !isDone : "Already done!";
108            PackageCodegen codegen = package2codegen.get(fqName);
109            if (codegen == null) {
110                codegen = new PackageCodegen(state, files, fqName);
111                package2codegen.put(fqName, codegen);
112            }
113    
114            return codegen;
115        }
116    
117        @NotNull
118        private static Collection<File> toIoFilesIgnoringNonPhysical(@NotNull Collection<? extends PsiFile> psiFiles) {
119            List<File> result = Lists.newArrayList();
120            for (PsiFile psiFile : psiFiles) {
121                VirtualFile virtualFile = psiFile.getVirtualFile();
122                // We ignore non-physical files here, because this code is needed to tell the make what inputs affect which outputs
123                // a non-physical file cannot be processed by make
124                if (virtualFile != null) {
125                    result.add(new File(virtualFile.getPath()));
126                }
127            }
128            return result;
129        }
130    
131        private class OutputClassFile implements OutputFile {
132            private final String relativeClassFilePath;
133    
134            public OutputClassFile(String relativeClassFilePath) {
135                this.relativeClassFilePath = relativeClassFilePath;
136            }
137    
138            @NotNull
139            @Override
140            public String getRelativePath() {
141                return relativeClassFilePath;
142            }
143    
144            @NotNull
145            @Override
146            public List<File> getSourceFiles() {
147                ClassBuilderAndSourceFileList pair = generators.get(relativeClassFilePath);
148                if (pair == null) {
149                    throw new IllegalStateException("No record for binary file " + relativeClassFilePath);
150                }
151    
152                return ContainerUtil.mapNotNull(
153                        pair.sourceFiles,
154                        new Function<PsiFile, File>() {
155                            @Override
156                            public File fun(PsiFile file) {
157                                VirtualFile virtualFile = file.getVirtualFile();
158                                if (virtualFile == null) return null;
159    
160                                return VfsUtilCore.virtualToIoFile(virtualFile);
161                            }
162                        }
163                );
164            }
165    
166            @NotNull
167            @Override
168            public byte[] asByteArray() {
169                return builderFactory.asBytes(generators.get(relativeClassFilePath).classBuilder);
170            }
171    
172            @NotNull
173            @Override
174            public String asText() {
175                return builderFactory.asText(generators.get(relativeClassFilePath).classBuilder);
176            }
177    
178            @NotNull
179            @Override
180            public String toString() {
181                return getRelativePath() + " (compiled from " + getSourceFiles() + ")";
182            }
183        }
184    
185        private static final class ClassBuilderAndSourceFileList {
186            private final ClassBuilder classBuilder;
187            private final Collection<? extends PsiFile> sourceFiles;
188    
189            private ClassBuilderAndSourceFileList(ClassBuilder classBuilder, Collection<? extends PsiFile> sourceFiles) {
190                this.classBuilder = classBuilder;
191                this.sourceFiles = sourceFiles;
192            }
193        }
194    
195        public void removeInlinedClasses(Set<String> classNamesToRemove) {
196            for (String classInternalName : classNamesToRemove) {
197                generators.remove(classInternalName + ".class");
198            }
199        }
200    }