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