new compiler api: persistent state splitted to source and output state
[idea/community.git] / java / compiler / impl / src / com / intellij / compiler / impl / NewCompilerRunner.java
1 /*
2  * Copyright 2000-2010 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.compiler.impl;
17
18 import com.google.common.base.Throwables;
19 import com.intellij.compiler.impl.newApi.*;
20 import com.intellij.openapi.application.ApplicationManager;
21 import com.intellij.openapi.application.ReadAction;
22 import com.intellij.openapi.application.Result;
23 import com.intellij.openapi.application.RunResult;
24 import com.intellij.openapi.compiler.*;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.progress.ProcessCanceledException;
27 import com.intellij.openapi.project.DumbService;
28 import com.intellij.openapi.project.Project;
29 import com.intellij.openapi.util.Ref;
30 import com.intellij.util.CommonProcessors;
31 import com.intellij.util.Processor;
32 import com.intellij.util.io.KeyDescriptor;
33 import gnu.trove.THashSet;
34 import gnu.trove.TObjectHashingStrategy;
35 import org.jetbrains.annotations.NotNull;
36
37 import java.io.File;
38 import java.io.IOException;
39 import java.util.*;
40
41 /**
42  * @author nik
43  */
44 public class NewCompilerRunner {
45   private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.NewCompilerRunner");
46   private CompileContext myContext;
47   private final boolean myForceCompile;
48   private final boolean myOnlyCheckStatus;
49   private final NewCompiler<?,?,?>[] myCompilers;
50   private final Project myProject;
51
52   public NewCompilerRunner(CompileContext context, CompilerManager compilerManager, boolean forceCompile, boolean onlyCheckStatus) {
53     myContext = context;
54     myForceCompile = forceCompile;
55     myOnlyCheckStatus = onlyCheckStatus;
56     myCompilers = compilerManager.getCompilers(NewCompiler.class);
57     myProject = myContext.getProject();
58   }
59
60   public boolean invokeCompilers(NewCompiler.CompileOrderPlace place) throws CompileDriver.ExitException {
61     boolean didSomething = false;
62     try {
63       for (NewCompiler<?,?,?> compiler : myCompilers) {
64         if (compiler.getOrderPlace().equals(place)) {
65           didSomething = invokeCompiler(compiler);
66         }
67       }
68     }
69     catch (IOException e) {
70       LOG.info(e);
71       myContext.requestRebuildNextTime(e.getMessage());
72       throw new CompileDriver.ExitException(CompileDriver.ExitStatus.ERRORS);
73     }
74     catch (CompileDriver.ExitException e) {
75       throw e;
76     }
77     catch (ProcessCanceledException e) {
78       throw e;
79     }
80     catch (Exception e) {
81       LOG.info(e);
82       myContext.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("compiler.error.exception", e.getMessage()), null, -1, -1);
83     }
84     return didSomething;
85   }
86
87   private <Key, SourceState, OutputState> boolean invokeCompiler(NewCompiler<Key, SourceState, OutputState> compiler) throws IOException, CompileDriver.ExitException {
88     return invokeCompiler(compiler, compiler.createInstance(myContext));
89   }
90
91   private <T extends BuildTarget, Item extends CompileItem<Key, SourceState, OutputState>, Key, SourceState, OutputState>
92   boolean invokeCompiler(NewCompiler<Key, SourceState, OutputState> compiler, CompilerInstance<T, Item, Key, SourceState, OutputState> instance) throws IOException, CompileDriver.ExitException {
93     NewCompilerCache<Key, SourceState, OutputState> cache = CompilerCacheManager.getInstance(myProject).getNewCompilerCache(compiler);
94     NewCompilerPersistentData data = new NewCompilerPersistentData(getNewCompilerCacheDir(myProject, compiler), compiler.getVersion());
95     if (data.isVersionChanged()) {
96       LOG.info("Clearing cache for " + compiler.getDescription());
97       cache.wipe();
98     }
99
100     Set<String> targetsToRemove = new HashSet<String>(data.getAllTargets());
101     for (T target : instance.getAllTargets()) {
102       targetsToRemove.remove(target.getId());
103     }
104     if (!myOnlyCheckStatus) {
105       for (String target : targetsToRemove) {
106         int id = data.removeId(target);
107         if (LOG.isDebugEnabled()) {
108           LOG.debug("Removing obsolete target '" + target + "' (id=" + id + ")");
109         }
110         List<Key> keys = new ArrayList<Key>();
111         cache.processSources(id, new CommonProcessors.CollectProcessor<Key>(keys));
112         List<NewCompilerItemState<Key, SourceState, OutputState>> obsoleteSources = new ArrayList<NewCompilerItemState<Key,SourceState,OutputState>>();
113         for (Key key : keys) {
114           final NewCompilerCache.PersistentStateData<SourceState, OutputState> state = cache.getState(id, key);
115           obsoleteSources.add(new NewCompilerItemState<Key,SourceState,OutputState>(key, state.mySourceState, state.myOutputState));
116         }
117         instance.processObsoleteTarget(target, obsoleteSources);
118         if (myContext.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
119           return true;
120         }
121         for (Key key : keys) {
122           cache.remove(id, key);
123         }
124       }
125     }
126
127     boolean didSomething = false;
128     for (T target : instance.getSelectedTargets()) {
129       int id = data.getId(target.getId());
130       didSomething |= processTarget(target, id, compiler, instance, cache);
131     }
132
133     data.save();
134     return didSomething;
135   }
136
137   public static File getNewCompilerCacheDir(Project project, NewCompiler<?,?,?> compiler) {
138     return new File(CompilerPaths.getCacheStoreDirectory(project), compiler.getId());
139   }
140
141   private <T extends BuildTarget, Item extends CompileItem<Key, SourceState, OutputState>, Key, SourceState, OutputState>
142   boolean processTarget(T target, final int targetId, final NewCompiler<Key, SourceState, OutputState> compiler, final CompilerInstance<T, Item, Key, SourceState, OutputState> instance,
143                         final NewCompilerCache<Key, SourceState, OutputState> cache) throws IOException, CompileDriver.ExitException {
144     if (LOG.isDebugEnabled()) {
145       LOG.debug("Processing target '" + target + "' (id=" + targetId + ")");
146     }
147     final List<Item> items = instance.getItems(target);
148     if (myContext.getMessageCount(CompilerMessageCategory.ERROR) > 0) return true;
149
150     final List<NewCompilerItemState<Item, SourceState, OutputState>> toProcess = new ArrayList<NewCompilerItemState<Item,SourceState,OutputState>>();
151     final THashSet<Key> keySet = new THashSet<Key>(new SourceItemHashingStrategy<Key>(compiler));
152     final Ref<IOException> exception = Ref.create(null);
153     DumbService.getInstance(myProject).waitForSmartMode();
154     final Map<Item, SourceState> sourceStates = new HashMap<Item,SourceState>();
155     ApplicationManager.getApplication().runReadAction(new Runnable() {
156       @Override
157       public void run() {
158         try {
159           for (Item item : items) {
160             final Key key = item.getKey();
161             keySet.add(key);
162             final NewCompilerCache.PersistentStateData<SourceState, OutputState> data = cache.getState(targetId, key);
163             SourceState sourceState = data != null ? data.mySourceState : null;
164             final OutputState outputState = data != null ? data.myOutputState : null;
165             if (myForceCompile || sourceState == null || !item.isSourceUpToDate(sourceState)
166                                || outputState == null || !item.isOutputUpToDate(outputState)) {
167               sourceStates.put(item, item.computeSourceState());
168               toProcess.add(new NewCompilerItemState<Item, SourceState, OutputState>(item, sourceState, outputState));
169             }
170           }
171         }
172         catch (IOException e) {
173           exception.set(e);
174         }
175       }
176     });
177     if (!exception.isNull()) {
178       throw exception.get();
179     }
180
181     final List<Key> toRemove = new ArrayList<Key>();
182     cache.processSources(targetId, new Processor<Key>() {
183       @Override
184       public boolean process(Key key) {
185         if (!keySet.contains(key)) {
186           toRemove.add(key);
187         }
188         return true;
189       }
190     });
191
192     if (LOG.isDebugEnabled()) {
193       LOG.debug(toProcess.size() + " items will be processed, " + toRemove.size() + " items will be removed");
194     }
195
196     if (toProcess.isEmpty() && toRemove.isEmpty()) {
197       return false;
198     }
199
200     if (myOnlyCheckStatus) {
201       throw new CompileDriver.ExitException(CompileDriver.ExitStatus.CANCELLED);
202     }
203
204     List<NewCompilerItemState<Key, SourceState, OutputState>> obsoleteItems = new ArrayList<NewCompilerItemState<Key,SourceState,OutputState>>();
205     for (Key key : toRemove) {
206       final NewCompilerCache.PersistentStateData<SourceState, OutputState> data = cache.getState(targetId, key);
207       obsoleteItems.add(new NewCompilerItemState<Key,SourceState,OutputState>(key, data.mySourceState, data.myOutputState));
208     }
209
210     final List<Item> processedItems = new ArrayList<Item>();
211     final List<File> toRefresh = new ArrayList<File>();
212     instance.processItems(target, toProcess, obsoleteItems, new CompilerInstance.OutputConsumer<Item>() {
213       @Override
214       public void addFileToRefresh(@NotNull File file) {
215         toRefresh.add(file);
216       }
217       @Override
218       public void addProcessedItem(@NotNull Item sourceItem) {
219         processedItems.add(sourceItem);
220       }
221     });
222     if (myContext.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
223       return true;
224     }
225
226     for (Key key : toRemove) {
227       cache.remove(targetId, key);
228     }
229     CompilerUtil.refreshIOFiles(toRefresh);
230
231     final RunResult runResult = new ReadAction() {
232       protected void run(final Result result) throws Throwable {
233         for (Item item : processedItems) {
234           SourceState sourceState = sourceStates.get(item);
235           if (sourceState == null) {
236             sourceState = item.computeSourceState();
237           }
238           cache.putState(targetId, item.getKey(), sourceState, item.computeOutputState());
239         }
240       }
241     }.executeSilently();
242     Throwables.propagateIfPossible(runResult.getThrowable(), IOException.class);
243
244     return true;
245
246   }
247
248   private class SourceItemHashingStrategy<S> implements TObjectHashingStrategy<S> {
249     private KeyDescriptor<S> myKeyDescriptor;
250
251     public SourceItemHashingStrategy(NewCompiler<S, ?, ?> compiler) {
252       myKeyDescriptor = compiler.getItemKeyDescriptor();
253     }
254
255     @Override
256     public int computeHashCode(S object) {
257       return myKeyDescriptor.getHashCode(object);
258     }
259
260     @Override
261     public boolean equals(S o1, S o2) {
262       return myKeyDescriptor.isEqual(o1, o2);
263     }
264   }
265 }