category engines added
diff --git a/core/plugins/org.eclipse.dltk.testing/plugin.xml b/core/plugins/org.eclipse.dltk.testing/plugin.xml
index b9e279e..1e71bc5 100755
--- a/core/plugins/org.eclipse.dltk.testing/plugin.xml
+++ b/core/plugins/org.eclipse.dltk.testing/plugin.xml
@@ -5,6 +5,7 @@
    <extension-point id="testingLaunchConfigs" name="%testingLaunchConfigs.name" schema="schema/testingLaunchConfigs.exsd"/>
    <extension-point id="memberResolver" name="%extension-point.name.memberResolver" schema="schema/memberResolver.exsd"/>
    <extension-point id="engine" name="engine" schema="schema/testingEngine.exsd"/>
+   <extension-point id="categoryEngine" name="categoryEngine" schema="schema/testCategoryEngine.exsd"/>
 
    <extension
          point="org.eclipse.ui.views">
diff --git a/core/plugins/org.eclipse.dltk.testing/schema/testCategoryEngine.exsd b/core/plugins/org.eclipse.dltk.testing/schema/testCategoryEngine.exsd
new file mode 100644
index 0000000..b7733dc
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.testing/schema/testCategoryEngine.exsd
@@ -0,0 +1,130 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- Schema file written by PDE -->
+<schema targetNamespace="org.eclipse.dltk.testing" xmlns="http://www.w3.org/2001/XMLSchema">
+<annotation>
+      <appinfo>
+         <meta.schema plugin="org.eclipse.dltk.testing" id="testCategoryEngine" name="DLTK Test Category Engine"/>
+      </appinfo>
+      <documentation>
+         [Enter description of this extension point.]
+      </documentation>
+   </annotation>
+
+   <element name="extension">
+      <annotation>
+         <appinfo>
+            <meta.element />
+         </appinfo>
+      </annotation>
+      <complexType>
+         <sequence minOccurs="1" maxOccurs="unbounded">
+            <element ref="testCategoryEngine" minOccurs="1" maxOccurs="unbounded"/>
+         </sequence>
+         <attribute name="point" type="string" use="required">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="id" type="string">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="name" type="string">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+               <appinfo>
+                  <meta.attribute translatable="true"/>
+               </appinfo>
+            </annotation>
+         </attribute>
+      </complexType>
+   </element>
+
+   <element name="testCategoryEngine">
+      <complexType>
+         <attribute name="class" type="string" use="required">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+               <appinfo>
+                  <meta.attribute kind="java" basedOn=":org.eclipse.dltk.testing.ITestCategoryEngine"/>
+               </appinfo>
+            </annotation>
+         </attribute>
+         <attribute name="priority" type="string" use="default" value="0">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="id" type="string" use="required">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="name" type="string" use="required">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="testingEngineId" type="string" use="required">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+      </complexType>
+   </element>
+
+   <annotation>
+      <appinfo>
+         <meta.section type="since"/>
+      </appinfo>
+      <documentation>
+         [Enter the first release in which this extension point appears.]
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appinfo>
+         <meta.section type="examples"/>
+      </appinfo>
+      <documentation>
+         [Enter extension point usage example here.]
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appinfo>
+         <meta.section type="apiInfo"/>
+      </appinfo>
+      <documentation>
+         [Enter API information here.]
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appinfo>
+         <meta.section type="implementation"/>
+      </appinfo>
+      <documentation>
+         [Enter information about supplied implementation of this extension point.]
+      </documentation>
+   </annotation>
+
+
+</schema>
diff --git a/core/plugins/org.eclipse.dltk.testing/src/org/eclipse/dltk/internal/testing/TestCategoryEngineManager.java b/core/plugins/org.eclipse.dltk.testing/src/org/eclipse/dltk/internal/testing/TestCategoryEngineManager.java
new file mode 100644
index 0000000..9e78d7a
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.testing/src/org/eclipse/dltk/internal/testing/TestCategoryEngineManager.java
@@ -0,0 +1,133 @@
+/*******************************************************************************
+ * Copyright (c) 2008 xored software, Inc.
+ *
+ * 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:
+ *     xored software, Inc. - initial API and Implementation (Alex Panchenko)
+ *******************************************************************************/
+package org.eclipse.dltk.internal.testing;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.dltk.internal.testing.util.NumberUtils;
+import org.eclipse.dltk.testing.DLTKTestingPlugin;
+import org.eclipse.dltk.testing.ITestCategoryEngine;
+import org.eclipse.dltk.testing.ITestRunnerUI;
+import org.eclipse.dltk.utils.NatureExtensionManager;
+
+public class TestCategoryEngineManager extends NatureExtensionManager {
+
+	private static final String EXTENSION_POINT = DLTKTestingPlugin.PLUGIN_ID
+			+ ".categoryEngine"; //$NON-NLS-1$
+
+	protected String getCategoryAttributeName() {
+		return "testingEngineId"; //$NON-NLS-1$
+	}
+
+	private static class Descriptor {
+
+		final IConfigurationElement element;
+		final int priority;
+
+		/**
+		 * @param confElement
+		 * @param priority
+		 */
+		public Descriptor(IConfigurationElement confElement, int priority) {
+			this.element = confElement;
+			this.priority = priority;
+		}
+
+	}
+
+	private TestCategoryEngineManager() {
+		super(EXTENSION_POINT, Descriptor.class);
+	}
+
+	private static final String PRIORITY_ATTR = "priority"; //$NON-NLS-1$
+
+	protected Object createDescriptor(IConfigurationElement confElement) {
+		final String strPriority = confElement.getAttribute(PRIORITY_ATTR);
+		int priority = NumberUtils.toInt(strPriority);
+		return new Descriptor(confElement, priority);
+	}
+
+	private final Comparator descriptorComparator = new Comparator() {
+
+		public int compare(Object o1, Object o2) {
+			Descriptor descriptor1 = (Descriptor) o1;
+			Descriptor descriptor2 = (Descriptor) o2;
+			return descriptor1.priority - descriptor2.priority;
+		}
+
+	};
+
+	protected void initializeDescriptors(List descriptors) {
+		Collections.sort(descriptors, descriptorComparator);
+	}
+
+	protected Object createInstanceByDescriptor(Object descriptor)
+			throws CoreException {
+		return descriptor;
+	}
+
+	protected Object[] createEmptyResult() {
+		return new Descriptor[0];
+	}
+
+	private static TestCategoryEngineManager instance = null;
+
+	private static TestCategoryEngineManager getInstance() {
+		if (instance == null) {
+			instance = new TestCategoryEngineManager();
+		}
+		return instance;
+	}
+
+	/**
+	 * Returns the category engines registered for the specified testing engine
+	 * or <code>null</code>.
+	 * 
+	 * @param runnerUI
+	 * @return
+	 */
+	public static ITestCategoryEngine[] getCategoryEngines(
+			ITestRunnerUI runnerUI) {
+		final Descriptor[] descriptors = (Descriptor[]) getInstance()
+				.getInstances(runnerUI.getTestingEngine().getId());
+		if (descriptors == null) {
+			return null;
+		}
+		final List result = new ArrayList(descriptors.length);
+		for (int i = 0; i < descriptors.length; ++i) {
+			final Descriptor descriptor = descriptors[i];
+			ITestCategoryEngine categoryEngine;
+			try {
+				categoryEngine = (ITestCategoryEngine) descriptor.element
+						.createExecutableExtension("class"); //$NON-NLS-1$
+				if (categoryEngine.initialize(runnerUI)) {
+					result.add(categoryEngine);
+				}
+			} catch (CoreException e) {
+				DLTKTestingPlugin.log("Error creating category engine", e); //$NON-NLS-1$
+			} catch (ClassCastException e) {
+				DLTKTestingPlugin.log("Error creating category engine", e); //$NON-NLS-1$
+			}
+		}
+		if (result.isEmpty()) {
+			return null;
+		} else {
+			return (ITestCategoryEngine[]) result
+					.toArray(new ITestCategoryEngine[result.size()]);
+		}
+	}
+}
diff --git a/core/plugins/org.eclipse.dltk.testing/src/org/eclipse/dltk/internal/testing/launcher/NullTestRunnerUI.java b/core/plugins/org.eclipse.dltk.testing/src/org/eclipse/dltk/internal/testing/launcher/NullTestRunnerUI.java
index f372739..a6ad091 100644
--- a/core/plugins/org.eclipse.dltk.testing/src/org/eclipse/dltk/internal/testing/launcher/NullTestRunnerUI.java
+++ b/core/plugins/org.eclipse.dltk.testing/src/org/eclipse/dltk/internal/testing/launcher/NullTestRunnerUI.java
@@ -11,8 +11,10 @@
  *******************************************************************************/
 package org.eclipse.dltk.internal.testing.launcher;
 
+import org.eclipse.dltk.core.IScriptProject;
 import org.eclipse.dltk.testing.AbstractTestRunnerUI;
 import org.eclipse.dltk.testing.ITestRunnerUI;
+import org.eclipse.dltk.testing.ITestingEngine;
 
 public class NullTestRunnerUI extends AbstractTestRunnerUI {
 
@@ -36,4 +38,18 @@
 		return null;
 	}
 
+	/*
+	 * @see org.eclipse.dltk.testing.ITestRunnerUI#getProject()
+	 */
+	public IScriptProject getProject() {
+		return null;
+	}
+
+	/*
+	 * @see org.eclipse.dltk.testing.ITestRunnerUI#getTestingEngine()
+	 */
+	public ITestingEngine getTestingEngine() {
+		return null;
+	}
+
 }
diff --git a/core/plugins/org.eclipse.dltk.testing/src/org/eclipse/dltk/internal/testing/model/TestRunSession.java b/core/plugins/org.eclipse.dltk.testing/src/org/eclipse/dltk/internal/testing/model/TestRunSession.java
index 3ef83bb..7dacd11 100755
--- a/core/plugins/org.eclipse.dltk.testing/src/org/eclipse/dltk/internal/testing/model/TestRunSession.java
+++ b/core/plugins/org.eclipse.dltk.testing/src/org/eclipse/dltk/internal/testing/model/TestRunSession.java
@@ -29,17 +29,20 @@
 import org.eclipse.debug.core.ILaunchManager;
 import org.eclipse.debug.core.ILaunchesListener2;
 import org.eclipse.dltk.core.IScriptProject;
+import org.eclipse.dltk.internal.testing.TestCategoryEngineManager;
 import org.eclipse.dltk.internal.testing.launcher.NullTestRunnerUI;
 import org.eclipse.dltk.internal.testing.launcher.NullTestingEngine;
 import org.eclipse.dltk.internal.testing.model.TestElement.Status;
 import org.eclipse.dltk.testing.DLTKTestingConstants;
 import org.eclipse.dltk.testing.DLTKTestingMessages;
 import org.eclipse.dltk.testing.DLTKTestingPlugin;
+import org.eclipse.dltk.testing.ITestCategoryEngine;
 import org.eclipse.dltk.testing.ITestRunnerUI;
 import org.eclipse.dltk.testing.ITestSession;
 import org.eclipse.dltk.testing.ITestingClient;
 import org.eclipse.dltk.testing.ITestingEngine;
 import org.eclipse.dltk.testing.MessageIds;
+import org.eclipse.dltk.testing.TestCategoryDescriptor;
 import org.eclipse.dltk.testing.model.ITestElement;
 import org.eclipse.dltk.testing.model.ITestElementContainer;
 import org.eclipse.dltk.testing.model.ITestRunSession;
@@ -62,6 +65,7 @@
 
 	private final ITestingEngine fTestingEngine;
 	private final ITestRunnerUI testRunnerUI;
+	private final ITestCategoryEngine[] categoryEngines;
 	
 	/**
 	 * Test runner client or <code>null</code>.
@@ -143,6 +147,7 @@
 		fTestRunName= testRunName;
 		fTestingEngine= NullTestingEngine.getInstance();
 		testRunnerUI= NullTestRunnerUI.getInstance();
+		categoryEngines = null;
 		
 		fTestRoot= new TestRoot(this);
 		fIdToTest= new HashMap();
@@ -167,10 +172,13 @@
 			fTestRunName= launchConfiguration.getName();
 			fTestingEngine= DLTKTestingConstants.getTestingEngine(launchConfiguration);
 			testRunnerUI= fTestingEngine.getTestRunnerUI(project, launchConfiguration);
+			categoryEngines = TestCategoryEngineManager
+					.getCategoryEngines(testRunnerUI);
 		} else {
 			fTestRunName= project.getElementName();
 			fTestingEngine= NullTestingEngine.getInstance();
 			testRunnerUI= NullTestRunnerUI.getInstance();
+			categoryEngines = null;
 		}
 		
 		fTestRoot= new TestRoot(this);
@@ -540,17 +548,23 @@
 
 	private TestCategoryElement selectCategory(String id, String testName,
 			boolean isSuite) {
+		if (categoryEngines != null) {
+			for (int i = 0; i < categoryEngines.length; ++i) {
+				final TestCategoryDescriptor descriptor = categoryEngines[i]
+						.getCategory(id, testName, isSuite);
+				if (descriptor != null) {
+					TestCategoryElement categoryElement = (TestCategoryElement) fCategoryMap
+							.get(descriptor.getId());
+					if (categoryElement == null) {
+						categoryElement = new TestCategoryElement(fTestRoot,
+								descriptor.getId(), descriptor.getName());
+						fCategoryMap.put(descriptor.getId(), categoryElement);
+					}
+					return categoryElement;
+				}
+			}
+		}
 		return null;
-//		TestCategoryElement category;
-//		if (fCategoryMap.isEmpty()) {
-//			category = new TestCategoryElement(fTestRoot, "AAAAAAAAAAAAAA",
-//					"AAAAAAAAAAAAAAAAAA");
-//			fCategoryMap.put(category.getId(), category);
-//		} else {
-//			category = (TestCategoryElement) fCategoryMap.values().iterator()
-//					.next();
-//		}
-//		return category;
 	}
 
 	private TestElement addTreeEntry(String treeEntry) {
diff --git a/core/plugins/org.eclipse.dltk.testing/src/org/eclipse/dltk/testing/ITestCategoryEngine.java b/core/plugins/org.eclipse.dltk.testing/src/org/eclipse/dltk/testing/ITestCategoryEngine.java
new file mode 100644
index 0000000..bbdd474
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.testing/src/org/eclipse/dltk/testing/ITestCategoryEngine.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (c) 2008 xored software, Inc.
+ *
+ * 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:
+ *     xored software, Inc. - initial API and Implementation (Alex Panchenko)
+ *******************************************************************************/
+package org.eclipse.dltk.testing;
+
+/**
+ * Test category engine. New instances of this interface are created for each
+ * test run. It used only during test launch and building internal data model.
+ * After the test launch is completed all internal data structures are built and
+ * implementations of this interface are not used anymore.
+ */
+public interface ITestCategoryEngine {
+
+	/**
+	 * Initializes this category engine. Returns <code>true</code> if this
+	 * engine should be used for the specified launch or <code>false</code>
+	 * otherwise.
+	 * 
+	 * @param runnerUI
+	 * @return
+	 */
+	boolean initialize(ITestRunnerUI runnerUI);
+
+	/**
+	 * Returns the array of categories to be placed in the test tree initially.
+	 * This method could return <code>null</code> if it does not require initial
+	 * categories to be added to the test tree.
+	 * 
+	 * @return
+	 */
+	TestCategoryDescriptor[] getInitialCategories();
+
+	/**
+	 * Returns the category this test should be placed in or <code>null</code>.
+	 * 
+	 * @param id
+	 * @param name
+	 * @param isSuite
+	 * @return
+	 */
+	TestCategoryDescriptor getCategory(String id, String name, boolean isSuite);
+
+}
diff --git a/core/plugins/org.eclipse.dltk.testing/src/org/eclipse/dltk/testing/ITestRunnerUI.java b/core/plugins/org.eclipse.dltk.testing/src/org/eclipse/dltk/testing/ITestRunnerUI.java
index 41ba119..cf2b233 100644
--- a/core/plugins/org.eclipse.dltk.testing/src/org/eclipse/dltk/testing/ITestRunnerUI.java
+++ b/core/plugins/org.eclipse.dltk.testing/src/org/eclipse/dltk/testing/ITestRunnerUI.java
@@ -12,11 +12,22 @@
 package org.eclipse.dltk.testing;
 
 import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.dltk.core.IScriptProject;
+import org.eclipse.dltk.internal.testing.launcher.NullTestRunnerUI;
 import org.eclipse.dltk.testing.model.ITestCaseElement;
 import org.eclipse.dltk.testing.model.ITestElement;
 import org.eclipse.jface.action.Action;
 import org.eclipse.jface.action.IAction;
 
+/**
+ * UI part of the testing engine implementation. New instances of this interface
+ * are supposed to be created for each test session (=launch).
+ * 
+ * Instances of this interface are acquire via the call to the
+ * {@link ITestingEngine#getTestRunnerUI(org.eclipse.dltk.core.IScriptProject, org.eclipse.debug.core.ILaunchConfiguration)}
+ * 
+ * The implementations should support adapting to {@link ITestElementResolver}
+ */
 public interface ITestRunnerUI extends IAdaptable {
 
 	/**
@@ -89,4 +100,17 @@
 	 */
 	void setFilterStack(boolean value);
 
+	/**
+	 * Returns the engine this UI is acquired from. Should not be
+	 * <code>null</code> (at the moment only {@link NullTestRunnerUI} designed
+	 * for compatibility issues returns <code>null</code> here).
+	 */
+	ITestingEngine getTestingEngine();
+
+	/**
+	 * Returns the project of the current launch. Could return <code>null</code>
+	 * if the session was loaded from XML and there is no such project now.
+	 */
+	IScriptProject getProject();
+
 }
diff --git a/core/plugins/org.eclipse.dltk.testing/src/org/eclipse/dltk/testing/TestCategoryDescriptor.java b/core/plugins/org.eclipse.dltk.testing/src/org/eclipse/dltk/testing/TestCategoryDescriptor.java
new file mode 100644
index 0000000..382073b
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.testing/src/org/eclipse/dltk/testing/TestCategoryDescriptor.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2008 xored software, Inc.
+ *
+ * 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:
+ *     xored software, Inc. - initial API and Implementation (Alex Panchenko)
+ *******************************************************************************/
+package org.eclipse.dltk.testing;
+
+/**
+ * Test category descriptor.
+ * 
+ * Instances of these objects are acquired via calls to the
+ * {@link ITestCategoryEngine#getCategory(String, String, boolean)}
+ */
+public class TestCategoryDescriptor {
+
+	private final String id;
+	private final String name;
+
+	/**
+	 * @param id
+	 * @param name
+	 */
+	public TestCategoryDescriptor(String id, String name) {
+		this.id = id;
+		this.name = name;
+	}
+
+	/**
+	 * @return the id
+	 */
+	public String getId() {
+		return id;
+	}
+
+	/**
+	 * @return the name
+	 */
+	public String getName() {
+		return name;
+	}
+
+}