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