1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.github.maven_nar.cpptasks;
21
22 import java.io.BufferedWriter;
23 import java.io.File;
24 import java.io.FileOutputStream;
25 import java.io.FileWriter;
26 import java.io.IOException;
27 import java.io.OutputStreamWriter;
28 import java.io.UnsupportedEncodingException;
29 import java.util.Enumeration;
30 import java.util.Hashtable;
31 import java.util.Map;
32 import java.util.Vector;
33
34 import javax.xml.parsers.SAXParser;
35 import javax.xml.parsers.SAXParserFactory;
36
37 import org.apache.tools.ant.BuildException;
38 import org.xml.sax.Attributes;
39 import org.xml.sax.SAXException;
40 import org.xml.sax.helpers.DefaultHandler;
41
42 import com.github.maven_nar.cpptasks.compiler.ProcessorConfiguration;
43
44
45
46
47
48
49
50 public final class TargetHistoryTable {
51
52
53
54
55 private class TargetHistoryTableHandler extends DefaultHandler {
56 private final File baseDir;
57 private String config;
58 private final Hashtable<String, TargetHistory> history;
59 private String output;
60 private long outputLastModified;
61 private final Vector<SourceHistory> sources = new Vector<>();
62
63
64
65
66
67
68
69 private TargetHistoryTableHandler(final Hashtable<String, TargetHistory> history, final File baseDir) {
70 this.history = history;
71 this.config = null;
72 this.output = null;
73 this.baseDir = baseDir;
74 }
75
76 @Override
77 public void endElement(final String namespaceURI, final String localName, final String qName) throws SAXException {
78
79
80
81
82
83
84 if (qName.equals("target")) {
85 if (this.config != null && this.output != null) {
86 final File existingFile = new File(this.baseDir, this.output);
87
88
89
90
91 if (existingFile.exists()) {
92
93
94
95
96
97
98 final long existingLastModified = existingFile.lastModified();
99 if (!CUtil.isSignificantlyBefore(existingLastModified, this.outputLastModified)
100 && !CUtil.isSignificantlyAfter(existingLastModified, this.outputLastModified)) {
101 final SourceHistory[] sourcesArray = new SourceHistory[this.sources.size()];
102 this.sources.copyInto(sourcesArray);
103 final TargetHistory targetHistory = new TargetHistory(this.config, this.output, this.outputLastModified,
104 sourcesArray);
105 this.history.put(this.output, targetHistory);
106 }
107 }
108 }
109 this.output = null;
110 this.sources.setSize(0);
111 } else {
112
113
114
115
116 if (qName.equals("processor")) {
117 this.config = null;
118 }
119 }
120 }
121
122
123
124
125 @Override
126 public void startElement(final String namespaceURI, final String localName, final String qName,
127 final Attributes atts) throws SAXException {
128
129
130
131 if (qName.equals("source")) {
132 final String sourceFile = atts.getValue("file");
133 final long sourceLastModified = Long.parseLong(atts.getValue("lastModified"), 16);
134 this.sources.addElement(new SourceHistory(sourceFile, sourceLastModified));
135 } else {
136
137
138
139
140
141 if (qName.equals("target")) {
142 this.sources.setSize(0);
143 this.output = atts.getValue("file");
144 this.outputLastModified = Long.parseLong(atts.getValue("lastModified"), 16);
145 } else {
146
147
148
149
150 if (qName.equals("processor")) {
151 this.config = atts.getValue("signature");
152 }
153 }
154 }
155 }
156 }
157
158
159
160
161 private boolean dirty;
162
163
164
165 private final Hashtable<String, TargetHistory> history = new Hashtable<>();
166
167
168
169 private final
170 private final
171 private String outputDirPath;
172
173
174
175
176
177
178
179
180
181
182 public TargetHistoryTable(final CCTask task, final File outputDir) throws BuildException
183
184 {
185 if (outputDir == null) {
186 throw new NullPointerException("outputDir");
187 }
188 if (!outputDir.isDirectory()) {
189 throw new BuildException("Output directory is not a directory");
190 }
191 if (!outputDir.exists()) {
192 throw new BuildException("Output directory does not exist");
193 }
194 this.outputDir = outputDir;
195 try {
196 this.outputDirPath = outputDir.getCanonicalPath();
197 } catch (final IOException ex) {
198 this.outputDirPath = outputDir.toString();
199 }
200
201
202
203
204
205 this.historyFile = new File(outputDir, "history.xml");
206
207 if (this.historyFile.exists()) {
208 final SAXParserFactory factory = SAXParserFactory.newInstance();
209 factory.setValidating(false);
210 try {
211 final SAXParser parser = factory.newSAXParser();
212 parser.parse(this.historyFile, new TargetHistoryTableHandler(this.history, outputDir));
213 } catch (final Exception ex) {
214
215
216
217 task.log("Error reading history.xml: " + ex.toString());
218 }
219 } else {
220
221
222
223
224
225
226
227
228 try {
229 final File temp = File.createTempFile("history.xml", Long.toString(System.nanoTime()), outputDir);
230 try (FileWriter writer = new FileWriter(temp)) {
231 writer.write("<history/>");
232 }
233 if (!temp.renameTo(this.historyFile)) {
234 throw new IOException("Could not rename " + temp + " to " + this.historyFile);
235 }
236 } catch (final IOException ex) {
237 throw new BuildException("Can't create history file", ex);
238 }
239 }
240 }
241
242 public void commit() throws IOException {
243
244
245
246 if (this.dirty) {
247
248
249
250 final Hashtable<String, String> configs = new Hashtable<>(20);
251 Enumeration<TargetHistory> elements = this.history.elements();
252 while (elements.hasMoreElements()) {
253 final TargetHistory targetHistory = elements.nextElement();
254 final String configId = targetHistory.getProcessorConfiguration();
255 if (configs.get(configId) == null) {
256 configs.put(configId, configId);
257 }
258 }
259 final FileOutputStream outStream = new FileOutputStream(this.historyFile);
260 OutputStreamWriter outWriter;
261
262
263
264
265 String encodingName = "UTF-8";
266 try {
267 outWriter = new OutputStreamWriter(outStream, "UTF-8");
268 } catch (final UnsupportedEncodingException ex) {
269 outWriter = new OutputStreamWriter(outStream);
270 encodingName = outWriter.getEncoding();
271 }
272 final BufferedWriter writer = new BufferedWriter(outWriter);
273 writer.write("<?xml version='1.0' encoding='");
274 writer.write(encodingName);
275 writer.write("'?>\n");
276 writer.write("<history>\n");
277 final StringBuffer buf = new StringBuffer(200);
278 final Enumeration<String> configEnum = configs.elements();
279 while (configEnum.hasMoreElements()) {
280 final String configId = configEnum.nextElement();
281 buf.setLength(0);
282 buf.append(" <processor signature=\"");
283 buf.append(CUtil.xmlAttribEncode(configId));
284 buf.append("\">\n");
285 writer.write(buf.toString());
286 elements = this.history.elements();
287 while (elements.hasMoreElements()) {
288 final TargetHistory targetHistory = elements.nextElement();
289 if (targetHistory.getProcessorConfiguration().equals(configId)) {
290 buf.setLength(0);
291 buf.append(" <target file=\"");
292 buf.append(CUtil.xmlAttribEncode(targetHistory.getOutput()));
293 buf.append("\" lastModified=\"");
294 buf.append(Long.toHexString(targetHistory.getOutputLastModified()));
295 buf.append("\">\n");
296 writer.write(buf.toString());
297 final SourceHistory[] sourceHistories = targetHistory.getSources();
298 for (final SourceHistory sourceHistorie : sourceHistories) {
299 buf.setLength(0);
300 buf.append(" <source file=\"");
301 buf.append(CUtil.xmlAttribEncode(sourceHistorie.getRelativePath()));
302 buf.append("\" lastModified=\"");
303 buf.append(Long.toHexString(sourceHistorie.getLastModified()));
304 buf.append("\"/>\n");
305 writer.write(buf.toString());
306 }
307 writer.write(" </target>\n");
308 }
309 }
310 writer.write(" </processor>\n");
311 }
312 writer.write("</history>\n");
313 writer.close();
314 this.dirty = false;
315 }
316 }
317
318 public TargetHistory get(final String configId, final String outputName) {
319 TargetHistory targetHistory = this.history.get(outputName);
320 if (targetHistory != null) {
321 if (!targetHistory.getProcessorConfiguration().equals(configId)) {
322 targetHistory = null;
323 }
324 }
325 return targetHistory;
326 }
327
328 public File getHistoryFile() {
329 return this.historyFile;
330 }
331
332 public void markForRebuild(final Map<String, TargetInfo> targetInfos) {
333 for (final TargetInfo targetInfo : targetInfos.values()) {
334 markForRebuild(targetInfo);
335 }
336 }
337
338
339 public synchronized void markForRebuild(final TargetInfo targetInfo) {
340
341
342
343 if (!targetInfo.getRebuild()) {
344 final TargetHistory history = get(targetInfo.getConfiguration().toString(), targetInfo.getOutput().getName());
345 if (history == null) {
346 targetInfo.mustRebuild();
347 } else {
348 final SourceHistory[] sourceHistories = history.getSources();
349 final File[] sources = targetInfo.getSources();
350 if (sourceHistories.length != sources.length) {
351 targetInfo.mustRebuild();
352 } else {
353 final Hashtable<String, File> sourceMap = new Hashtable<>(sources.length);
354 for (final File source : sources) {
355 try {
356 sourceMap.put(source.getCanonicalPath(), source);
357 } catch (final IOException ex) {
358 sourceMap.put(source.getAbsolutePath(), source);
359 }
360 }
361 for (final SourceHistory sourceHistorie : sourceHistories) {
362
363
364
365
366 final String absPath = sourceHistorie.getAbsolutePath(this.outputDir);
367 File match = sourceMap.get(absPath);
368 if (match != null) {
369 try {
370 match = sourceMap.get(new File(absPath).getCanonicalPath());
371 } catch (final IOException ex) {
372 targetInfo.mustRebuild();
373 break;
374 }
375 }
376 if (match == null || match.lastModified() != sourceHistorie.getLastModified()) {
377 targetInfo.mustRebuild();
378 break;
379 }
380 }
381 }
382 }
383 }
384 }
385
386 public void update(final ProcessorConfiguration config, final String[] sources, final VersionInfo versionInfo) {
387 final String configId = config.getIdentifier();
388 final String[] onesource = new String[1];
389 String[] outputNames;
390 for (final String source : sources) {
391 onesource[0] = source;
392 outputNames = config.getOutputFileNames(source, versionInfo);
393 for (final String outputName : outputNames) {
394 update(configId, outputName, onesource);
395 }
396 }
397 }
398
399
400 private synchronized void update(final String configId, final String outputName, final String[] sources) {
401 final File outputFile = new File(this.outputDir, outputName);
402
403
404
405
406
407 if (outputFile.exists() && !CUtil.isSignificantlyBefore(outputFile.lastModified(), this.historyFile.lastModified())) {
408 this.dirty = true;
409 this.history.remove(outputName);
410 final SourceHistory[] sourceHistories = new SourceHistory[sources.length];
411 for (int i = 0; i < sources.length; i++) {
412 final File sourceFile = new File(sources[i]);
413 final long lastModified = sourceFile.lastModified();
414 final String relativePath = CUtil.getRelativePath(this.outputDirPath, sourceFile);
415 sourceHistories[i] = new SourceHistory(relativePath, lastModified);
416 }
417 final TargetHistory newHistory = new TargetHistory(configId, outputName, outputFile.lastModified(),
418 sourceHistories);
419 this.history.put(outputName, newHistory);
420 }
421 }
422
423
424 public synchronized void update(final TargetInfo linkTarget) {
425 final File outputFile = linkTarget.getOutput();
426 final String outputName = outputFile.getName();
427
428
429
430
431
432 if (outputFile.exists() && !CUtil.isSignificantlyBefore(outputFile.lastModified(), this.historyFile.lastModified())) {
433 this.dirty = true;
434 this.history.remove(outputName);
435 final SourceHistory[] sourceHistories = linkTarget.getSourceHistories(this.outputDirPath);
436 final TargetHistory newHistory = new TargetHistory(linkTarget.getConfiguration().getIdentifier(), outputName,
437 outputFile.lastModified(), sourceHistories);
438 this.history.put(outputName, newHistory);
439 }
440 }
441 }