test for required bundle files
diff --git a/tests/org.eclipse.wtp.releng.tests/src/org/eclipse/wtp/layout/tests/TestLayout.java b/tests/org.eclipse.wtp.releng.tests/src/org/eclipse/wtp/layout/tests/TestLayout.java
new file mode 100644
index 0000000..6b6203f
--- /dev/null
+++ b/tests/org.eclipse.wtp.releng.tests/src/org/eclipse/wtp/layout/tests/TestLayout.java
@@ -0,0 +1,364 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2009 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
+ * This file originally came from 'Eclipse Orbit' project then adapted to use 
+ * in WTP and improved to use 'Manifest' to read manifest.mf, instead of reading 
+ * it as a properties file.
+ ******************************************************************************/
+package org.eclipse.wtp.layout.tests;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.regex.PatternSyntaxException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import org.eclipse.osgi.util.ManifestElement;
+import org.osgi.framework.BundleException;
+
+import junit.framework.*;
+
+/**
+ * @since 3.3
+ */
+public class TestLayout extends TestCase {
+
+	private static final String EXTENSION_JAR = ".jar";
+	private static final String EXTENSION_ZIP = ".zip";
+	private static final String PROPERTY_BUNDLE_ID = "Bundle-SymbolicName";
+	private static final String CONFIG = "config.properties";
+	private static final String KEY_DFT_BIN_JAR = "default.binary.jar";
+	private static final String KEY_DFT_SRC_JAR = "default.source.jar";
+	private static final String KEY_DFT_BIN_ZIP = "default.binary.zip";
+	private static final String KEY_DFT_SRC_ZIP = "default.source.zip";
+	private Properties config;
+	private List errors = new ArrayList();
+
+	public static Test suite() {
+		return new TestSuite(TestLayout.class);
+	}
+
+	public void addError(String message) {
+		errors.add(message);
+	}
+
+	public void testBundleLayout() {
+		String property = System.getProperty("org.eclipse.wtp.inputDir");
+		assertNotNull("Need to set inputDir for tests.", property);
+		File inputdir = new File(property);
+		assertTrue("inputDir (" + property + ") must be an existing directory.", inputdir.exists() && inputdir.isDirectory());
+		File[] children = inputdir.listFiles();
+		for (int i = 0; i < children.length; i++) {
+			File child = children[i];
+			String id = getBundleId(child);
+			if (id != null) {
+				if (id.endsWith(".source") || id.endsWith(".infopop") || id.endsWith(".doc.user") || id.endsWith(".doc") || id.endsWith(".doc.isv") || id.endsWith(".doc.dev") || id.endsWith(".doc.api") || id.endsWith("standard.schemas") || id.endsWith(".branding"))
+					processBundle(child, getExpected(id, true, child.getName().endsWith(EXTENSION_ZIP)));
+				else
+					processBundle(child, getExpected(id, false, child.getName().endsWith(EXTENSION_ZIP)));
+			}
+		}
+		if (errors.size() > 0) {
+			for (Iterator iter = errors.iterator(); iter.hasNext();)
+				System.err.println(iter.next());
+			fail("Errors in bundle layout. Check error log for details.");
+		}
+	}
+
+	/*
+	 * Check the configuration file and return a set of regular expressions
+	 * which match the list of files that are expected to be in the bundle.
+	 */
+	private Set getExpected(String id, boolean source, boolean zip) {
+		// is the config cached?
+		if (config == null) {
+			config = new Properties();
+			InputStream input = null;
+			try {
+				input = this.getClass().getResourceAsStream(CONFIG);
+				assertNotNull("Unable to load configuration file.", input);
+				config.load(input);
+			}
+			catch (IOException e) {
+				e.printStackTrace();
+				fail(e.getMessage());
+			}
+			finally {
+				try {
+					if (input != null)
+						input.close();
+				}
+				catch (IOException e) {
+					// ignore
+				}
+			}
+		}
+		String line = config.getProperty(id);
+		if (line == null) {
+			if (source)
+				line = zip ? config.getProperty(KEY_DFT_SRC_ZIP) : config.getProperty(KEY_DFT_SRC_JAR);
+			else
+				line = zip ? config.getProperty(KEY_DFT_BIN_ZIP) : config.getProperty(KEY_DFT_BIN_JAR);
+		}
+		if (line == null)
+			fail("Unable to load settings for: " + id);
+		Set result = new HashSet();
+		for (StringTokenizer tokenizer = new StringTokenizer(line, ","); tokenizer.hasMoreTokens();)
+			result.add(tokenizer.nextToken().trim());
+		return result;
+	}
+
+	/*
+	 * Process the bundle at the specified location, with the given set of
+	 * expected results.
+	 */
+	private void processBundle(File file, Set expected) {
+		if (file.isDirectory()) {
+			String[] array = (String[]) expected.toArray(new String[expected.size()]);
+			processDir("", file, array);
+			for (int i = 0; i < array.length; i++)
+				if (array[i] != null)
+					addError("Missing pattern: " + array[i] + " in dir: " + file.getAbsolutePath());
+		}
+		else
+			processArchive(file, (String[]) expected.toArray(new String[expected.size()]));
+	}
+
+	/*
+	 * The bundle is an archive. Make sure it has the right contents.
+	 */
+	private void processArchive(File file, String[] expected) {
+		ZipFile zip = null;
+		try {
+			zip = new ZipFile(file, ZipFile.OPEN_READ);
+			for (Enumeration e = zip.entries(); e.hasMoreElements();) {
+				ZipEntry entry = (ZipEntry) e.nextElement();
+				String name = entry.getName();
+				for (int i = 0; i < expected.length; i++) {
+					String pattern = expected[i];
+					if (pattern == null)
+						continue;
+					try {
+						if (name.matches(pattern))
+							expected[i] = null;
+					}
+					catch (PatternSyntaxException ex) {
+						ex.printStackTrace();
+						fail(ex.getMessage());
+					}
+				}
+			}
+			for (int i = 0; i < expected.length; i++) {
+				if (expected[i] != null)
+					addError("Missing pattern: " + expected[i] + " in file: " + file.getAbsolutePath());
+			}
+		}
+		catch (IOException e) {
+			e.printStackTrace();
+			fail(e.getMessage());
+		}
+		finally {
+			if (zip != null)
+				try {
+					zip.close();
+				}
+				catch (IOException e) {
+					// ignore
+				}
+		}
+	}
+
+	/*
+	 * The bundle is in a directory.
+	 */
+	private void processDir(String root, File dir, String[] expected) {
+		File[] children = dir.listFiles();
+		for (int index = 0; index < children.length; index++) {
+			File child = children[index];
+			String name = root.length() == 0 ? child.getName() : root + '/' + child.getName();
+			if (child.isDirectory()) {
+				processDir(name, child, expected);
+				continue;
+			}
+			for (int i = 0; i < expected.length; i++) {
+				String pattern = expected[i];
+				if (pattern == null)
+					continue;
+				try {
+					if (name.matches(pattern))
+						expected[i] = null;
+				}
+				catch (PatternSyntaxException ex) {
+					ex.printStackTrace();
+					addError(ex.getMessage());
+					continue;
+				}
+			}
+		}
+	}
+
+	/*
+	 * Return the bundle id from the manifest pointed to by the given input
+	 * stream.
+	 */
+	private String getBundleIdFromManifest(InputStream input, String path) {
+		String id = null;
+		try {
+			Map attributes = ManifestElement.parseBundleManifest(input, null);
+			id = (String) attributes.get(PROPERTY_BUNDLE_ID);
+			if (id == null || id.length() == 0) {
+				addError("BundleSymbolicName header not set in manifest for bundle: " + path);
+			}
+			else {
+				int pos = id.indexOf(';');
+				if (pos > 0) {
+					id = id.substring(0, pos);
+				}
+			}
+
+		}
+		catch (BundleException e) {
+			e.printStackTrace();
+			addError(e.getMessage());
+		}
+		catch (IOException e) {
+			e.printStackTrace();
+			addError(e.getMessage());
+		}
+		finally {
+			if (input != null)
+				try {
+					input.close();
+				}
+				catch (IOException e) {
+					// ignore
+				}
+		}
+
+		return id;
+	}
+
+	/*
+	 * Return the bundle identifier for the bundle contained in the given
+	 * archive/directory.
+	 */
+	private String getBundleId(File file) {
+		String id = null;
+		if (file.isDirectory())
+			id = getBundleIdFromDir(file);
+		else if (file.getName().toLowerCase().endsWith(EXTENSION_ZIP))
+			id = getBundleIdFromZIP(file);
+		else if (file.getName().toLowerCase().endsWith(EXTENSION_JAR))
+			id = getBundleIdFromJAR(file);
+		return id;
+	}
+
+	private String getBundleIdFromZIP(File file) {
+		ZipFile zip = null;
+		try {
+			zip = new ZipFile(file);
+			for (Enumeration e = zip.entries(); e.hasMoreElements();) {
+				ZipEntry entry = (ZipEntry) e.nextElement();
+				if (entry.getName().matches("^.*/" + JarFile.MANIFEST_NAME)) {
+					InputStream input = zip.getInputStream(entry);
+					try {
+						return getBundleIdFromManifest(input, file.getAbsolutePath());
+					}
+					finally {
+						try {
+							input.close();
+						}
+						catch (IOException ex) {
+							// ignore
+						}
+					}
+				}
+			}
+		}
+		catch (IOException ex) {
+			ex.printStackTrace();
+			addError(ex.getMessage());
+			// ignore
+		}
+		finally {
+			try {
+				zip.close();
+			}
+			catch (IOException ex) {
+				// ignore
+			}
+		}
+		addError("Bundle manifest (MANIFEST.MF) not found in bundle: " + file.getAbsolutePath());
+		return null;
+	}
+
+	/*
+	 * The given file points to an expanded bundle on disc. Look into the
+	 * bundle manifest file to find the bundle identifier.
+	 */
+	private String getBundleIdFromDir(File dir) {
+		String id = null;
+		File manifestFile = new File(dir, JarFile.MANIFEST_NAME);
+		if (!manifestFile.exists() || !manifestFile.isFile()) {
+			addError("Bundle manifest (MANIFEST.MF) not found at: " + manifestFile.getAbsolutePath());
+		}
+		else {
+			try {
+				id = getBundleIdFromManifest(new FileInputStream(manifestFile), manifestFile.getAbsolutePath());
+			}
+			catch (FileNotFoundException e) {
+				e.printStackTrace();
+				addError(e.getMessage());
+			}
+		}
+		return id;
+	}
+
+	/*
+	 * The given file points to a bundle contained in an archive. Look into
+	 * the bundle manifest file to find the bundle identifier.
+	 */
+	private String getBundleIdFromJAR(File file) {
+		InputStream input = null;
+		JarFile jar = null;
+		try {
+			jar = new JarFile(file, false, ZipFile.OPEN_READ);
+			JarEntry entry = jar.getJarEntry(JarFile.MANIFEST_NAME);
+			if (entry == null) {
+				addError("Bundle does not contain a MANIFEST.MF file: " + file.getAbsolutePath());
+				return null;
+			}
+			input = jar.getInputStream(entry);
+			return getBundleIdFromManifest(input, file.getAbsolutePath());
+		}
+		catch (IOException e) {
+			e.printStackTrace();
+			addError(e.getMessage());
+			return null;
+		}
+		finally {
+			if (input != null)
+				try {
+					input.close();
+				}
+				catch (IOException e) {
+					// ignore
+				}
+			if (jar != null)
+				try {
+					jar.close();
+				}
+				catch (IOException e) {
+					// ignore
+				}
+		}
+	}
+}
diff --git a/tests/org.eclipse.wtp.releng.tests/src/org/eclipse/wtp/layout/tests/config.properties b/tests/org.eclipse.wtp.releng.tests/src/org/eclipse/wtp/layout/tests/config.properties
new file mode 100644
index 0000000..08695af
--- /dev/null
+++ b/tests/org.eclipse.wtp.releng.tests/src/org/eclipse/wtp/layout/tests/config.properties
@@ -0,0 +1,55 @@
+# default layout for a source bundle. contains a manifest,
+# about file, plugin.xml and plugin.properties for translation,
+# as well as a zip file with source and an about.
+default.source.jar = META-INF/MANIFEST\.MF, \
+		about\.html, \
+		.*(plugin|bundle)\.properties
+
+# default layout for a binary bundle. contains a manifest, 
+# about file, and at least one class file or JAR
+default.binary.jar = META-INF/MANIFEST\.MF, \
+		about\.html, \
+		.*(plugin|bundle)\.properties, \
+		.*\.(class|jar)$
+
+# default layout for a source bundle. contains a manifest,
+# about file, plugin.xml and plugin.properties for translation,
+# as well as a zip file with source and an about. Note the
+# extra directory entry at the beginning
+default.source.zip = ^.*/META-INF/MANIFEST\.MF, \
+		^.*/about\.html, \
+		^.*/(plugin|bundle)\.properties
+
+# default layout for a binary bundle. contains a manifest, 
+# about file, and at least one class file or JAR. Note the
+# extra directory entry at the beginning
+default.binary.zip = ^.*/META-INF/MANIFEST\.MF, \
+		^.*/about\.html, \
+		^.*/(plugin|bundle)\.properties, \
+		.*\.(class|jar)$
+
+# special case of a code bundle not containing code ?
+org.eclipse.wst.xsl.saxon= META-INF/MANIFEST\.MF, \
+		about\.html, \
+		.*(plugin|bundle)\.properties 
+		
+# special case with no code: branding bundle
+org.eclipse.wst.xsl= META-INF/MANIFEST\.MF, \
+		about\.html, \
+		.*(plugin|bundle)\.properties 
+		
+# special case with no code: branding bundle	
+org.eclipse.wst.common.fproj.sdk= META-INF/MANIFEST\.MF, \
+		about\.html, \
+		.*(plugin|bundle)\.properties 	
+
+# special case with no code: branding bundle			
+org.eclipse.jst.common.fproj.enablement.jdt.sdk= META-INF/MANIFEST\.MF, \
+		about\.html, \
+		.*(plugin|bundle)\.properties 	
+		
+# special case of a bundle with no code		
+org.eclipse.jst.ws.creation.ejb.ui= META-INF/MANIFEST\.MF, \
+		about\.html, \
+		.*(plugin|bundle)\.properties 	
+					
\ No newline at end of file
diff --git a/tests/org.eclipse.wtp.releng.tests/src/org/eclipse/wtp/releng/tests/TestBuild.java b/tests/org.eclipse.wtp.releng.tests/src/org/eclipse/wtp/releng/tests/TestBuild.java
index 59f70e2..61e9103 100644
--- a/tests/org.eclipse.wtp.releng.tests/src/org/eclipse/wtp/releng/tests/TestBuild.java
+++ b/tests/org.eclipse.wtp.releng.tests/src/org/eclipse/wtp/releng/tests/TestBuild.java
@@ -1,5 +1,7 @@
 package org.eclipse.wtp.releng.tests;
 
+import org.eclipse.wtp.layout.tests.TestLayout;
+
 import junit.framework.Test;
 import junit.framework.TestSuite;
 
@@ -11,5 +13,6 @@
 	public TestBuild() {
 		super("Build Test Suite");
 		addTest(new TestSuite(BuildTests.class, "Build Tests"));
+		addTest(new TestSuite(TestLayout.class, "Test Bundle Layout"));
 	}
 }	
\ No newline at end of file
diff --git a/tests/org.eclipse.wtp.releng.tests/test.xml b/tests/org.eclipse.wtp.releng.tests/test.xml
index 9a3674a..f6888a1 100644
--- a/tests/org.eclipse.wtp.releng.tests/test.xml
+++ b/tests/org.eclipse.wtp.releng.tests/test.xml
@@ -15,6 +15,10 @@
               value="org.eclipse.wtp.releng.tests.TestBuild" />
     <property name="testType" value="core-test" />
 
+     <!-- if not otherwise defined, try the workbench value -->
+	<property name="eclipse-home" value="${eclipse.home}" />
+	
+
     <!-- should be little need to change what's below -->
 
     <echo message="basedir: ${basedir}" />