Package org.eclipse.jface.text.reconciler

Source Code of org.eclipse.jface.text.reconciler.AbstractReconciler$BackgroundThread

/*******************************************************************************
* Copyright (c) 2000, 2008 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.jface.text.reconciler;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;

import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.ITextInputListener;
import org.eclipse.jface.text.ITextViewer;


/**
* Abstract implementation of {@link IReconciler}. The reconciler
* listens to input document changes as well as changes of
* the input document of the text viewer it is installed on. Depending on
* its configuration it manages the received change notifications in a
* queue folding neighboring or overlapping changes together. The reconciler
* processes the dirty regions as a background activity after having waited for further
* changes for the configured duration of time. A reconciler is started using the
* {@link #install(ITextViewer)} method.  As a first step {@link #initialProcess()} is
* executed in the background. Then, the reconciling thread waits for changes that
* need to be reconciled. A reconciler can be resumed by calling {@link #forceReconciling()}
* independent from the existence of actual changes. This mechanism is for subclasses only.
* It is the clients responsibility to stop a reconciler using its {@link #uninstall()}
* method. Unstopped reconcilers do not free their resources.
* <p>
* It is subclass responsibility to specify how dirty regions are processed.
* </p>
*
* @see org.eclipse.jface.text.IDocumentListener
* @see org.eclipse.jface.text.ITextInputListener
* @see org.eclipse.jface.text.reconciler.DirtyRegion
* @since 2.0
*/
abstract public class AbstractReconciler implements IReconciler {


  /**
   * Background thread for the reconciling activity.
   */
  class BackgroundThread extends Thread {

    /** Has the reconciler been canceled. */
    private boolean fCanceled= false;
    /** Has the reconciler been reset. */
    private boolean fReset= false;
    /** Some changes need to be processed. */
    private boolean fIsDirty= false;
    /** Is a reconciling strategy active. */
    private boolean fIsActive= false;

    /**
     * Creates a new background thread. The thread
     * runs with minimal priority.
     *
     * @param name the thread's name
     */
    public BackgroundThread(String name) {
      super(name);
      setPriority(Thread.MIN_PRIORITY);
      setDaemon(true);
    }

    /**
     * Returns whether a reconciling strategy is active right now.
     *
     * @return <code>true</code> if a activity is active
     */
    public boolean isActive() {
      return fIsActive;
    }

    /**
     * Returns whether some changes need to be processed.
     *
     * @return <code>true</code> if changes wait to be processed
     * @since 3.0
     */
    public synchronized boolean isDirty() {
      return fIsDirty;
    }

    /**
     * Cancels the background thread.
     */
    public void cancel() {
      fCanceled= true;
      IProgressMonitor pm= fProgressMonitor;
      if (pm != null)
        pm.setCanceled(true);
      synchronized (fDirtyRegionQueue) {
        fDirtyRegionQueue.notifyAll();
      }
    }

    /**
     * Suspends the caller of this method until this background thread has
     * emptied the dirty region queue.
     */
    public void suspendCallerWhileDirty() {
      boolean isDirty;
      do {
        synchronized (fDirtyRegionQueue) {
          isDirty= fDirtyRegionQueue.getSize() > 0;
          if (isDirty) {
            try {
              fDirtyRegionQueue.wait();
            } catch (InterruptedException x) {
            }
          }
        }
      } while (isDirty);
    }

    /**
     * Reset the background thread as the text viewer has been changed,
     */
    public void reset() {

      if (fDelay > 0) {

        synchronized (this) {
          fIsDirty= true;
          fReset= true;
        }

      } else {

        synchronized (this) {
          fIsDirty= true;
        }

        synchronized (fDirtyRegionQueue) {
          fDirtyRegionQueue.notifyAll();
        }
      }

            reconcilerReset();
    }

    /**
     * The background activity. Waits until there is something in the
     * queue managing the changes that have been applied to the text viewer.
     * Removes the first change from the queue and process it.
     * <p>
     * Calls {@link AbstractReconciler#initialProcess()} on entrance.
     * </p>
     */
    public void run() {

      synchronized (fDirtyRegionQueue) {
        try {
          fDirtyRegionQueue.wait(fDelay);
        } catch (InterruptedException x) {
        }
      }

      if (fCanceled)
        return;

      initialProcess();

      while (!fCanceled) {

        synchronized (fDirtyRegionQueue) {
          try {
            fDirtyRegionQueue.wait(fDelay);
          } catch (InterruptedException x) {
          }
        }

        if (fCanceled)
          break;

        if (!isDirty())
          continue;

        synchronized (this) {
          if (fReset) {
            fReset= false;
            continue;
          }
        }

        DirtyRegion r= null;
        synchronized (fDirtyRegionQueue) {
          r= fDirtyRegionQueue.removeNextDirtyRegion();
        }

        fIsActive= true;

        fProgressMonitor.setCanceled(false);

        process(r);

        synchronized (fDirtyRegionQueue) {
          if (0 == fDirtyRegionQueue.getSize()) {
            synchronized (this) {
              fIsDirty= fProgressMonitor.isCanceled();
            }
            fDirtyRegionQueue.notifyAll();
          }
        }

        fIsActive= false;
      }
    }
  }

  /**
   * Internal document listener and text input listener.
   */
  class Listener implements IDocumentListener, ITextInputListener {

    /*
     * @see IDocumentListener#documentAboutToBeChanged(DocumentEvent)
     */
    public void documentAboutToBeChanged(DocumentEvent e) {
    }

    /*
     * @see IDocumentListener#documentChanged(DocumentEvent)
     */
    public void documentChanged(DocumentEvent e) {

      if (!fThread.isDirty() && fThread.isAlive()) {
        if (!fIsAllowedToModifyDocument && Thread.currentThread() == fThread)
          throw new UnsupportedOperationException("The reconciler thread is not allowed to modify the document"); //$NON-NLS-1$
        aboutToBeReconciled();
      }

      /*
       * The second OR condition handles the case when the document
       * gets changed while still inside initialProcess().
       */
      if (fThread.isActive() || fThread.isDirty() && fThread.isAlive())
        fProgressMonitor.setCanceled(true);

      if (fIsIncrementalReconciler)
        createDirtyRegion(e);

      fThread.reset();

    }

    /*
     * @see ITextInputListener#inputDocumentAboutToBeChanged(IDocument, IDocument)
     */
    public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) {

      if (oldInput == fDocument) {

        if (fDocument != null)
          fDocument.removeDocumentListener(this);

        if (fIsIncrementalReconciler) {
          synchronized (fDirtyRegionQueue) {
            fDirtyRegionQueue.purgeQueue();
          }
          if (fDocument != null && fDocument.getLength() > 0 && fThread.isDirty() && fThread.isAlive()) {
            DocumentEvent e= new DocumentEvent(fDocument, 0, fDocument.getLength(), ""); //$NON-NLS-1$
            createDirtyRegion(e);
            fThread.reset();
            fThread.suspendCallerWhileDirty();
          }
        }

        fDocument= null;
      }
    }

    /*
     * @see ITextInputListener#inputDocumentChanged(IDocument, IDocument)
     */
    public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {

      fDocument= newInput;
      if (fDocument == null)
        return;


      reconcilerDocumentChanged(fDocument);

      fDocument.addDocumentListener(this);

      if (!fThread.isDirty())
        aboutToBeReconciled();

      startReconciling();
    }
  }

  /** Queue to manage the changes applied to the text viewer. */
  private DirtyRegionQueue fDirtyRegionQueue;
  /** The background thread. */
  private BackgroundThread fThread;
  /** Internal document and text input listener. */
  private Listener fListener;
  /** The background thread delay. */
  private int fDelay= 500;
  /** Are there incremental reconciling strategies? */
  private boolean fIsIncrementalReconciler= true;
  /** The progress monitor used by this reconciler. */
  private IProgressMonitor fProgressMonitor;
  /**
   * Tells whether this reconciler is allowed to modify the document.
   * @since 3.2
   */
  private boolean fIsAllowedToModifyDocument= true;


  /** The text viewer's document. */
  private IDocument fDocument;
  /** The text viewer */
  private ITextViewer fViewer;


  /**
   * Processes a dirty region. If the dirty region is <code>null</code> the whole
   * document is consider being dirty. The dirty region is partitioned by the
   * document and each partition is handed over to a reconciling strategy registered
   * for the partition's content type.
   *
   * @param dirtyRegion the dirty region to be processed
   */
  abstract protected void process(DirtyRegion dirtyRegion);

  /**
   * Hook called when the document whose contents should be reconciled
   * has been changed, i.e., the input document of the text viewer this
   * reconciler is installed on. Usually, subclasses use this hook to
   * inform all their reconciling strategies about the change.
   *
   * @param newDocument the new reconciler document
   */
  abstract protected void reconcilerDocumentChanged(IDocument newDocument);


  /**
   * Creates a new reconciler without configuring it.
   */
  protected AbstractReconciler() {
    fProgressMonitor= new NullProgressMonitor();
  }

  /**
   * Tells the reconciler how long it should wait for further text changes before
   * activating the appropriate reconciling strategies.
   *
   * @param delay the duration in milliseconds of a change collection period.
   */
  public void setDelay(int delay) {
    fDelay= delay;
  }

  /**
   * Tells the reconciler whether any of the available reconciling strategies
   * is interested in getting detailed dirty region information or just in the
   * fact that the document has been changed. In the second case, the reconciling
   * can not incrementally be pursued.
   *
   * @param isIncremental indicates whether this reconciler will be configured with
   *    incremental reconciling strategies
   *
   * @see DirtyRegion
   * @see IReconcilingStrategy
   */
  public void setIsIncrementalReconciler(boolean isIncremental) {
    fIsIncrementalReconciler= isIncremental;
  }
 
  /**
   * Tells the reconciler whether it is allowed to change the document
   * inside its reconciler thread.
   * <p>
   * If this is set to <code>false</code> an {@link UnsupportedOperationException}
   * will be thrown when this restriction will be violated.
   * </p>
   *
   * @param isAllowedToModify indicates whether this reconciler is allowed to modify the document
   * @since 3.2
   */
  public void setIsAllowedToModifyDocument(boolean isAllowedToModify) {
    fIsAllowedToModifyDocument= isAllowedToModify;
  }

  /**
   * Sets the progress monitor of this reconciler.
   *
   * @param monitor the monitor to be used
   */
  public void setProgressMonitor(IProgressMonitor monitor) {
    Assert.isLegal(monitor != null);
    fProgressMonitor= monitor;
  }

  /**
   * Returns whether any of the reconciling strategies is interested in
   * detailed dirty region information.
   *
   * @return whether this reconciler is incremental
   *
   * @see IReconcilingStrategy
   */
  protected boolean isIncrementalReconciler() {
    return fIsIncrementalReconciler;
  }

  /**
   * Returns the input document of the text viewer this reconciler is installed on.
   *
   * @return the reconciler document
   */
  protected IDocument getDocument() {
    return fDocument;
  }

  /**
   * Returns the text viewer this reconciler is installed on.
   *
   * @return the text viewer this reconciler is installed on
   */
  protected ITextViewer getTextViewer() {
    return fViewer;
  }

  /**
   * Returns the progress monitor of this reconciler.
   *
   * @return the progress monitor of this reconciler
   */
  protected IProgressMonitor getProgressMonitor() {
    return fProgressMonitor;
  }

  /*
   * @see IReconciler#install(ITextViewer)
   */
  public void install(ITextViewer textViewer) {

    Assert.isNotNull(textViewer);
    fViewer= textViewer;

    synchronized (this) {
      if (fThread != null)
        return;
      fThread= new BackgroundThread(getClass().getName());
    }

    fDirtyRegionQueue= new DirtyRegionQueue();

    fListener= new Listener();
    fViewer.addTextInputListener(fListener);

    // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=67046
    // if the reconciler gets installed on a viewer that already has a document
    // (e.g. when reusing editors), we force the listener to register
    // itself as document listener, because there will be no input change
    // on the viewer.
    // In order to do that, we simulate an input change.
    IDocument document= textViewer.getDocument();
    if (document != null) {
      fListener.inputDocumentAboutToBeChanged(fDocument, document);
      fListener.inputDocumentChanged(fDocument, document);
    }
  }

  /*
   * @see IReconciler#uninstall()
   */
  public void uninstall() {
    if (fListener != null) {

      fViewer.removeTextInputListener(fListener);
      if (fDocument != null) {
        fListener.inputDocumentAboutToBeChanged(fDocument, null);
        fListener.inputDocumentChanged(fDocument, null);
      }
      fListener= null;

            synchronized (this) {
                // http://dev.eclipse.org/bugs/show_bug.cgi?id=19135
          BackgroundThread bt= fThread;
          fThread= null;
          bt.cancel();
            }
    }
  }

  /**
   * Creates a dirty region for a document event and adds it to the queue.
   *
   * @param e the document event for which to create a dirty region
   */
  private void createDirtyRegion(DocumentEvent e) {
    synchronized (fDirtyRegionQueue) {
      if (e.getLength() == 0 && e.getText() != null) {
        // Insert
        fDirtyRegionQueue.addDirtyRegion(new DirtyRegion(e.getOffset(), e.getText().length(), DirtyRegion.INSERT, e.getText()));
 
      } else if (e.getText() == null || e.getText().length() == 0) {
        // Remove
        fDirtyRegionQueue.addDirtyRegion(new DirtyRegion(e.getOffset(), e.getLength(), DirtyRegion.REMOVE, null));
 
      } else {
        // Replace (Remove + Insert)
        fDirtyRegionQueue.addDirtyRegion(new DirtyRegion(e.getOffset(), e.getLength(), DirtyRegion.REMOVE, null));
        fDirtyRegionQueue.addDirtyRegion(new DirtyRegion(e.getOffset(), e.getText().length(), DirtyRegion.INSERT, e.getText()));
      }
    }
  }

  /**
   * Hook for subclasses which want to perform some
   * action as soon as reconciliation is needed.
   * <p>
   * Default implementation is to do nothing.
   * </p>
   *
   * @since 3.0
   */
  protected void aboutToBeReconciled() {
  }

  /**
   * This method is called on startup of the background activity. It is called only
   * once during the life time of the reconciler. Clients may reimplement this method.
   */
  protected void initialProcess() {
  }

  /**
   * Forces the reconciler to reconcile the structure of the whole document.
   * Clients may extend this method.
   */
  protected void forceReconciling() {

    if (fDocument != null) {

      if (!fThread.isDirty()&& fThread.isAlive())
        aboutToBeReconciled();

      if (fThread.isActive())
        fProgressMonitor.setCanceled(true);
     
      if (fIsIncrementalReconciler) {
        DocumentEvent e= new DocumentEvent(fDocument, 0, fDocument.getLength(), fDocument.get());
        createDirtyRegion(e);
      }

      startReconciling();
    }
  }

  /**
   * Starts the reconciler to reconcile the queued dirty-regions.
   * Clients may extend this method.
   */
  protected synchronized void startReconciling() {
    if (fThread == null)
      return;

    if (!fThread.isAlive()) {
      try {
        fThread.start();
      } catch (IllegalThreadStateException e) {
        // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=40549
        // This is the only instance where the thread is started; since
        // we checked that it is not alive, it must be dead already due
        // to a run-time exception or error. Exit.
      }
    } else {
      fThread.reset();
    }
  }

    /**
     * Hook that is called after the reconciler thread has been reset.
     */
    protected void reconcilerReset() {
    }
   
    /**
   * Tells whether the code is running in this reconciler's
   * background thread.
   *
   * @return <code>true</code> if running in this reconciler's background thread
   * @since 3.4
   */
  protected boolean isRunningInReconcilerThread() {
    return Thread.currentThread() == fThread;
  }
}
TOP

Related Classes of org.eclipse.jface.text.reconciler.AbstractReconciler$BackgroundThread

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.