blob: 9fb0a7b642d1cfa30d29ce6b92ededcd73866bf3 [file] [log] [blame]
/*
*
* Copyright 2002-2004 The Ant-Contrib project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.antcontrib.cpptasks;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import net.sf.antcontrib.cpptasks.compiler.ProcessorConfiguration;
import org.apache.tools.ant.BuildException;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
/**
* A history of the compiler and linker settings used to build the files in the
* same directory as the history.
*
* @author Curt Arnold
*/
public final class TargetHistoryTable {
/**
* This class handles populates the TargetHistory hashtable in response to
* SAX parse events
*/
private class TargetHistoryTableHandler extends DefaultHandler {
private final File baseDir;
private String config;
private final Hashtable history;
private String output;
private long outputLastModified;
private final Vector sources = new Vector();
/**
* Constructor
*
* @param history
* hashtable of TargetHistory keyed by output name
* @param outputFiles
* existing files in output directory
*/
private TargetHistoryTableHandler(Hashtable history, File baseDir) {
this.history = history;
config = null;
output = null;
this.baseDir = baseDir;
}
public void endElement(String namespaceURI, String localName,
String qName) throws SAXException {
//
// if </target> then
// create TargetHistory object and add to hashtable
// if corresponding output file exists and
// has the same timestamp
//
if (qName.equals("target")) {
if (config != null && output != null) {
File existingFile = new File(baseDir, output);
//
// if the corresponding files doesn't exist or has a
// different
// modification time, then discard this record
if (existingFile.exists()) {
long existingLastModified = existingFile.lastModified();
//
// would have expected exact time stamps
// but have observed slight differences
// in return value for multiple evaluations of
// lastModified(). Check if times are within
// a second
long diff = outputLastModified - existingLastModified;
if (diff >= -500 && diff <= 500) {
SourceHistory[] sourcesArray = new SourceHistory[sources
.size()];
sources.copyInto(sourcesArray);
TargetHistory targetHistory = new TargetHistory(
config, output, outputLastModified,
sourcesArray);
history.put(output, targetHistory);
}
}
}
output = null;
sources.setSize(0);
} else {
//
// reset config so targets not within a processor element
// don't pick up a previous processors signature
//
if (qName.equals("processor")) {
config = null;
}
}
}
/**
* startElement handler
*/
public void startElement(String namespaceURI, String localName,
String qName, Attributes atts) throws SAXException {
//
// if sourceElement
//
if (qName.equals("source")) {
String sourceFile = atts.getValue("file");
long sourceLastModified = Long.parseLong(atts
.getValue("lastModified"), 16);
sources.addElement(new SourceHistory(sourceFile,
sourceLastModified));
} else {
//
// if <target> element,
// grab file name and lastModified values
// TargetHistory object will be created in endElement
//
if (qName.equals("target")) {
sources.setSize(0);
output = atts.getValue("file");
outputLastModified = Long.parseLong(atts
.getValue("lastModified"), 16);
} else {
//
// if <processor> element,
// grab signature attribute
//
if (qName.equals("processor")) {
config = atts.getValue("signature");
}
}
}
}
}
/** Flag indicating whether the cache should be written back to file. */
private boolean dirty;
/**
* a hashtable of TargetHistory's keyed by output file name
*/
private final Hashtable history = new Hashtable();
/** The file the cache was loaded from. */
private/* final */File historyFile;
private/* final */File outputDir;
private String outputDirPath;
/**
* Creates a target history table from history.xml in the output directory,
* if it exists. Otherwise, initializes the history table empty.
*
* @param task
* task used for logging history load errors
* @param outputDir
* output directory for task
*/
public TargetHistoryTable(CCTask task, File outputDir)
throws BuildException {
if (outputDir == null) {
throw new NullPointerException("outputDir");
}
if (!outputDir.isDirectory()) {
throw new BuildException("Output directory is not a directory");
}
if (!outputDir.exists()) {
throw new BuildException("Output directory does not exist");
}
this.outputDir = outputDir;
try {
outputDirPath = outputDir.getCanonicalPath();
} catch (IOException ex) {
outputDirPath = outputDir.toString();
}
//
// load any existing history from file
// suppressing any records whose corresponding
// file does not exist, is zero-length or
// last modified dates differ
historyFile = new File(outputDir, "history.xml");
if (historyFile.exists()) {
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setValidating(false);
try {
SAXParser parser = factory.newSAXParser();
parser.parse(historyFile, new TargetHistoryTableHandler(
history, outputDir));
} catch (Exception ex) {
//
// a failure on loading this history is not critical
// but should be logged
task.log("Error reading history.xml: " + ex.toString());
}
} else {
//
// create empty history file for identifying new files by last
// modified
// timestamp comperation (to compare with
// System.currentTimeMillis() don't work on Unix, because it
// maesure timestamps only in seconds).
//
try {
FileOutputStream outputStream = new FileOutputStream(
historyFile);
byte[] historyElement = new byte[]{0x3C, 0x68, 0x69, 0x73,
0x74, 0x6F, 0x72, 0x79, 0x2F, 0x3E};
outputStream.write(historyElement);
outputStream.close();
} catch (IOException ex) {
throw new BuildException("Can't create history file", ex);
}
}
}
public void commit() throws IOException {
//
// if not dirty, no need to update file
//
if (dirty) {
//
// build (small) hashtable of config id's in history
//
Hashtable configs = new Hashtable(20);
Enumeration elements = history.elements();
while (elements.hasMoreElements()) {
TargetHistory targetHistory = (TargetHistory) elements
.nextElement();
String configId = targetHistory.getProcessorConfiguration();
if (configs.get(configId) == null) {
configs.put(configId, configId);
}
}
FileOutputStream outStream = new FileOutputStream(historyFile);
OutputStreamWriter outWriter;
//
// early VM's don't support UTF-8 encoding
// try and fallback to the default encoding
// otherwise
String encodingName = "UTF-8";
try {
outWriter = new OutputStreamWriter(outStream, "UTF-8");
} catch (UnsupportedEncodingException ex) {
outWriter = new OutputStreamWriter(outStream);
encodingName = outWriter.getEncoding();
}
BufferedWriter writer = new BufferedWriter(outWriter);
writer.write("<?xml version='1.0' encoding='");
writer.write(encodingName);
writer.write("'?>\n");
writer.write("<history>\n");
StringBuffer buf = new StringBuffer(200);
Enumeration configEnum = configs.elements();
while (configEnum.hasMoreElements()) {
String configId = (String) configEnum.nextElement();
buf.setLength(0);
buf.append(" <processor signature=\"");
buf.append(CUtil.xmlAttribEncode(configId));
buf.append("\">\n");
writer.write(buf.toString());
elements = history.elements();
while (elements.hasMoreElements()) {
TargetHistory targetHistory = (TargetHistory) elements
.nextElement();
if (targetHistory.getProcessorConfiguration().equals(
configId)) {
buf.setLength(0);
buf.append(" <target file=\"");
buf.append(CUtil.xmlAttribEncode(targetHistory
.getOutput()));
buf.append("\" lastModified=\"");
buf.append(Long.toHexString(targetHistory
.getOutputLastModified()));
buf.append("\">\n");
writer.write(buf.toString());
SourceHistory[] sourceHistories = targetHistory
.getSources();
for (int i = 0; i < sourceHistories.length; i++) {
buf.setLength(0);
buf.append(" <source file=\"");
buf.append(CUtil.xmlAttribEncode(sourceHistories[i]
.getRelativePath()));
buf.append("\" lastModified=\"");
buf.append(Long.toHexString(sourceHistories[i]
.getLastModified()));
buf.append("\"/>\n");
writer.write(buf.toString());
}
writer.write(" </target>\n");
}
}
writer.write(" </processor>\n");
}
writer.write("</history>\n");
writer.close();
dirty = false;
}
}
public TargetHistory get(String configId, String outputName) {
TargetHistory targetHistory = (TargetHistory) history.get(outputName);
if (targetHistory != null) {
if (!targetHistory.getProcessorConfiguration().equals(configId)) {
targetHistory = null;
}
}
return targetHistory;
}
public void markForRebuild(Hashtable targetInfos) {
Enumeration targetInfoEnum = targetInfos.elements();
while (targetInfoEnum.hasMoreElements()) {
markForRebuild((TargetInfo) targetInfoEnum.nextElement());
}
}
public void markForRebuild(TargetInfo targetInfo) {
//
// if it must already be rebuilt, no need to check further
//
if (!targetInfo.getRebuild()) {
TargetHistory history = get(targetInfo.getConfiguration()
.toString(), targetInfo.getOutput().getName());
if (history == null) {
targetInfo.mustRebuild();
} else {
SourceHistory[] sourceHistories = history.getSources();
File[] sources = targetInfo.getSources();
if (sourceHistories.length != sources.length) {
targetInfo.mustRebuild();
} else {
for (int i = 0; i < sourceHistories.length
&& !targetInfo.getRebuild(); i++) {
//
// relative file name, must absolutize it on output
// directory
//
boolean foundMatch = false;
String historySourcePath = sourceHistories[i]
.getAbsolutePath(outputDir);
for (int j = 0; j < sources.length; j++) {
File targetSource = sources[j];
String targetSourcePath = targetSource
.getAbsolutePath();
if (targetSourcePath.equals(historySourcePath)) {
foundMatch = true;
if (targetSource.lastModified() != sourceHistories[i]
.getLastModified()) {
targetInfo.mustRebuild();
break;
}
}
}
if (!foundMatch) {
targetInfo.mustRebuild();
}
}
}
}
}
}
public void update(ProcessorConfiguration config, String[] sources) {
String configId = config.getIdentifier();
String[] onesource = new String[1];
String outputName;
for (int i = 0; i < sources.length; i++) {
onesource[0] = sources[i];
outputName = config.getOutputFileName(sources[i]);
update(configId, outputName, onesource);
}
}
private void update(String configId, String outputName, String[] sources) {
File outputFile = new File(outputDir, outputName);
//
// if output file doesn't exist or predates the start of the
// compile step (most likely a compilation error) then
// do not write add a history entry
//
if (outputFile.exists()
&& outputFile.lastModified() >= historyFile.lastModified()) {
dirty = true;
history.remove(outputName);
SourceHistory[] sourceHistories = new SourceHistory[sources.length];
for (int i = 0; i < sources.length; i++) {
File sourceFile = new File(sources[i]);
long lastModified = sourceFile.lastModified();
String relativePath = CUtil.getRelativePath(outputDirPath,
sourceFile);
sourceHistories[i] = new SourceHistory(relativePath,
lastModified);
}
TargetHistory newHistory = new TargetHistory(configId, outputName,
outputFile.lastModified(), sourceHistories);
history.put(outputName, newHistory);
}
}
public void update(TargetInfo linkTarget) {
File outputFile = linkTarget.getOutput();
String outputName = outputFile.getName();
//
// if output file doesn't exist or predates the start of the
// compile or link step (most likely a compilation error) then
// do not write add a history entry
//
if (outputFile.exists()
&& outputFile.lastModified() >= historyFile.lastModified()) {
dirty = true;
history.remove(outputName);
SourceHistory[] sourceHistories = linkTarget
.getSourceHistories(outputDirPath);
TargetHistory newHistory = new TargetHistory(linkTarget
.getConfiguration().getIdentifier(), outputName, outputFile
.lastModified(), sourceHistories);
history.put(outputName, newHistory);
}
}
}