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.jet.OutputFile;
028    import org.jetbrains.jet.OutputFileCollection;
029    import org.jetbrains.jet.codegen.state.GenerationState;
030    import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
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    import static org.jetbrains.jet.codegen.AsmUtil.asmTypeByFqNameWithoutInnerClasses;
039    import static org.jetbrains.jet.codegen.AsmUtil.isPrimitive;
040    import static org.jetbrains.jet.lang.resolve.java.PackageClassUtils.getPackageClassFqName;
041    
042    public class ClassFileFactory implements OutputFileCollection {
043        private final GenerationState state;
044        private final ClassBuilderFactory builderFactory;
045        private final Map<FqName, PackageCodegen> package2codegen = new HashMap<FqName, PackageCodegen>();
046        private final Map<String, ClassBuilderAndSourceFileList> generators = new LinkedHashMap<String, ClassBuilderAndSourceFileList>();
047    
048        private boolean isDone = false;
049    
050        public ClassFileFactory(@NotNull GenerationState state, @NotNull ClassBuilderFactory builderFactory) {
051            this.state = state;
052            this.builderFactory = builderFactory;
053        }
054    
055        @NotNull
056        ClassBuilder newVisitor(@NotNull Type asmType, @NotNull PsiFile sourceFile) {
057            return newVisitor(asmType, Collections.singletonList(sourceFile));
058        }
059    
060        @NotNull
061        private ClassBuilder newVisitor(@NotNull Type asmType, @NotNull Collection<? extends PsiFile> sourceFiles) {
062            String outputFilePath = asmType.getInternalName() + ".class";
063            state.getProgress().reportOutput(toIoFilesIgnoringNonPhysical(sourceFiles), new File(outputFilePath));
064            ClassBuilder answer = builderFactory.newClassBuilder();
065            generators.put(outputFilePath, new ClassBuilderAndSourceFileList(answer, sourceFiles));
066            return answer;
067        }
068    
069        void done() {
070            if (!isDone) {
071                isDone = true;
072                for (PackageCodegen codegen : package2codegen.values()) {
073                    codegen.done();
074                }
075            }
076        }
077    
078        @NotNull
079        @Override
080        public List<OutputFile> asList() {
081            done();
082            return ContainerUtil.map(generators.keySet(), new Function<String, OutputFile>() {
083                @Override
084                public OutputFile fun(String relativeClassFilePath) {
085                    return new OutputClassFile(relativeClassFilePath);
086                }
087            });
088        }
089    
090        @Override
091        @Nullable
092        public OutputFile get(@NotNull String relativePath) {
093            if (generators.containsKey(relativePath)) return new OutputClassFile(relativePath);
094    
095            return null;
096        }
097    
098        public String createText() {
099            StringBuilder answer = new StringBuilder();
100    
101            for (OutputFile file : asList()) {
102                answer.append("@").append(file.getRelativePath()).append('\n');
103                answer.append(file.asText());
104            }
105    
106            return answer.toString();
107        }
108    
109        public PackageCodegen forPackage(final FqName fqName, final Collection<JetFile> files) {
110            assert !isDone : "Already done!";
111            PackageCodegen codegen = package2codegen.get(fqName);
112            if (codegen == null) {
113                ClassBuilderOnDemand onDemand = new ClassBuilderOnDemand() {
114                    @NotNull
115                    @Override
116                    protected ClassBuilder createClassBuilder() {
117                        return newVisitor(asmTypeByFqNameWithoutInnerClasses(getPackageClassFqName(fqName)), files);
118                    }
119                };
120                codegen = new PackageCodegen(onDemand, fqName, state, files);
121                package2codegen.put(fqName, codegen);
122            }
123    
124            return codegen;
125        }
126    
127        public ClassBuilder forClassImplementation(ClassDescriptor aClass, PsiFile sourceFile) {
128            Type type = state.getTypeMapper().mapClass(aClass);
129            if (isPrimitive(type)) {
130                throw new IllegalStateException("Codegen for primitive type is not possible: " + aClass);
131            }
132            return newVisitor(type, sourceFile);
133        }
134    
135        public ClassBuilder forLambdaInlining(Type lambdaType, PsiFile sourceFile) {
136            if (isPrimitive(lambdaType)) {
137                throw new IllegalStateException("Codegen for primitive type is not possible: " + lambdaType);
138            }
139            return newVisitor(lambdaType, sourceFile);
140        }
141    
142        @NotNull
143        public ClassBuilder forPackagePart(@NotNull Type asmType, @NotNull PsiFile sourceFile) {
144            return newVisitor(asmType, sourceFile);
145        }
146    
147        @NotNull
148        public ClassBuilder forTraitImplementation(@NotNull ClassDescriptor aClass, @NotNull GenerationState state, @NotNull PsiFile file) {
149            return newVisitor(state.getTypeMapper().mapTraitImpl(aClass), file);
150        }
151    
152        private static Collection<File> toIoFilesIgnoringNonPhysical(Collection<? extends PsiFile> psiFiles) {
153            List<File> result = Lists.newArrayList();
154            for (PsiFile psiFile : psiFiles) {
155                VirtualFile virtualFile = psiFile.getVirtualFile();
156                // We ignore non-physical files here, because this code is needed to tell the make what inputs affect which outputs
157                // a non-physical file cannot be processed by make
158                if (virtualFile != null) {
159                    result.add(new File(virtualFile.getPath()));
160                }
161            }
162            return result;
163        }
164    
165        private final class OutputClassFile implements OutputFile {
166            final String relativeClassFilePath;
167    
168            OutputClassFile(String relativeClassFilePath) {
169                this.relativeClassFilePath = relativeClassFilePath;
170            }
171    
172            @NotNull
173            @Override
174            public String getRelativePath() {
175                return relativeClassFilePath;
176            }
177    
178            @NotNull
179            @Override
180            public List<File> getSourceFiles() {
181                ClassBuilderAndSourceFileList pair = generators.get(relativeClassFilePath);
182                if (pair == null) {
183                    throw new IllegalStateException("No record for binary file " + relativeClassFilePath);
184                }
185    
186                return ContainerUtil.mapNotNull(
187                        pair.sourceFiles,
188                        new Function<PsiFile, File>() {
189                            @Override
190                            public File fun(PsiFile file) {
191                                VirtualFile virtualFile = file.getVirtualFile();
192                                if (virtualFile == null) return null;
193    
194                                return VfsUtilCore.virtualToIoFile(virtualFile);
195                            }
196                        }
197                );
198            }
199    
200            @NotNull
201            @Override
202            public byte[] asByteArray() {
203                return builderFactory.asBytes(generators.get(relativeClassFilePath).classBuilder);
204            }
205    
206            @NotNull
207            @Override
208            public String asText() {
209                return builderFactory.asText(generators.get(relativeClassFilePath).classBuilder);
210            }
211    
212            @NotNull
213            @Override
214            public String toString() {
215                return getRelativePath() + " (compiled from " + getSourceFiles() + ")";
216            }
217        }
218    
219        private static final class ClassBuilderAndSourceFileList {
220            private final ClassBuilder classBuilder;
221            private final Collection<? extends PsiFile> sourceFiles;
222    
223            private ClassBuilderAndSourceFileList(ClassBuilder classBuilder, Collection<? extends PsiFile> sourceFiles) {
224                this.classBuilder = classBuilder;
225                this.sourceFiles = sourceFiles;
226            }
227        }
228    
229        public void removeInlinedClasses(Set<String> classNamesToRemove) {
230            for (String classInternalName : classNamesToRemove) {
231                generators.remove(classInternalName + ".class");
232            }
233        }
234    }