Bug 568993 - Add special support for unusually slow source files (Core)
diff --git a/org.eclipse.handly.ui/src/org/eclipse/handly/ui/viewer/DeferredElementTreeContentProvider.java b/org.eclipse.handly.ui/src/org/eclipse/handly/ui/viewer/DeferredElementTreeContentProvider.java
index 2174b10..4c2f574 100644
--- a/org.eclipse.handly.ui/src/org/eclipse/handly/ui/viewer/DeferredElementTreeContentProvider.java
+++ b/org.eclipse.handly.ui/src/org/eclipse/handly/ui/viewer/DeferredElementTreeContentProvider.java
@@ -14,6 +14,8 @@
 
 import static org.eclipse.handly.context.Contexts.EMPTY_CONTEXT;
 
+import java.util.Collection;
+
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.jobs.ISchedulingRule;
@@ -21,6 +23,7 @@
 import org.eclipse.handly.model.Elements;
 import org.eclipse.handly.model.IElement;
 import org.eclipse.handly.model.ISourceFile;
+import org.eclipse.handly.util.ICollector;
 import org.eclipse.jface.viewers.AbstractTreeViewer;
 import org.eclipse.ui.IWorkbenchPartSite;
 import org.eclipse.ui.progress.IElementCollector;
@@ -62,11 +65,10 @@
     @Override
     public Object[] getChildren(Object parentElement)
     {
-        if (parentElement instanceof ISourceFile && !Elements.isWorkingCopy(
-            (ISourceFile)parentElement))
+        if (shouldDefer(parentElement))
             return getDeferredTreeContentManager().getChildren(parentElement);
 
-        return getChildren(parentElement, null);
+        return getImmediateChildren(parentElement);
     }
 
     @Override
@@ -81,43 +83,43 @@
     @Override
     public boolean hasChildren(Object element)
     {
-        if (element instanceof ISourceFile && !Elements.isWorkingCopy(
-            (ISourceFile)element))
-            return true;
+        if (shouldDefer(element))
+            return getDeferredTreeContentManager().mayHaveChildren(element);
 
-        return getChildren(element, null).length > 0;
+        return getImmediateChildren(element).length > 0;
     }
 
-    @Override
-    protected void fetchDeferredChildren(Object parentElement,
-        IElementCollector collector, IProgressMonitor monitor)
+    /**
+     * Returns whether the given element should be processed
+     * via deferred tree content manager.
+     *
+     * @param element an element
+     * @return <code>true</code> if the given element should be processed
+     *  via deferred tree content manager, and <code>false</code> otherwise
+     */
+    protected boolean shouldDefer(Object element)
     {
-        collector.add(getChildren(parentElement, monitor), null);
-        collector.done();
+        return element instanceof ISourceFile && !Elements.isWorkingCopy(
+            (ISourceFile)element);
     }
 
     /**
      * Returns the child elements of the given parent element.
      * <p>
-     * Default implementation invokes <code>Elements.getChildren((IElement)parentElement,
-     * EMPTY_CONTEXT, monitor)</code> if the parent element is an IElement.
-     * Subclasses may override or extend.
+     * Default implementation invokes <code>Elements.getChildren((IElement)parentElement)</code>
+     * if the parent element is an IElement. Subclasses may override or extend.
      * </p>
      *
      * @param parentElement the parent element
-     * @param monitor a progress monitor to support reporting and cancellation
-     *  (may be <code>null</code>)
      * @return an array of child elements (not <code>null</code>)
      */
-    protected Object[] getChildren(Object parentElement,
-        IProgressMonitor monitor)
+    protected Object[] getImmediateChildren(Object parentElement)
     {
         if (parentElement instanceof IElement)
         {
             try
             {
-                return Elements.getChildren((IElement)parentElement,
-                    EMPTY_CONTEXT, monitor);
+                return Elements.getChildren((IElement)parentElement);
             }
             catch (CoreException e)
             {
@@ -127,6 +129,41 @@
         return NO_CHILDREN;
     }
 
+    @Override
+    protected void fetchDeferredChildren(Object parentElement,
+        IElementCollector collector, IProgressMonitor monitor)
+    {
+        if (parentElement instanceof IElement)
+        {
+            try
+            {
+                Elements.fetchChildren((IElement)parentElement, EMPTY_CONTEXT,
+                    new ICollector<Object>()
+                    {
+                        @Override
+                        public void add(Object e)
+                        {
+                            collector.add(e, null);
+                        }
+
+                        @Override
+                        public void addAll(Collection<? extends Object> c)
+                        {
+                            collector.add(c.toArray(), null);
+                        }
+                    }, monitor);
+            }
+            catch (CoreException e)
+            {
+                Activator.logError(e);
+            }
+            finally
+            {
+                collector.done();
+            }
+        }
+    }
+
     /**
      * Returns the rule used to schedule the deferred fetching of children
      * for the given parent element.
diff --git a/org.eclipse.handly/.settings/.api_filters b/org.eclipse.handly/.settings/.api_filters
index dae170b..033bd75 100644
--- a/org.eclipse.handly/.settings/.api_filters
+++ b/org.eclipse.handly/.settings/.api_filters
@@ -25,6 +25,20 @@
             </message_arguments>
         </filter>
     </resource>
+    <resource path="src/org/eclipse/handly/model/impl/IElementImpl.java" type="org.eclipse.handly.model.impl.IElementImpl">
+        <filter id="404000815">
+            <message_arguments>
+                <message_argument value="org.eclipse.handly.model.impl.IElementImpl"/>
+                <message_argument value="fetchChildrenOfType_(Class&lt;T&gt;, IContext, ICollector&lt;? super T&gt;, IProgressMonitor)"/>
+            </message_arguments>
+        </filter>
+        <filter id="404000815">
+            <message_arguments>
+                <message_argument value="org.eclipse.handly.model.impl.IElementImpl"/>
+                <message_argument value="fetchChildren_(IContext, ICollector&lt;? super IElement&gt;, IProgressMonitor)"/>
+            </message_arguments>
+        </filter>
+    </resource>
     <resource path="src/org/eclipse/handly/refactoring/SourceFileChange.java" type="org.eclipse.handly.refactoring.SourceFileChange$PreviewEditProcessor">
         <filter id="571473929">
             <message_arguments>
diff --git a/org.eclipse.handly/src/org/eclipse/handly/model/Elements.java b/org.eclipse.handly/src/org/eclipse/handly/model/Elements.java
index faa5b9b..cd22b0e 100644
--- a/org.eclipse.handly/src/org/eclipse/handly/model/Elements.java
+++ b/org.eclipse.handly/src/org/eclipse/handly/model/Elements.java
@@ -44,6 +44,7 @@
 import org.eclipse.handly.model.impl.ISourceFileImpl;
 import org.eclipse.handly.snapshot.ISnapshot;
 import org.eclipse.handly.snapshot.StaleSnapshotException;
+import org.eclipse.handly.util.ICollector;
 import org.eclipse.handly.util.Property;
 import org.eclipse.handly.util.TextRange;
 
@@ -991,6 +992,54 @@
     }
 
     /**
+     * Adds the immediate children of the element to the given collector.
+     * Unless otherwise specified by the parent element, the children are
+     * added in no particular order.
+     *
+     * @param element not <code>null</code>
+     * @param context the operation context (not <code>null</code>)
+     * @param collector the element collector (not <code>null</code>)
+     * @param monitor a progress monitor, or <code>null</code>
+     *  if progress reporting is not desired. The caller must not rely on
+     *  {@link IProgressMonitor#done()} having been called by the receiver
+     * @throws CoreException if the element does not exist or if an
+     *  exception occurs while accessing its corresponding resource
+     * @throws OperationCanceledException if this method is canceled
+     * @since 1.5
+     */
+    public static void fetchChildren(IElement element, IContext context,
+        ICollector<? super IElement> collector, IProgressMonitor monitor)
+        throws CoreException
+    {
+        ((IElementImpl)element).fetchChildren_(context, collector, monitor);
+    }
+
+    /**
+     * Adds the immediate children of the element that have the given type
+     * to the given collector. Unless otherwise specified by the parent element,
+     * the children are added in no particular order.
+     *
+     * @param element not <code>null</code>
+     * @param type not <code>null</code>
+     * @param context the operation context (not <code>null</code>)
+     * @param collector the element collector (not <code>null</code>)
+     * @param monitor a progress monitor, or <code>null</code>
+     *  if progress reporting is not desired. The caller must not rely on
+     *  {@link IProgressMonitor#done()} having been called by the receiver
+     * @throws CoreException if the element does not exist or if an
+     *  exception occurs while accessing its corresponding resource
+     * @throws OperationCanceledException if this method is canceled
+     * @since 1.5
+     */
+    public static <T> void fetchChildrenOfType(IElement element, Class<T> type,
+        IContext context, ICollector<? super T> collector,
+        IProgressMonitor monitor) throws CoreException
+    {
+        ((IElementImpl)element).fetchChildrenOfType_(type, context, collector,
+            monitor);
+    }
+
+    /**
      * Returns a string representation of the element in a form suitable for
      * debugging purposes. Clients can influence the result with options
      * specified in the given context; unrecognized options are ignored and
diff --git a/org.eclipse.handly/src/org/eclipse/handly/model/impl/IElementImpl.java b/org.eclipse.handly/src/org/eclipse/handly/model/impl/IElementImpl.java
index e894a0e..6754d67 100644
--- a/org.eclipse.handly/src/org/eclipse/handly/model/impl/IElementImpl.java
+++ b/org.eclipse.handly/src/org/eclipse/handly/model/impl/IElementImpl.java
@@ -19,14 +19,17 @@
 import java.lang.reflect.Array;
 import java.net.URI;
 import java.util.ArrayList;
+import java.util.Arrays;
 
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
 import org.eclipse.handly.context.IContext;
 import org.eclipse.handly.model.Elements;
 import org.eclipse.handly.model.IElement;
 import org.eclipse.handly.model.IModel;
+import org.eclipse.handly.util.ICollector;
 import org.eclipse.handly.util.ToStringOptions.FormatStyle;
 
 /**
@@ -227,6 +230,52 @@
     }
 
     /**
+     * Adds the immediate children of this element to the given collector.
+     * Unless otherwise specified by the implementing element, the children are
+     * added in no particular order.
+     *
+     * @param context the operation context (not <code>null</code>)
+     * @param collector the element collector (not <code>null</code>)
+     * @param monitor a progress monitor, or <code>null</code>
+     *  if progress reporting is not desired. The caller must not rely on
+     *  {@link IProgressMonitor#done()} having been called by the receiver
+     * @throws CoreException if this element does not exist or if an
+     *  exception occurs while accessing its corresponding resource
+     * @throws OperationCanceledException if this method is canceled
+     * @since 1.5
+     */
+    default void fetchChildren_(IContext context,
+        ICollector<? super IElement> collector, IProgressMonitor monitor)
+        throws CoreException
+    {
+        collector.addAll(Arrays.asList(getChildren_(context, monitor)));
+    }
+
+    /**
+     * Adds the immediate children of this element that have the given type
+     * to the given collector. Unless otherwise specified by the implementing
+     * element, the children are added in no particular order.
+     *
+     * @param type not <code>null</code>
+     * @param context the operation context (not <code>null</code>)
+     * @param collector the element collector (not <code>null</code>)
+     * @param monitor a progress monitor, or <code>null</code>
+     *  if progress reporting is not desired. The caller must not rely on
+     *  {@link IProgressMonitor#done()} having been called by the receiver
+     * @throws CoreException if this element does not exist or if an
+     *  exception occurs while accessing its corresponding resource
+     * @throws OperationCanceledException if this method is canceled
+     * @since 1.5
+     */
+    default <T> void fetchChildrenOfType_(Class<T> type, IContext context,
+        ICollector<? super T> collector, IProgressMonitor monitor)
+        throws CoreException
+    {
+        collector.addAll(Arrays.asList(getChildrenOfType_(type, context,
+            monitor)));
+    }
+
+    /**
      * Returns a string representation of this element in a form suitable for
      * debugging purposes. Clients can influence the result with format options
      * specified in the given context; unrecognized options are ignored and
diff --git a/org.eclipse.handly/src/org/eclipse/handly/util/ICollector.java b/org.eclipse.handly/src/org/eclipse/handly/util/ICollector.java
new file mode 100644
index 0000000..b07230a
--- /dev/null
+++ b/org.eclipse.handly/src/org/eclipse/handly/util/ICollector.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2020 1C-Soft LLC.
+ *
+ * This program and the accompanying materials are made available under
+ * the terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Vladimir Piskarev (1C) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.handly.util;
+
+import java.util.Collection;
+
+/**
+ * A common protocol for collecting elements.
+ *
+ * @param <E> the type of collected elements
+ * @since 1.5
+ */
+public interface ICollector<E>
+{
+    /**
+     * Adds the given element to this collector.
+     *
+     * @param e element to be added (not <code>null</code>)
+     */
+    void add(E e);
+
+    /**
+     * Adds all of the elements contained in the given collection
+     * to this collector.
+     *
+     * @param c collection containing elements to be added
+     *  (not <code>null</code>)
+     */
+    void addAll(Collection<? extends E> c);
+}