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        @Override
084        public List<OutputFile> asList() {
085            done();
086            return ContainerUtil.map(generators.keySet(), new Function<String, OutputFile>() {
087                @Override
088                public OutputFile fun(String relativeClassFilePath) {
089                    return new OutputClassFile(relativeClassFilePath);
090                }
091            });
092        }
093    
094        @Override
095        @Nullable
096        public OutputFile get(@NotNull String relativePath) {
097            if (generators.containsKey(relativePath)) return new OutputClassFile(relativePath);
098    
099            return null;
100        }
101    
102        public String createText() {
103            StringBuilder answer = new StringBuilder();
104    
105            for (OutputFile file : asList()) {
106                answer.append("@").append(file.getRelativePath()).append('\n');
107                answer.append(file.asText());
108            }
109    
110            return answer.toString();
111        }
112    
113        public PackageCodegen forPackage(final FqName fqName, final Collection<JetFile> files) {
114            assert !isDone : "Already done!";
115            PackageCodegen codegen = package2codegen.get(fqName);
116            if (codegen == null) {
117                ClassBuilderOnDemand onDemand = new ClassBuilderOnDemand() {
118                    @NotNull
119                    @Override
120                    protected ClassBuilder createClassBuilder() {
121                        return newVisitor(asmTypeByFqNameWithoutInnerClasses(getPackageClassFqName(fqName)), files);
122                    }
123                };
124                codegen = new PackageCodegen(onDemand, fqName, state, files);
125                package2codegen.put(fqName, codegen);
126            }
127    
128            return codegen;
129        }
130    
131        public ClassBuilder forClassImplementation(ClassDescriptor aClass, PsiFile sourceFile) {
132            Type type = state.getTypeMapper().mapClass(aClass);
133            if (isPrimitive(type)) {
134                throw new IllegalStateException("Codegen for primitive type is not possible: " + aClass);
135            }
136            return newVisitor(type, sourceFile);
137        }
138    
139        public ClassBuilder forLambdaInlining(Type lambaType, PsiFile sourceFile) {
140            if (isPrimitive(lambaType)) {
141                throw new IllegalStateException("Codegen for primitive type is not possible: " + lambaType);
142            }
143            return newVisitor(lambaType, sourceFile);
144        }
145    
146        @NotNull
147        public ClassBuilder forPackagePart(@NotNull Type asmType, @NotNull PsiFile sourceFile) {
148            return newVisitor(asmType, sourceFile);
149        }
150    
151        @NotNull
152        public ClassBuilder forTraitImplementation(@NotNull ClassDescriptor aClass, @NotNull GenerationState state, @NotNull PsiFile file) {
153            return newVisitor(state.getTypeMapper().mapTraitImpl(aClass), file);
154        }
155    
156        private static Collection<File> toIoFilesIgnoringNonPhysical(Collection<? extends PsiFile> psiFiles) {
157            List<File> result = Lists.newArrayList();
158            for (PsiFile psiFile : psiFiles) {
159                VirtualFile virtualFile = psiFile.getVirtualFile();
160                // We ignore non-physical files here, because this code is needed to tell the make what inputs affect which outputs
161                // a non-physical file cannot be processed by make
162                if (virtualFile != null) {
163                    result.add(new File(virtualFile.getPath()));
164                }
165            }
166            return result;
167        }
168    
169        private final class OutputClassFile implements OutputFile {
170            final String relativeClassFilePath;
171    
172            OutputClassFile(String relativeClassFilePath) {
173                this.relativeClassFilePath = relativeClassFilePath;
174            }
175    
176            @Override
177            public String getRelativePath() {
178                return relativeClassFilePath;
179            }
180    
181            @Override
182            public List<File> getSourceFiles() {
183                ClassBuilderAndSourceFileList pair = generators.get(relativeClassFilePath);
184                if (pair == null) {
185                    throw new IllegalStateException("No record for binary file " + relativeClassFilePath);
186                }
187    
188                return ContainerUtil.mapNotNull(
189                        pair.sourceFiles,
190                        new Function<PsiFile, File>() {
191                            @Override
192                            public File fun(PsiFile file) {
193                                VirtualFile virtualFile = file.getVirtualFile();
194                                if (virtualFile == null) return null;
195    
196                                return VfsUtilCore.virtualToIoFile(virtualFile);
197                            }
198                        }
199                );
200            }
201    
202            @NotNull
203            @Override
204            public byte[] asByteArray() {
205                return builderFactory.asBytes(generators.get(relativeClassFilePath).classBuilder);
206            }
207    
208            @NotNull
209            @Override
210            public String asText() {
211                return builderFactory.asText(generators.get(relativeClassFilePath).classBuilder);
212            }
213        }
214    
215        private static final class ClassBuilderAndSourceFileList {
216            private final ClassBuilder classBuilder;
217            private final Collection<? extends PsiFile> sourceFiles;
218    
219            private ClassBuilderAndSourceFileList(ClassBuilder classBuilder, Collection<? extends PsiFile> sourceFiles) {
220                this.classBuilder = classBuilder;
221                this.sourceFiles = sourceFiles;
222            }
223        }
224    }