[436229] Eclipse/OSGi Bundle queries are not working on Luna snapshots

Create new class to understand bundle information from newer Eclipse
releases, plus tests and documentation.

Change-Id: Ic574254dc21e02e5037543f383a5e747914c7cf1
diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/osgi/BundleRegistryQuery.java b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/osgi/BundleRegistryQuery.java
index 04ed840..b942310 100644
--- a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/osgi/BundleRegistryQuery.java
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/osgi/BundleRegistryQuery.java
@@ -39,15 +39,17 @@
 import org.eclipse.mat.query.ResultMetaData;

 import org.eclipse.mat.query.annotations.Argument;

 import org.eclipse.mat.query.annotations.CommandName;

+import org.eclipse.mat.query.annotations.HelpUrl;

 import org.eclipse.mat.query.annotations.Icon;

 import org.eclipse.mat.snapshot.ISnapshot;

-import org.eclipse.mat.snapshot.extension.Subject;

+import org.eclipse.mat.snapshot.extension.Subjects;

 import org.eclipse.mat.util.IProgressListener;

 import org.eclipse.mat.util.MessageUtil;

 

 @CommandName("bundle_registry")

 @Icon("/META-INF/icons/osgi/registry.gif")

-@Subject("org.eclipse.osgi.framework.internal.core.BundleRepository")

+@Subjects({"org.eclipse.osgi.framework.internal.core.BundleRepository","org.eclipse.osgi.internal.framework.EquinoxBundle"})

+@HelpUrl("/org.eclipse.mat.ui.help/tasks/bundleregistry.html")

 public class BundleRegistryQuery implements IQuery

 {

     @Argument

@@ -398,6 +400,10 @@
                         return ((BundleDescriptor) row).getState();

                     if (row instanceof Folder && ((Folder) row).type.equals(Type.HOST))

                         return ((BundleFragment) ((Folder) row).bundle).getHost().getState();

+                    if (row instanceof DescriptorFolder)

+                        return ((DescriptorFolder) row).descriptor.getState();

+                    if (row instanceof ExtensionFolder && ((ExtensionFolder)row).type == Type.CONTRIBUTED_BY)

+                        return ((ExtensionFolder)row).extension.getContributedBy().getState();

             }

             return null;

         }

diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/osgi/model/BundleReaderFactory.java b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/osgi/model/BundleReaderFactory.java
index 8b48651..1164134 100644
--- a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/osgi/model/BundleReaderFactory.java
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/osgi/model/BundleReaderFactory.java
@@ -34,6 +34,11 @@
         if (classes != null && !classes.isEmpty())

             // Equinox OSGi framework

             return new EquinoxBundleReader(snapshot);

+        classes = snapshot.getClassesByName(

+                        "org.eclipse.osgi.container.ModuleLoader", false); //$NON-NLS-1$

+        if (classes != null && !classes.isEmpty())

+            // Equinox OSGi framework

+            return new EquinoxBundleReader2(snapshot);

         else

             throw new SnapshotException(Messages.BundleReaderFactory_ErrorMsg_EquinoxNotFound);

 

diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/osgi/model/EquinoxBundleReader2.java b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/osgi/model/EquinoxBundleReader2.java
new file mode 100644
index 0000000..912f606
--- /dev/null
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/osgi/model/EquinoxBundleReader2.java
@@ -0,0 +1,1055 @@
+/*******************************************************************************

+ * Copyright (c) 2008, 2017 SAP AG, 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:

+ *    SAP AG - initial API and implementation

+ *    James Livingston - expose collection utils as API

+ *    Andrew Johnson - new Equinox implementation since Kepler/Luna

+ *******************************************************************************/

+package org.eclipse.mat.inspections.osgi.model;

+

+import java.util.ArrayList;

+import java.util.Collection;

+import java.util.HashMap;

+import java.util.Iterator;

+import java.util.List;

+import java.util.Map;

+import java.util.Map.Entry;

+import java.util.Set;

+

+import org.eclipse.mat.SnapshotException;

+import org.eclipse.mat.inspections.ReferenceQuery;

+import org.eclipse.mat.inspections.collectionextract.CollectionExtractionUtils;

+import org.eclipse.mat.inspections.collectionextract.ExtractedCollection;

+import org.eclipse.mat.inspections.collectionextract.ExtractedMap;

+import org.eclipse.mat.inspections.osgi.model.BundleDescriptor.Type;

+import org.eclipse.mat.inspections.osgi.model.eclipse.ConfigurationElement;

+import org.eclipse.mat.inspections.osgi.model.eclipse.Extension;

+import org.eclipse.mat.inspections.osgi.model.eclipse.ExtensionPoint;

+import org.eclipse.mat.internal.MATPlugin;

+import org.eclipse.mat.internal.Messages;

+import org.eclipse.mat.snapshot.ISnapshot;

+import org.eclipse.mat.snapshot.model.Field;

+import org.eclipse.mat.snapshot.model.IClass;

+import org.eclipse.mat.snapshot.model.IInstance;

+import org.eclipse.mat.snapshot.model.IObject;

+import org.eclipse.mat.snapshot.model.IObjectArray;

+import org.eclipse.mat.snapshot.model.ObjectReference;

+import org.eclipse.mat.util.IProgressListener;

+import org.eclipse.mat.util.MessageUtil;

+

+public class EquinoxBundleReader2 implements IBundleReader

+{

+    private Map<String, Bundle> bundles = new HashMap<String, Bundle>();

+    private ISnapshot snapshot;

+    private Map<BundleDescriptor, List<Service>> registeredServices = new HashMap<BundleDescriptor, List<Service>>();

+    private Map<BundleDescriptor, List<Service>> usedServices = new HashMap<BundleDescriptor, List<Service>>();

+    private Map<Long, BundleDescriptor> bundleDescriptors = new HashMap<Long, BundleDescriptor>();

+    // bundle name -> ExtensionPoints

+    private Map<BundleDescriptor, List<ExtensionPoint>> extensionPointsByBundle = new HashMap<BundleDescriptor, List<ExtensionPoint>>();

+    // bundle name -> Extensions

+    private Map<BundleDescriptor, List<Extension>> extensionsByBundle = new HashMap<BundleDescriptor, List<Extension>>();

+    // bundle name -> dependencies

+    private Map<BundleDescriptor, List<BundleDescriptor>> bundleDependencies = new HashMap<BundleDescriptor, List<BundleDescriptor>>();

+    // bundle name -> dependents

+    private Map<BundleDescriptor, List<BundleDescriptor>> bundleDependents = new HashMap<BundleDescriptor, List<BundleDescriptor>>();

+    private static final int STEPS = 1000000;

+    private int maxWarnings = 100;

+

+    public EquinoxBundleReader2(ISnapshot snapshot)

+    {

+        this.snapshot = snapshot;

+    }

+

+    private enum BundleState

+    {

+        ACTIVE(Messages.EquinoxBundleReader_State_Active, 4), //

+        INSTALLED(Messages.EquinoxBundleReader_State_Installed, 0), //

+        RESOLVED(Messages.EquinoxBundleReader_State_Resolved, 1), //

+        LAZY_STARTING(Messages.EquinoxBundleReader_State_LazyStarting, 2), //

+        STARTING(Messages.EquinoxBundleReader_State_Starting, 3), //

+        STOPPING(Messages.EquinoxBundleReader_State_Stopping, 5), //

+        UNINSTALLED(Messages.EquinoxBundleReader_State_Uninstalled, -1);//

+

+        private String label;

+        private int value;

+

+        private BundleState(String label, int value)

+        {

+            this.label = label;

+            this.value = value;

+        }

+

+        public String getLabel()

+        {

+            return label;

+        }

+

+        public int getValue()

+        {

+            return value;

+        }

+    }

+

+    public OSGiModel readOSGiModel(IProgressListener listener) throws SnapshotException

+    {

+        listener.beginTask(Messages.EquinoxBundleReader_ProcessListenerBundles, STEPS * 4);

+        List<BundleDescriptor> descriptors = getBundleDescriptors(listener);

+        collectDependencies(listener);

+        List<Service> services = collectServiceInfo(listener);

+        List<ExtensionPoint> extensionPoints = collectExtensionsInfo(listener);

+

+        OSGiModel model = new OSGiModel(this, descriptors, services, extensionPoints);

+        listener.done();

+        return model;

+

+    }

+

+    private List<BundleDescriptor> getBundleDescriptors(IProgressListener listener) throws SnapshotException

+    {

+        listener.subTask(Messages.EquinoxBundleReader_ReadingBundles);

+        Collection<IClass> classes = snapshot.getClassesByName(

+                        "org.eclipse.osgi.internal.framework.EquinoxBundle", true); //$NON-NLS-1$

+        List<BundleDescriptor> bundleDescriptors = new ArrayList<BundleDescriptor>();

+        if (classes == null || classes.isEmpty())

+            return bundleDescriptors;

+

+        int nobjs = 0;

+        for (IClass clazz : classes)

+            nobjs += clazz.getNumberOfObjects();

+

+        for (IClass clazz : classes)

+        {

+            int[] objs = clazz.getObjectIds();

+

+            for (int i = 0; i < objs.length; i++)

+            {

+                IInstance obj = (IInstance) snapshot.getObject(objs[i]);

+                if (listener.isCanceled())

+                    throw new IProgressListener.OperationCanceledException();

+                IObject bundleObject = obj;

+                BundleDescriptor.Type type = BundleDescriptor.Type.BUNDLE;

+                if (isFragment(bundleObject))

+                {

+                    type = BundleDescriptor.Type.FRAGMENT;

+                }

+                BundleDescriptor descriptor = getBundleDescriptor(bundleObject, type);

+                bundleDescriptors.add(descriptor);

+                listener.worked(STEPS / nobjs);

+            }

+        }

+        return bundleDescriptors;

+    }

+    

+    private boolean isFragment(IObject bundleHostObject) throws SnapshotException

+    {

+        IObject revs = (IObject) bundleHostObject.resolveValue("module.revisions.revisions");//$NON-NLS-1$

+        if (revs == null)

+            return false;

+        Iterator<IObject> it1 = CollectionExtractionUtils.extractList(revs).iterator();

+        if (!it1.hasNext())

+            return false;

+        IObject rev = it1.next();

+        IObject caps = (IObject) rev.resolveValue("capabilities");//$NON-NLS-1$

+        if (caps == null)

+            return false;

+        for (IObject o : CollectionExtractionUtils.extractList(caps))

+        {

+            if ("equinox.fragment".equals(((IObject)(o.resolveValue("namespace"))).getClassSpecificName()))//$NON-NLS-1$ //$NON-NLS-2$

+            {

+                return true;

+            }

+        }

+        return false;

+    }

+

+    private List<BundleDescriptor> getBundleFragments(IObject bundleHostObject) throws SnapshotException

+    {

+        List<BundleDescriptor> fragments = new ArrayList<BundleDescriptor>();

+        for (BundleDescriptor bd : bundleDescriptors.values())

+        {

+            if (bd.getType().equals(Type.FRAGMENT))

+            {

+                BundleDescriptor bd2 = getFragmentHost(snapshot.getObject(bd.getObjectId()));

+                if (bd2.getObjectId() == bundleHostObject.getObjectId())

+                {

+                    fragments.add(bd);

+                }

+            }

+        }

+        return fragments;

+    }

+

+    private List<Service> collectServiceInfo(IProgressListener listener) throws SnapshotException

+    {

+        listener.subTask(Messages.EquinoxBundleReader_ReadingServices);

+

+        Collection<IClass> classes = snapshot.getClassesByName( // 3.5

+                        "org.eclipse.osgi.internal.serviceregistry.ServiceRegistry", false); //$NON-NLS-1$

+        if (classes == null || classes.isEmpty())

+            classes = snapshot.getClassesByName( // 3.4

+                            "org.eclipse.osgi.framework.internal.core.ServiceRegistryImpl", false); //$NON-NLS-1$

+

+        List<Service> services = new ArrayList<Service>();

+        if (classes == null || classes.isEmpty())

+            return services;

+

+        int nobjs = 0;

+        for (IClass clazz : classes)

+            nobjs += clazz.getNumberOfObjects();

+

+        for (IClass clazz : classes)

+        {

+            int[] objs = clazz.getObjectIds();

+

+            for (int i = 0; i < objs.length; i++)

+            {

+                IObject obj = snapshot.getObject(objs[i]);

+                IObject publishedServices = (IObject) obj.resolveValue("allPublishedServices");//$NON-NLS-1$

+                for (IObject serviceInstance : CollectionExtractionUtils.extractList(publishedServices))

+                {

+                    if (listener.isCanceled())

+                        throw new IProgressListener.OperationCanceledException();

+                    IObject bundleObj = (IObject) serviceInstance.resolveValue("bundle"); //$NON-NLS-1$

+                    BundleDescriptor bundleDescriptor = getBundleDescriptor(bundleObj, Type.BUNDLE);

+

+                    List<BundleDescriptor> bundlesUsing = null;

+                    IObject bundlesList = (IObject) serviceInstance.resolveValue("contextsUsing");//$NON-NLS-1$

+                    ExtractedCollection bunds = CollectionExtractionUtils.extractList(bundlesList);

+                    bundlesUsing = new ArrayList<BundleDescriptor>(bunds.size());

+                    for (IObject bundleInstance : bunds)

+                    {

+                        IObject bundleObject = (IObject) bundleInstance.resolveValue("bundle");//$NON-NLS-1$

+                        if (bundleObject == null)

+                            continue;

+

+                        BundleDescriptor usingBundleDescriptor = getBundleDescriptor(bundleObject, Type.BUNDLE);

+                        bundlesUsing.add(usingBundleDescriptor);

+                    }

+                    // get service name

+                    IObjectArray clazzes = (IObjectArray) serviceInstance.resolveValue("clazzes");//$NON-NLS-1$

+                    Iterator<IObject> it = CollectionExtractionUtils.extractList(clazzes).iterator();

+                    String serviceName = it.next().getClassSpecificName();

+                    // get properties

+                    IObject propertiesObject = (IObject) serviceInstance.resolveValue("properties");//$NON-NLS-1$

+                    String[] keys = null;

+                    String[] values = null;

+                    if (propertiesObject != null)

+                    {

+                        ExtractedMap em = CollectionExtractionUtils.extractMap(propertiesObject);

+                        if (em != null && em.hasSize())

+                        {

+                            // From Oxygen onwards

+                            keys = new String[em.size()];

+                            values = new String[em.size()];

+                            int i1 = 0;

+                            for (Entry<IObject,IObject> en : em)

+                            {

+                                keys[i1] = ((IObject)en.getKey().resolveValue("key")).getClassSpecificName(); //$NON-NLS-1$

+                                values[i1] = en.getValue().getClassSpecificName();

+                                ++i1;

+                            }

+                        }

+                        else

+                        {

+                            IObjectArray keysArray = (IObjectArray) propertiesObject.resolveValue("headers"); //$NON-NLS-1$

+                            if (keysArray != null)

+                            {

+                                long[] keyAddresses = keysArray.getReferenceArray();

+                                if (keyAddresses != null)

+                                {

+                                    keys = getServiceProperties(new String[keyAddresses.length], keyAddresses);

+                                }

+                            }

+                            IObjectArray valuesArray = (IObjectArray) propertiesObject.resolveValue("values"); //$NON-NLS-1$

+                            if (valuesArray != null)

+                            {

+                                long[] valueAddresses = valuesArray.getReferenceArray();

+                                if (valueAddresses != null)

+                                {

+                                    values = getServiceProperties(new String[valueAddresses.length], valueAddresses);

+                                }

+                            }

+                        }

+                    }

+

+                    services.add(new Service(serviceName, serviceInstance.getObjectId(), bundleDescriptor,

+                                    bundlesUsing, keys, values));

+                }

+                listener.worked(STEPS / nobjs);

+            }

+        }

+

+        if (services.size() > 0)

+            updateServiceMap(services);

+        return services;

+    }

+

+    private String[] getServiceProperties(String[] values, long[] valueAddresses)

+    {

+        for (int j = 0; j < valueAddresses.length; j++)

+        {

+            if (valueAddresses[j] == 0)

+                continue;

+            try

+            {

+                int valueId = snapshot.mapAddressToId(valueAddresses[j]);

+                IObject valueObject = snapshot.getObject(valueId);

+                if (valueObject == null)

+                    continue;

+                if (valueObject.getClazz().isArrayType())

+                {

+                    long[] addresses = ((IObjectArray) valueObject).getReferenceArray();

+

+                    for (int k = 0; k < addresses.length; k++)

+                    {

+                        if (valueAddresses[k] == 0)

+                            continue;

+                        int id = snapshot.mapAddressToId(addresses[k]);

+                        IObject object = snapshot.getObject(id);

+                        if (object == null)

+                            continue;

+                        values[j] = object.getClassSpecificName();

+                        break; // is more than one element possible in that

+                        // array?

+                    }

+                }

+                else

+                {

+                    values[j] = valueObject.getClassSpecificName();

+                }

+            }

+            catch (SnapshotException e)

+            {

+                values[j] = null;

+                MATPlugin.log(e,

+                                MessageUtil.format(Messages.EquinoxBundleReader_ErrorMsg_ServiceProperty,

+                                                Long.toHexString(valueAddresses[j])));

+            }

+        }

+        return values;

+    }

+

+    private void updateServiceMap(List<Service> services)

+    {

+        for (Service service : services)

+        {

+            BundleDescriptor contributedBy = service.getBundleDescriptor();

+            // update registered services

+            doUpdate(service, contributedBy, registeredServices);

+

+            // update used services

+            List<BundleDescriptor> bundlesUsing = service.getBundlesUsing();

+            if (bundlesUsing != null)

+                for (BundleDescriptor descriptor : bundlesUsing)

+                {

+                    doUpdate(service, descriptor, usedServices);

+                }

+        }

+

+    }

+

+    private void doUpdate(Service service, BundleDescriptor bundleDescriptor,

+                    Map<BundleDescriptor, List<Service>> serviceMap)

+    {

+        List<Service> listOfServices = serviceMap.get(bundleDescriptor);

+        if (listOfServices == null)

+        {

+            List<Service> bundleServices = new ArrayList<Service>(1);

+            bundleServices.add(service);

+            serviceMap.put(bundleDescriptor, bundleServices);

+        }

+        else if (!listOfServices.contains(service))

+        {

+            listOfServices.add(service);

+        }

+    }

+

+    private BundleDescriptor getBundleDescriptor(IObject bundleHostObject, BundleDescriptor.Type type)

+                    throws SnapshotException

+    {

+        IInstance bundleData = (IInstance) bundleHostObject.resolveValue("module");//$NON-NLS-1$

+        IInstance bundleData2 = (IInstance) bundleData.resolveValue("id");//$NON-NLS-1$

+        Field idField = bundleData2.getField("value"); //$NON-NLS-1$

+        Long id = null;

+        if (idField != null)

+        {

+            id = (Long) idField.getValue();

+        }

+        // check whether this bundle descriptor is already in the map

+        BundleDescriptor bundleDescriptor = bundleDescriptors.get(id);

+        if (bundleDescriptor != null)

+            return bundleDescriptor;

+

+        String bundleName = extractBundleName(bundleData);

+        String state = getState(bundleHostObject);

+

+        BundleDescriptor descriptor = new BundleDescriptor(bundleHostObject.getObjectId(), id, bundleName, state, type);

+        // add new bundle name to the map. Key is bundleId

+        bundleDescriptors.put(id, descriptor);

+

+        return descriptor;

+    }

+

+    private String extractBundleName(IObject bundleData) throws SnapshotException

+    {

+        IObject revs = (IObject) bundleData.resolveValue("revisions.revisions");//$NON-NLS-1$

+        IObject rev = CollectionExtractionUtils.extractList(revs).iterator().next();

+        IObject name = (IObject) rev.resolveValue("symbolicName");//$NON-NLS-1$

+        String symbolicName = name.getClassSpecificName();

+

+        IObject versionObj = (IObject) rev.resolveValue("version.qualifier");//$NON-NLS-1$

+        String version = null;

+        if (versionObj != null)

+        {

+            version = rev.resolveValue("version.major") + //$NON-NLS-1$

+                            "." + //$NON-NLS-1$

+                            rev.resolveValue("version.minor") + //$NON-NLS-1$

+                            "." + //$NON-NLS-1$

+                            rev.resolveValue("version.micro") + //$NON-NLS-1$

+                            "." + //$NON-NLS-1$

+                            versionObj.getClassSpecificName();

+        }

+        String bundleName = version == null || version.equals("") ? symbolicName : symbolicName + " (" + version + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

+        return bundleName;

+    }

+

+    public Bundle getBundle(BundleDescriptor descriptor) throws SnapshotException

+    {

+        Bundle bundle = bundles.get(descriptor.getBundleName());

+        if (bundle == null)

+            bundle = load(descriptor);

+        return bundle;

+

+    }

+

+    private Bundle load(BundleDescriptor descriptor) throws SnapshotException

+    {

+        int objectId = descriptor.getObjectId();

+        IInstance obj = (IInstance) snapshot.getObject(objectId);

+

+        IObject locationObj = (IObject) obj.resolveValue("module.location");//$NON-NLS-1$

+        String location = null;

+        if (locationObj != null)

+        {

+            location = locationObj.getClassSpecificName();

+        }

+        if (descriptor.getType().equals(BundleDescriptor.Type.FRAGMENT))

+        {

+            BundleDescriptor host = getFragmentHost(obj);

+            return new BundleFragment(descriptor, location, host);

+        }

+        List<BundleDescriptor> dependencies = null;

+        List<BundleDescriptor> dependents = null;

+

+        dependencies = bundleDependencies.get(descriptor);

+        dependents = bundleDependents.get(descriptor);

+

+        List<Extension> extensions = extensionsByBundle.get(descriptor);

+        List<ExtensionPoint> extensionPoints = extensionPointsByBundle.get(descriptor);

+

+        List<Service> registeredServices = this.registeredServices.get(descriptor);

+        List<Service> usedServices = this.usedServices.get(descriptor);

+

+        List<BundleDescriptor> fragments = getBundleFragments(obj);

+

+        return new Bundle(descriptor, location, dependencies, dependents, extensionPoints, extensions,

+                        registeredServices, usedServices, fragments);

+

+    }

+

+    private BundleDescriptor getFragmentHost(IObject bundleFragmentObject) throws SnapshotException

+    {

+        IObject revs = (IObject) bundleFragmentObject.resolveValue("module.revisions.revisions");//$NON-NLS-1$

+        IObject rev = CollectionExtractionUtils.extractList(revs).iterator().next();

+        IObject caps = (IObject) rev.resolveValue("capabilities");//$NON-NLS-1$

+        for (IObject o : CollectionExtractionUtils.extractList(caps))

+        {

+            if ("equinox.fragment".equals(((IObject)(o.resolveValue("namespace"))).getClassSpecificName())) //$NON-NLS-1$ //$NON-NLS-2$

+            {

+                IObject attribs = (IObject)o.resolveValue("attributes"); //$NON-NLS-1$

+                ExtractedMap kvs = CollectionExtractionUtils.extractMap(attribs);

+                for (Entry<IObject,IObject> et : kvs)

+                {

+                    if ("equinox.fragment".equals(et.getKey().getClassSpecificName())) //$NON-NLS-1$

+                    {

+                        String hostName = et.getValue().getClassSpecificName();

+                        if (hostName != null)

+                        {

+                            for (BundleDescriptor bd : bundleDescriptors.values())

+                            {

+                                if (bd.getBundleName().equals(hostName) || bd.getBundleName().startsWith(hostName + " (")) //$NON-NLS-1$

+                                {

+                                    return bd;

+                                }

+                            }

+                        }

+                    }

+                }

+            }

+        }

+        MATPlugin.log(MessageUtil.format(Messages.EquinoxBundleReader_ErrorMsg_BundleNotFound,

+                            Long.toHexString(bundleFragmentObject.getObjectAddress())));

+        return null;

+    }

+

+    private String getState(IObject obj) throws SnapshotException

+    {

+        IInstance bundleHostObject = (IInstance) obj.resolveValue("module.state");//$NON-NLS-1$

+        Field stateField = bundleHostObject.getField("ordinal");//$NON-NLS-1$

+        if (stateField == null)

+            return Messages.EquinoxBundleReader_NotApplicable;

+

+        int state = ((Integer) stateField.getValue()).intValue();

+

+        for (BundleState stateType : BundleState.values())

+        {

+            if (stateType.getValue() == state)

+                return stateType.getLabel();

+        }

+

+        return Messages.EquinoxBundleReader_NotApplicable;

+    }

+

+    private List<BundleDescriptor> getDependencies(ISnapshot snapshot, IObject resolvedValue) throws SnapshotException

+    {

+        List<BundleDescriptor> dependencyDescriptors = null;

+        ExtractedCollection coll = CollectionExtractionUtils.extractList(resolvedValue);

+        if (coll != null)

+        {

+            dependencyDescriptors = new ArrayList<BundleDescriptor>(coll.size());

+            for (IObject bundleDescriptionObject : coll)

+            {

+                IObject bundleHostObject = (IObject) bundleDescriptionObject.resolveValue("userObject.bundle");//$NON-NLS-1$

+                if (bundleHostObject == null)

+                    continue;

+

+                BundleDescriptor.Type type = BundleDescriptor.Type.BUNDLE;

+                if (bundleHostObject.getClazz().getName()

+                                .equals("org.eclipse.osgi.framework.internal.core.BundleFragment"))//$NON-NLS-1$

+                    type = BundleDescriptor.Type.FRAGMENT;

+

+                BundleDescriptor descriptor = getBundleDescriptor(bundleHostObject, type);

+                dependencyDescriptors.add(descriptor);

+            }

+        }

+        return dependencyDescriptors;

+    }

+

+    private void collectDependencies(IProgressListener listener) throws SnapshotException

+    {

+        listener.subTask(Messages.EquinoxBundleReader_ReadingDependencies);

+        Collection<IClass>classes = snapshot.getClassesByName("org.eclipse.osgi.container.ModuleWire", false); //$NON-NLS-1$

+        if (classes != null)

+        {

+            int nobjs = 0;

+            for (IClass clazz : classes)

+                nobjs += clazz.getNumberOfObjects();

+            

+            for (IClass clazz : classes)

+            {

+                int[] objs = clazz.getObjectIds();

+

+                for (int i = 0; i < objs.length; i++)

+                {

+                    IInstance obj = (IInstance) snapshot.getObject(objs[i]);

+                    

+                    IObject providerBundleHostObject = (IObject) obj.resolveValue("hostingProvider.revisions.module.this$0");//$NON-NLS-1$

+                    if (providerBundleHostObject == null)

+                        continue;

+

+                    BundleDescriptor.Type providerType = BundleDescriptor.Type.BUNDLE;

+                    if (isFragment(providerBundleHostObject))

+                        providerType = BundleDescriptor.Type.FRAGMENT;

+

+                    BundleDescriptor providerDescriptor = getBundleDescriptor(providerBundleHostObject, providerType);

+                    

+                    IObject requirerBundleHostObject = (IObject) obj.resolveValue("hostingRequirer.revisions.module.this$0");//$NON-NLS-1$

+                    if (requirerBundleHostObject == null)

+                        continue;

+

+                    BundleDescriptor.Type requirerType = BundleDescriptor.Type.BUNDLE;

+                    if (isFragment(providerBundleHostObject))

+                        requirerType = BundleDescriptor.Type.FRAGMENT;

+

+                    BundleDescriptor requirerDescriptor = getBundleDescriptor(requirerBundleHostObject, requirerType);

+                    

+                    List<BundleDescriptor> dependents = bundleDependents.get(providerDescriptor);

+                    if (dependents == null)

+                    {

+                        dependents = new ArrayList<BundleDescriptor>();

+                        dependents.add(requirerDescriptor);

+                        bundleDependents.put(providerDescriptor, dependents);

+                    }

+                    else

+                    {

+                        if (!dependents.contains(requirerDescriptor))

+                        {

+                            dependents.add(requirerDescriptor);

+                        }

+                    }

+                    

+                    List<BundleDescriptor> dependencies = bundleDependencies.get(requirerDescriptor);

+                    if (dependencies == null)

+                    {

+                        dependencies = new ArrayList<BundleDescriptor>();

+                        dependencies.add(providerDescriptor);

+                        bundleDependencies.put(requirerDescriptor, dependencies);

+                    }

+                    else

+                    {

+                        if (!dependencies.contains(providerDescriptor))

+                        {

+                            dependencies.add(providerDescriptor);

+                        }

+                    }

+                    

+                    listener.worked(STEPS / nobjs);

+                }

+            }

+        }

+    }

+

+    private List<ExtensionPoint> collectExtensionsInfo(IProgressListener listener) throws SnapshotException

+    {

+        listener.subTask(Messages.EquinoxBundleReader_ReadingExtensions);

+        Collection<IClass> classes = snapshot.getClassesByName(

+                        "org.eclipse.core.internal.registry.ExtensionRegistry", false); //$NON-NLS-1$

+

+        if (classes == null || classes.isEmpty())

+            return null;

+        Map<String, ExtensionPoint> extensionPoints = new HashMap<String, ExtensionPoint>();

+        Map<Integer, Extension> extensions = new HashMap<Integer, Extension>();

+        Map<Integer, ConfigurationElement> configElements = new HashMap<Integer, ConfigurationElement>();

+

+        int nobjs = 0;

+        for (IClass clazz : classes)

+            nobjs += clazz.getNumberOfObjects();

+

+        for (IClass clazz : classes)

+        {

+            int[] objs = clazz.getObjectIds();

+

+            for (int i = 0; i < objs.length; i++)

+            {

+                int work = STEPS / nobjs;

+                IInstance obj = (IInstance) snapshot.getObject(objs[i]);

+

+                IObjectArray heldObjectsArray = (IObjectArray) obj.resolveValue("registryObjects.heldObjects.elements");//$NON-NLS-1$

+                ExtractedCollection heldObjects = CollectionExtractionUtils.extractList(heldObjectsArray);

+                if (heldObjects != null)

+                {

+                    Integer size1 = heldObjects.size();

+                    for (IObject instance : heldObjects)

+                    {

+                        if (listener.isCanceled())

+                            throw new IProgressListener.OperationCanceledException();

+                        extractElements(instance, extensionPoints, extensions, configElements, listener);

+                        listener.worked(work / size1 / 2);

+                    }

+                }

+

+                IObjectArray cachedObjectsArray = (IObjectArray) obj.resolveValue("registryObjects.cache.table");//$NON-NLS-1$

+                // The cached objects can find extras, but causes duplicate

+                // extensions, extension points etc.

+                boolean useCachedObjects = true;

+                if (useCachedObjects && cachedObjectsArray != null)

+                {

+                    ExtractedCollection refList = CollectionExtractionUtils.extractList(cachedObjectsArray);

+                    int size2 = refList.size() / 2;

+                    for (IObject instance : refList)

+                    {

+                        if (listener.isCanceled())

+                            throw new IProgressListener.OperationCanceledException();

+

+                        ObjectReference ref = ReferenceQuery.getReferent((IInstance) instance);

+                        if (ref != null)

+                        {

+                            instance = ref.getObject();

+                        }

+                        extractElements(instance, extensionPoints, extensions, configElements, listener);

+                        listener.worked(work / size2);

+                    }

+                }

+            }

+        }

+        // add ConfigurationElements to corresponding Extensions or

+        // ConfigurationElements

+        Set<Entry<Integer, ConfigurationElement>> configElementSet = configElements.entrySet();

+        for (Entry<Integer, ConfigurationElement> entry : configElementSet)

+        {

+            if (listener.isCanceled())

+                throw new IProgressListener.OperationCanceledException();

+            ConfigurationElement element = entry.getValue();

+            Extension extension = extensions.get(element.getParentId());

+            if (extension == null)

+            {

+                ConfigurationElement configElement = configElements.get(element.getParentId());

+                if (configElement == null)

+                    continue;

+                configElement.addConfigurationElement(element);

+                continue;

+            }

+            extension.addConfigurationElement(element);

+        }

+

+        // add Extensions to corresponding ExtensionPoints

+        Set<Entry<Integer, Extension>> set = extensions.entrySet();

+        for (Entry<Integer, Extension> entry : set)

+        {

+            if (listener.isCanceled())

+                throw new IProgressListener.OperationCanceledException();

+            Extension extension = entry.getValue();

+            String name = extension.getName();

+            ExtensionPoint extensionPoint = extensionPoints.get(name);

+            if (extensionPoint == null)

+                continue;

+            extensionPoint.addExtension(extension);

+        }

+

+        // fill maps extensionsByBundle, ExtensionPointsByBundle

+        Set<Entry<Integer, Extension>> extensionsSet = extensions.entrySet();

+        for (Entry<Integer, Extension> entry : extensionsSet)

+        {

+            if (listener.isCanceled())

+                throw new IProgressListener.OperationCanceledException();

+            Extension extension = entry.getValue();

+            BundleDescriptor bundleDescriptor = extension.getContributedBy();

+

+            List<Extension> listOfExtensions = extensionsByBundle.get(bundleDescriptor);

+            if (listOfExtensions == null)

+            {

+                List<Extension> extensionList = new ArrayList<Extension>(1);

+                extensionList.add(extension);

+                extensionsByBundle.put(bundleDescriptor, extensionList);

+            }

+            else if (!listOfExtensions.contains(extension))

+            {

+                listOfExtensions.add(extension);

+            }

+        }

+        Set<Entry<String, ExtensionPoint>> pointsSet = extensionPoints.entrySet();

+        for (Entry<String, ExtensionPoint> entry : pointsSet)

+        {

+            if (listener.isCanceled())

+                throw new IProgressListener.OperationCanceledException();

+            ExtensionPoint point = entry.getValue();

+            BundleDescriptor bundleDescriptor = point.getContributedBy();

+            List<ExtensionPoint> listOfExtensions = extensionPointsByBundle.get(bundleDescriptor);

+            if (listOfExtensions == null)

+            {

+                List<ExtensionPoint> bundleExtensions = new ArrayList<ExtensionPoint>(1);

+                bundleExtensions.add(point);

+                extensionPointsByBundle.put(bundleDescriptor, bundleExtensions);

+            }

+            else if (!listOfExtensions.contains(point))

+            {

+                listOfExtensions.add(point);

+            }

+        }

+        // return a list of extension points

+        Set<Entry<BundleDescriptor, List<ExtensionPoint>>> extensionPointSet = extensionPointsByBundle.entrySet();

+        List<ExtensionPoint> points = new ArrayList<ExtensionPoint>();

+        for (Entry<BundleDescriptor, List<ExtensionPoint>> entry : extensionPointSet)

+        {

+            if (listener.isCanceled())

+                throw new IProgressListener.OperationCanceledException();

+            List<ExtensionPoint> list = entry.getValue();

+            for (ExtensionPoint extensionPoint : list)

+            {

+                if (!points.contains(extensionPoint))

+                    points.add(extensionPoint);

+            }

+        }

+        return points;

+

+    }

+

+    private void extractElements(IObject instance, Map<String, ExtensionPoint> extensionPoints,

+                    Map<Integer, Extension> extensions, Map<Integer, ConfigurationElement> configElements,

+                    IProgressListener listener) throws SnapshotException

+    {

+        // get type of object (Extension, ExtensionPoint,

+        // ConfigurationElement)

+        String className = instance.getClazz().getName();

+        if (className.equals("org.eclipse.core.internal.registry.ExtensionPoint")) //$NON-NLS-1$

+        {

+            ExtensionPoint extensionPoint = extractExtensionPointInfo(instance);

+            if (extensionPoint != null && !extensionPoints.containsValue(extensionPoint))

+                extensionPoints.put(extensionPoint.getName(), extensionPoint);

+            else if (extensionPoint != null && instance.getObjectId() != extensionPoint.getObjectId())

+                MATPlugin.log(MessageUtil.format(Messages.EquinoxBundleReader_ErrorMsg_DuplicateExtensionPoint,

+                                extensionPoint.getName(),

+                                Long.toHexString(snapshot.mapIdToAddress(extensionPoint.getObjectId())),

+                                Long.toHexString(instance.getObjectAddress())));

+        }

+        else if (className.equals("org.eclipse.core.internal.registry.ConfigurationElement")) //$NON-NLS-1$

+        {

+            ConfigurationElement configElement = extractConfigurationElementInfo(instance, listener);

+            if (configElement != null && !configElements.containsValue(configElement))

+                configElements.put(configElement.getElementId(), configElement);

+            else if (configElement != null && instance.getObjectId() != configElement.getObjectId())

+                MATPlugin.log(MessageUtil.format(Messages.EquinoxBundleReader_ErrorMsg_DuplicateConfigurationElement,

+                                configElement.getName(),

+                                Long.toHexString(snapshot.mapIdToAddress(configElement.getObjectId())),

+                                Long.toHexString(instance.getObjectAddress())));

+        }

+        else if (className.equals("org.eclipse.core.internal.registry.Extension")) //$NON-NLS-1$

+        {

+            Extension extension = extractExtensionInfo(instance);

+            if (extension != null && !extensions.containsValue(extension))

+                extensions.put(extension.getExtensionId(), extension);

+            else if (extension != null && instance.getObjectId() != extension.getObjectId())

+                MATPlugin.log(MessageUtil.format(Messages.EquinoxBundleReader_ErrorMsg_DuplicateExtension,

+                                extension.getName(),

+                                Long.toHexString(snapshot.mapIdToAddress(extension.getObjectId())),

+                                Long.toHexString(instance.getObjectAddress())));

+        }

+        else

+        {

+            MATPlugin.log(MessageUtil.format(Messages.EquinoxBundleReader_ErrorMsg_UnknownElementType,

+                            Long.toHexString(instance.getObjectAddress()), instance.getClazz().getName()));

+        }

+    }

+

+    private ConfigurationElement extractConfigurationElementInfo(IObject instance, IProgressListener listener)

+                    throws SnapshotException

+    {

+        Field idField = ((IInstance) instance).getField("parentId"); //$NON-NLS-1$

+        if (idField == null)

+        {

+            MATPlugin.log(MessageUtil.format(Messages.EquinoxBundleReader_ErrorMsg_ExpectedFieldParent,

+                            Long.toHexString(instance.getObjectAddress())));

+            return null;

+        }

+

+        Integer parentId = (Integer) idField.getValue();

+

+        Field objectIdField = ((IInstance) instance).getField("objectId"); //$NON-NLS-1$

+        if (objectIdField == null)

+        {

+            MATPlugin.log(MessageUtil.format(Messages.EquinoxBundleReader_ErrorMsg_ExpectedFieldObjectId,

+                            Long.toHexString(instance.getObjectAddress())));

+            return null;

+        }

+

+        Integer objectId = (Integer) objectIdField.getValue();

+

+        IObject contributorObject = (IObject) instance.resolveValue("contributorId");//$NON-NLS-1$

+        if (contributorObject == null)

+        {

+            MATPlugin.log(MessageUtil.format(Messages.EquinoxBundleReader_ExpectedFieldContributorId,

+                            Long.toHexString(instance.getObjectAddress())));

+            return null;

+        }

+

+        /*

+         * in some heap dumps the the contributorID was a fully qualified name

+         * instead of a number. The following lines are an attempt to read the

+         * data as number

+         */

+        Long contributorId = null;

+        BundleDescriptor contributedBy = null;

+        String contributorIdString = contributorObject.getClassSpecificName();

+        try

+        {

+            contributorId = Long.valueOf(contributorIdString);

+        }

+        catch (NumberFormatException e)

+        {

+            MATPlugin.log(MessageUtil.format(Messages.EquinoxBundleReader_CannotFindContributorID, contributorIdString));

+        }

+        if (contributorId != null)

+        {

+            contributedBy = bundleDescriptors.get(contributorId);

+        }

+

+        IObject nameObject = (IObject) instance.resolveValue("name");//$NON-NLS-1$

+        if (nameObject == null)

+        {

+            // some configuration elements contain only description. In that

+            // case attribute name is not available.

+            return null;

+        }

+        String name = nameObject.getClassSpecificName();

+        IObject propertiesObject = (IObject) instance.resolveValue("propertiesAndValue");//$NON-NLS-1$

+        if (propertiesObject == null)

+        {

+            MATPlugin.log(MessageUtil.format(Messages.EquinoxBundleReader_ExpectedFieldPropertiesAndValues,

+                            Long.toHexString(instance.getObjectAddress())));

+        }

+        else if (propertiesObject.getClazz().isArrayType())

+        {

+            long[] addresses = ((IObjectArray) propertiesObject).getReferenceArray();

+            String[] propertiesAndValues = new String[addresses.length];

+            for (int i = 0; i < addresses.length; i++)

+            {

+                if (listener.isCanceled())

+                    throw new IProgressListener.OperationCanceledException();

+                if (addresses[i] == 0)

+                    continue;

+                try

+                {

+                    int id = snapshot.mapAddressToId(addresses[i]);

+

+                    IObject object = snapshot.getObject(id);

+                    propertiesAndValues[i] = object.getClassSpecificName();

+                }

+                catch (SnapshotException e)

+                {

+                    // Some HPROF dumps have String arrays with invalid entries

+                    // Generating 10,000 messages takes too long

+                    if (maxWarnings-- > 0)

+                        MATPlugin.log(e,

+                                        MessageUtil.format(Messages.EquinoxBundleReader_ErrorMsg_ReadingProperty,

+                                                        Long.toHexString(addresses[i])));

+                    propertiesAndValues[i] = null;

+                }

+

+            }

+            return new ConfigurationElement(instance.getObjectId(), name, parentId, objectId, contributedBy,

+                            propertiesAndValues);

+        }

+        else

+        {

+            MATPlugin.log(MessageUtil.format(Messages.EquinoxBundleReader_ErrorMsg_ExpectedStringArray,

+                            Long.toHexString(instance.getObjectAddress())));

+        }

+        return new ConfigurationElement(instance.getObjectId(), name, parentId, objectId, contributedBy, null);

+

+    }

+

+    private Extension extractExtensionInfo(IObject instance) throws SnapshotException

+    {

+        // Expect at least 3 properties

+        String[] properties = getExtensionProperties(instance);

+        if (properties == null)

+            return null;

+

+        Field id = ((IInstance) instance).getField("objectId"); //$NON-NLS-1$

+        if (id == null)

+        {

+            MATPlugin.log(MessageUtil.format(Messages.EquinoxBundleReader_ErrorMsg_ExpectedFieldObjectId,

+                            Long.toHexString(instance.getObjectAddress())));

+            return null;

+        }

+        Integer extensionId = (Integer) id.getValue();

+        Extension extension = new Extension(instance.getObjectId(), extensionId, properties);

+

+        /*

+         * in some heap dumps the the contributorID was a fully qualified name

+         * instead of a number. The following lines are an attempt to read the

+         * data as number

+         */

+        Long contributorId = null;

+        String contributorIdString = extension.getContributorId();

+        try

+        {

+            contributorId = Long.valueOf(contributorIdString);

+        }

+        catch (NumberFormatException e)

+        {

+            MATPlugin.log(MessageUtil.format(Messages.EquinoxBundleReader_CannotFindContributorID, contributorIdString));

+        }

+        if (contributorId != null)

+        {

+            BundleDescriptor contributedBy = bundleDescriptors.get(contributorId);

+            extension.setContributedBy(contributedBy);

+        }

+

+        return extension;

+    }

+

+    private String[] getExtensionProperties(IObject instance) throws SnapshotException

+    {

+        IObject extraInfoObject = (IObject) instance.resolveValue("extraInformation");//$NON-NLS-1$

+        if (extraInfoObject == null)

+        {

+            MATPlugin.log(MessageUtil.format(Messages.EquinoxBundleReader_ErrorMsg_ExpectedFieldExtraInformation,

+                            Long.toHexString(instance.getObjectAddress())));

+            return null;

+        }

+

+        if (extraInfoObject instanceof IInstance)

+        {

+            // Handle soft references

+            ObjectReference ref = ReferenceQuery.getReferent((IInstance) extraInfoObject);

+            if (ref != null)

+            {

+                extraInfoObject = ref.getObject();

+            }

+        }

+

+        if (extraInfoObject.getClazz().isArrayType())

+        {

+            long[] addresses = ((IObjectArray) extraInfoObject).getReferenceArray();

+            String[] properties = new String[addresses.length];

+            for (int i = 0; i < addresses.length; i++)

+            {

+                if (addresses[i] == 0)

+                    continue;

+                int id = snapshot.mapAddressToId(addresses[i]);

+                IObject object = snapshot.getObject(id);

+                properties[i] = object.getClassSpecificName();

+

+            }

+            return properties;

+        }

+        else

+        {

+            // TODO SoftReferences are not handled, as referents were always

+            // null. Log, if otherwise.

+            IObject referentObject = (IObject) extraInfoObject.resolveValue("referent"); //$NON-NLS-1$

+            if (referentObject != null)

+                MATPlugin.log(MessageUtil.format(Messages.EquinoxBundleReader_ErrorMsg_SoftReferencesNotHandled,

+                                Long.toHexString(instance.getObjectAddress())));

+            return null;

+        }

+    }

+

+    private ExtensionPoint extractExtensionPointInfo(IObject instance) throws SnapshotException

+    {

+        // Expect at least 5 properties

+        String[] properties = getExtensionProperties(instance);

+        if (properties == null)

+            return null;

+

+        Field id = ((IInstance) instance).getField("objectId"); //$NON-NLS-1$

+        if (id == null)

+        {

+            MATPlugin.log(MessageUtil.format(Messages.EquinoxBundleReader_ErrorMsg_ExpectedFieldObjectId,

+                            Long.toHexString(instance.getObjectAddress())));

+            return null;

+        }

+        Integer extensionPointId = (Integer) id.getValue();

+        ExtensionPoint extensionPoint = new ExtensionPoint(instance.getObjectId(), extensionPointId, properties);

+

+        /*

+         * in some heap dumps the the contributorID was a fully qualified name

+         * instead of a number. The following lines are an attempt to read the

+         * data as number

+         */

+        Long contributorId = null;

+        String contributorIdString = extensionPoint.getContributorId();

+        try

+        {

+            contributorId = Long.valueOf(contributorIdString);

+        }

+        catch (NumberFormatException e)

+        {

+            MATPlugin.log(MessageUtil.format(Messages.EquinoxBundleReader_CannotFindContributorID, contributorIdString));

+        }

+        if (contributorId != null)

+        {

+            BundleDescriptor contributedBy = bundleDescriptors.get(contributorId);

+            extensionPoint.setContributedBy(contributedBy);

+        }

+

+        return extensionPoint;

+    }

+

+}

diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/Messages.java b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/Messages.java
index 96d3825..bf31735 100644
--- a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/Messages.java
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/Messages.java
@@ -216,6 +216,7 @@
     public static String EquinoxBundleReader_State_Installed;

     public static String EquinoxBundleReader_State_Resolved;

     public static String EquinoxBundleReader_State_Starting;

+    public static String EquinoxBundleReader_State_LazyStarting;

     public static String EquinoxBundleReader_State_Stopping;

     public static String EquinoxBundleReader_State_Uninstalled;

 

diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/collectionextract/KnownCollectionInfo.java b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/collectionextract/KnownCollectionInfo.java
index d7c1c29..8c1000d 100644
--- a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/collectionextract/KnownCollectionInfo.java
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/collectionextract/KnownCollectionInfo.java
@@ -159,6 +159,8 @@
                     // also works for CheckedSortedMap
                     new CollectionExtractionInfo("java.util.Collections$CheckedMap", new WrapperCollectionExtractor("m")), //$NON-NLS-1$ //$NON-NLS-2$
                     new CollectionExtractionInfo("java.util.Collections$CheckedMap$CheckedEntrySet", new WrapperCollectionExtractor("s")), //$NON-NLS-1$ //$NON-NLS-2$
+                    new CollectionExtractionInfo("org.eclipse.osgi.framework.util.CaseInsensitiveDictionaryMap", new WrapperMapExtractor("map")), //$NON-NLS-1$ //$NON-NLS-2$
+
 
                     // singletons
                     new CollectionExtractionInfo("java.util.Collections$SingletonSet", new SingletonCollectionExtractor("element")), //$NON-NLS-1$ //$NON-NLS-2$
diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/collectionextract/WrapperCollectionExtractor.java b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/collectionextract/WrapperCollectionExtractor.java
index e6af307..a2712e2 100644
--- a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/collectionextract/WrapperCollectionExtractor.java
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/collectionextract/WrapperCollectionExtractor.java
@@ -115,7 +115,7 @@
         if (ec instanceof ExtractedMap)
             return (ExtractedMap) ec;
         else
-            throw new UnsupportedOperationException("not a map: " + coll.getDisplayName() + "; " + ec.getDisplayName());
+            throw new UnsupportedOperationException("not a map: " + coll.getDisplayName() + ec != null ? ("; " + ec.getDisplayName()) : "");
     }
 
     protected AbstractExtractedCollection extractCollection(IObject coll)
diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/messages.properties b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/messages.properties
index 2be5e42..62c94fb 100644
--- a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/messages.properties
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/messages.properties
@@ -204,6 +204,7 @@
 EquinoxBundleReader_State_Installed=installed

 EquinoxBundleReader_State_Resolved=resolved

 EquinoxBundleReader_State_Starting=starting

+EquinoxBundleReader_State_LazyStarting=lazy starting

 EquinoxBundleReader_State_Stopping=stopping

 EquinoxBundleReader_State_Uninstalled=uninstalled

 ExtractListValuesQuery_CollectingElements=Collecting {0} element(s) of {1}

diff --git a/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/acquire/AcquireDumpTest.java b/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/acquire/AcquireDumpTest.java
index 3b5097d..e55c970 100644
--- a/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/acquire/AcquireDumpTest.java
+++ b/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/acquire/AcquireDumpTest.java
@@ -1,5 +1,5 @@
 /*******************************************************************************

- * Copyright (c) 2015 IBM Corporation.

+ * Copyright (c) 2015,2017 IBM Corporation.

  * 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

@@ -13,6 +13,9 @@
 import static org.hamcrest.CoreMatchers.notNullValue;

 import static org.hamcrest.number.OrderingComparison.greaterThan;

 import static org.hamcrest.number.OrderingComparison.greaterThanOrEqualTo;

+import static org.junit.Assert.assertEquals;

+import static org.junit.Assert.assertNotNull;

+import static org.junit.Assert.assertTrue;

 

 import java.io.File;

 import java.io.IOException;

@@ -23,10 +26,14 @@
 import org.eclipse.mat.SnapshotException;

 import org.eclipse.mat.internal.acquire.HeapDumpProviderDescriptor;

 import org.eclipse.mat.internal.acquire.HeapDumpProviderRegistry;

+import org.eclipse.mat.query.IContextObject;

+import org.eclipse.mat.query.IResult;

+import org.eclipse.mat.query.IResultTree;

 import org.eclipse.mat.snapshot.ISnapshot;

 import org.eclipse.mat.snapshot.SnapshotFactory;

 import org.eclipse.mat.snapshot.acquire.IHeapDumpProvider;

 import org.eclipse.mat.snapshot.acquire.VmInfo;

+import org.eclipse.mat.snapshot.query.SnapshotQuery;

 import org.eclipse.mat.tests.TestSnapshots;

 import org.eclipse.mat.util.IProgressListener;

 import org.eclipse.mat.util.VoidProgressListener;

@@ -126,6 +133,7 @@
                         {

                             collector.checkThat("Snapshot", answer, notNullValue());

                             found++;

+                            checkEclipseBundleQuery(answer);

                         }

                         finally

                         {

@@ -143,4 +151,82 @@
         collector.checkThat("Available VMs", count, greaterThan(0));

         collector.checkThat("Available dumps from VMs", found, greaterThan(0));

     }

+

+    /**

+     * This query requires an heap dump from a running Eclipse system to 

+     * test the Eclipse bundle query. Other test dumps from non-Eclipse programs won't work.

+     * @param snapshot

+     * @throws SnapshotException

+     */

+    private void checkEclipseBundleQuery(ISnapshot snapshot) throws SnapshotException

+    {

+        SnapshotQuery query = SnapshotQuery.parse("bundle_registry -groupby NONE", snapshot);

+        assertNotNull(query);

+        IResult result = query.execute(new VoidProgressListener());

+        assertNotNull(result);

+        IResultTree tree = (IResultTree) result;

+        int found = 0;

+        int f2 = 0;

+        for (Object o : tree.getElements())

+        {

+            IContextObject ctx = tree.getContext(o);

+            Object o2 = tree.getColumnValue(o, 0);

+            if (o2.toString().startsWith("org.eclipse.mat.tests "))

+            {

+                found++;

+            }

+            if (o2.toString().startsWith("org.eclipse.mat.api "))

+            {

+                List<?> l3 = tree.getChildren(o);

+                for (Object o3 : l3)

+                {

+                    Object o4 = tree.getColumnValue(o3, 0);

+                    if (o4.toString().startsWith("Dependencies"))

+                    {

+                        f2 |= 1;

+                        checkSubtree(tree, o3, 2, "org.eclipse.mat.report ", "Expected dependencies of org.eclipse.mat.api to include");

+                    }

+                    if (o4.toString().startsWith("Dependents"))

+                    {

+                        f2 |= 2;

+                        checkSubtree(tree, o3, 2, "org.eclipse.mat.parser ", "Expected dependendents of org.eclipse.mat.api to include");

+                    }

+                    if (o4.toString().startsWith("Extension Points"))

+                    {

+                        f2 |= 4;

+                        checkSubtree(tree, o3, 2, "org.eclipse.mat.api.factory", "Expected extension points of org.eclipse.mat.api to include");

+                    }

+                    if (o4.toString().startsWith("Extensions"))

+                    {

+                        f2 |= 8;

+                        checkSubtree(tree, o3, 2, "org.eclipse.mat.api.nameResolver", "Expected extensions of org.eclipse.mat.api to include");

+                    }

+                    if (o4.toString().startsWith("Used Services"))

+                    {

+                        f2 |= 16;

+                        checkSubtree(tree, o3, 1, "org.eclipse.osgi.service.debug.DebugOptions", "Expected used services of org.eclipse.mat.api to include");

+                    }

+                }

+            }

+        }

+        assertEquals("Expected to find a org.eclipse.mat.tests plugin", found, 1);

+        assertEquals("Expected Dependencies,Dependents, Extension Points, Extensions, Used Services from org.eclipse.mat.api", f2, 31);

+    }

+

+    private void checkSubtree(IResultTree tree, Object o3, int minElements, String toFind, String errMsg)

+    {

+        List<?> l5 = tree.getChildren(o3);

+        assertTrue(l5.size() >= minElements);

+        boolean foundItem = false;

+        for (Object o6 : l5)

+        {

+            Object o7 = tree.getColumnValue(o6, 0);

+            if (o7.toString().startsWith(toFind))

+            {

+                foundItem = true;

+            }

+            //System.out.println("Found "+o7+" "+errMsg);

+        }

+        assertTrue(errMsg+" "+toFind, foundItem);

+    }

 }

diff --git a/plugins/org.eclipse.mat.ui.help/tasks/bundleregistry.dita b/plugins/org.eclipse.mat.ui.help/tasks/bundleregistry.dita
new file mode 100644
index 0000000..fe2e30e
--- /dev/null
+++ b/plugins/org.eclipse.mat.ui.help/tasks/bundleregistry.dita
@@ -0,0 +1,141 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<!--

+    Copyright (c) 2017 IBM Corporation.

+    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 documentation

+ -->

+<!DOCTYPE task PUBLIC "-//OASIS//DTD DITA Task//EN" "task.dtd" >

+<task id="task_bundleregistry" xml:lang="en-us">

+	<title>Eclipse Equinox Bundle Registry</title>

+	<prolog>

+		<copyright>

+			<copyryear year=""></copyryear>

+			<copyrholder>

+				Copyright (c) 2017 IBM Corporation.

+			    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

+			</copyrholder>

+		</copyright>

+	</prolog>

+

+	<taskbody>

+		<context>

+		    <p><b>Introduction</b></p>

+			<p>

+			Eclipse uses an OSGi framework implementation called Equinox.

+			An OSGi application is built out of multiple bundles.

+			This query allows the bundles in a Eclipse program to be explored using a heap dump from that program. 

+			</p>

+

+			<p>

+				Bundles (or plugins) allow different parts of a system to be isolated. Each has its own class loader

+				and interactions between bundles occurs using defined interfaces.

+				See <xref format="html" href="https://help.eclipse.org/neon/index.jsp?topic=%2Forg.eclipse.platform.doc.isv%2Fguide%2Fruntime_model_bundles.htm">Plug-ins and bundles</xref>

+				and <xref format="html" href="https://www.eclipse.org/equinox/bundles/">Equinox Bundles</xref>.

+			</p>

+			<p>

+				Definitions

+				<dl>

+					<dlentry>

+						<dt>Bundle</dt>

+						<dd>The OSGi module.</dd>

+					</dlentry>

+					<dlentry>

+						<dt>Bundle fragment</dt>

+						<dd>A modification to the bundle supplied for purposes such as internationalization, providing

+						updates for particular languages or countries, or for particular platforms.</dd>

+					</dlentry>

+					<dlentry>

+						<dt>hosted by</dt>

+						<dd>The bundle which is modified by this fragment.</dd>

+					</dlentry>

+					<dlentry>

+						<dt>Dependent</dt>

+						<dd>A bundle which relies on this bundle.</dd>

+					</dlentry>

+					<dlentry>

+						<dt>Dependency</dt>

+						<dd>A bundle which is required by this bundle.</dd>

+					</dlentry>

+					<dlentry>

+						<dt>Extension Point</dt>

+						<dd>A way of extending the function of a bundle; other bundles may register an extension matching

+						an extension point and then bundles searching for implementations of the extension point

+						would find those extensions and could call them as appropriate.

+						See <xref format="html" href="https://wiki.eclipse.org/FAQ_What_are_extensions_and_extension_points%3F">FAQ What are extensions and extension points?</xref></dd>

+					</dlentry>

+					<dlentry>

+						<dt>Extension</dt>

+						<dd>An implementation of an extension point offered by a bundle.</dd>

+					</dlentry>

+					<dlentry>

+						<dt>contributed by</dt>

+						<dd>Which bundles provide an extension to satisfy an extension point definition.</dd>

+					</dlentry>

+					<dlentry>

+						<dt>registered by</dt>

+						<dd>Which bundle provided an extension point definition, or which bundle provided a service

+						definition.</dd>

+					</dlentry>

+					<dlentry>

+						<dt>Service</dt>

+						<dd>Similar to extensions and extension points, but an OSGi standard.</dd>

+					</dlentry>

+					<dlentry>

+						<dt>Registered Service</dt>

+						<dd>A service offered by this bundle which is available for other bundles to use.</dd>

+					</dlentry>

+					<dlentry>

+						<dt>Used Service</dt>

+						<dd>A service provided by other bundles which is needed by this bundle.</dd>

+					</dlentry>

+					<dlentry>

+						<dt>Bundles Using</dt>

+						<dd>Which bundles use this implementation of a service.</dd>

+					</dlentry>

+				</dl>

+			</p>

+			<p>

+				Bundle states

+				<dl>

+

+					<dlentry>

+						<dt>Installed</dt>

+						<dd>The bundle is installed, but is not yet resolved, perhaps due to missing dependencies.</dd>

+					</dlentry>

+					<dlentry>

+						<dt>Resolved</dt>

+						<dd>Links between the bundle and dependents and dependencies have been established and the bundle is ready to be started.</dd>

+					</dlentry>

+					<dlentry>

+						<dt>Lazy starting</dt>

+						<dd>The bundle is ready to start, for example when one of its classes is referenced by another bundle.</dd>

+					</dlentry>

+					<dlentry>

+						<dt>Starting</dt>

+						<dd>The bundle is starting.</dd>

+					</dlentry>

+					<dlentry>

+						<dt>Active</dt>

+						<dd>The bundle is running.</dd>

+					</dlentry>

+					<dlentry>

+						<dt>Stopping</dt>

+						<dd>The bundle is stopping.</dd>

+					</dlentry>

+					<dlentry>

+						<dt>Uninstalled</dt>

+						<dd>The bundle is no longer available for use.</dd>

+					</dlentry>

+				</dl>

+			</p>

+		</context>

+	</taskbody>

+</task>

diff --git a/plugins/org.eclipse.mat.ui.help/tasks/bundleregistry.html b/plugins/org.eclipse.mat.ui.help/tasks/bundleregistry.html
new file mode 100644
index 0000000..39cd83d
--- /dev/null
+++ b/plugins/org.eclipse.mat.ui.help/tasks/bundleregistry.html
@@ -0,0 +1,188 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html
+  PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xml:lang="en-us" lang="en-us">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<meta name="DC.Type" content="task"/>
+<meta name="DC.Title" content="Eclipse Equinox Bundle Registry"/>
+<meta name="copyright" content="Copyright (c) 2017 IBM Corporation. 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 " type="primary"/>
+<meta name="DC.Rights.Owner" content="Copyright (c) 2017 IBM Corporation. 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 " type="primary"/>
+<meta name="DC.Format" content="XHTML"/>
+<meta name="DC.Identifier" content="task_bundleregistry"/>
+<meta name="DC.Language" content="en-us"/>
+<link rel="stylesheet" type="text/css" href="../styles/commonltr.css"/>
+<title>Eclipse Equinox Bundle Registry</title>
+</head>
+<body id="task_bundleregistry">
+
+
+	<h1 class="title topictitle1">Eclipse Equinox Bundle Registry</h1>
+
+	
+
+	<div class="body taskbody">
+		<div class="section context">
+		    <p class="p"><strong class="ph b">Introduction</strong></p>
+
+			<p class="p">
+			Eclipse uses an OSGi framework implementation called Equinox.
+			An OSGi application is built out of multiple bundles.
+			This query allows the bundles in a Eclipse program to be explored using a heap dump from that program. 
+			</p>
+
+
+			<p class="p">
+				Bundles (or plugins) allow different parts of a system to be isolated. Each has its own class loader
+				and interactions between bundles occurs using defined interfaces.
+				See <a class="xref" href="https://help.eclipse.org/neon/index.jsp?topic=%2Forg.eclipse.platform.doc.isv%2Fguide%2Fruntime_model_bundles.htm">Plug-ins and bundles</a>
+				and <a class="xref" href="https://www.eclipse.org/equinox/bundles/">Equinox Bundles</a>.
+			</p>
+
+			<div class="p">
+				Definitions
+				<dl class="dl">
+					
+						<dt class="dt dlterm">Bundle</dt>
+
+						<dd class="dd">The OSGi module.</dd>
+
+					
+					
+						<dt class="dt dlterm">Bundle fragment</dt>
+
+						<dd class="dd">A modification to the bundle supplied for purposes such as internationalization, providing
+						updates for particular languages or countries, or for particular platforms.</dd>
+
+					
+					
+						<dt class="dt dlterm">hosted by</dt>
+
+						<dd class="dd">The bundle which is modified by this fragment.</dd>
+
+					
+					
+						<dt class="dt dlterm">Dependent</dt>
+
+						<dd class="dd">A bundle which relies on this bundle.</dd>
+
+					
+					
+						<dt class="dt dlterm">Dependency</dt>
+
+						<dd class="dd">A bundle which is required by this bundle.</dd>
+
+					
+					
+						<dt class="dt dlterm">Extension Point</dt>
+
+						<dd class="dd">A way of extending the function of a bundle; other bundles may register an extension matching
+						an extension point and then bundles searching for implementations of the extension point
+						would find those extensions and could call them as appropriate.
+						See <a class="xref" href="https://wiki.eclipse.org/FAQ_What_are_extensions_and_extension_points%3F">FAQ What are extensions and extension points?</a></dd>
+
+					
+					
+						<dt class="dt dlterm">Extension</dt>
+
+						<dd class="dd">An implementation of an extension point offered by a bundle.</dd>
+
+					
+					
+						<dt class="dt dlterm">contributed by</dt>
+
+						<dd class="dd">Which bundles provide an extension to satisfy an extension point definition.</dd>
+
+					
+					
+						<dt class="dt dlterm">registered by</dt>
+
+						<dd class="dd">Which bundle provided an extension point definition, or which bundle provided a service
+						definition.</dd>
+
+					
+					
+						<dt class="dt dlterm">Service</dt>
+
+						<dd class="dd">Similar to extensions and extension points, but an OSGi standard.</dd>
+
+					
+					
+						<dt class="dt dlterm">Registered Service</dt>
+
+						<dd class="dd">A service offered by this bundle which is available for other bundles to use.</dd>
+
+					
+					
+						<dt class="dt dlterm">Used Service</dt>
+
+						<dd class="dd">A service provided by other bundles which is needed by this bundle.</dd>
+
+					
+					
+						<dt class="dt dlterm">Bundles Using</dt>
+
+						<dd class="dd">Which bundles use this implementation of a service.</dd>
+
+					
+				</dl>
+
+			</div>
+
+			<div class="p">
+				Bundle states
+				<dl class="dl">
+
+					
+						<dt class="dt dlterm">Installed</dt>
+
+						<dd class="dd">The bundle is installed, but is not yet resolved, perhaps due to missing dependencies.</dd>
+
+					
+					
+						<dt class="dt dlterm">Resolved</dt>
+
+						<dd class="dd">Links between the bundle and dependents and dependencies have been established and the bundle is ready to be started.</dd>
+
+					
+					
+						<dt class="dt dlterm">Lazy starting</dt>
+
+						<dd class="dd">The bundle is ready to start, for example when one of its classes is referenced by another bundle.</dd>
+
+					
+					
+						<dt class="dt dlterm">Starting</dt>
+
+						<dd class="dd">The bundle is starting.</dd>
+
+					
+					
+						<dt class="dt dlterm">Active</dt>
+
+						<dd class="dd">The bundle is running.</dd>
+
+					
+					
+						<dt class="dt dlterm">Stopping</dt>
+
+						<dd class="dd">The bundle is stopping.</dd>
+
+					
+					
+						<dt class="dt dlterm">Uninstalled</dt>
+
+						<dd class="dd">The bundle is no longer available for use.</dd>
+
+					
+				</dl>
+
+			</div>
+
+		</div>
+
+	</div>
+
+
+</body>
+</html>
\ No newline at end of file
diff --git a/plugins/org.eclipse.mat.ui.help/welcome.dita b/plugins/org.eclipse.mat.ui.help/welcome.dita
index 5a884bf..469cc90 100644
--- a/plugins/org.eclipse.mat.ui.help/welcome.dita
+++ b/plugins/org.eclipse.mat.ui.help/welcome.dita
@@ -66,6 +66,7 @@
 		<p><xref href="tasks/analyzingthreads.dita" /></p>
 		<p><xref href="tasks/analyzingjavacollectionusage.dita" /></p>
 		<p><xref href="tasks/analyzingfinalizer.dita" /></p>
+		<p><xref href="tasks/bundleregistry.dita" /></p>
 		<p><xref href="tasks/comparingdata.dita" /></p>
 		<p><xref href="tasks/exportdata.dita" /></p>
 		<p><xref href="tasks/configure_mat.dita" /></p>
diff --git a/plugins/org.eclipse.mat.ui.help/welcome.html b/plugins/org.eclipse.mat.ui.help/welcome.html
index 972b382..59d6f13 100644
--- a/plugins/org.eclipse.mat.ui.help/welcome.html
+++ b/plugins/org.eclipse.mat.ui.help/welcome.html
@@ -79,6 +79,8 @@
 
 		<p class="p"><a class="xref" href="tasks/analyzingfinalizer.html">Analyzing Finalizer</a></p>
 
+		<p class="p"><a class="xref" href="tasks/bundleregistry.html">Eclipse Equinox Bundle Registry</a></p>
+
 		<p class="p"><a class="xref" href="tasks/comparingdata.html">Comparing Objects</a></p>
 
 		<p class="p"><a class="xref" href="tasks/exportdata.html">Export Data</a></p>
diff --git a/plugins/org.eclipse.mat.ui/src/org/eclipse/mat/ui/snapshot/panes/BundlesPane.java b/plugins/org.eclipse.mat.ui/src/org/eclipse/mat/ui/snapshot/panes/BundlesPane.java
index e324a6a..2e9d975 100644
--- a/plugins/org.eclipse.mat.ui/src/org/eclipse/mat/ui/snapshot/panes/BundlesPane.java
+++ b/plugins/org.eclipse.mat.ui/src/org/eclipse/mat/ui/snapshot/panes/BundlesPane.java
@@ -70,7 +70,7 @@
                             break;

                     }

 

-                    final QueryResult queryResult = new QueryResult(null, "bundle_registry -groupBy " + target.name(),//$NON-NLS-1$

+                    final QueryResult queryResult = new QueryResult(((BundlesPane)getPane()).srcQueryResult.getQuery(), "bundle_registry -groupBy " + target.name(),//$NON-NLS-1$

                                     tree);

 

                     top.getDisplay().asyncExec(new Runnable()