[275087] Improve performance when editing large XML Schema documents
diff --git a/bundles/org.eclipse.wst.xsd.ui/src-adt-xsd/org/eclipse/wst/xsd/ui/internal/text/XSDModelDelayedReconciler.java b/bundles/org.eclipse.wst.xsd.ui/src-adt-xsd/org/eclipse/wst/xsd/ui/internal/text/XSDModelDelayedReconciler.java
new file mode 100644
index 0000000..02b7b6d
--- /dev/null
+++ b/bundles/org.eclipse.wst.xsd.ui/src-adt-xsd/org/eclipse/wst/xsd/ui/internal/text/XSDModelDelayedReconciler.java
@@ -0,0 +1,195 @@
+/*******************************************************************************
+ * Copyright (c) 2009 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * 
+ * 
+ * Contributors:
+ *     IBM Corporation - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xsd.ui.internal.text;
+
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.progress.UIJob;
+import org.eclipse.wst.xsd.ui.internal.editor.InternalXSDMultiPageEditor;
+import org.eclipse.xsd.XSDConcreteComponent;
+import org.eclipse.xsd.XSDSchema;
+import org.w3c.dom.Element;
+
+
+/**
+ * Provides  delayed reconciliation between the SSE DOM and the XSD EMF model. 
+ * Changes in the DOM are queued and processed by a UI job. When a new request 
+ * comes in, the current run is cancelled, the new request is added to the queue, 
+ * then the job is re-scheduled.
+ */
+public class XSDModelDelayedReconciler
+{
+  /**
+   * The model reconciler job.
+   */
+  private ReconcilerJob reconcilerJob;
+
+  /**
+   * The time in milliseconds to delay updating the EMF model.
+   */
+  private static final int DELAY = 300;
+
+  /**
+   * The elements to reconcile.
+   */
+  private List elementsToReconcile = new ArrayList();
+
+  /**
+   * Determines if the delayed reconciler should kick in.
+   */
+  public boolean shouldDelay(XSDSchema schema)
+  {
+    boolean shouldDelay = false;
+
+    // The delayed reconciler should not be used when the editor is in graphical editing mode.
+
+    IWorkbenchWindow workbenchWindow = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
+    IWorkbenchPage workbenchPage = workbenchWindow.getActivePage();
+    IEditorPart editorPart = workbenchPage != null ? workbenchPage.getActiveEditor() : null;
+    if (editorPart != null && editorPart instanceof InternalXSDMultiPageEditor)
+    {
+      InternalXSDMultiPageEditor xsdEditor = (InternalXSDMultiPageEditor)editorPart;
+      shouldDelay = xsdEditor.isSourcePageActive();
+    }
+
+    return shouldDelay;
+  }
+
+  /**
+   * Updates the XSD EMF component corresponding to the DOM element.
+   * 
+   * @param element the changed element.
+   * @param schema the containing schema.
+   */
+  public void elementChanged(Element element, XSDSchema schema)
+  {
+    synchronized (elementsToReconcile)
+    {
+      // The number of elements should be small so a linear search should be fine.
+
+      if (!elementsToReconcile.contains(element))
+      {
+        elementsToReconcile.add(element);
+      }
+
+      if (reconcilerJob == null)
+      {
+        reconcilerJob = new ReconcilerJob(schema);
+      }
+      
+      reconcilerJob.schedule(DELAY);
+    }
+  }
+
+  /**
+   * A UI job used to reconcile the XSD EMF model with the associated SSE DOM.
+   */
+  class ReconcilerJob extends UIJob
+  {
+    /**
+     * The target schema.
+     */
+    private XSDSchema schema;
+
+    /**
+     * The number of times allowed to wake up and do nothing.
+     */
+    private static final int MAX_INACTIVE_COUNT = 10;
+
+    /**
+     * The job will terminate once this counter reaches MAX_INACTIVE_COUNT. 
+     */
+    private int timesAwakeAndIdle = 0;
+
+    /**
+     * Constructs the reconciler job and configures some of its properties.
+     */
+    public ReconcilerJob(XSDSchema schema)
+    {
+      super("Reconciling the XSD EMF model"); //$NON-NLS-1$
+      setSystem(true);
+      setPriority(Job.LONG);
+      this.schema = schema;
+    }
+
+    public IStatus runInUIThread(IProgressMonitor monitor)
+    {
+      if (monitor.isCanceled())
+      {
+        return Status.CANCEL_STATUS;
+      }
+
+      Element[] elements = null;
+      
+      synchronized (elementsToReconcile)
+      {
+        if (!elementsToReconcile.isEmpty())
+        {
+          elements = new Element[elementsToReconcile.size()];
+          elementsToReconcile.toArray(elements);
+          elementsToReconcile.clear();
+        }
+        else
+        {
+          if (shouldTerminate())
+          {
+            reconcilerJob = null;
+            return Status.CANCEL_STATUS;
+          }
+        }
+      }
+
+      reconcile(elements);
+
+      schedule(DELAY);
+
+      return Status.OK_STATUS;
+    }
+
+    private void reconcile(Element[] modifiedElements)
+    {
+      if (modifiedElements != null)
+      {
+        for (int index = 0; index < modifiedElements.length; index++)
+        {
+          Element modifiedElement = modifiedElements[index];
+
+          reconcile(modifiedElement);
+        }
+      }
+    }
+
+    private void reconcile(Element modifiedElement)
+    {
+      if (modifiedElement != null)
+      {
+        XSDConcreteComponent concreteComponent = schema.getCorrespondingComponent(modifiedElement);
+        concreteComponent.elementChanged(modifiedElement);
+      }
+    }
+    
+    private boolean shouldTerminate()
+    {
+      return timesAwakeAndIdle++ == MAX_INACTIVE_COUNT;
+    }
+  }
+}
\ No newline at end of file
diff --git a/bundles/org.eclipse.wst.xsd.ui/src-adt-xsd/org/eclipse/wst/xsd/ui/internal/text/XSDModelReconcileAdapter.java b/bundles/org.eclipse.wst.xsd.ui/src-adt-xsd/org/eclipse/wst/xsd/ui/internal/text/XSDModelReconcileAdapter.java
index 80a6db5..44b0626 100644
--- a/bundles/org.eclipse.wst.xsd.ui/src-adt-xsd/org/eclipse/wst/xsd/ui/internal/text/XSDModelReconcileAdapter.java
+++ b/bundles/org.eclipse.wst.xsd.ui/src-adt-xsd/org/eclipse/wst/xsd/ui/internal/text/XSDModelReconcileAdapter.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2004, 2008 IBM Corporation and others.
+ * Copyright (c) 2004, 2009 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -24,11 +24,13 @@
 public class XSDModelReconcileAdapter extends ModelReconcileAdapter
 {
   protected XSDSchema schema;
+  protected XSDModelDelayedReconciler delayedReconciler;
   
   public XSDModelReconcileAdapter(Document document, XSDSchema schema)
   {
     super(document);
     this.schema = schema;
+    this.delayedReconciler = new XSDModelDelayedReconciler();
   }
   
   protected void handleNodeChanged(Node node)
@@ -50,9 +52,17 @@
 
     if (node instanceof Element)
     {  
-      XSDConcreteComponent concreteComponent = schema.getCorrespondingComponent(node);    
-      concreteComponent.elementChanged((Element)node);
-    }
+		Element element = (Element) node;
+		if (delayedReconciler.shouldDelay(schema))
+		{
+			delayedReconciler.elementChanged(element, schema);
+		}
+		else
+		{
+	        XSDConcreteComponent concreteComponent = schema.getCorrespondingComponent(node);
+			concreteComponent.elementChanged(element);
+		}
+	}
     else if (node instanceof Document)
     {
       // The document changed so we may need to fix up the 
diff --git a/bundles/org.eclipse.wst.xsd.ui/src-adt/org/eclipse/wst/xsd/ui/internal/adt/outline/ADTContentOutlineProvider.java b/bundles/org.eclipse.wst.xsd.ui/src-adt/org/eclipse/wst/xsd/ui/internal/adt/outline/ADTContentOutlineProvider.java
index a063250..25f337c 100644
--- a/bundles/org.eclipse.wst.xsd.ui/src-adt/org/eclipse/wst/xsd/ui/internal/adt/outline/ADTContentOutlineProvider.java
+++ b/bundles/org.eclipse.wst.xsd.ui/src-adt/org/eclipse/wst/xsd/ui/internal/adt/outline/ADTContentOutlineProvider.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2001, 2007 IBM Corporation and others.
+ * Copyright (c) 2001, 2009 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -11,7 +11,6 @@
 package org.eclipse.wst.xsd.ui.internal.adt.outline;
 
 import org.eclipse.jface.viewers.ITreeContentProvider;
-import org.eclipse.jface.viewers.TreeViewer;
 import org.eclipse.jface.viewers.Viewer;
 import org.eclipse.wst.xsd.ui.internal.adt.facade.IADTObject;
 import org.eclipse.wst.xsd.ui.internal.adt.facade.IADTObjectListener;
@@ -20,6 +19,7 @@
 {
   protected Viewer viewer = null;
   protected Object oldInput, newInput;
+  protected ADTContentOutlineRefreshJob refreshJob;
 
   public ADTContentOutlineProvider()
   {
@@ -89,6 +89,11 @@
     {
       removeListener((IADTObject) input);
     }
+    if (refreshJob != null)
+    {
+      refreshJob.cancel();
+      refreshJob = null;
+    }
   }
 
   /* (non-Javadoc)
@@ -99,6 +104,11 @@
     this.viewer = viewer;
     this.oldInput = oldInput;
     this.newInput = newInput;
+    if (refreshJob != null)
+    {
+      refreshJob.cancel();
+    }
+    refreshJob = new ADTContentOutlineRefreshJob(viewer);
   }
 
   /* (non-Javadoc)
@@ -106,15 +116,7 @@
    */
   public void propertyChanged(Object object, String property)
   {
-    if (viewer instanceof TreeViewer)
-    {
-      TreeViewer treeViewer = (TreeViewer) viewer;
-      if (treeViewer.getTree() != null && !treeViewer.getTree().isDisposed())
-      {
-        treeViewer.refresh(object);
-        treeViewer.reveal(object);
-      }
-    }
+    refreshJob.refresh((IADTObject)object);
   }
 
   /**
diff --git a/bundles/org.eclipse.wst.xsd.ui/src-adt/org/eclipse/wst/xsd/ui/internal/adt/outline/ADTContentOutlineRefreshJob.java b/bundles/org.eclipse.wst.xsd.ui/src-adt/org/eclipse/wst/xsd/ui/internal/adt/outline/ADTContentOutlineRefreshJob.java
new file mode 100644
index 0000000..e83082c
--- /dev/null
+++ b/bundles/org.eclipse.wst.xsd.ui/src-adt/org/eclipse/wst/xsd/ui/internal/adt/outline/ADTContentOutlineRefreshJob.java
@@ -0,0 +1,143 @@
+/*******************************************************************************
+ * Copyright (c) 2009 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * 
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xsd.ui.internal.adt.outline;
+
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.wst.xsd.ui.internal.adapters.CategoryAdapter;
+import org.eclipse.wst.xsd.ui.internal.adt.facade.IADTObject;
+
+
+/**
+ * This job holds a queue of updates (affected nodes) for the XSD editor's
+ * outline view. When a new request comes in, the current run is canceled, 
+ * the new request is added to the queue, then the job is re-scheduled.
+ * This class is loosely based on RefreshStructureJob.
+ */
+class ADTContentOutlineRefreshJob extends Job
+{
+  /**
+   * The delay time in milliseconds.
+   */
+  private static final long UPDATE_DELAY = 300;
+
+  private final List nodesToRefresh = new ArrayList(5);
+
+  private final TreeViewer viewer;
+
+  public ADTContentOutlineRefreshJob(Viewer viewer)
+  {
+    super("Refreshing XSD outline"); //$NON-NLS-1$
+    setPriority(Job.LONG);
+    setSystem(true);
+    this.viewer = (TreeViewer)viewer;
+  }
+
+  private synchronized void addRefreshRequest(IADTObject adtObject)
+  {
+    if (nodesToRefresh.contains(adtObject))
+    {
+      return;
+    }
+
+    nodesToRefresh.add(adtObject);
+  }
+
+  protected void canceling()
+  {
+    nodesToRefresh.clear();
+    super.canceling();
+  }
+
+  private void doRefresh(final IADTObject adtObject)
+  {
+    final Display display = PlatformUI.getWorkbench().getDisplay();
+    display.asyncExec(new Runnable()
+      {
+        public void run()
+        {
+          boolean isValidViewer = viewer != null && !viewer.getControl().isDisposed();
+          if (isValidViewer)
+          {
+            viewer.refresh(adtObject);
+
+            // Needlessly revealing the category nodes causes a lot of UI flicker.
+            
+            if (!(adtObject instanceof CategoryAdapter))
+            {
+              viewer.reveal(adtObject);
+            }
+          }
+        }
+      });
+  }
+
+  private synchronized IADTObject[] getNodesToRefresh()
+  {
+    IADTObject[] toRefresh = new IADTObject [nodesToRefresh.size()];
+    nodesToRefresh.toArray(toRefresh);
+    nodesToRefresh.clear();
+
+    return toRefresh;
+  }
+
+  public void refresh(IADTObject adtObject)
+  {
+    if (adtObject == null)
+    {
+      return;
+    }
+
+    addRefreshRequest(adtObject);
+
+    schedule(UPDATE_DELAY);
+  }
+
+  protected IStatus run(IProgressMonitor monitor)
+  {
+    IStatus status = Status.OK_STATUS;
+    try
+    {
+      performRefreshes(monitor);
+    }
+    finally
+    {
+      monitor.done();
+    }
+    return status;
+  }
+
+  private void performRefreshes(IProgressMonitor monitor)
+  {
+    IADTObject[] nodes = getNodesToRefresh();
+
+    for (int index = 0; index < nodes.length; index++)
+    {
+      if (monitor.isCanceled())
+      {
+        throw new OperationCanceledException();
+      }
+      IADTObject node = nodes[index];
+      doRefresh(node);
+    }
+  }
+}
\ No newline at end of file