From 96d5245af3a1fe13f25298d090585f40d4228159 Mon Sep 17 00:00:00 2001
From: david_williams <david_williams>
Date: Sat, 24 Feb 2007 04:59:42 +0000
Subject: [PATCH] create wtp tools project

---
 .../org.eclipse.wtp.releng.tools/.classpath   |    7 +
 .../org.eclipse.wtp.releng.tools/.cvsignore   |    4 +
 .../org.eclipse.wtp.releng.tools/.project     |   28 +
 .../META-INF/MANIFEST.MF                      |    8 +
 .../build.properties                          |    5 +
 .../plugin.properties                         |    2 +
 .../org.eclipse.wtp.releng.tools/plugin.xml   |   27 +
 .../wtp/releng/tools/ErrorTracker.java        |  224 +++
 .../eclipse/wtp/releng/tools/FileCounter.java |  146 ++
 .../wtp/releng/tools/PlatformStatus.java      |   59 +
 .../releng/tools/TestResultsGenerator.java    | 1250 +++++++++++++++++
 .../wtpRelengTools.jar                        |  Bin 0 -> 19421 bytes
 12 files changed, 1760 insertions(+)
 create mode 100644 archive/releng.builder/tools/org.eclipse.wtp.releng.tools/.classpath
 create mode 100644 archive/releng.builder/tools/org.eclipse.wtp.releng.tools/.cvsignore
 create mode 100644 archive/releng.builder/tools/org.eclipse.wtp.releng.tools/.project
 create mode 100644 archive/releng.builder/tools/org.eclipse.wtp.releng.tools/META-INF/MANIFEST.MF
 create mode 100644 archive/releng.builder/tools/org.eclipse.wtp.releng.tools/build.properties
 create mode 100644 archive/releng.builder/tools/org.eclipse.wtp.releng.tools/plugin.properties
 create mode 100644 archive/releng.builder/tools/org.eclipse.wtp.releng.tools/plugin.xml
 create mode 100644 archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/ErrorTracker.java
 create mode 100644 archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/FileCounter.java
 create mode 100644 archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/PlatformStatus.java
 create mode 100644 archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/TestResultsGenerator.java
 create mode 100644 archive/releng.builder/tools/org.eclipse.wtp.releng.tools/wtpRelengTools.jar

diff --git a/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/.classpath b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/.classpath
new file mode 100644
index 000000000..751c8f2e5
--- /dev/null
+++ b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/.cvsignore b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/.cvsignore
new file mode 100644
index 000000000..5022c63e8
--- /dev/null
+++ b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/.cvsignore
@@ -0,0 +1,4 @@
+javaCompiler.wtpRelengTools.jar.args
+build.xml
+bin
+temp.folder
diff --git a/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/.project b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/.project
new file mode 100644
index 000000000..99c64d284
--- /dev/null
+++ b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.eclipse.wtp.releng.tools</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/META-INF/MANIFEST.MF b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/META-INF/MANIFEST.MF
new file mode 100644
index 000000000..048d2df1c
--- /dev/null
+++ b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/META-INF/MANIFEST.MF
@@ -0,0 +1,8 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %Bundle-Name.0
+Bundle-SymbolicName: org.eclipse.wtp.releng.tools;singleton:=true
+Bundle-Version: 1.0.0
+Require-Bundle: org.apache.ant
+Bundle-Localization: plugin
+Bundle-ClassPath: wtpRelengTools.jar
diff --git a/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/build.properties b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/build.properties
new file mode 100644
index 000000000..73f4109b7
--- /dev/null
+++ b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/build.properties
@@ -0,0 +1,5 @@
+bin.includes = META-INF/,\
+               plugin.xml,\
+               wtpRelengTools.jar,\
+               plugin.properties
+source.wtpRelengTools.jar = src/
diff --git a/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/plugin.properties b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/plugin.properties
new file mode 100644
index 000000000..be037b9d4
--- /dev/null
+++ b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/plugin.properties
@@ -0,0 +1,2 @@
+# properties file for org.eclipse.wtp.releng.tools
+Bundle-Name.0 = Tools
\ No newline at end of file
diff --git a/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/plugin.xml b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/plugin.xml
new file mode 100644
index 000000000..0dee10e32
--- /dev/null
+++ b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/plugin.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.2"?>
+<plugin>
+
+  <extension
+         point="org.eclipse.ant.core.extraClasspathEntries">
+      <extraClasspathEntry
+            library="wtpRelengTools.jar">
+      </extraClasspathEntry>
+   </extension>
+	<!-- Tasks -->
+   <extension point="org.eclipse.ant.core.antTasks">
+   
+       <antTask
+            library="wtpRelengTools.jar"
+            name="indexResults"
+            class="org.eclipse.wtp.releng.tools.TestResultsGenerator">
+      </antTask>
+       <antTask
+            library="wtpRelengTools.jar"
+            name="indexResults"
+            class="org.eclipse.wtp.releng.tools.FileCounter">
+      </antTask>
+ 
+    </extension>
+
+</plugin>
diff --git a/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/ErrorTracker.java b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/ErrorTracker.java
new file mode 100644
index 000000000..37efce70a
--- /dev/null
+++ b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/ErrorTracker.java
@@ -0,0 +1,224 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wtp.releng.tools;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.io.File;
+
+import java.util.Vector;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+/**
+ * @version 	1.0
+ * @author
+ */
+public class ErrorTracker {
+
+	// List of test logs expected at end of build
+	private Vector testLogs = new Vector();
+
+
+	// Platforms keyed on 
+	private Hashtable platforms = new Hashtable();
+	private Hashtable logFiles = new Hashtable();
+	private Hashtable typesMap = new Hashtable();
+	private Vector typesList = new Vector();
+	
+	public static void main(String[] args) {
+		
+		// For testing only.  Should not be invoked
+		
+		ErrorTracker anInstance = new ErrorTracker();
+		anInstance.loadFile("C:\\junk\\testManifest.xml");
+		String[] theTypes = anInstance.getTypes();
+		for (int i=0; i < theTypes.length; i++) {
+			// System.out.println("Type: " + theTypes[i]);
+			PlatformStatus[] thePlatforms = anInstance.getPlatforms(theTypes[i]);
+			for (int j=0; j < thePlatforms.length; j++) {
+				// System.out.println("Out ID: " + thePlatforms[j].getId());
+			}
+		}
+	}
+	
+	public void loadFile(String fileName) {
+		DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
+		DocumentBuilder parser=null;
+		try {
+			parser = docBuilderFactory.newDocumentBuilder();
+		} catch (ParserConfigurationException e1) {
+			e1.printStackTrace();
+		}
+		try {
+			
+			Document document = parser.parse(fileName);
+			NodeList elements = document.getElementsByTagName("platform");
+			int elementCount = elements.getLength();
+			for (int i = 0; i < elementCount; i++) {
+				PlatformStatus aPlatform = new PlatformStatus((Element) elements.item(i));
+				//System.out.println("ID: " + aPlatform.getId());
+				platforms.put(aPlatform.getId(), aPlatform);
+				
+				Node zipType = elements.item(i).getParentNode();
+				String zipTypeName = (String) zipType.getAttributes().getNamedItem("name").getNodeValue();
+				
+				Vector aVector = (Vector) typesMap.get(zipTypeName);
+				if (aVector == null) {
+					typesList.add(zipTypeName);
+					aVector = new Vector();
+					typesMap.put(zipTypeName, aVector);
+				}
+				aVector.add(aPlatform.getId());
+				
+			}
+
+			NodeList effectedFiles = document.getElementsByTagName("effectedFile");
+			int effectedFilesCount = effectedFiles.getLength();
+			for (int i = 0; i < effectedFilesCount; i++) {
+				Node anEffectedFile = effectedFiles.item(i);
+				Node logFile = anEffectedFile.getParentNode();
+				String logFileName = (String) logFile.getAttributes().getNamedItem("name").getNodeValue();
+				logFileName=convertPathDelimiters(logFileName);
+				String effectedFileID = (String) anEffectedFile.getAttributes().getNamedItem("id").getNodeValue();				
+				//System.out.println(logFileName);
+				Vector aVector = (Vector) logFiles.get(logFileName);
+				if (aVector == null) {
+					aVector = new Vector();
+					logFiles.put(logFileName, aVector);
+					
+				}
+				PlatformStatus ps=(PlatformStatus) platforms.get(effectedFileID);
+				if (ps!=null)
+					aVector.addElement(ps);
+			}
+			
+			// store a list of the test logs expected after testing
+			NodeList testLogList = document.getElementsByTagName("logFile");
+				int testLogCount = testLogList.getLength();
+				for (int i = 0; i < testLogCount; i++) {
+								
+					Node testLog = testLogList.item(i);
+					String testLogName = (String) testLog.getAttributes().getNamedItem("name").getNodeValue();
+					Node typeNode=testLog.getAttributes().getNamedItem("type");
+					String type="test";
+					if (typeNode!=null){
+						type = typeNode.getNodeValue();
+					}
+					if (testLogName.endsWith(".xml")&&type.equals("test")){
+						testLogs.add(testLogName);
+						//System.out.println(testLogName);
+					}
+			
+			}
+
+
+//			// Test this mess.
+//			Object[] results = platforms.values().toArray();
+//			for (int i=0; i < results.length; i++) {
+//				PlatformStatus ap = (PlatformStatus) results[i];
+//				System.out.println("ID: " + ap.getId() + " passed: " + ap.getPassed());
+//			}
+//		
+//			Enumeration anEnumeration = logFiles.keys();
+//			while (anEnumeration.hasMoreElements()) {
+//				String aKey = (String) anEnumeration.nextElement();
+//				System.out.println("Whack a key: " + aKey);
+//				((PlatformStatus) logFiles.get(aKey)).setPassed(false);
+//			}
+//			
+//			results = platforms.values().toArray();
+//			for (int i=0; i < results.length; i++) {
+//				PlatformStatus ap = (PlatformStatus) results[i];
+//				System.out.println("ID: " + ap.getId() + " passed: " + ap.getPassed());
+//			}
+			
+			
+		
+			
+			
+		} catch (IOException e) {
+			System.out.println("IOException: " + fileName);
+			// e.printStackTrace();
+			
+		} catch (SAXException e) {
+			System.out.println("SAXException: " + fileName);
+			e.printStackTrace();
+			
+		}
+	}
+	
+	public void registerError(String fileName) {
+		// System.out.println("Found an error in: " + fileName);
+		if (logFiles.containsKey(fileName)) {
+			Vector aVector = (Vector) logFiles.get(fileName);
+			for (int i = 0; i < aVector.size(); i++) {
+				((PlatformStatus) aVector.elementAt(i)).registerError();
+			}
+		} else {
+			
+			// If a log file is not specified explicitly it effects
+			// all "platforms" except JDT
+			
+			Enumeration values = platforms.elements();
+			while (values.hasMoreElements()) {
+				PlatformStatus aValue = (PlatformStatus) values.nextElement();
+				if (!aValue.getId().equals("JA") && 
+					!aValue.getId().equals("EW") && 
+					!aValue.getId().equals("EA")) {
+						aValue.registerError();
+				}
+			}
+		}
+	}
+	
+	public boolean hasErrors(String id) {
+		return ((PlatformStatus) platforms.get(id)).hasErrors();
+	}
+	
+	// Answer a string array of the zip type names in the order they appear in
+	// the .xml file.
+	public String[] getTypes() {
+		return (String[]) typesList.toArray(new String[typesList.size()]);
+	}
+	
+	// Answer an array of PlatformStatus objects for a given type.
+
+	public PlatformStatus[] getPlatforms(String type) {
+		Vector platformIDs = (Vector) typesMap.get(type);
+		PlatformStatus[] result = new PlatformStatus[platformIDs.size()];
+		for (int i = 0; i < platformIDs.size(); i++) {
+			result[i] = (PlatformStatus) platforms.get((String) platformIDs.elementAt(i));
+		}
+		return  result;
+	}	
+
+	/**
+	 * Returns the testLogs.
+	 * @return Vector
+	 */
+	public Vector getTestLogs() {
+		return testLogs;
+	}
+
+	private String convertPathDelimiters(String path){
+		return new File(path).getPath();
+	}
+	
+}
diff --git a/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/FileCounter.java b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/FileCounter.java
new file mode 100644
index 000000000..f61c4474f
--- /dev/null
+++ b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/FileCounter.java
@@ -0,0 +1,146 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.wtp.releng.tools;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.StringTokenizer;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Task;
+
+/**
+ * This task will count the number of fils in a given directory
+ * that match a given filter.  The number of fils will be output
+ * to a given output file.  The output file will be overwritten
+ * if it already exists.
+ * 
+ * Note: Filter comparison is NOT case sensitive.  Do not use wild cards.
+ * ie .zip counts all files with .zip anywere in the name.
+ */
+public class FileCounter extends Task {
+	
+	private String sourceDirectory = "";
+	private String filterString = ".zip";
+	private String outputFile = "";
+	
+	public static void main(String args[]) {
+		// For testing only.
+		FileCounter aFileCounter = new FileCounter();
+		aFileCounter.setSourceDirectory("c:\\RelEng\\dean");
+		aFileCounter.setOutputFile("c:\\RelEng\\dean\\files.count");
+		aFileCounter.setFilterString(".zip");
+		aFileCounter.execute();
+	}
+
+	public void execute() throws BuildException {
+		// Do the work.
+		
+		int count = 0;
+		
+		System.out.println("Source Directory: " + this.getSourceDirectory());
+		System.out.println("Output File: " + this.getOutputFile());
+		System.out.println("Filter String: " + this.getFilterString());
+		
+		File aDirectory = new File(this.getSourceDirectory());
+		if (aDirectory == null) {
+			throw new BuildException("Directory " + this.getSourceDirectory() + " not found.");
+		}
+		
+		String[] names = aDirectory.list();
+		if (names == null) {
+			throw new BuildException("Directory " + this.getSourceDirectory() + " not found.");
+		}			
+		
+		System.out.println("List size: " + names.length);
+		
+		for (int i = 0; i < names.length; i++) {
+			System.out.println("Name: " + names[i]);
+			
+			int index = -1;
+			StringTokenizer types = getFileTypes();
+			
+			while (types.hasMoreTokens()){
+				index = names[i].toLowerCase().indexOf(types.nextToken().toLowerCase());
+				if (index != -1) {
+					count++;
+				}
+			}
+
+		}
+		
+		try {
+			FileOutputStream anOutputStream = new FileOutputStream(this.getOutputFile());
+			anOutputStream.write(String.valueOf(count).getBytes());
+			anOutputStream.close();
+		} catch (FileNotFoundException e) {
+			throw new BuildException("Can not create file.count file");
+		} catch (IOException e) {
+			throw new BuildException("Can not create file.count file");
+		}
+		
+	}
+
+	private StringTokenizer getFileTypes(){
+		return new StringTokenizer(getFilterString(),",");
+	}
+
+	/**
+	 * Gets the sourceDirectory.
+	 * @return Returns a String
+	 */
+	public String getSourceDirectory() {
+		return sourceDirectory;
+	}
+
+	/**
+	 * Sets the sourceDirectory.
+	 * @param sourceDirectory The sourceDirectory to set
+	 */
+	public void setSourceDirectory(String sourceDirectory) {
+		this.sourceDirectory = sourceDirectory;
+	}
+
+	/**
+	 * Gets the filterString.
+	 * @return Returns a String
+	 */
+	public String getFilterString() {
+		return filterString;
+	}
+
+	/**
+	 * Sets the filterString.
+	 * @param filterString The filterString to set
+	 */
+	public void setFilterString(String filterString) {
+		this.filterString = filterString;
+	}
+
+	/**
+	 * Gets the outputFile.
+	 * @return Returns a String
+	 */
+	public String getOutputFile() {
+		return outputFile;
+	}
+
+	/**
+	 * Sets the outputFile.
+	 * @param outputFile The outputFile to set
+	 */
+	public void setOutputFile(String outputFile) {
+		this.outputFile = outputFile;
+	}
+
+}
diff --git a/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/PlatformStatus.java b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/PlatformStatus.java
new file mode 100644
index 000000000..5da345143
--- /dev/null
+++ b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/PlatformStatus.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wtp.releng.tools;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+
+/**
+ * @version 	1.0
+ * @author
+ */
+public class PlatformStatus {
+	
+	private String id;
+	private String name;
+	private String fileName;
+	private boolean hasErrors = false;
+	
+	PlatformStatus(Element anElement) {
+		super();
+		NamedNodeMap attributes = anElement.getAttributes();
+		this.id = (String) attributes.getNamedItem("id").getNodeValue();
+		this.name = (String) attributes.getNamedItem("name").getNodeValue();
+		this.fileName = (String) attributes.getNamedItem("fileName").getNodeValue();
+
+	}
+
+	/**
+	 * Gets the id.
+	 * @return Returns a String
+	 */
+	public String getId() {
+		return id;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public String getFileName() {
+		return fileName;
+	}
+	
+	public void registerError() {
+		this.hasErrors = true;
+	}
+	
+	public boolean hasErrors() {
+		return this.hasErrors;
+	}
+}
diff --git a/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/TestResultsGenerator.java b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/TestResultsGenerator.java
new file mode 100644
index 000000000..4a6ed6de8
--- /dev/null
+++ b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/TestResultsGenerator.java
@@ -0,0 +1,1250 @@
+package org.eclipse.wtp.releng.tools;
+
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import java.util.Enumeration;
+
+import org.apache.tools.ant.Task;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+
+/**
+ * @version 1.0
+ * @author Dean Roberts
+ */
+public class TestResultsGenerator extends Task {
+	private static final String WARNING_SEVERITY = "WARNING";
+	private static final String ERROR_SEVERITY = "ERROR";
+	private static final String ForbiddenReferenceID = "ForbiddenReference";
+	private static final String DiscouragedReferenceID = "DiscouragedReference";
+
+	private static final int DEFAULT_READING_SIZE = 8192;
+
+	static final String elementName = "testsuite";
+	static final String testResultsToken = "%testresults%";
+	static final String compileLogsToken = "%compilelogs%";
+	public Vector dropTokens;
+	public Vector platformSpecs;
+	public Vector differentPlatforms;
+	public String testResultsWithProblems = "\n";
+
+	private DocumentBuilder parser = null;
+	public ErrorTracker anErrorTracker;
+	public String testResultsTemplateString = "";
+	public String dropTemplateString = "";
+
+	public Vector platformDescription;
+	public Vector platformTemplateString;
+	public Vector platformDropFileName;
+
+	// Status of tests results (pending, successful, failed), used to specify
+	// the color
+	// of the test Results link on the build pages (standard, green, red),
+	// once failures
+	// are encountered, this is set to failed
+	protected String testResultsStatus = "successful";
+	// assume tests ran. If no html files are found, this is set to false
+	private boolean testsRan = true;
+
+	// Parameters
+	// build runs JUnit automated tests
+	private boolean isBuildTested;
+
+	// buildType, I, N
+	public String buildType;
+
+	// Comma separated list of drop tokens
+	public String dropTokenList;
+
+	// Token in platform.php.template to be replaced by the desired platform
+	// ID
+	public String platformIdentifierToken;
+
+	// Location of the xml files
+	public String xmlDirectoryName;
+
+	// Location of the html files
+	public String htmlDirectoryName;
+
+	// Location of the resulting index.php file.
+	public String dropDirectoryName;
+
+	// Location and name of the template index.php file.
+	public String testResultsTemplateFileName;
+
+	// Platform specific template and output list (colon separated) in the
+	// following format:
+	// <descriptor, ie. OS name>,path to template file, path to output file
+	public String platformSpecificTemplateList = "";
+
+	// Location and name of the template drop index.php file.
+	public String dropTemplateFileName;
+
+	// Name of the generated index php file.
+	public String testResultsHtmlFileName;
+
+	// Name of the generated drop index php file;
+	public String dropHtmlFileName;
+
+	// Arbitrary path used in the index.php page to href the
+	// generated .html files.
+	public String hrefTestResultsTargetPath;
+
+	// Aritrary path used in the index.php page to reference the compileLogs
+	public String hrefCompileLogsTargetPath;
+
+	// Location of compile logs base directory
+	public String compileLogsDirectoryName;
+
+	// Location and name of test manifest file
+	public String testManifestFileName;
+
+	// Initialize the prefix to a default string
+	private String prefix = "default";
+	private String testShortName = "";
+	private int counter = 0;
+	// The four configurations, add new configurations to test results here +
+	// update
+	// testResults.php.template for changes
+	private String[] testsConfig = {"linux.gtk.x86.xml", "linux.gtk.x86_5.0.xml", "macosx.carbon.ppc.xml", "win32.win32.x86.xml", "win32.win32.x86_5.0.xml"};
+
+
+	public static void main(String[] args) {
+		TestResultsGenerator test = new TestResultsGenerator();
+		test.setDropTokenList("%sdk%,%tests%,%example%,%rcpruntime%,%rcpsdk%,%icubase%,%runtime%,%platformsdk%,%jdt%,%jdtsdk%,%pde%,%pdesdk%,%cvs%,%cvssdk%,%teamextras%,%swt%,%relengtools%");
+		test.setPlatformIdentifierToken("%platform%");
+		test.getDropTokensFromList(test.dropTokenList);
+		test.setIsBuildTested(true);
+		test.setXmlDirectoryName("C:\\junk\\testresults\\xml");
+		test.setHtmlDirectoryName("C:\\junk\\testresults");
+		test.setDropDirectoryName("C:\\junk");
+		test.setTestResultsTemplateFileName("C:\\junk\\templateFiles\\testResults.php.template");
+		test.setPlatformSpecificTemplateList("Windows,C:\\junk\\templateFiles\\platform.php.template,winPlatform.php;Linux,C:\\junk\\templateFiles\\platform.php.template,linPlatform.php;Solaris,C:\\junk\\templateFiles\\platform.php.template,solPlatform.php;AIX,C:\\junk\\templateFiles\\platform.php.template,aixPlatform.php;Macintosh,C:\\junk\\templateFiles\\platform.php.template,macPlatform.php;Source Build,C:\\junk\\templateFiles\\sourceBuilds.php.template,sourceBuilds.php");
+		test.setDropTemplateFileName("C:\\junk\\templateFiles\\index.php.template");
+		test.setTestResultsHtmlFileName("testResults.php");
+		// test.setDropHtmlFileName("index.php");
+		test.setDropHtmlFileName("index.html");
+
+		test.setHrefTestResultsTargetPath("testresults");
+		test.setCompileLogsDirectoryName("C:\\junk\\compilelogs\\plugins");
+		test.setHrefCompileLogsTargetPath("compilelogs");
+		test.setTestManifestFileName("C:\\junk\\testManifest.xml");
+		test.execute();
+	}
+
+	public void execute() {
+
+		anErrorTracker = new ErrorTracker();
+		platformDescription = new Vector();
+		platformTemplateString = new Vector();
+		platformDropFileName = new Vector();
+		anErrorTracker.loadFile(testManifestFileName);
+		getDropTokensFromList(dropTokenList);
+		testResultsTemplateString = readFile(testResultsTemplateFileName);
+		dropTemplateString = readFile(dropTemplateFileName);
+
+		// Specific to the platform build-page
+		if (platformSpecificTemplateList != "") {
+			String description, platformTemplateFile, platformDropFile;
+			// Retrieve the different platforms and their info
+			getDifferentPlatformsFromList(platformSpecificTemplateList);
+			// Parses the platform info and retrieves the platform name,
+			// template file, and drop file
+			for (int i = 0; i < differentPlatforms.size(); i++) {
+				getPlatformSpecsFromList(differentPlatforms.get(i).toString());
+				description = platformSpecs.get(0).toString();
+				platformTemplateFile = platformSpecs.get(1).toString();
+				platformDropFile = platformSpecs.get(2).toString();
+				platformDescription.add(description);
+				platformTemplateString.add(readFile(platformTemplateFile));
+				platformDropFileName.add(platformDropFile);
+
+			}
+
+		}
+
+		System.out.println("Begin: Generating test results index page");
+		System.out.println("Parsing XML files");
+		parseXml();
+		System.out.println("Parsing compile logs");
+		parseCompileLogs();
+		System.out.println("End: Generating test results index page");
+		writeTestResultsFile();
+		// For the platform build-page, write platform files, in addition to
+		// the index file
+		if (platformSpecificTemplateList != "") {
+			writeDropFiles();
+		}
+		else {
+			writeDropIndexFile();
+		}
+	}
+
+	public void parseCompileLogs() {
+
+		StringBuffer replaceString = new StringBuffer();
+		processCompileLogsDirectory(compileLogsDirectoryName, replaceString);
+		if (replaceString.length() == 0) {
+			replaceString.append("None");
+		}
+		testResultsTemplateString = replace(testResultsTemplateString, compileLogsToken, String.valueOf(replaceString));
+
+	}
+
+	private void processCompileLogsDirectory(String directoryName, StringBuffer buffer) {
+		File sourceDirectory = new File(directoryName);
+		if (sourceDirectory.isFile()) {
+			if (sourceDirectory.getName().endsWith(".log"))
+				readCompileLog(sourceDirectory.getAbsolutePath(), buffer);
+			if (sourceDirectory.getName().endsWith(".xml"))
+				parseCompileLog(sourceDirectory.getAbsolutePath(), buffer);
+		}
+		if (sourceDirectory.isDirectory()) {
+			File[] logFiles = sourceDirectory.listFiles();
+			Arrays.sort(logFiles);
+			for (int j = 0; j < logFiles.length; j++) {
+				processCompileLogsDirectory(logFiles[j].getAbsolutePath(), buffer);
+			}
+		}
+	}
+
+	private void readCompileLog(String log, StringBuffer buffer) {
+		String fileContents = readFile(log);
+
+		int errorCount = countCompileErrors(fileContents);
+		int warningCount = countCompileWarnings(fileContents);
+		int forbiddenWarningCount = countForbiddenWarnings(fileContents);
+		int discouragedWarningCount = countDiscouragedWarnings(fileContents);
+		if (errorCount != 0) {
+			// use wildcard in place of version number on directory names
+			String logName = log.substring(getCompileLogsDirectoryName().length() + 1);
+			StringBuffer stringBuffer = new StringBuffer(logName);
+			stringBuffer.replace(logName.indexOf("_") + 1, logName.indexOf(File.separator, logName.indexOf("_") + 1), "*");
+			logName = new String(stringBuffer);
+
+			anErrorTracker.registerError(logName);
+		}
+		formatCompileErrorRow(log, errorCount, warningCount, forbiddenWarningCount, discouragedWarningCount, buffer);
+	}
+
+	private void parseCompileLog(String log, StringBuffer stringBuffer) {
+		int errorCount = 0;
+		int warningCount = 0;
+		int forbiddenWarningCount = 0;
+		int discouragedWarningCount = 0;
+
+		File file = new File(log);
+		Document aDocument = null;
+		BufferedReader reader = null;
+		try {
+			reader = new BufferedReader(new FileReader(file));
+			InputSource inputSource = new InputSource(reader);
+			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+			DocumentBuilder builder = factory.newDocumentBuilder();
+			aDocument = builder.parse(inputSource);
+		}
+		catch (SAXException e) {
+			e.printStackTrace();
+		}
+		catch (IOException e) {
+			e.printStackTrace();
+		}
+		catch (ParserConfigurationException e) {
+			e.printStackTrace();
+		}
+		finally {
+			if (reader != null) {
+				try {
+					reader.close();
+				}
+				catch (IOException e) {
+					// ignore
+				}
+			}
+		}
+
+		if (aDocument == null)
+			return;
+		// Get summary of problems
+		NodeList nodeList = aDocument.getElementsByTagName("problem");
+		if (nodeList == null || nodeList.getLength() == 0)
+			return;
+
+		int length = nodeList.getLength();
+		for (int i = 0; i < length; i++) {
+			Node problemNode = nodeList.item(i);
+			NamedNodeMap aNamedNodeMap = problemNode.getAttributes();
+			Node severityNode = aNamedNodeMap.getNamedItem("severity");
+			Node idNode = aNamedNodeMap.getNamedItem("id");
+			if (severityNode != null) {
+				String severityNodeValue = severityNode.getNodeValue();
+				if (WARNING_SEVERITY.equals(severityNodeValue)) {
+					// this is a warning
+					// need to check the id
+					String nodeValue = idNode.getNodeValue();
+					if (ForbiddenReferenceID.equals(nodeValue)) {
+						forbiddenWarningCount++;
+					}
+					else if (DiscouragedReferenceID.equals(nodeValue)) {
+						discouragedWarningCount++;
+					}
+					else {
+						warningCount++;
+					}
+				}
+				else if (ERROR_SEVERITY.equals(severityNodeValue)) {
+					// this is an error
+					errorCount++;
+				}
+			}
+		}
+		if (errorCount != 0) {
+			// use wildcard in place of version number on directory names
+			// System.out.println(log + "/n");
+			String logName = log.substring(getCompileLogsDirectoryName().length() + 1);
+			StringBuffer buffer = new StringBuffer(logName);
+			buffer.replace(logName.indexOf("_") + 1, logName.indexOf(File.separator, logName.indexOf("_") + 1), "*");
+			logName = new String(buffer);
+
+			anErrorTracker.registerError(logName);
+		}
+		formatCompileErrorRow(log.replaceAll(".xml", ".html"), errorCount, warningCount, forbiddenWarningCount, discouragedWarningCount, stringBuffer);
+	}
+
+	public static byte[] getFileByteContent(String fileName) throws IOException {
+		InputStream stream = null;
+		try {
+			File file = new File(fileName);
+			stream = new FileInputStream(file);
+			return getInputStreamAsByteArray(stream, (int) file.length());
+		}
+		finally {
+			if (stream != null) {
+				try {
+					stream.close();
+				}
+				catch (IOException e) {
+					// ignore
+				}
+			}
+		}
+	}
+
+	/**
+	 * Returns the given input stream's contents as a byte array. If a length
+	 * is specified (ie. if length != -1), only length bytes are returned.
+	 * Otherwise all bytes in the stream are returned. Note this doesn't close
+	 * the stream.
+	 * 
+	 * @throws IOException
+	 *             if a problem occured reading the stream.
+	 */
+	public static byte[] getInputStreamAsByteArray(InputStream stream, int length) throws IOException {
+		byte[] contents;
+		if (length == -1) {
+			contents = new byte[0];
+			int contentsLength = 0;
+			int amountRead = -1;
+			do {
+				int amountRequested = Math.max(stream.available(), DEFAULT_READING_SIZE); // read
+																							// at
+																							// least
+																							// 8K
+
+				// resize contents if needed
+				if (contentsLength + amountRequested > contents.length) {
+					System.arraycopy(contents, 0, contents = new byte[contentsLength + amountRequested], 0, contentsLength);
+				}
+
+				// read as many bytes as possible
+				amountRead = stream.read(contents, contentsLength, amountRequested);
+
+				if (amountRead > 0) {
+					// remember length of contents
+					contentsLength += amountRead;
+				}
+			}
+			while (amountRead != -1);
+
+			// resize contents if necessary
+			if (contentsLength < contents.length) {
+				System.arraycopy(contents, 0, contents = new byte[contentsLength], 0, contentsLength);
+			}
+		}
+		else {
+			contents = new byte[length];
+			int len = 0;
+			int readSize = 0;
+			while ((readSize != -1) && (len != length)) {
+				// See PR 1FMS89U
+				// We record first the read size. In this case len is the
+				// actual read size.
+				len += readSize;
+				readSize = stream.read(contents, len, length - len);
+			}
+		}
+
+		return contents;
+	}
+
+	public String readFile(String fileName) {
+		byte[] aByteArray = null;
+		try {
+			aByteArray = getFileByteContent(fileName);
+		}
+		catch (IOException e) {
+			e.printStackTrace();
+		}
+		if (aByteArray == null) {
+			return "";
+		}
+		return new String(aByteArray);
+	}
+
+	private int countCompileErrors(String aString) {
+		return extractNumber(aString, "error");
+	}
+
+	private int countCompileWarnings(String aString) {
+		return extractNumber(aString, "warning");
+	}
+
+	private int countForbiddenWarnings(String aString) {
+		return extractNumber(aString, "Access restriction:");
+	}
+
+	private int countDiscouragedWarnings(String aString) {
+		return extractNumber(aString, "Discouraged access:");
+	}
+
+	private int extractNumber(String aString, String endToken) {
+		int endIndex = aString.lastIndexOf(endToken);
+		if (endIndex == -1) {
+			return 0;
+		}
+
+		int startIndex = endIndex;
+		while (startIndex >= 0 && aString.charAt(startIndex) != '(' && aString.charAt(startIndex) != ',') {
+			startIndex--;
+		}
+
+		String count = aString.substring(startIndex + 1, endIndex).trim();
+		try {
+			return Integer.parseInt(count);
+		}
+		catch (NumberFormatException e) {
+			return 0;
+		}
+
+	}
+
+	private int missingCount = 0;
+	private boolean includeAll;
+
+	private String verifyAllTestsRan(String directory) {
+		Enumeration enumeration = (anErrorTracker.getTestLogs()).elements();
+
+		String replaceString = "";
+		while (enumeration.hasMoreElements()) {
+			String testLogName = enumeration.nextElement().toString();
+
+			if (new File(directory + File.separator + testLogName).exists())
+				continue;
+
+			anErrorTracker.registerError(testLogName);
+			String tmp = ((platformSpecificTemplateList == "") ? formatRow(testLogName, -1, false) : formatRowReleng(testLogName, -1, false));
+			if (missingCount == 0) {
+				replaceString = replaceString + "</table></br>" + "\n" + "<table width=\"65%\" border=\"1\" bgcolor=\"#EEEEEE\" rules=\"groups\" align=\"center\">" + "<tr bgcolor=\"#9999CC\"> <th width=\"80%\" align=\"center\"> Missing Files </th><th  align=\"center\"> Status </th></tr>";
+			}
+			replaceString = replaceString + tmp;
+			testResultsWithProblems = testResultsWithProblems.concat("\n" + testLogName.substring(0, testLogName.length() - 4) + " (file missing)");
+			missingCount++;
+		}
+		return replaceString;
+	}
+
+	public void parseXml() {
+
+		File sourceDirectory = new File(xmlDirectoryName);
+
+		if (sourceDirectory.exists()) {
+
+			String replaceString = "";
+
+			File[] xmlFileNames = sourceDirectory.listFiles();
+			Arrays.sort(xmlFileNames);
+
+			for (int i = 0; i < xmlFileNames.length; i++) {
+				if (xmlFileNames[i].getPath().endsWith(".xml")) {
+					String fullName = xmlFileNames[i].getPath();
+					int errorCount = countErrors(fullName);
+					if (errorCount != 0) {
+						String testName = xmlFileNames[i].getName().substring(0, xmlFileNames[i].getName().length() - 4);
+						testResultsWithProblems = testResultsWithProblems.concat("\n" + testName);
+						anErrorTracker.registerError(fullName.substring(getXmlDirectoryName().length() + 1));
+					}
+
+
+					String tmp = ((platformSpecificTemplateList == "") ? formatRow(xmlFileNames[i].getPath(), errorCount, true) : formatRowReleng(xmlFileNames[i].getPath(), errorCount, true));
+					replaceString = replaceString + tmp;
+
+
+				}
+			}
+			// check for missing test logs
+			replaceString = replaceString + verifyAllTestsRan(xmlDirectoryName);
+
+			testResultsTemplateString = replace(testResultsTemplateString, testResultsToken, replaceString);
+			testsRan = true;
+
+		}
+		else {
+			testsRan = false;
+			System.out.println("Test results not found in " + sourceDirectory.getAbsolutePath());
+		}
+
+	}
+
+	private String replace(String source, String original, String replacement) {
+
+		int replaceIndex = source.indexOf(original);
+		if (replaceIndex > -1) {
+			String resultString = source.substring(0, replaceIndex);
+			resultString = resultString + replacement;
+			resultString = resultString + source.substring(replaceIndex + original.length());
+			return resultString;
+		}
+		else {
+			System.out.println("Could not find token: " + original);
+			return source;
+		}
+
+	}
+
+	protected void writeDropFiles() {
+		writeDropIndexFile();
+		// Write all the platform files
+		for (int i = 0; i < platformDescription.size(); i++) {
+			writePlatformFile(platformDescription.get(i).toString(), platformTemplateString.get(i).toString(), platformDropFileName.get(i).toString());
+		}
+	}
+
+	protected void writeDropIndexFile() {
+
+		String[] types = anErrorTracker.getTypes();
+		for (int i = 0; i < types.length; i++) {
+			PlatformStatus[] platforms = anErrorTracker.getPlatforms(types[i]);
+			String replaceString = processDropRows(platforms);
+			dropTemplateString = replace(dropTemplateString, dropTokens.get(i).toString(), replaceString);
+		}
+		// Replace the token %testsStatus% with the status of the test results
+		dropTemplateString = replace(dropTemplateString, "%testsStatus%", testResultsStatus);
+		String outputFileName = dropDirectoryName + File.separator + dropHtmlFileName;
+		writeFile(outputFileName, dropTemplateString);
+	}
+
+	// Writes the platform file (dropFileName) specific to "desiredPlatform"
+	protected void writePlatformFile(String desiredPlatform, String templateString, String dropFileName) {
+
+		String[] types = anErrorTracker.getTypes();
+		for (int i = 0; i < types.length; i++) {
+			PlatformStatus[] platforms = anErrorTracker.getPlatforms(types[i]);
+			// Call processPlatformDropRows passing the platform's name
+			String replaceString = processPlatformDropRows(platforms, desiredPlatform);
+			templateString = replace(templateString, dropTokens.get(i).toString(), replaceString);
+		}
+		// Replace the platformIdentifierToken with the platform's name and
+		// the testsStatus
+		// token with the status of the test results
+		templateString = replace(templateString, platformIdentifierToken, desiredPlatform);
+		templateString = replace(templateString, "%testsStatus%", testResultsStatus);
+		String outputFileName = dropDirectoryName + File.separator + dropFileName;
+		writeFile(outputFileName, templateString);
+	}
+
+	// Process drop rows specific to each of the platforms
+	protected String processPlatformDropRows(PlatformStatus[] platforms, String name) {
+
+		String result = "";
+		boolean found = false;
+		for (int i = 0; i < platforms.length; i++) {
+			// If the platform description indicates the platform's name, or
+			// "All",
+			// call processDropRow
+			if (platforms[i].getName().startsWith(name.substring(0, 3)) || platforms[i].getName().equals("All")) {
+				result = result + processDropRow(platforms[i]);
+				found = true;
+			}
+			// If the platform description indicates "All Other Platforms",
+			// process
+			// the row locally
+			else if (platforms[i].getName().equals("All Other Platforms") && !found) {
+				String imageName = "";
+
+				if (platforms[i].hasErrors()) {
+					imageName = "<a href=\"" + getTestResultsHtmlFileName() + "\"><img src = \"FAIL.gif\" width=19 height=23></a>";
+				}
+				else {
+					if (testsRan) {
+						imageName = "<img src = \"OK.gif\" width=19 height=23>";
+					}
+					else {
+						if (isBuildTested) {
+							imageName = "<font size=\"-1\" color=\"#FF0000\">pending</font>";
+						}
+						else {
+							imageName = "<img src = \"OK.gif\" width=19 height=23>";
+						}
+					}
+				}
+
+				result = result + "<tr>";
+				result = result + "<td><div align=left>" + imageName + "</div></td>\n";
+				result = result + "<td>All " + name + "</td>";
+				// generate ftp, http, md5 and sha1 links by calling php
+				// functions in the template
+				result = result + "<td><?php genLinks($_SERVER[\"SERVER_NAME\"],\"@buildlabel@\",\"" + platforms[i].getFileName() + "\"); ?></td>\n";
+				result = result + "</tr>\n";
+			}
+		}
+
+		return result;
+	}
+
+	protected String processDropRows(PlatformStatus[] platforms) {
+
+		String result = "";
+		for (int i = 0; i < platforms.length; i++) {
+			result = result + processDropRow(platforms[i]);
+		}
+
+		return result;
+	}
+
+	protected String processDropRow(PlatformStatus aPlatform) {
+
+		String imageName = "";
+
+		if (aPlatform.hasErrors()) {
+			imageName = "<a href=\"" + getTestResultsHtmlFileName() + "\"><img src = \"FAIL.gif\" width=19 height=23></a>";
+			// Failure in tests
+			testResultsStatus = "failed";
+		}
+		else {
+			if (testsRan) {
+				imageName = "<img src = \"OK.gif\" width=19 height=23>";
+			}
+			else {
+				if (isBuildTested) {
+					imageName = "<font size=\"-1\" color=\"#FF0000\">pending</font>";
+					// Tests are pending
+					testResultsStatus = "pending";
+				}
+				else {
+					imageName = "<img src = \"OK.gif\" width=19 height=23>";
+				}
+			}
+		}
+
+		String result = "<tr>";
+
+		result = result + "<td><div align=left>" + imageName + "</div></td>\n";
+		result = result + "<td>" + aPlatform.getName() + "</td>";
+		result = result + "<td>" + aPlatform.getFileName() + "</td>\n";
+		result = result + "</tr>\n";
+
+		return result;
+	}
+
+	public void writeTestResultsFile() {
+
+		String outputFileName = dropDirectoryName + File.separator + testResultsHtmlFileName;
+		writeFile(outputFileName, testResultsTemplateString);
+	}
+
+	private void writeFile(String outputFileName, String contents) {
+		FileOutputStream outputStream = null;
+		try {
+			outputStream = new FileOutputStream(outputFileName);
+			outputStream.write(contents.getBytes());
+		}
+		catch (FileNotFoundException e) {
+			System.out.println("File not found exception writing: " + outputFileName);
+		}
+		catch (IOException e) {
+			System.out.println("IOException writing: " + outputFileName);
+		}
+		finally {
+			if (outputStream != null) {
+				try {
+					outputStream.close();
+				}
+				catch (IOException e) {
+					// ignore
+				}
+			}
+		}
+	}
+
+	public void setTestResultsHtmlFileName(String aString) {
+		testResultsHtmlFileName = aString;
+	}
+
+	public String getTestResultsHtmlFileName() {
+		return testResultsHtmlFileName;
+	}
+
+	public void setTestResultsTemplateFileName(String aString) {
+		testResultsTemplateFileName = aString;
+	}
+
+	public String getTestResultsTemplateFileName() {
+		return testResultsTemplateFileName;
+	}
+
+	public void setXmlDirectoryName(String aString) {
+		xmlDirectoryName = aString;
+	}
+
+	public String getXmlDirectoryName() {
+		return xmlDirectoryName;
+	}
+
+	public void setHtmlDirectoryName(String aString) {
+		htmlDirectoryName = aString;
+	}
+
+	public String getHtmlDirectoryName() {
+		return htmlDirectoryName;
+	}
+
+	public void setDropDirectoryName(String aString) {
+		dropDirectoryName = aString;
+	}
+
+	public String getDropDirectoryName() {
+		return dropDirectoryName;
+	}
+
+	private void formatCompileErrorRow(String fileName, int errorCount, int warningCount, int forbiddenAccessWarningCount, int discouragedAccessWarningCount, StringBuffer buffer) {
+
+		int accessRuleWarningCount = forbiddenAccessWarningCount + discouragedAccessWarningCount;
+		if (!includeAll) {
+			if (errorCount == 0 && warningCount == 0 && accessRuleWarningCount == 0) {
+				return;
+			}
+		}
+
+		int i = fileName.indexOf(getHrefCompileLogsTargetPath());
+
+		String shortName = fileName.substring(i + getHrefCompileLogsTargetPath().length());
+
+		buffer.append("<tr>\n<td>\n").append("<a href=").append("\"").append(getHrefCompileLogsTargetPath()).append(shortName).append("\">").append(shortName).append("</a>").append("</td><td align=\"center\">").append("<a href=").append("\"").append(getHrefCompileLogsTargetPath()).append(shortName).append("#ERRORS").append("\">").append(errorCount).append("</a>").append("</td><td align=\"center\">").append("<a href=").append("\"").append(getHrefCompileLogsTargetPath()).append(shortName).append("#ACCESSRULES_WARNINGS").append("\">").append(accessRuleWarningCount).append("</a>").append("(").append(forbiddenAccessWarningCount).append("/").append(discouragedAccessWarningCount).append(")").append("</td><td align=\"center\">").append("<a href=").append("\"").append(getHrefCompileLogsTargetPath()).append(shortName).append("#OTHER_WARNINGS").append("\">").append(warningCount).append("</a>").append("</td>\n</tr>\n");
+	}
+
+	private String formatRow(String fileName, int errorCount, boolean link) {
+
+		// replace .xml with .html
+
+		String aString = "";
+		if (!link) {
+			return "<tr><td>" + fileName + " (missing)" + "</td><td>" + "DNF";
+		}
+
+		if (fileName.endsWith(".xml")) {
+
+			int begin = fileName.lastIndexOf(File.separatorChar);
+			int end = fileName.lastIndexOf(".xml");
+
+			String shortName = fileName.substring(begin + 1, end);
+			String displayName = shortName;
+			if (errorCount != 0)
+				aString = aString + "<tr><td><b>";
+			else
+				aString = aString + "<tr><td>";
+
+
+			if (errorCount != 0) {
+				displayName = "<font color=\"#ff0000\">" + displayName + "</font>";
+			}
+			if (errorCount == -1) {
+				aString = aString.concat(displayName);
+			}
+			else {
+				aString = aString + "<a href=" + "\"" + hrefTestResultsTargetPath + "/" + shortName + ".html" + "\">" + displayName + "</a>";
+			}
+			if (errorCount > 0)
+				aString = aString + "</td><td><b>";
+			else
+				aString = aString + "</td><td>";
+
+			if (errorCount == -1)
+				aString = aString + "<font color=\"#ff0000\">DNF";
+
+			else if (errorCount > 0)
+				aString = aString + "<font color=\"#ff0000\">" + String.valueOf(errorCount);
+			else
+				aString = aString + String.valueOf(errorCount);
+
+			if (errorCount != 0)
+				aString = aString + "</font></b></td></tr>";
+			else
+				aString = aString + "</td></tr>";
+		}
+
+		return aString;
+
+	}
+
+	// Specific to the RelEng test results page
+	private String formatRowReleng(String fileName, int errorCount, boolean link) {
+
+		// If the file name doesn't end with any of the set test
+		// configurations, do nothing
+		boolean endsWithConfig = false;
+		int card = testsConfig.length;
+		for (int i = 0; i < card; i++) {
+			if (fileName.endsWith(testsConfig[i]))
+				endsWithConfig = true;
+		}
+		if (!endsWithConfig)
+			return "";
+
+		String aString = "";
+		if (!link) {
+			return "<tr><td>" + fileName + "</td><td align=\"center\">" + "DNF </tr>";
+		}
+
+		if (fileName.endsWith(".xml")) {
+
+			int begin = fileName.lastIndexOf(File.separatorChar);
+
+			// Get org.eclipse. out of the component name
+			String shortName = fileName.substring(begin + 13, fileName.indexOf('_'));
+			String displayName = shortName;
+
+			// If the short name does not start with this prefix
+			if (!shortName.startsWith(prefix)) {
+				// If the prefix is not yet set
+				if (prefix == "default") {
+					// Set the testShortName variable to the current short
+					// name
+					testShortName = shortName;
+					counter = 0;
+					// Set new prefix
+					prefix = shortName.substring(0, shortName.indexOf(".tests") + 6);
+					aString = aString + "<tbody><tr><td><b>" + prefix + ".*" + "</b><td><td><td><td>";
+					aString = aString + "<tr><td><P>" + shortName;
+
+					// Loop until the matching string postfix(test config.) is
+					// found
+					while (counter < card && !fileName.endsWith(testsConfig[counter])) {
+						aString = aString + "<td align=\"center\">-</td>";
+						counter++;
+					}
+				}
+				else {
+					// Set new prefix
+					prefix = shortName.substring(0, shortName.indexOf(".tests") + 6);
+
+					// Loop until the matching string postfix(test config.) is
+					// found
+					while (counter < card && !fileName.endsWith(testsConfig[counter])) {
+						aString = aString + "<td align=\"center\">-</td>";
+						counter++;
+					}
+
+					// In this case, the new prefix should be set with the
+					// short name under it,
+					// since this would mean that the team has more than one
+					// component test
+					if (!shortName.endsWith("tests")) {
+						aString = aString + "<tbody><tr><td><b>" + prefix + ".*" + "</b><td><td><td><td>";
+						aString = aString + "<tr><td><P>" + shortName;
+					}
+					// The team has only one component test
+					else
+						aString = aString + "<tbody><tr><td><b>" + shortName;
+					testShortName = shortName;
+
+					counter = 0;
+				}
+			}
+			// If the file's short name starts with the current prefix
+			if (shortName.startsWith(prefix)) {
+				// If the new file has a different short name than the current
+				// one
+				if (!shortName.equals(testShortName)) {
+					// Fill the remaining cells with '-'. These files will
+					// later be listed as
+					// missing
+					while (counter < card) {
+						aString = aString + "<td align=\"center\">-</td>";
+						counter++;
+					}
+					counter = 0;
+					// Print the component name
+					aString = aString + "<tr><td><P>" + shortName;
+					// Loop until the matching string postfix(test config.) is
+					// found
+					while (counter < card && !fileName.endsWith(testsConfig[counter])) {
+						aString = aString + "<td align=\"center\">-</td>";
+						counter++;
+					}
+				}
+				else {
+					// Loop until the matching string postfix(test config.) is
+					// found
+					while (counter < card && !fileName.endsWith(testsConfig[counter])) {
+						aString = aString + "<td align=\"center\">-</td>";
+						counter++;
+					}
+					// If the previous component has no more test files left
+					if (counter == card) {
+						counter = 0;
+						// Print the new component name
+						aString = aString + "<tr><td><P>" + shortName;
+						// Loop until the matching string postfix(test
+						// config.) is found
+						while (counter < card && !fileName.endsWith(testsConfig[counter])) {
+							aString = aString + "<td align=\"center\">-</td>";
+							counter++;
+						}
+					}
+				}
+
+				testShortName = shortName;
+
+				if (errorCount != 0)
+					aString = aString + "<td align=\"center\"><b>";
+				else
+					aString = aString + "<td align=\"center\">";
+
+				// Print number of errors
+				if (errorCount != 0) {
+					displayName = "<font color=\"#ff0000\">" + "(" + String.valueOf(errorCount) + ")" + "</font>";
+				}
+				else {
+					displayName = "(0)";
+				}
+
+				// Reference
+				if (errorCount == -1) {
+					aString = aString.concat(displayName);
+				}
+				else {
+					aString = aString + "<a href=" + "\"" + hrefTestResultsTargetPath + "/" + fileName.substring(begin + 1, fileName.length() - 4) + ".html" + "\">" + displayName + "</a>";
+				}
+
+				if (errorCount == -1)
+					aString = aString + "<font color=\"#ff0000\">DNF";
+
+				if (errorCount != 0)
+					aString = aString + "</font></b></td>";
+				else
+					aString = aString + "</td>";
+				counter++;
+			}
+		}
+
+		return aString;
+	}
+
+	private int countErrors(String fileName) {
+		int errorCount = 0;
+
+		if (new File(fileName).length() == 0)
+			return -1;
+
+		try {
+			DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
+			parser = docBuilderFactory.newDocumentBuilder();
+
+			Document document = parser.parse(fileName);
+			NodeList elements = document.getElementsByTagName(elementName);
+
+			int elementCount = elements.getLength();
+			if (elementCount == 0)
+				return -1;
+			for (int i = 0; i < elementCount; i++) {
+				Element element = (Element) elements.item(i);
+				NamedNodeMap attributes = element.getAttributes();
+				Node aNode = attributes.getNamedItem("errors");
+				errorCount = errorCount + Integer.parseInt(aNode.getNodeValue());
+				aNode = attributes.getNamedItem("failures");
+				errorCount = errorCount + Integer.parseInt(aNode.getNodeValue());
+
+			}
+
+		}
+		catch (IOException e) {
+			System.out.println("IOException: " + fileName);
+			// e.printStackTrace();
+			return 0;
+		}
+		catch (SAXException e) {
+			System.out.println("SAXException: " + fileName);
+			// e.printStackTrace();
+			return 0;
+		}
+		catch (ParserConfigurationException e) {
+			e.printStackTrace();
+		}
+		return errorCount;
+	}
+
+
+
+	/**
+	 * Gets the hrefTestResultsTargetPath.
+	 * 
+	 * @return Returns a String
+	 */
+	public String getHrefTestResultsTargetPath() {
+		return hrefTestResultsTargetPath;
+	}
+
+	/**
+	 * Sets the hrefTestResultsTargetPath.
+	 * 
+	 * @param hrefTestResultsTargetPath
+	 *            The hrefTestResultsTargetPath to set
+	 */
+	public void setHrefTestResultsTargetPath(String htmlTargetPath) {
+		this.hrefTestResultsTargetPath = htmlTargetPath;
+	}
+
+	/**
+	 * Gets the compileLogsDirectoryName.
+	 * 
+	 * @return Returns a String
+	 */
+	public String getCompileLogsDirectoryName() {
+		return compileLogsDirectoryName;
+	}
+
+	/**
+	 * Sets the compileLogsDirectoryName.
+	 * 
+	 * @param compileLogsDirectoryName
+	 *            The compileLogsDirectoryName to set
+	 */
+	public void setCompileLogsDirectoryName(String compileLogsDirectoryName) {
+		this.compileLogsDirectoryName = compileLogsDirectoryName;
+	}
+
+	/**
+	 * Gets the hrefCompileLogsTargetPath.
+	 * 
+	 * @return Returns a String
+	 */
+	public String getHrefCompileLogsTargetPath() {
+		return hrefCompileLogsTargetPath;
+	}
+
+	/**
+	 * Sets the hrefCompileLogsTargetPath.
+	 * 
+	 * @param hrefCompileLogsTargetPath
+	 *            The hrefCompileLogsTargetPath to set
+	 */
+	public void setHrefCompileLogsTargetPath(String hrefCompileLogsTargetPath) {
+		this.hrefCompileLogsTargetPath = hrefCompileLogsTargetPath;
+	}
+
+	/**
+	 * Gets the testManifestFileName.
+	 * 
+	 * @return Returns a String
+	 */
+	public String getTestManifestFileName() {
+		return testManifestFileName;
+	}
+
+	/**
+	 * Sets the testManifestFileName.
+	 * 
+	 * @param testManifestFileName
+	 *            The testManifestFileName to set
+	 */
+	public void setTestManifestFileName(String testManifestFileName) {
+		this.testManifestFileName = testManifestFileName;
+	}
+
+	/**
+	 * Gets the dropHtmlFileName.
+	 * 
+	 * @return Returns a String
+	 */
+	public String getDropHtmlFileName() {
+		return dropHtmlFileName;
+	}
+
+	/**
+	 * Sets the dropHtmlFileName.
+	 * 
+	 * @param dropHtmlFileName
+	 *            The dropHtmlFileName to set
+	 */
+	public void setDropHtmlFileName(String dropHtmlFileName) {
+		this.dropHtmlFileName = dropHtmlFileName;
+	}
+
+	/**
+	 * Gets the dropTemplateFileName.
+	 * 
+	 * @return Returns a String
+	 */
+	public String getDropTemplateFileName() {
+		return dropTemplateFileName;
+	}
+
+	/**
+	 * Sets the dropTemplateFileName.
+	 * 
+	 * @param dropTemplateFileName
+	 *            The dropTemplateFileName to set
+	 */
+	public void setDropTemplateFileName(String dropTemplateFileName) {
+		this.dropTemplateFileName = dropTemplateFileName;
+	}
+
+	protected void getDropTokensFromList(String list) {
+		StringTokenizer tokenizer = new StringTokenizer(list, ",");
+		dropTokens = new Vector();
+
+		while (tokenizer.hasMoreTokens()) {
+			dropTokens.add(tokenizer.nextToken());
+		}
+	}
+
+	protected void getDifferentPlatformsFromList(String list) {
+		StringTokenizer tokenizer = new StringTokenizer(list, ";");
+		differentPlatforms = new Vector();
+
+		while (tokenizer.hasMoreTokens()) {
+			differentPlatforms.add(tokenizer.nextToken());
+		}
+	}
+
+	protected void getPlatformSpecsFromList(String list) {
+		StringTokenizer tokenizer = new StringTokenizer(list, ",");
+		platformSpecs = new Vector();
+
+		while (tokenizer.hasMoreTokens()) {
+			platformSpecs.add(tokenizer.nextToken());
+		}
+	}
+
+	public String getDropTokenList() {
+		return dropTokenList;
+	}
+
+	public void setDropTokenList(String dropTokenList) {
+		this.dropTokenList = dropTokenList;
+	}
+
+	public boolean isBuildTested() {
+		return isBuildTested;
+	}
+
+	public void setIsBuildTested(boolean isBuildTested) {
+		this.isBuildTested = isBuildTested;
+	}
+
+
+	/**
+	 * @return
+	 */
+	public boolean testsRan() {
+		return testsRan;
+	}
+
+	/**
+	 * @param b
+	 */
+	public void setTestsRan(boolean b) {
+		testsRan = b;
+	}
+
+	/**
+	 * @return
+	 */
+	public Vector getDropTokens() {
+		return dropTokens;
+	}
+
+	/**
+	 * @param vector
+	 */
+	public void setDropTokens(Vector vector) {
+		dropTokens = vector;
+	}
+
+	/**
+	 * @return
+	 */
+	public String getTestResultsWithProblems() {
+		return testResultsWithProblems;
+	}
+
+	/**
+	 * @param string
+	 */
+	public void setTestResultsWithProblems(String string) {
+		testResultsWithProblems = string;
+	}
+
+	public String getBuildType() {
+		return buildType;
+	}
+
+	public void setBuildType(String buildType) {
+		this.buildType = buildType;
+	}
+
+	public String getPlatformSpecificTemplateList() {
+		return platformSpecificTemplateList;
+	}
+
+	public void setPlatformSpecificTemplateList(String platformSpecificTemplateList) {
+		this.platformSpecificTemplateList = platformSpecificTemplateList;
+	}
+
+	public void setPlatformIdentifierToken(String platformIdentifierToken) {
+		this.platformIdentifierToken = platformIdentifierToken;
+	}
+
+	public String getPlatformIdentifierToken() {
+		return platformIdentifierToken;
+	}
+
+	public boolean isIncludeAll() {
+		return includeAll;
+	}
+
+	public void setIncludeAll(boolean includeAll) {
+		this.includeAll = includeAll;
+	}
+
+}
diff --git a/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/wtpRelengTools.jar b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/wtpRelengTools.jar
new file mode 100644
index 0000000000000000000000000000000000000000..623e66dbde5a9b822295894dee3f27e8d95c5706
GIT binary patch
literal 19421
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
E51FVZMgRZ+

literal 0
HcmV?d00001

-- 
GitLab