/**
 * Copyright (c) 2019 Eclipse contributors and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v20.html
 */
package org.eclipse.emf.codegen.presentation;


import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.operations.IUndoContext;
import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.IDocumentSetupParticipant;
import org.eclipse.core.filebuffers.IStateValidationSupport;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.filebuffers.ITextFileBufferManager;
import org.eclipse.core.filebuffers.LocationKind;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IEncodedStorage;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.PlatformObject;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.emf.codegen.CodeGenPlugin;
import org.eclipse.emf.codegen.jet.JETAttributeItem;
import org.eclipse.emf.codegen.jet.JETAttributeListItem;
import org.eclipse.emf.codegen.jet.JETCommentItem;
import org.eclipse.emf.codegen.jet.JETCompilationUnit;
import org.eclipse.emf.codegen.jet.JETCompilationUnit.JETJavaRange;
import org.eclipse.emf.codegen.jet.JETCompileTemplateOperation;
import org.eclipse.emf.codegen.jet.JETDirectiveItem;
import org.eclipse.emf.codegen.jet.JETException;
import org.eclipse.emf.codegen.jet.JETExpressionItem;
import org.eclipse.emf.codegen.jet.JETItem;
import org.eclipse.emf.codegen.jet.JETJavaItem;
import org.eclipse.emf.codegen.jet.JETLiteralItem;
import org.eclipse.emf.codegen.jet.JETMark;
import org.eclipse.emf.codegen.jet.JETNature;
import org.eclipse.emf.codegen.jet.JETParser;
import org.eclipse.emf.codegen.jet.JETProblemListener;
import org.eclipse.emf.codegen.jet.JETRootItem;
import org.eclipse.emf.codegen.jet.JETScriptletItem;
import org.eclipse.emf.codegen.jet.JETSkeleton;
import org.eclipse.emf.codegen.jet.JETSubItem;
import org.eclipse.emf.codegen.jet.JETTokenItem;
import org.eclipse.emf.codegen.jet.JETValueElementItem;
import org.eclipse.emf.codegen.jet.JETValueItem;
import org.eclipse.emf.codegen.util.CodeGenUtil;
import org.eclipse.emf.common.CommonPlugin;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.SegmentSequence;
import org.eclipse.emf.common.util.URI;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.IBufferChangedListener;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.ILocalVariable;
import org.eclipse.jdt.core.IOpenable;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.ToolFactory;
import org.eclipse.jdt.core.WorkingCopyOwner;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTRequestor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.formatter.CodeFormatter;
import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants;
import org.eclipse.jdt.ui.JavaElementImageDescriptor;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jdt.ui.PreferenceConstants;
import org.eclipse.jdt.ui.actions.IJavaEditorActionDefinitionIds;
import org.eclipse.jdt.ui.actions.OpenAction;
import org.eclipse.jdt.ui.refactoring.RenameSupport;
import org.eclipse.jdt.ui.text.IJavaPartitions;
import org.eclipse.jdt.ui.text.java.hover.IJavaEditorTextHover;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceDialog;
import org.eclipse.jface.preference.PreferenceStore;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPositionCategoryException;
import org.eclipse.jface.text.DefaultInformationControl;
import org.eclipse.jface.text.DefaultPositionUpdater;
import org.eclipse.jface.text.DefaultTextHover;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.DocumentRewriteSessionType;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension4;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.IInformationControl;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.jface.text.IPositionUpdater;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ISelectionValidator;
import org.eclipse.jface.text.ITextHover;
import org.eclipse.jface.text.ITextHoverExtension;
import org.eclipse.jface.text.ITextHoverExtension2;
import org.eclipse.jface.text.ITextInputListener;
import org.eclipse.jface.text.ITextPresentationListener;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.ITextViewerExtension;
import org.eclipse.jface.text.ITextViewerExtension2;
import org.eclipse.jface.text.ITextViewerExtension4;
import org.eclipse.jface.text.ITextViewerExtension5;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.IUndoManager;
import org.eclipse.jface.text.IUndoManagerExtension;
import org.eclipse.jface.text.IViewportListener;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextAttribute;
import org.eclipse.jface.text.TextPresentation;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.contentassist.ContentAssistEvent;
import org.eclipse.jface.text.contentassist.ContentAssistant;
import org.eclipse.jface.text.contentassist.ICompletionListener;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension3;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension4;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension5;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension6;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.text.contentassist.IContentAssistant;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.contentassist.IContextInformationValidator;
import org.eclipse.jface.text.hyperlink.IHyperlink;
import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
import org.eclipse.jface.text.presentation.IPresentationReconciler;
import org.eclipse.jface.text.presentation.PresentationReconciler;
import org.eclipse.jface.text.quickassist.IQuickAssistAssistant;
import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext;
import org.eclipse.jface.text.quickassist.IQuickAssistProcessor;
import org.eclipse.jface.text.quickassist.QuickAssistAssistant;
import org.eclipse.jface.text.rules.BufferedRuleBasedScanner;
import org.eclipse.jface.text.rules.DefaultDamagerRepairer;
import org.eclipse.jface.text.rules.ICharacterScanner;
import org.eclipse.jface.text.rules.IRule;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.ITokenScanner;
import org.eclipse.jface.text.rules.Token;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.AnnotationModel;
import org.eclipse.jface.text.source.DefaultCharacterPairMatcher;
import org.eclipse.jface.text.source.IAnnotationHover;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IAnnotationPresentation;
import org.eclipse.jface.text.source.ICharacterPairMatcher;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.IVerticalRuler;
import org.eclipse.jface.text.source.ImageUtilities;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.text.source.TextInvocationContext;
import org.eclipse.jface.text.templates.Template;
import org.eclipse.jface.text.templates.TemplateCompletionProcessor;
import org.eclipse.jface.text.templates.TemplateContext;
import org.eclipse.jface.text.templates.TemplateContextType;
import org.eclipse.jface.text.templates.TemplateProposal;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.IOpenListener;
import org.eclipse.jface.viewers.IPostSelectionProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.OpenEvent;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.StyledCellLabelProvider;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.StyledString.Styler;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CaretEvent;
import org.eclipse.swt.custom.CaretListener;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.custom.VerifyKeyListener;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.TypedEvent;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.TextStyle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.text.undo.DocumentUndoEvent;
import org.eclipse.text.undo.DocumentUndoManagerRegistry;
import org.eclipse.text.undo.IDocumentUndoListener;
import org.eclipse.text.undo.IDocumentUndoManager;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorActionBarContributor;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorRegistry;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.INavigationLocation;
import org.eclipse.ui.IPathEditorInput;
import org.eclipse.ui.IPersistableElement;
import org.eclipse.ui.IPerspectiveDescriptor;
import org.eclipse.ui.ISaveablesLifecycleListener;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.IStorageEditorInput;
import org.eclipse.ui.IURIEditorInput;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.NavigationLocation;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.Saveable;
import org.eclipse.ui.SaveablesLifecycleEvent;
import org.eclipse.ui.SubActionBars;
import org.eclipse.ui.dialogs.PreferencesUtil;
import org.eclipse.ui.editors.text.ForwardingDocumentProvider;
import org.eclipse.ui.editors.text.TextSourceViewerConfiguration;
import org.eclipse.ui.handlers.CollapseAllHandler;
import org.eclipse.ui.handlers.ExpandAllHandler;
import org.eclipse.ui.handlers.IHandlerActivation;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.part.IShowInSource;
import org.eclipse.ui.part.ShowInContext;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditor;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
import org.eclipse.ui.texteditor.AbstractTextEditor;
import org.eclipse.ui.texteditor.ChainedPreferenceStore;
import org.eclipse.ui.texteditor.ContentAssistAction;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.IDocumentProviderExtension;
import org.eclipse.ui.texteditor.IElementStateListener;
import org.eclipse.ui.texteditor.ITextEditorActionConstants;
import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
import org.eclipse.ui.texteditor.MarkerAnnotation;
import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;
import org.eclipse.ui.texteditor.spelling.SpellingService;
import org.eclipse.ui.themes.ColorUtil;
import org.eclipse.ui.views.contentoutline.ContentOutlinePage;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;


/**
 * @since 2.19
 */
public final class JETEditor extends AbstractDecoratedTextEditor
{
  public static final String ID = "org.eclipse.emf.codegen.presentation.JETEditorID";

  static final String REFACTOR_QUICKMENU_COMMAND_ID = "org.eclipse.codegen.ui.jet.refactor.quickmenu";

  private static final String REFACTOR_QUICKMENU_MENU_ID = "org.eclipse.codegen.ui.jet.refactor";

  static final String SOURCE_QUICKMENU_COMMAND_ID = "org.eclipse.codegen.ui.jet.source.quickmenu";

  static final String SOURCE_QUICKMENU_MENU_ID = "org.eclipse.codegen.ui.jet.source";

  static final String SELECT_ENCLOSING_JET_ELEMENT_ACTION_ID = "SelectEnclosingJETElement";

  static final String EXTRACT_LOCAL_VARIABLE_ACTION_ID = "ExtractLocalVariable";

  static final String GOTO_BRACKET_COMMAND_ID = "org.eclipse.emf.codegen.ui.jet.goto.matching.bracket";

  static final String SELECT_ENCLOSING_JET_ELEMENT_COMMAND_ID = "org.eclipse.emf.codegen.ui.jet.select.enclosing";

  static final String RENAME_COMMAND_ID = "org.eclipse.codegen.ui.jet.rename";

  static final String RENAME_ACTION_ID = "Rename";

  static final String FORMAT_COMMAND_ID = "org.eclipse.codegen.ui.jet.format";

  static final String FORMAT_ACTION_ID = "Format";

  private static boolean trace;

  private final BracketInserter bracketInserter = new BracketInserter(this);

  private final DelegatingTextHover delegatingTextHover = new DelegatingTextHover(this);

  private final JETDocumentManager jetDocumentManager = new JETDocumentManager(this);

  private final JETBracketMatcher jetBracketMatcher = new JETBracketMatcher(this);

  private final List<Runnable> disposables = new ArrayList<Runnable>();

  private final AtomicReference<JETCompilerResult> pendingJETCompilerResult = new AtomicReference<JETCompilerResult>();;

  private JETCompilerResult jetCompilerResult;

  private JavaEditor javaEditor;

  private long expectedModificationStamp;

  private JETContentOutlinePage contentOutlinePage;

  private JETBreadcrumbViewer breadcrumbViewer;

  private Runnable pendingUntilDocumentsAvailable;

  private Runnable dismissLandingPage;

  private boolean isSaving;

  private boolean isShowingContentAssist;

  private TrackedPosition jetPosition = new TrackedPosition(TrackedPosition.Type.JET);

  private TrackedPosition javaPosition = new TrackedPosition(TrackedPosition.Type.JAVA);

  private int[] styleRangeOffsets;

  private JETToken[] jetTokens;

  private SelectionSynchronizer selectionSynchronizer;

  private Runnable deferredInvalidatePresentation;

  private boolean editorContextMenuAboutToShow;

  public JETEditor()
  {
    jetPosition.setOpposite(javaPosition);
  }

  JETNature getJETNature()
  {
    IFile file = getEditorInputFile();
    return file == null ? null : JETNature.getRuntime(file.getProject());
  }

  @Override
  public IEditorInput getEditorInput()
  {
    if (editorContextMenuAboutToShow)
    {
      try
      {
        // Prevent potential stack overflow.
        editorContextMenuAboutToShow = false;
        return jetDocumentManager.getEditorInput();
      }
      finally
      {
        editorContextMenuAboutToShow = true;
      }
    }
    else
    {
      return super.getEditorInput();
    }
  }

  SourceViewer getJETSourceViewer()
  {
    return (SourceViewer)getSourceViewer();
  }

  JavaEditor getJavaEditor()
  {
    return javaEditor;
  }

  SourceViewer getJavaSourceViewer()
  {
    return getJavaEditor().getJavaSourceViewer();
  }

  TrackedPosition getJavaPosition()
  {
    return javaPosition;
  }

  TrackedPosition getJETPosition()
  {
    return jetPosition;
  }

  boolean isJavaEditorInSync()
  {
    return expectedModificationStamp == getModificationStamp();
  }

  long getModificationStamp()
  {
    IDocument document = getSourceViewer().getDocument();
    return document == null ? 0 : ((IDocumentExtension4)document).getModificationStamp();
  }

  void resetExpectedModificationStamp()
  {
    expectedModificationStamp = 0;
  }

  void setExpectedModificationStamp()
  {
    expectedModificationStamp = getModificationStamp();
  }

  JETDocumentManager getDocumentManager()
  {
    return jetDocumentManager;
  }

  int getFileID()
  {
    return jetDocumentManager.getFileID();
  }

  IFile getEditorInputFile()
  {
    return toFile(getEditorInput());
  }

  JETBreadcrumbViewer getBreadcrumbViewer()
  {
    return breadcrumbViewer;
  }

  JETCompilerResult getCompilerResult()
  {
    return jetCompilerResult;
  }

  JETCompilationUnit getCompilationUnit()
  {
    return jetCompilerResult == null ? null : jetCompilerResult.getCompilationUnit();
  }

  void setShowingContentAssist(boolean isShowingContentAssist)
  {
    this.isShowingContentAssist = isShowingContentAssist;
    if (!isShowingContentAssist)
    {
      jetDocumentManager.setEnableJavaSynchronization(true);
    }
  }

  void handleCompilerResult(final JETCompilerResult jetCompilerResult)
  {
    // This is called from the JETDynamicCompiler's thread.
    JETCompilerResult oldJETCompilerResult = pendingJETCompilerResult.getAndSet(jetCompilerResult);

    // If there is not a value that was already pending consumption, dispatch a new runnable to the UI thread.
    if (oldJETCompilerResult == null)
    {
      getSite().getShell().getDisplay().asyncExec(new Runnable()
        {
          public void run()
          {
            // If the pending result is still not consumed by waitForCompilerResult, consume it now.
            JETCompilerResult newJETCompilerResult = pendingJETCompilerResult.getAndSet(null);
            if (newJETCompilerResult != null)
            {
              setCompilerResult(newJETCompilerResult);
            }
          }
        });
    }
  }

  void waitForCompilerResult(long timeInMillis)
  {
    long count = timeInMillis / 100;
    for (int i = 0; i < count; ++i)
    {
      JETCompilerResult jetCompilerResult = pendingJETCompilerResult.getAndSet(null);
      if (jetCompilerResult == null)
      {
        try
        {
          Thread.sleep(100);
        }
        catch (InterruptedException exception)
        {
          Thread.interrupted();
        }
      }
      else
      {
        setCompilerResult(jetCompilerResult);
        break;
      }
    }
  }

  void setCompilerResult(JETCompilerResult jetCompilerResult)
  {
    if (!isShowingContentAssist)
    {
      this.jetCompilerResult = jetCompilerResult;

      jetDocumentManager.setCompilerResult(jetCompilerResult);

      handleProblems(getFileID());

      IFile javaFile = jetCompilerResult.getJavaFile();
      JETCompilationUnit jetCompilationUnit = jetCompilerResult.getCompilationUnit();
      if (javaFile != null)
      {
        final ISourceViewer jetSourceViewer = getSourceViewer();
        setExpectedModificationStamp();

        ISourceViewer javaSourceViewer = getJavaSourceViewer();
        StyledText javaTextWidget = javaSourceViewer.getTextWidget();
        try
        {
          javaTextWidget.setRedraw(false);

          String compilationUnit = jetCompilationUnit.getJavaCompilationUnit();
          javaEditor.setInput(javaFile, compilationUnit);

          IDocument document = javaSourceViewer.getDocument();
          if (document != null)
          {
            String oldDocument = document.get();
            if (!compilationUnit.equals(oldDocument))
            {
              document.set(compilationUnit);
            }
            else
            {
              invalidateVisibleTextPresentation(true);
            }

            // Synchronize the cursor/selection.
            jetSourceViewer.getTextWidget().notifyListeners(SWT.Selection, new Event());
          }
        }
        finally
        {
          javaTextWidget.setRedraw(true);
        }
      }

      if (jetCompilationUnit != null && pendingUntilDocumentsAvailable != null)
      {
        try
        {
          pendingUntilDocumentsAvailable.run();
        }
        finally
        {
          pendingUntilDocumentsAvailable = null;
        }
      }

      if (dismissLandingPage != null)
      {
        dismissLandingPage.run();
      }

      selectionSynchronizer.sync(getSourceViewer());
    }
  }

  List<JETJavaRange> getRanges()
  {
    JETCompilationUnit compilerResult = getCompilationUnit();
    return compilerResult == null ? Collections.<JETJavaRange> emptyList() : compilerResult.getRanges();
  }

  JETItem getJETItem(int templateOffset, boolean leaf)
  {
    JETCompilationUnit compilerResult = getCompilationUnit();
    return compilerResult == null ? null : compilerResult.getJETItem(getFileID(), templateOffset, leaf);
  }

  Position getCorrespondingJavaPosition(int templateOffset, int templateLength)
  {
    JETCompilationUnit compilerResult = getCompilationUnit();
    if (compilerResult != null)
    {
      int[] correspondingJavaPoint = compilerResult.getCorrespondingJavaPoint(getFileID(), templateOffset, templateLength);
      if (correspondingJavaPoint != null)
      {
        return new Position(correspondingJavaPoint[0], correspondingJavaPoint[1]);
      }
    }

    return null;
  }

  int getCorrespondingTemplateFileID(int javaOffset)
  {
    JETCompilationUnit compilerResult = getCompilationUnit();
    return compilerResult == null ? -1 : compilerResult.getCorrespondingTemplateFileID(javaOffset);
  }

  Position getCorrespondingTemplatePosition(int javaOffset, int javaLength)
  {
    JETCompilationUnit compilerResult = getCompilationUnit();
    if (compilerResult != null)
    {
      int[] correspondingTemplatePoint = compilerResult.getCorrespondingTemplatePoint(getFileID(), javaOffset, javaLength);
      if (correspondingTemplatePoint != null)
      {
        return new Position(correspondingTemplatePoint[0], correspondingTemplatePoint[1]);
      }
    }
    return null;
  }

  void handleFileIDChanged(int fileID)
  {
    firePropertyChange(PROP_DIRTY);
    handleProblems(fileID);
  }

  void handleProblems(int fileID)
  {
    if (jetCompilerResult != null)
    {
      applyProblemAnnotations(jetCompilerResult.getProblems(fileID));
    }
  }

  void applyProblemAnnotations(List<JETProblemAnnotation> jetProblemAnnotations)
  {
    SourceViewer sourceViewer = getJETSourceViewer();
    IAnnotationModel annotationModel = sourceViewer.getAnnotationModel();
    List<Annotation> annotationsToRemove = new ArrayList<Annotation>();
    for (Iterator<Annotation> i = annotationModel.getAnnotationIterator(); i.hasNext();)
    {
      Annotation annotation = i.next();
      if (annotation instanceof JETProblemAnnotation)
      {
        annotationsToRemove.add(annotation);
      }
      else if (annotation instanceof MarkerAnnotation && isDirty())
      {
        Position position = annotationModel.getPosition(annotation);
        annotationModel.addAnnotation(new JETProblemAnnotation((MarkerAnnotation)annotation, position), position);
      }
    }

    for (Annotation annotation : annotationsToRemove)
    {
      annotationModel.removeAnnotation(annotation);
    }

    for (JETProblemAnnotation jetProblemAnnotation : jetProblemAnnotations)
    {
      Position position = jetProblemAnnotation.getPosition();
      annotationModel.addAnnotation(jetProblemAnnotation, position);
    }
  }

  @Override
  protected void initializeEditor()
  {
    super.initializeEditor();

    configureInsertMode(SMART_INSERT, true);
    configureInsertMode(INSERT, false);
    configureInsertMode(INSERT, true);
    setInsertMode(SMART_INSERT);

    IPreferenceStore preferenceStore = getPreferenceStore();
    IPreferenceStore jetPreferenceStore = CodeGenUIPlugin.getPlugin().getPreferenceStore();
    ChainedPreferenceStore chainedPreferenceStore = new ChainedPreferenceStore(new IPreferenceStore []{ jetPreferenceStore, preferenceStore })
      {
        @Override
        public boolean getBoolean(String name)
        {
          if ("matchingBrackets".equals(name))
          {
            return true;
          }

          return !SpellingService.PREFERENCE_SPELLING_ENABLED.equals(name) && super.getBoolean(name);
        }
      };
    setPreferenceStore(chainedPreferenceStore);
    setSourceViewerConfiguration(new JETSourceViewerConfiguration(chainedPreferenceStore, this));
  }

  @Override
  public void createPartControl(Composite parent)
  {
    final SashForm sashForm = new SashForm(parent, SWT.VERTICAL);

    final Composite composite = new Composite(sashForm, SWT.NONE);
    GridLayout gridLayout = new GridLayout();
    gridLayout.marginHeight = 0;
    gridLayout.marginWidth = 0;
    gridLayout.verticalSpacing = 0;
    gridLayout.horizontalSpacing = 0;
    composite.setLayout(gridLayout);

    breadcrumbViewer = new JETBreadcrumbViewer(composite, SWT.HORIZONTAL)
      {
        @Override
        public void configureDropDownViewer(TreeViewer viewer, Object input)
        {
          viewer.setLabelProvider(contentOutlinePage.newLabelProvider());
          viewer.setContentProvider(contentOutlinePage.getContentProvider());
        }
      };
    GridData breadcrumbLayoutData = new GridData(SWT.FILL, SWT.FILL, true, false);
    breadcrumbViewer.getControl().setLayoutData(breadcrumbLayoutData);

    Composite jetSourceViewerComposite = new Composite(composite, SWT.NONE);
    jetSourceViewerComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
    jetSourceViewerComposite.setLayout(new FillLayout());

    super.createPartControl(jetSourceViewerComposite);

    ToolBarManager toolBarManager = new ToolBarManager(SWT.FLAT);

    final ImageDescriptor up = CodeGenUIPlugin.getPlugin().getImage("full/up.png");
    final ImageDescriptor down = CodeGenUIPlugin.getPlugin().getImage("full/down.png");
    final Action toggleAction = new Action("", IAction.AS_PUSH_BUTTON)
      {
        @Override
        public void run()
        {
          if (sashForm.getMaximizedControl() == null)
          {
            sashForm.setMaximizedControl(composite);
            setImageDescriptor(up);
          }
          else
          {
            sashForm.setMaximizedControl(null);
            setImageDescriptor(down);
          }
        }
      };
    toggleAction.setImageDescriptor(down);
    toolBarManager.add(toggleAction);

    ToolBar toolBar = toolBarManager.createControl(composite);

    GridData toolBarLayoutData = new GridData(SWT.CENTER, SWT.BOTTOM, true, false);
    toolBarLayoutData.heightHint = 11;
    toolBar.setLayoutData(toolBarLayoutData);

    StyledText sourceViewerStyledText = getSourceViewer().getTextWidget();
    Color sourceViewerBackground = sourceViewerStyledText.getBackground();
    breadcrumbViewer.getControl().setBackground(sourceViewerBackground);
    composite.setBackground(sourceViewerStyledText.getBackground());
    IMenuManager contextMenu = (IMenuManager)sourceViewerStyledText.getMenu().getData("org.eclipse.jface.action.MenuManager.managerKey");
    contextMenu.addMenuListener(new IMenuListener()
      {
        public void menuAboutToShow(IMenuManager manager)
        {
          editorContextMenuAboutToShow = false;
        }
      });

    JETContentOutlinePage contentOutlinePage = getContentOutlinePage();
    breadcrumbViewer.setLabelProvider(contentOutlinePage.newLabelProvider());
    breadcrumbViewer.setContentProvider(contentOutlinePage.getContentProvider());
    breadcrumbViewer.setInput(contentOutlinePage.itemManager.getRootItem());

    jetDocumentManager.initializeDocumentManager();

    try
    {
      javaEditor = new JavaEditor(this);
      javaEditor.init(getEditorSite(), getEditorInput());

      final Composite javaComposite = new Composite(sashForm, SWT.NONE);
      final StackLayout stackLayout = new StackLayout();
      javaComposite.setLayout(stackLayout);

      javaEditor.createPartControl(javaComposite);

      final Control[] javaEditorChildren = javaComposite.getChildren();

      final ScrolledComposite scrolledComposite = new ScrolledComposite(javaComposite, SWT.V_SCROLL | SWT.H_SCROLL);
      scrolledComposite.setExpandHorizontal(true);
      scrolledComposite.setExpandVertical(true);
      scrolledComposite.setAlwaysShowScrollBars(false);

      final StyledText landingPage = new StyledText(scrolledComposite, SWT.READ_ONLY | SWT.NO_FOCUS);
      landingPage.setBackground(sourceViewerStyledText.getBackground());
      scrolledComposite.setContent(landingPage);

      final Runnable updateLandingPageInfo = new Runnable()
        {
          public void run()
          {
            StyledString landingPageInfo = generateLandingPageInfo(landingPage.getLineDelimiter());
            landingPage.setText(landingPageInfo.toString());
            landingPage.setStyleRanges(landingPageInfo.getStyleRanges());
            landingPage.redraw();

            landingPage.pack(true);

            Point landingPageSize = landingPage.getSize();
            scrolledComposite.setMinWidth(landingPageSize.x);
            scrolledComposite.setMinHeight(landingPageSize.y);
          }
        };
      updateLandingPageInfo.run();

      stackLayout.topControl = scrolledComposite;

      final ImageDescriptor star = CodeGenUIPlugin.getPlugin().getImage("full/star.png");
      final ImageDescriptor blank = CodeGenUIPlugin.getPlugin().getImage("full/blank.png");
      final Action showInfoAction = new Action("", IAction.AS_PUSH_BUTTON)
        {
          @Override
          public void run()
          {
            if (stackLayout.topControl == scrolledComposite)
            {
              stackLayout.topControl = javaEditorChildren[0];
            }
            else
            {
              stackLayout.topControl = scrolledComposite;
              updateLandingPageInfo.run();
            }

            if (sashForm.getMaximizedControl() == composite)
            {
              sashForm.setMaximizedControl(null);
              toggleAction.setImageDescriptor(down);
            }

            javaComposite.layout();
          }
        };
      showInfoAction.setImageDescriptor(star);
      showInfoAction.setId("show-info");
      toolBarManager.add(showInfoAction);

      toolBarManager.update(true);

      class HyperlinkListener implements MouseTrackListener, MouseMoveListener, MouseListener
      {
        final Cursor defaultCursor = landingPage.getCursor();

        final Cursor handCursor = landingPage.getDisplay().getSystemCursor(SWT.CURSOR_HAND);

        final Runnable infoActionAnimator = new Runnable()
          {
            public void run()
            {
              if (hyperLinkRange != null && hyperLinkRange.length == 1)
              {
                showInfoAction.setImageDescriptor(showInfoAction.getImageDescriptor() == blank ? star : blank);
                landingPage.getDisplay().timerExec(300, this);
              }
              else
              {
                showInfoAction.setImageDescriptor(star);
              }
            }
          };

        StyleRange hyperLinkRange;

        public void mouseDoubleClick(MouseEvent event)
        {
          clearHyperlink();
        }

        public void mouseDown(MouseEvent event)
        {
          if (hyperLinkRange != null)
          {
            if (hyperLinkRange.length == 1)
            {
              showInfoAction.run();
            }
            else
            {
              landingPage.getDisplay().asyncExec(new Runnable()
                {
                  final URI documentURI = URI.createPlatformResourceURI(landingPage.getText(hyperLinkRange.start, hyperLinkRange.start + hyperLinkRange.length - 1), true);

                  public void run()
                  {
                    IEditorPart editorPart = open(getEditorSite(), documentURI.toString());
                    if (editorPart instanceof JETEditor)
                    {
                      ((JETEditor)editorPart).selectAndReveal(getEditorInput(), 0, 0);
                    }
                  }
                });
            }
          }

          clearHyperlink();
        }

        public void mouseUp(MouseEvent event)
        {
          clearHyperlink();
        }

        public void mouseEnter(MouseEvent event)
        {
          clearHyperlink();
        }

        public void mouseExit(MouseEvent event)
        {
          clearHyperlink();
        }

        public void mouseHover(MouseEvent event)
        {
        }

        public void mouseMove(MouseEvent event)
        {
          updateStyleRange(event);
        }

        private void clearHyperlink()
        {
          if (hyperLinkRange != null)
          {
            hyperLinkRange.fontStyle = SWT.NORMAL;
            hyperLinkRange.underline = false;
            landingPage.setStyleRange(hyperLinkRange);
            hyperLinkRange = null;
            landingPage.setCursor(defaultCursor);
          }
        }

        @SuppressWarnings("deprecation")
        private int getOffset(MouseEvent event)
        {
          try
          {
            try
            {
              return (Integer)landingPage.getClass().getMethod("getOffsetAtPoint", Point.class).invoke(landingPage, new Point(event.x, event.y));
            }
            catch (Exception exception)
            {
            }

            return landingPage.getOffsetAtLocation(new Point(event.x, event.y));
          }
          catch (IllegalArgumentException exception)
          {
            return -1;
          }
        }

        private void updateStyleRange(MouseEvent event)
        {
          StyleRange oldHyperLinkRange = hyperLinkRange;
          hyperLinkRange = null;
          int offsetAtPoint = getOffset(event);
          if (offsetAtPoint != -1)
          {
            StyleRange styleRange = landingPage.getStyleRangeAtOffset(offsetAtPoint);
            if (styleRange != null)
            {
              if (styleRange.data instanceof StyleRange)
              {
                hyperLinkRange = (StyleRange)styleRange.data;
                hyperLinkRange.underline = true;
                hyperLinkRange.fontStyle = SWT.BOLD;
                landingPage.setStyleRange(hyperLinkRange);
                landingPage.setCursor(handCursor);
                infoActionAnimator.run();
              }
            }
          }

          if (oldHyperLinkRange != null && oldHyperLinkRange != hyperLinkRange)
          {
            oldHyperLinkRange.fontStyle = SWT.NORMAL;
            oldHyperLinkRange.underline = false;
            landingPage.setStyleRange(oldHyperLinkRange);
            if (hyperLinkRange == null)
            {
              landingPage.setCursor(defaultCursor);
            }
          }
        }
      }

      HyperlinkListener hyperlinkListener = new HyperlinkListener();
      landingPage.addMouseTrackListener(hyperlinkListener);
      landingPage.addMouseListener(hyperlinkListener);
      landingPage.addMouseMoveListener(hyperlinkListener);
      dismissLandingPage = new Runnable()
        {
          public void run()
          {
            dismissLandingPage = null;

            JETCompilationUnit compilationUnit = getCompilationUnit();
            if (compilationUnit != null)
            {
              for (JETException problem : compilationUnit.getProblems())
              {
                String problemKey = problem.getProblemKey();
                if (JETProblemListener.FILE_NOT_A_TEMPLATE.equals(problemKey) || JETProblemListener.FILE_NOT_ON_TEMPLATE_SOURCE_PATH.equals(problemKey))
                {
                  return;
                }
              }

              stackLayout.topControl = javaEditorChildren[0];
              toggleAction.run();
              javaComposite.layout();
            }
          }
        };

      selectionSynchronizer = new SelectionSynchronizer(this, javaEditor);

      delegatingTextHover.setEditor(javaEditor);

      new VisibleCaretHandler(sourceViewerStyledText, javaEditor.getJavaSourceViewer().getTextWidget());
    }
    catch (PartInitException exception)
    {
      throw new RuntimeException(exception);
    }

    if (fSourceViewerDecorationSupport != null)
    {
      fSourceViewerDecorationSupport.install(getPreferenceStore());
    }

    new ViewPortHandler(this);

    setAction(SELECT_ENCLOSING_JET_ELEMENT_ACTION_ID, new SelectEnclosingJETElementAction(this));
    setAction(RENAME_ACTION_ID, new JETRenameLocalVariableAction(this));
    setAction(FORMAT_ACTION_ID, new JETFormatAction(this));
  }

  private StyledString generateLandingPageInfo(String lineDelimiter)
  {
    StyledString styledString = new StyledString(lineDelimiter);
    IEditorInput editorInput = getEditorInput();
    IFile file = toFile(editorInput);
    String indent = "      ";
    if (file == null)
    {
      URI uri = toURI(editorInput);
      String name = uri == null ? editorInput.getName() : uri.toString();
      styledString.append(indent);
      styledString.append("The file ");
      styledString.append(name, StyledString.DECORATIONS_STYLER);
      styledString.append(" cannot be compiled because it is not located in the workspace.");
    }
    else if (getJETNature() == null)
    {
      styledString.append(indent);
      styledString.append("The file ");
      styledString.append(file.getFullPath().toString(), StyledString.DECORATIONS_STYLER);
      styledString.append(" cannot be compiled because it is not in a project with a JET nature.");
      styledString.append(lineDelimiter);
      styledString.append(lineDelimiter);

      IJavaProject javaProject = JavaCore.create(file.getProject());
      boolean isWellFormedJavaProject = false;
      try
      {
        isWellFormedJavaProject = javaProject.isStructureKnown();
      }
      catch (JavaModelException e)
      {
      }

      if (!isWellFormedJavaProject)
      {
        styledString.append(indent);
        styledString.append("Furthermore, its containing project ");
        styledString.append(file.getParent().getFullPath().toString(), StyledString.DECORATIONS_STYLER);
        styledString.append(" is not in a project with a properly configured Java nature.");
        styledString.append(lineDelimiter);
        styledString.append(lineDelimiter);
      }

      styledString.append(indent);
      styledString.append("Use ");
      styledString.append(
        "File \u2192 New \u2192 Other... \u2192 Java Emitter Templates \u2192 Convert Projects to JET Projects",
        JETContentOutlinePage.Item.getFontStyler(SWT.ITALIC));
      styledString.append(" to convert a Java project into a JET project.");
      styledString.append(lineDelimiter);
      styledString.append(lineDelimiter);
      styledString.append(indent);
      styledString.append("You will need to reopen this editor after having done so.");
    }
    else if (getJETNature().getJavaSourceContainer() == null)
    {
      styledString.append(indent);
      styledString.append("The file ");
      styledString.append(file.getFullPath().toString(), StyledString.DECORATIONS_STYLER);
      styledString.append(" cannot be compiled because the JET nature is not properly configured.");
      styledString.append(lineDelimiter);
    }
    else
    {
      JETCompilationUnit compilationUnit = getCompilationUnit();
      String className = null;
      boolean notOnSourcePath = false;
      if (compilationUnit != null)
      {
        className = compilationUnit.getClassName();
        for (JETException problem : compilationUnit.getProblems())
        {
          if (JETProblemListener.FILE_NOT_ON_TEMPLATE_SOURCE_PATH.equals(problem.getProblemKey()))
          {
            notOnSourcePath = true;
            break;
          }
        }
      }

      styledString.append(indent);
      styledString.append("The file ");
      styledString.append(file.getFullPath().toString(), StyledString.DECORATIONS_STYLER);
      styledString.append(" is being compiled.");
      styledString.append(lineDelimiter);
      styledString.append(lineDelimiter);

      Styler problemStyler = new Styler()
        {
          @Override
          public void applyStyles(TextStyle textStyle)
          {
            textStyle.foreground = ColorManager.INSTANCE.getForeground("directive");
          }
        };

      String fileExtension = file.getFileExtension();
      boolean isNotTemplate = fileExtension == null || !fileExtension.endsWith("jet");
      if (isNotTemplate)
      {
        styledString.append(indent);
        styledString.append("However, the file is not a root JET template.", problemStyler);
        styledString.append(lineDelimiter);
        styledString.append(lineDelimiter);

        styledString.append(indent);
        styledString.append("A root JET template must have have a file extension that ends with ");
        styledString.append("jet", StyledString.DECORATIONS_STYLER);
        styledString.append(", e.g, ");
        styledString.append("MyTemplate.jet", StyledString.DECORATIONS_STYLER);
        styledString.append(" or ");
        styledString.append("MyTemplate.xmljet", StyledString.DECORATIONS_STYLER);
        styledString.append(".");
        styledString.append(lineDelimiter);
        styledString.append(lineDelimiter);
      }
      else
      {
        if (notOnSourcePath)
        {
          styledString.append(indent);
          styledString.append("The class ");
          if (className != null)
          {
            styledString.append(className, StyledString.DECORATIONS_STYLER);
            styledString.append(' ');
          }
          styledString.append("would be generated in ");
          styledString.append(getJETNature().getJavaSourceContainer().getFullPath().toString(), StyledString.DECORATIONS_STYLER);
          styledString.append(", but the template is not on the template path and it will not be built when the project builds.");
          styledString.append(lineDelimiter);
          styledString.append(lineDelimiter);
        }
        else
        {
          styledString.append(indent);
          styledString.append("The class ");
          if (className != null)
          {
            styledString.append(className, StyledString.DECORATIONS_STYLER);
            styledString.append(' ');
          }
          styledString.append("will be generated in ");
          styledString.append(getJETNature().getJavaSourceContainer().getFullPath().toString(), StyledString.DECORATIONS_STYLER);
          styledString.append(".");
          styledString.append(lineDelimiter);
          styledString.append(lineDelimiter);
        }
      }

      styledString.append(indent);
      styledString.append("The following template path is used:");
      styledString.append(lineDelimiter);
      styledString.append(lineDelimiter);
      List<Object> templateSourceContainers = getJETNature().getTemplateSourceContainers();
      for (Object container : getJETNature().getTemplateContainers())
      {
        String containerName = container instanceof IContainer ? ((IContainer)container).getFullPath().toString() : container.toString();
        styledString.append(indent);
        styledString.append("    ");
        styledString.append("@ ", templateSourceContainers.contains(container) ? new Styler()
          {
            @Override
            public void applyStyles(TextStyle textStyle)
            {
              textStyle.foreground = getSourceViewer().getTextWidget().getBackground();
            }
          } : StyledString.COUNTER_STYLER);
        styledString.append(containerName, StyledString.DECORATIONS_STYLER);
        styledString.append(lineDelimiter);
      }

      styledString.append(lineDelimiter);
      styledString.append(indent);
      styledString.append("Click the tiny ", StyledString.COUNTER_STYLER);
      styledString.append("\u2605", new Styler()
        {
          @Override
          public void applyStyles(TextStyle textStyle)
          {
            textStyle.data = textStyle;
            textStyle.foreground = ColorManager.INSTANCE.getColor(230, 179, 60);
          }
        });
      styledString.append(" icon on the divider above to preview the Java results.", StyledString.COUNTER_STYLER);
      if (isNotTemplate)
      {
        styledString.append("  Unresolved names will not be reported.", problemStyler);
      }

      styledString.append(lineDelimiter);
      styledString.append(lineDelimiter);

      Set<URI> includingTemplates = JETNature.getIncludingTemplates(toURI(file));
      if (!includingTemplates.isEmpty())
      {
        Set<String> values = new TreeSet<String>();
        for (URI includingTemplate : includingTemplates)
        {
          values.add(includingTemplate.isPlatformResource() ? includingTemplate.toPlatformString(true) : includingTemplate.toString());
        }

        styledString.append(indent);
        styledString.append("This template is included by the following templates. Click any of these links to open this template in the including context:");
        styledString.append(lineDelimiter);
        styledString.append(lineDelimiter);
        for (String includingTemplate : values)
        {
          styledString.append(indent);
          styledString.append(indent);
          Styler linkStyler = new Styler()
            {
              @Override
              public void applyStyles(TextStyle textStyle)
              {
                StyledString.DECORATIONS_STYLER.applyStyles(textStyle);
                textStyle.data = textStyle;
              }
            };

          styledString.append(includingTemplate, linkStyler);
          styledString.append(lineDelimiter);
        }
      }
    }

    return styledString;
  }

  void invalidateVisibleTextPresentation(boolean defer)
  {
    ISourceViewer jetSourceViewer = getSourceViewer();
    if (defer)
    {
      final StyledText textWidget = jetSourceViewer.getTextWidget();
      deferredInvalidatePresentation = new Runnable()
        {
          public void run()
          {
            if (deferredInvalidatePresentation == this && !textWidget.isDisposed())
            {
              invalidateVisibleTextPresentation(false);
            }
          }
        };
      textWidget.getDisplay().timerExec(200, deferredInvalidatePresentation);
    }
    else
    {
      deferredInvalidatePresentation = null;
      int start = jetSourceViewer.getTopIndexStartOffset();
      int end = jetSourceViewer.getBottomIndexEndOffset();
      ((ITextViewerExtension2)jetSourceViewer).invalidateTextPresentation(start, end - start);
    }
  }

  @Override
  protected void doSetInput(IEditorInput input) throws CoreException
  {
    super.doSetInput(input);

    jetDocumentManager.handleInputChanged(input);
  }

  @Override
  protected ISourceViewer createSourceViewer(Composite parent, IVerticalRuler ruler, int styles)
  {
    ISourceViewer sourceViewer = super.createSourceViewer(parent, ruler, styles);

    ColorManager.INSTANCE.register(sourceViewer);

    if (sourceViewer instanceof ITextViewerExtension)
    {
      ((ITextViewerExtension)sourceViewer).prependVerifyKeyListener(bracketInserter);
    }

    return sourceViewer;
  }

  @SuppressWarnings("restriction")
  @Override
  protected void createActions()
  {
    super.createActions();

    Action action = new ContentAssistAction(CodeGenUIPlugin.getResourceBundle(), "_UI_ContentAssistProposal_", this);
    String id = ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS;
    action.setActionDefinitionId(id);
    setAction("ContentAssistProposal", action);
    markAsStateDependentAction("ContentAssistProposal", true);

    final IHandlerService service = (IHandlerService)getSite().getService(IHandlerService.class);

    org.eclipse.jdt.internal.ui.actions.JDTQuickMenuCreator refactorMenuCreator = new org.eclipse.jdt.internal.ui.actions.JDTQuickMenuCreator(getJavaEditor())
      {
        @Override
        protected void fillMenu(IMenuManager menu)
        {
          addRefactorActions(menu);
        }
      };

    final IHandlerActivation refactorHandlerActivation = service.activateHandler(REFACTOR_QUICKMENU_COMMAND_ID, refactorMenuCreator.createHandler());

    org.eclipse.jdt.internal.ui.actions.JDTQuickMenuCreator sourceMenuCreator = new org.eclipse.jdt.internal.ui.actions.JDTQuickMenuCreator(getJavaEditor())
      {
        @Override
        protected void fillMenu(IMenuManager menu)
        {
          addSourceActions(menu);
        }
      };

    final IHandlerActivation sourceHandlerActivation = service.activateHandler(SOURCE_QUICKMENU_COMMAND_ID, sourceMenuCreator.createHandler());

    disposables.add(new Runnable()
      {
        public void run()
        {
          service.deactivateHandler(refactorHandlerActivation);
          service.deactivateHandler(sourceHandlerActivation);
        }
      });
  }

  @Override
  protected void editorContextMenuAboutToShow(IMenuManager menu)
  {
    editorContextMenuAboutToShow = true;
    super.editorContextMenuAboutToShow(menu);

    Action colorPreferencesAction = new Action(CodeGenUIPlugin.getPlugin().getString("_UI_JETColorPreferences_label"), CodeGenUIPlugin.getPlugin().getImage("full/obj16/JETFile"))
      {
        @Override
        public void run()
        {
          JETToken token = getTokenAtSelection(0);
          String matchingProperty = "selectColor:" + ColorManager.INSTANCE.getProperty(token);
          PreferenceDialog preferenceDialog = PreferencesUtil.createPreferenceDialogOn(
            getSite().getShell(),
            "org.eclipse.ui.preferencePages.ColorsAndFonts",
            null,
            matchingProperty);
          preferenceDialog.open();
        }
      };

    menu.insertAfter(ITextEditorActionConstants.CONTEXT_PREFERENCES, colorPreferencesAction);

    IContributionItem quickAssist = menu.find(ITextEditorActionConstants.QUICK_ASSIST);
    if (quickAssist != null)
    {
      MenuManager refactorSubmenu = new MenuManager("Refac&tor", REFACTOR_QUICKMENU_MENU_ID);
      refactorSubmenu.setActionDefinitionId(REFACTOR_QUICKMENU_COMMAND_ID);

      addRefactorActions(refactorSubmenu);
      menu.insertAfter(ITextEditorActionConstants.QUICK_ASSIST, refactorSubmenu);

      MenuManager sourceSubmenu = new MenuManager("&Source", SOURCE_QUICKMENU_MENU_ID);
      sourceSubmenu.setActionDefinitionId(SOURCE_QUICKMENU_COMMAND_ID);

      addSourceActions(sourceSubmenu);
      menu.insertAfter(ITextEditorActionConstants.QUICK_ASSIST, sourceSubmenu);
    }
  }

  void addRefactorActions(IMenuManager refactorSubmenu)
  {
    IAction action = getAction(RENAME_ACTION_ID);
    refactorSubmenu.add(action);

    action = javaEditor.getAction(EXTRACT_LOCAL_VARIABLE_ACTION_ID);
    refactorSubmenu.add(action);
  }

  void addSourceActions(IMenuManager sourceSubmenu)
  {
    IAction action = getAction(FORMAT_ACTION_ID);
    sourceSubmenu.add(action);
  }

  @Override
  public boolean isSaveAsAllowed()
  {
    return false;
  }

  @Override
  public void dispose()
  {
    jetDocumentManager.dispose();

    super.dispose();

    if (javaEditor != null)
    {
      ((IWorkbenchPart)javaEditor).dispose();
    }

    if (contentOutlinePage != null)
    {
      contentOutlinePage.dispose();
    }

    javaPosition.dispose();
    jetPosition.dispose();

    for (Runnable runnable : disposables)
    {
      runnable.run();
    }
    disposables.clear();
  }

  @Override
  public void saveState(IMemento memento)
  {
    super.saveState(memento);
    memento.putString("document-id", getDocumentManager().getDocumentID());
  }

  @Override
  protected void doRestoreState(IMemento memento)
  {
    String documentID = memento.getString("document-id");
    if (documentID != null)
    {
      getDocumentManager().setDocumentID(documentID);
    }
    super.doRestoreState(memento);
  }

  @Override
  public INavigationLocation createEmptyNavigationLocation()
  {
    return new JETTextSelectionNavigationLocation(this, false);
  }

  @Override
  public INavigationLocation createNavigationLocation()
  {
    return new JETTextSelectionNavigationLocation(this, true);
  }

  @Override
  protected void editorSaved()
  {
    INavigationLocation[] locations = getSite().getPage().getNavigationHistory().getLocations();
    IEditorInput input = getEditorInput();
    for (int i = 0; i < locations.length; i++)
    {
      if (locations[i] instanceof JETTextSelectionNavigationLocation)
      {
        if (input.equals(locations[i].getInput()))
        {
          JETTextSelectionNavigationLocation location = (JETTextSelectionNavigationLocation)locations[i];
          location.partSaved(this);
        }
      }
    }
  }

  @Override
  protected void performSave(boolean overwrite, IProgressMonitor progressMonitor)
  {
    isSaving = true;
    try
    {
      jetDocumentManager.performSave(overwrite, progressMonitor);
    }
    finally
    {
      isSaving = false;
    }
  }

  @Override
  protected void safelySanityCheckState(IEditorInput input)
  {
    if (!isSaving)
    {
      super.safelySanityCheckState(input);
    }
  }

  @Override
  protected void performRevert()
  {
    jetDocumentManager.performRevert();
  }

  void selectAndReveal(IDocument document, int selectionStart, int selectionLength)
  {
    jetDocumentManager.selectAndReveal(document, selectionStart, selectionLength);
  }

  void selectAndReveal(final JETTextSelectionNavigationLocation navigationLocation, final int selectionStart, final int selectionLength)
  {
    IDocument document = jetDocumentManager.setDocumentID(navigationLocation.getDocumentID());
    if (document != null)
    {
      navigationLocation.restoreDocument(this, document);
      selectAndReveal(selectionStart, selectionLength);
      selectionSynchronizer.sync(getSourceViewer());
    }
    else
    {
      pendingUntilDocumentsAvailable = new Runnable()
        {
          public void run()
          {
            selectAndReveal(navigationLocation, selectionStart, selectionLength);
          }
        };
    }
  }

  void selectAndReveal(final IEditorInput editorInput, final int selectionStart, final int selectionLength)
  {
    String documentID = jetDocumentManager.getDocumentID(editorInput);
    if (documentID != null)
    {
      jetDocumentManager.setDocumentID(documentID);
      selectAndReveal(selectionStart, selectionLength);
      selectionSynchronizer.sync(getSourceViewer());
    }
    else
    {
      pendingUntilDocumentsAvailable = new Runnable()
        {
          public void run()
          {
            selectAndReveal(editorInput, selectionStart, selectionLength);
          }
        };
    }
  }

  @Override
  protected void selectAndReveal(int selectionStart, int selectionLength, int revealStart, int revealLength)
  {
    super.selectAndReveal(selectionStart, selectionLength, revealStart, revealLength);
    invalidateVisibleTextPresentation(false);
  }

  @Override
  public Saveable[] getSaveables()
  {
    return jetDocumentManager.getSaveables();
  }

  @Override
  public boolean isDirty()
  {
    return jetDocumentManager != null && jetDocumentManager.isDirty();
  }

  @Override
  public boolean isEditable()
  {
    return jetDocumentManager.isEditable();
  }

  @Override
  @SuppressWarnings({ "rawtypes", "unchecked" })
  public Object getAdapter(Class adapter)
  {
    if (adapter.equals(IContentOutlinePage.class))
    {
      return getContentOutlinePage();
    }
    else if (adapter.equals(IShowInSource.class))
    {
      return new IShowInSource()
        {
          public ShowInContext getShowInContext()
          {
            return new ShowInContext(jetDocumentManager.getEditorInput(), getSelectionProvider().getSelection());
          }
        };
    }
    else
    {
      return super.getAdapter(adapter);
    }
  }

  JETContentOutlinePage getContentOutlinePage()
  {
    if (contentOutlinePage == null)
    {
      contentOutlinePage = new JETContentOutlinePage(this);
    }
    return contentOutlinePage;
  }

  JETBracketMatcher getJetBracketMatcher()
  {
    return jetBracketMatcher;
  }

  @Override
  protected void configureSourceViewerDecorationSupport(SourceViewerDecorationSupport support)
  {
    support.setCharacterPairMatcher(jetBracketMatcher);
    support.setMatchingCharacterPainterPreferenceKeys("matchingBrackets", "matchingBracketColor");
    super.configureSourceViewerDecorationSupport(support);
  }

  @Override
  protected void initializeKeyBindingScopes()
  {
    setKeyBindingScopes(new String []{ "org.eclipse.emf.codegen.ui.jetEditorScope" }); //$NON-NLS-1$
  }

  IHyperlink[] detectHyperlinks(ITextViewer textViewer, IRegion region, boolean canShowMultipleHyperlinks)
  {
    List<IHyperlink> hyperlinks = new ArrayList<IHyperlink>();
    JETCompilationUnit compilerResult = getCompilationUnit();
    if (compilerResult != null)
    {
      URI templateURI = toPlatformResourceURI(getEditorInput());
      if (templateURI != null)
      {
        JETItem leafJETItem = compilerResult.getJETItem(getFileID(), region.getOffset(), true);
        if (leafJETItem != null)
        {
          JETItem rootJETItem = leafJETItem.getRoot();
          if (rootJETItem instanceof JETDirectiveItem)
          {
            JETDirectiveItem jetDirectiveItem = (JETDirectiveItem)rootJETItem;
            String directiveName = jetDirectiveItem.getNameItem().getText();
            if ("include".equals(directiveName))
            {
              Object data = jetDirectiveItem.getData(JETDirectiveItem.RESOLVED_INCLUDE);
              if (data != null && Boolean.TRUE.equals(jetDirectiveItem.getData(JETDirectiveItem.RESOLVED_INCLUDE_SUCCESS)))
              {
                JETAttributeItem fileAttribute = jetDirectiveItem.getAttribute("file");
                JETItem valueItem = fileAttribute.getValueToken().getValueItem();
                hyperlinks.add(new JETHyperLink(this, toRegion(valueItem), (Integer)jetDirectiveItem.getData(JETDirectiveItem.RESOLVED_INCLUDE_FILE_ID), (String)data));
              }
            }
            else if ("jet".equals(directiveName))
            {
              JETAttributeItem skeletonAttribute = jetDirectiveItem.getAttribute("skeleton");
              if (skeletonAttribute != null && skeletonAttribute.isAncestor(leafJETItem))
              {
                Object data = jetDirectiveItem.getData(JETDirectiveItem.RESOLVED_SKELETON);
                if (data != null)
                {
                  JETItem valueItem = skeletonAttribute.getValueToken().getValueItem();
                  hyperlinks.add(new JETHyperLink(this, toRegion(valueItem), -1, (String)data));
                }
              }
              JETAttributeItem importsAttribute = jetDirectiveItem.getAttribute("imports");
              if (importsAttribute != null && importsAttribute.isAncestor(leafJETItem) && leafJETItem instanceof JETValueElementItem)
              {
                detectedJavaHyperlinks(hyperlinks, compilerResult, region, canShowMultipleHyperlinks);
              }
            }
            else if ("start".equals(directiveName) || "end".equals(directiveName))
            {
              JETDirectiveItem otherEndItem = (JETDirectiveItem)jetDirectiveItem.getData(JETDirectiveItem.SECTION_OTHER_END);
              if (otherEndItem != null)
              {
                hyperlinks.add(new JETNavigationHyperLink(this, toRegion(jetDirectiveItem.getNameItem()), toRegion(((JETDirectiveItem)otherEndItem).getNameItem())));
              }
            }
          }
          else if (rootJETItem instanceof JETScriptletItem || rootJETItem instanceof JETExpressionItem)
          {
            detectedJavaHyperlinks(hyperlinks, compilerResult, region, canShowMultipleHyperlinks);
          }
        }
      }
    }

    return hyperlinks.isEmpty() ? null : hyperlinks.toArray(new IHyperlink [hyperlinks.size()]);
  }

  void detectedJavaHyperlinks(List<IHyperlink> hyperlinks, JETCompilationUnit compilerResult, IRegion region, boolean canShowMultipleHyperlinks)
  {
    int fileID = getFileID();
    int[] correspondingJavaPoint = compilerResult.getCorrespondingJavaPoint(fileID, region.getOffset(), 0);
    if (correspondingJavaPoint != null)
    {
      javaEditor.selectAndReveal(correspondingJavaPoint[0], 0);
      ISourceViewer javaSourceViewer = javaEditor.getJavaSourceViewer();
      StyledText textWidget = javaSourceViewer.getTextWidget();
      Rectangle bounds = textWidget.getCaret().getBounds();
      textWidget.redraw(bounds.x, bounds.y, bounds.width, bounds.height, false);
      IHyperlinkDetector[] hyperlinkDetectors = javaEditor.getHyperlinkDetectors();
      if (hyperlinkDetectors != null)
      {
        for (IHyperlinkDetector javaHyperlinkDetector : hyperlinkDetectors)
        {
          IHyperlink[] javaHyperlinks = javaHyperlinkDetector.detectHyperlinks(javaSourceViewer, new Region(correspondingJavaPoint[0], 0), canShowMultipleHyperlinks);
          if (javaHyperlinks != null)
          {
            for (IHyperlink javaHyperlink : javaHyperlinks)
            {
              if (!(javaHyperlink instanceof JETHyperLink))
              {
                IRegion hyperlinkRegion = javaHyperlink.getHyperlinkRegion();
                int[] correspondingTemplatePoint = compilerResult.getCorrespondingTemplatePoint(fileID, hyperlinkRegion.getOffset(), hyperlinkRegion.getLength());
                if (correspondingTemplatePoint != null)
                {
                  hyperlinks.add(new JavaHyperLink(this, new Region(correspondingTemplatePoint[0], correspondingTemplatePoint[1]), javaHyperlink));
                }
              }
            }
          }
        }
      }
    }
  }

  void setTokenData(int[] styleRangeOffsets, JETToken[] jetTokens)
  {
    this.styleRangeOffsets = styleRangeOffsets;
    this.jetTokens = jetTokens;
  }

  JETToken getTokenAtSelection(int relativeOffsetFromSelection)
  {
    Point selectedRange = getSourceViewer().getSelectedRange();
    return getTokenAt(selectedRange.x + relativeOffsetFromSelection);
  }

  JETToken getTokenAt(int offset)
  {
    ISourceViewer sourceViewer = getSourceViewer();
    StyledText textWidget = sourceViewer.getTextWidget();
    int charCount = textWidget.getCharCount();
    if (charCount != 0)
    {
      JETTokenRegion tokenRegion = getTokenRegionAt(offset);
      if (tokenRegion != null)
      {
        IToken token = tokenRegion.getToken();
        if (token instanceof JETToken)
        {
          return (JETToken)token;
        }
      }
    }

    return JETContentRule.TOKEN;
  }

  Point getTokenRangeAt(int offset)
  {
    JETTokenRegion tokenRegion = getTokenRegionAt(offset);
    if (tokenRegion != null)
    {
      return new Point(tokenRegion.getOffset(), tokenRegion.getLength());
    }
    return new Point(offset, 0);
  }

  JETTokenRegion getTokenRegionAt(int offset)
  {
    ISourceViewer sourceViewer = getSourceViewer();
    StyledText textWidget = sourceViewer.getTextWidget();
    int charCount = textWidget.getCharCount();
    if (charCount > 0)
    {
      int index = Arrays.binarySearch(styleRangeOffsets, offset);
      if (index < 0)
      {
        index = -index - 2;
      }

      if (index + 1 < styleRangeOffsets.length)
      {
        JETTokenRegion jetTokenRegion = new JETTokenRegion(styleRangeOffsets[index], styleRangeOffsets[index + 1] - styleRangeOffsets[index], jetTokens[index]);
        return jetTokenRegion;
      }
    }

    return null;
  }

  void openDialog(String title, IStatus status)
  {
    if (status != null && !status.isOK())
    {
      String message = status.getMessage();
      if (message.length() == 0)
      {
        Throwable exception = status.getException();
        if (exception != null)
        {
          message = exception.getClass().getName();
        }
      }

      switch (status.getSeverity())
      {
        case IStatus.INFO:
        {
          MessageDialog.openInformation(getSite().getShell(), title, message);
          break;
        }
        case IStatus.WARNING:
        {
          MessageDialog.openWarning(getSite().getShell(), title, message);
          break;
        }
        case IStatus.ERROR:
        default:
        {
          MessageDialog.openError(getSite().getShell(), title, message);
          break;
        }
      }
    }
  }

  static class TrackedPosition extends Position
  {
    private static final String CATEGORY = "__jet_" + TrackedPosition.class.hashCode();

    private static final IPositionUpdater POSITION_UPDATER = new PositionUpdater();

    enum Type
    {
      JET, JAVA
    }

    private IDocument document;

    private final Type type;

    private TrackedPosition opposite;

    public TrackedPosition(Type type)
    {
      this.type = type;
    }

    public void setOpposite(TrackedPosition opposite)
    {
      this.opposite = opposite;
      opposite.opposite = this;
    }

    public TrackedPosition getOpposite()
    {
      return opposite;
    }

    public void addToDocument(IDocument document)
    {
      if (this.document != null)
      {
        uninstallFromDocument(document, this, CATEGORY, POSITION_UPDATER);
      }

      this.document = document;

      setOffset(0);
      setLength(0);
      undelete();

      installOnDocument(document, this, CATEGORY, POSITION_UPDATER);
    }

    public void update(JETJavaItem jetJavaItem)
    {
      if (type == Type.JAVA)
      {
        int javaOffset = jetJavaItem.getJavaOffset();
        int javaLength = jetJavaItem.getJavaLength();
        update(javaOffset, javaLength);
      }
      else
      {
        int jetOffset = jetJavaItem.getStartOffset();
        int jetLength = jetJavaItem.getLength();
        update(jetOffset, jetLength);
      }
    }

    public void update(int offset, int length)
    {
      undelete();
      setOffset(offset);
      setLength(length);
    }

    public String getText()
    {
      if (document != null && !isDeleted())
      {
        try
        {
          return document.get(offset, length);
        }
        catch (BadLocationException exception)
        {
        }
      }
      return null;
    }

    public void dispose()
    {
      if (document != null)
      {
        uninstallFromDocument(document, this, CATEGORY, POSITION_UPDATER);
        document = null;
      }

      delete();
    }

    @Override
    public String toString()
    {
      String toString = super.toString();
      return isDeleted() ? toString : toString + " " + JETItem.toString(getText());
    }

    public static class PositionUpdater implements IPositionUpdater
    {
      protected Position position;

      protected int originalPositionLength;

      protected int originalPositionOffset;

      protected int offset;

      protected int length;

      protected String text;

      protected int replaceLength;

      protected IDocument document;

      public PositionUpdater()
      {
      }

      protected String getCategory()
      {
        return CATEGORY;
      }

      protected void adaptToInsert()
      {
        int myStart = position.offset;
        int myEnd2 = position.offset + position.length;
        int myEnd = position.offset + position.length - 1;
        myEnd = Math.max(myStart, myEnd);

        int yourStart = offset;
        int yourEnd = offset + length;

        if (myStart == yourEnd)
        {
          position.offset = offset;
          position.length = myEnd2 - offset + replaceLength;
        }
        else if (myEnd2 == yourStart)
        {
          position.length += replaceLength;
        }
        else if (myEnd >= yourStart)
        {
          if (myStart < yourStart)
          {
            position.length += replaceLength;
          }
          else
          {
            position.offset += replaceLength;
          }
        }
      }

      protected void adaptToRemove()
      {
        int myStart = position.offset;
        int myEnd = position.offset + position.length - 1;
        myEnd = Math.max(myStart, myEnd);

        int yoursStart = offset;
        int yoursEnd = offset + length - 1;
        yoursEnd = Math.max(yoursStart, yoursEnd);

        if (myEnd >= yoursStart)
        {
          if (myStart <= yoursStart)
          {
            if (yoursEnd <= myEnd)
            {
              position.length -= length;
            }
            else
            {
              position.length -= (myEnd - yoursStart + 1);
            }
          }
          else if (yoursStart < myStart)
          {
            if (yoursEnd < myStart)
            {
              position.offset -= length;
            }
            else
            {
              position.offset -= (myStart - yoursStart);
              position.length -= (yoursEnd - myStart + 1);
            }
          }

          // validate position to allowed values
          if (position.offset < 0)
          {
            position.offset = 0;
          }

          if (position.length < 0)
          {
            position.length = 0;
          }
        }
      }

      protected void adaptToReplace()
      {
        if (length > 0 && position.offset <= offset && offset + length <= position.offset + position.length)
        {
          position.length += replaceLength - length;
        }
        else
        {
          if (length > 0)
          {
            adaptToRemove();
          }

          if (replaceLength > 0)
          {
            adaptToInsert();
          }
        }
      }

      protected boolean notDeleted()
      {
        if (offset < position.offset && position.offset + position.length < offset + length)
        {
          position.delete();
          return false;
        }

        return true;
      }

      public void update(DocumentEvent event)
      {
        try
        {
          document = event.getDocument();
          Position[] category = document.getPositions(CATEGORY);
          if (category.length > 0)
          {
            offset = event.getOffset();
            length = event.getLength();
            text = event.getText();
            if (text == null)
            {
              replaceLength = 0;
              text = "";
            }
            else
            {
              replaceLength = text.length();
            }

            for (Position element : category)
            {
              position = element;
              originalPositionOffset = position.offset;
              originalPositionLength = position.length;

              if (notDeleted())
              {
                adaptToReplace();
              }
            }
          }
        }
        catch (BadPositionCategoryException x)
        {
        }
        finally
        {
          document = null;
          text = null;
        }
      }
    }
  }

  static class JETEditorSaveable extends Saveable
  {
    private JETEditor jetEditor;

    private IEditorInput editorInput;

    private IDocument fDocument;

    public JETEditorSaveable(JETEditor jetEditor, IEditorInput editorInput)
    {
      this.jetEditor = jetEditor;
      this.editorInput = editorInput;
    }

    public IEditorInput getEditorInput()
    {
      return editorInput;
    }

    public void disconnectEditor()
    {
      getAdapter(IDocument.class);
      jetEditor = null;
    }

    @Override
    public String getName()
    {
      return editorInput.getName();
    }

    @Override
    public String getToolTipText()
    {
      return editorInput.getToolTipText();
    }

    @Override
    public ImageDescriptor getImageDescriptor()
    {
      return editorInput.getImageDescriptor();
    }

    @Override
    public void doSave(IProgressMonitor monitor) throws CoreException
    {
      jetEditor.doSave(monitor);
    }

    @Override
    public boolean isDirty()
    {
      return jetEditor.getDocumentProvider().canSaveDocument(editorInput);
    }

    @Override
    public int hashCode()
    {
      Object document = getAdapter(IDocument.class);
      return document == null ? 0 : document.hashCode();
    }

    @Override
    public boolean equals(Object that)
    {
      if (this == that)
      {
        return true;
      }

      if (!(that instanceof Saveable))
      {
        return false;
      }

      Object thisDocument = getAdapter(IDocument.class);
      Object thatDocument = ((Saveable)that).getAdapter(IDocument.class);
      return thisDocument == null ? thatDocument == null : thisDocument.equals(thatDocument);
    }

    @Override
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public Object getAdapter(Class adapter)
    {
      if (adapter == IDocument.class)
      {
        if (fDocument == null)
        {
          IDocumentProvider documentProvider = jetEditor.getDocumentProvider();
          if (documentProvider != null)
          {
            fDocument = documentProvider.getDocument(editorInput);
          }
        }
        return fDocument;
      }

      return super.getAdapter(adapter);
    }
  }

  static class JETDocumentManager
  {
    private final JETEditor jetEditor;

    private final Map<IEditorInput, IDocument> managedDocuments = new LinkedHashMap<IEditorInput, IDocument>();

    private final Map<Integer, IDocument> fileIDs = new LinkedHashMap<Integer, IDocument>();

    private final Map<Integer, String> documentIDs = new LinkedHashMap<Integer, String>();

    private final Set<JETEditorSaveable> saveables = new LinkedHashSet<JETEditorSaveable>();

    private final JavaSynchronizer javaSynchronizer;

    private JETDynamicCompilerJob jetDynamicCompilerJob;

    private int currentFileID;

    private IEditorInput currentEditorInput;

    public JETDocumentManager(JETEditor jetEditor)
    {
      this.jetEditor = jetEditor;
      javaSynchronizer = new JavaSynchronizer(jetEditor);
    }

    public void setEnableJavaSynchronization(boolean enable)
    {
      javaSynchronizer.setEnableJavaSynchronization(enable);
      if (jetDynamicCompilerJob != null)
      {
        jetDynamicCompilerJob.setDisabled(!enable);
      }
    }

    public void initializeDocumentManager()
    {
      jetEditor.getDocumentProvider().addElementStateListener(new IElementStateListener()
        {
          public void elementMoved(Object originalElement, Object movedElement)
          {
          }

          public void elementDirtyStateChanged(Object element, boolean isDirty)
          {
            JETDocumentManager.this.jetEditor.firePropertyChange(PROP_DIRTY);
          }

          public void elementDeleted(Object element)
          {
          }

          public void elementContentReplaced(Object element)
          {
            JETDocumentManager.this.jetEditor.firePropertyChange(PROP_DIRTY);
          }

          public void elementContentAboutToBeReplaced(Object element)
          {
          }
        });

      registerOutlineHandler();
      registerBreadcrumbHandler();

      ISourceViewer jetSourceViewer = jetEditor.getSourceViewer();
      IDocument document = jetSourceViewer.getDocument();
      if (document != null)
      {
        registerDocument(currentEditorInput, document, 0, "~root");
      }

      updateDynamicCompilerJob(currentEditorInput);

      registerSaveable(currentEditorInput);
    }

    protected void registerBreadcrumbHandler()
    {
      jetEditor.getBreadcrumbViewer().addOpenListener(new IOpenListener()
        {
          public void open(OpenEvent event)
          {
            handleItemSelection(event.getSelection());
            jetEditor.setFocus();
          }
        });
    }

    protected void registerOutlineHandler()
    {
      jetEditor.getContentOutlinePage().addSelectionChangedListener(new ISelectionChangedListener()
        {
          public void selectionChanged(SelectionChangedEvent event)
          {
            if (jetEditor.getContentOutlinePage().getControl().isFocusControl())
            {
              handleItemSelection(event.getSelection());
            }
          }
        });
    }

    public void performSave(boolean overwrite, IProgressMonitor progressMonitor)
    {
      List<IEditorInput> changedEditorInputs = new ArrayList<IEditorInput>();
      IDocumentProvider provider = jetEditor.getDocumentProvider();
      try
      {
        for (Map.Entry<IEditorInput, IDocument> entry : managedDocuments.entrySet())
        {
          IEditorInput editorInput = entry.getKey();
          IDocument document = entry.getValue();
          if (provider.canSaveDocument(editorInput))
          {
            provider.aboutToChange(editorInput);
            provider.saveDocument(progressMonitor, editorInput, document, overwrite);
            changedEditorInputs.add(editorInput);
          }
        }

        jetEditor.editorSaved();
      }
      catch (CoreException exception)
      {
        IStatus status = exception.getStatus();
        if (status == null || status.getSeverity() != IStatus.CANCEL)
        {
          jetEditor.handleExceptionOnSave(exception, progressMonitor);
        }
      }
      finally
      {
        for (IEditorInput editorInput : changedEditorInputs)
        {
          provider.changed(editorInput);
        }

        refreshBreadcrumbs();
      }

      if (ResourcesPlugin.getWorkspace().isAutoBuilding() && jetDynamicCompilerJob != null)
      {
        Job job = new Job("Wait for build")
          {
            @Override
            protected IStatus run(IProgressMonitor monitor)
            {
              boolean wasInterrupted = false;
              do
              {
                try
                {
                  getJobManager().join(ResourcesPlugin.FAMILY_AUTO_BUILD, monitor);
                  jetDynamicCompilerJob.documentChanged(null);
                  break;
                }
                catch (OperationCanceledException exception)
                {
                  break;
                }
                catch (InterruptedException exception)
                {
                  wasInterrupted = true;
                }
              }
              while (wasInterrupted);
              return Status.OK_STATUS;
            }
          };

        job.setSystem(true);
        job.schedule();
      }
    }

    public void performRevert()
    {
      List<IEditorInput> changedEditorInputs = new ArrayList<IEditorInput>();
      IDocumentProvider provider = jetEditor.getDocumentProvider();
      try
      {
        for (IEditorInput editorInput : managedDocuments.keySet())
        {
          if (provider.canSaveDocument(editorInput))
          {
            provider.aboutToChange(editorInput);
            provider.resetDocument(editorInput);
            changedEditorInputs.add(editorInput);
          }
        }
        jetEditor.editorSaved();
      }
      catch (CoreException exception)
      {
        IStatus status = exception.getStatus();
        if (status == null || status.getSeverity() != IStatus.CANCEL)
        {
          Shell shell = jetEditor.getSite().getShell();
          String title = "Problems while reverting to saved state";
          String msg = "Could not revert to saved state.";
          ErrorDialog.openError(shell, title, msg, exception.getStatus());
        }
      }
      finally
      {
        for (IEditorInput editorInput : changedEditorInputs)
        {
          provider.changed(editorInput);
        }

        refreshBreadcrumbs();
      }
    }

    public Saveable[] getSaveables()
    {
      return saveables.toArray(new Saveable [saveables.size()]);
    }

    public int getFileID()
    {
      return currentFileID;
    }

    public String getDocumentID()
    {
      return getDocumentID(currentFileID);
    }

    private String getDocumentID(int fileID)
    {
      return documentIDs.get(fileID);
    }

    public IDocument getDocument(int fileID)
    {
      return fileIDs.get(fileID);
    }

    String getDocumentID(IEditorInput editorInput)
    {
      for (Map.Entry<Integer, String> entry : this.documentIDs.entrySet())
      {
        String documentID = entry.getValue();
        if (editorInput.equals(getEditorInput(documentID)))
        {
          return documentID;
        }
      }
      return null;
    }

    public int getFileID(IDocument document)
    {
      for (Map.Entry<Integer, IDocument> entry : fileIDs.entrySet())
      {
        if (entry.getValue() == document)
        {
          return entry.getKey();
        }
      }
      return -1;
    }

    public IDocument setDocumentID(String documentID)
    {
      for (Map.Entry<Integer, String> entry : documentIDs.entrySet())
      {
        if (entry.getValue().equals(documentID))
        {
          return setFileID(entry.getKey());
        }
      }

      if (documentID != null)
      {
        IEditorInput editorInput = getEditorInput(documentID);
        int fileID = -1;
        while (fileIDs.containsKey(fileID))
        {
          --fileID;
        }
        getRegisteredDocument(editorInput, fileID, documentID);
        return setFileID(fileID);
      }

      return null;
    }

    public void selectAndReveal(IDocument document, int offset, int length)
    {
      ISourceViewer jetSourceViewer = jetEditor.getSourceViewer();
      if (jetSourceViewer.getDocument() != document)
      {
        for (Map.Entry<Integer, IDocument> entry : fileIDs.entrySet())
        {
          if (entry.getValue() == document)
          {
            basicSetFileID(entry.getKey());
            break;
          }
        }
      }

      if (!jetSourceViewer.overlapsWithVisibleRegion(offset, length))
      {
        jetSourceViewer.resetVisibleRegion();
      }

      jetSourceViewer.setSelectedRange(offset, length);
      jetSourceViewer.revealRange(offset, length);
    }

    public IDocument setFileID(int fileID)
    {
      IDocument document = basicSetFileID(fileID);

      JETContentOutlinePage.Item selectionForFileID = jetEditor.getContentOutlinePage().getSelectionForDocumentID(getDocumentID(fileID));
      if (selectionForFileID != null)
      {
        jetEditor.getBreadcrumbViewer().setInput(selectionForFileID);
        jetEditor.getContentOutlinePage().setSelection(new StructuredSelection(selectionForFileID), true);
      }

      return document;
    }

    public IEditorInput getEditorInput()
    {
      IEditorInput editorInput = getEditorInput(getDocument(currentFileID));
      return editorInput == null ? jetEditor.getEditorInput() : editorInput;
    }

    protected IEditorInput getEditorInput(IDocument document)
    {
      for (Map.Entry<IEditorInput, IDocument> entry : managedDocuments.entrySet())
      {
        if (entry.getValue() == document)
        {
          return entry.getKey();
        }
      }
      return null;
    }

    protected IEditorInput getEditorInput(int fileID)
    {
      if (fileID == 0)
      {
        return jetEditor.getEditorInput();
      }
      else
      {
        String documentID = documentIDs.get(fileID);
        return getEditorInput(documentID);
      }
    }

    private IEditorInput getEditorInput(String documentID)
    {
      String templateURI = documentID.replaceAll("~[0-9]+$", "");
      return toEditorInput(URI.createURI(templateURI));
    }

    protected IDocument basicSetFileID(int fileID)
    {
      IEditorInput editorInput = getEditorInput(fileID);
      IDocument registeredDocument = getRegisteredDocument(editorInput, fileID, documentIDs.get(fileID));

      ISourceViewer jetSourceViewer = jetEditor.getSourceViewer();
      if (registeredDocument != null && registeredDocument != jetSourceViewer.getDocument())
      {
        IAnnotationModel model = jetEditor.getDocumentProvider().getAnnotationModel(editorInput);
        if (model == null)
        {
          model = new AnnotationModel();
        }

        boolean javaEditorInSync = jetEditor.isJavaEditorInSync();
        setDocument(editorInput, registeredDocument, model);
        if (javaEditorInSync)
        {
          jetEditor.setExpectedModificationStamp();
        }

        this.currentFileID = fileID;
        jetEditor.handleFileIDChanged(fileID);

        registerSaveable(editorInput);
        hookDynamicCompilerJob(registeredDocument);
      }
      else if (this.currentFileID != fileID)
      {
        this.currentFileID = fileID;
        jetEditor.handleFileIDChanged(fileID);
      }

      return registeredDocument;
    }

    protected void setDocument(IEditorInput editorInput, IDocument document, IAnnotationModel annotationModel)
    {
      ISourceViewer jetSourceViewer = jetEditor.getSourceViewer();
      jetSourceViewer.setDocument(document, annotationModel);
      IDocumentProvider documentProvider = jetEditor.getDocumentProvider();
      if (documentProvider instanceof IDocumentProviderExtension)
      {
        jetSourceViewer.setEditable(!((IDocumentProviderExtension)documentProvider).isReadOnly(editorInput));
      }
    }

    public boolean isEditable()
    {
      IDocumentProviderExtension documentProvider = (IDocumentProviderExtension)jetEditor.getDocumentProvider();
      IDocument document = fileIDs.get(currentFileID);
      IEditorInput editorInput = getEditorInput(document);
      return editorInput != null && !documentProvider.isReadOnly(editorInput);
    }

    public boolean isDirty(int fileID)
    {
      IDocument document = fileIDs.get(fileID);
      IEditorInput editorInput = getEditorInput(document);
      return editorInput != null && jetEditor.getDocumentProvider().canSaveDocument(editorInput);
    }

    public boolean isDirty()
    {
      IDocumentProvider documentProvider = jetEditor.getDocumentProvider();
      if (documentProvider != null)
      {
        for (IEditorInput editorInput : managedDocuments.keySet())
        {
          if (documentProvider.canSaveDocument(editorInput))
          {
            return true;
          }
        }
      }
      return false;
    }

    protected void registerSaveable(IEditorInput editorInput)
    {
      // Equality checking will prevent duplicates.
      // The first one added will not send a life cycle event because that happens automatically when the part is opened.
      JETEditorSaveable saveable = new JETEditorSaveable(jetEditor, editorInput);
      if (saveables.add(saveable) && saveables.size() > 1)
      {
        ISaveablesLifecycleListener listener = (ISaveablesLifecycleListener)jetEditor.getSite().getService(ISaveablesLifecycleListener.class);
        if (listener != null)
        {
          listener.handleLifecycleEvent(new SaveablesLifecycleEvent(jetEditor, SaveablesLifecycleEvent.POST_OPEN, new Saveable []{ saveable }, false));
        }
      }
    }

    public void handleInputChanged(IEditorInput editorInput)
    {
      ISaveablesLifecycleListener listener = (ISaveablesLifecycleListener)jetEditor.getSite().getService(ISaveablesLifecycleListener.class);
      if (listener != null)
      {
        if (!saveables.isEmpty())
        {
          listener.handleLifecycleEvent(new SaveablesLifecycleEvent(jetEditor, SaveablesLifecycleEvent.POST_CLOSE, getSaveables(), false));
          discardSaveables();
        }

        registerSaveable(editorInput);
      }

      this.currentEditorInput = editorInput;

      updateDynamicCompilerJob(editorInput);
    }

    public void dispose()
    {
      if (jetDynamicCompilerJob != null)
      {
        jetDynamicCompilerJob.cancel();
        for (IDocument document : managedDocuments.values())
        {
          unhookDynamicCompilerJob(document);
        }
      }

      IDocumentProvider documentProvider = jetEditor.getDocumentProvider();
      for (IEditorInput editorInput : managedDocuments.keySet())
      {
        // The source viewer will disconnect itself from the editor's current input so skip that one.
        if (!editorInput.equals(this.currentEditorInput))
        {
          documentProvider.disconnect(editorInput);
        }
      }

      discardSaveables();

      for (IDocument document : managedDocuments.values())
      {
        document.removeDocumentListener(javaSynchronizer);
      }

      managedDocuments.clear();
      fileIDs.clear();
      documentIDs.clear();
    }

    protected void discardSaveables()
    {
      for (JETEditorSaveable saveable : saveables)
      {
        saveable.disconnectEditor();
      }

      saveables.clear();
    }

    protected void updateDynamicCompilerJob(IEditorInput input)
    {
      if (jetDynamicCompilerJob != null)
      {
        jetDynamicCompilerJob.cancel();

        for (IDocument document : managedDocuments.values())
        {
          unhookDynamicCompilerJob(document);
        }

        jetDynamicCompilerJob = null;
      }

      IFile file = toFile(input);
      if (file != null)
      {
        jetDynamicCompilerJob = new JETDynamicCompilerJob(jetEditor, file);
        jetDynamicCompilerJob.schedule();

        for (IDocument document : managedDocuments.values())
        {
          hookDynamicCompilerJob(document);
        }
      }
    }

    protected void hookDynamicCompilerJob(IDocument document)
    {
      document.addDocumentListener(jetDynamicCompilerJob);
    }

    protected void unhookDynamicCompilerJob(IDocument document)
    {
      document.removeDocumentListener(jetDynamicCompilerJob);
    }

    protected void handleItemSelection(ISelection selection)
    {
      if (!selection.isEmpty())
      {
        JETContentOutlinePage contentOutlinePage = jetEditor.getContentOutlinePage();
        int fileID = contentOutlinePage.getFileID(selection);
        basicSetFileID(fileID);

        Region region = contentOutlinePage.getRegion(selection);
        if (region != null)
        {
          jetEditor.selectAndReveal(region.getOffset(), region.getLength());
        }

        JETContentOutlinePage.Item selectedItem = contentOutlinePage.getSelectionForDocumentID(getDocumentID(fileID));
        if (selectedItem != null)
        {
          jetEditor.getBreadcrumbViewer().setInput(selectedItem);
          contentOutlinePage.setSelection(new StructuredSelection(selectedItem), true);
        }
      }
    }

    protected IDocument getRegisteredDocument(IEditorInput editorInput, int fileID, String documentID)
    {
      IDocument document = fileIDs.get(fileID);
      if (document == null && editorInput != null)
      {
        IDocumentProvider documentProvider = jetEditor.getDocumentProvider();
        if (!managedDocuments.containsKey(editorInput))
        {
          try
          {
            documentProvider.connect(editorInput);
          }
          catch (CoreException exception)
          {
            CodeGenUIPlugin.getPlugin().log(exception);
          }
        }

        document = documentProvider.getDocument(editorInput);
        if (document != null)
        {
          registerDocument(editorInput, document, fileID, documentID);
        }
      }

      return document;
    }

    protected void registerDocument(IEditorInput editorInput, IDocument document, int fileID, String documentID)
    {
      managedDocuments.put(editorInput, document);
      fileIDs.put(fileID, document);
      documentIDs.put(fileID, documentID);
      document.addDocumentListener(javaSynchronizer);
      jetEditor.getJETPosition().addToDocument(document);
    }

    public void setCompilerResult(JETCompilerResult jetCompilerResult)
    {
      String currentDocumentID = getDocumentID();

      Set<IDocument> orphanDocuments = new LinkedHashSet<IDocument>(fileIDs.values());

      JETCompilationUnit compilationUnit = jetCompilerResult.getCompilationUnit();
      if (compilationUnit != null)
      {
        fileIDs.clear();
        documentIDs.clear();
        int fileID = 0;
        for (String templateURI : compilationUnit.getTemplateURIs())
        {
          String documentID;
          if (fileID == 0)
          {
            documentID = "~root";
            documentIDs.put(0, documentID);
          }
          else
          {
            documentID = templateURI;
            String collision = documentIDs.put(fileID, templateURI);
            for (int count = 1; collision != null; ++count)
            {
              documentIDs.put(fileID, collision);
              documentID = templateURI + "~" + count;
              collision = documentIDs.put(fileID, documentID);
            }
          }

          IDocument document = managedDocuments.get(toEditorInput(URI.createURI(templateURI)));
          if (document != null)
          {
            fileIDs.put(fileID, document);
            if (documentID.equals(currentDocumentID))
            {
              this.currentFileID = fileID;
            }
          }

          ++fileID;
        }

        // Assign orphaned documents negative file IDs.
        orphanDocuments.removeAll(fileIDs.values());
        if (!orphanDocuments.isEmpty())
        {
          fileID = -1;
          for (IDocument orphanDocument : orphanDocuments)
          {
            fileIDs.put(fileID, orphanDocument);

            for (Map.Entry<IEditorInput, IDocument> entry : managedDocuments.entrySet())
            {
              if (entry.getValue() == orphanDocument)
              {
                URI templateURI = toURI(entry.getKey());
                documentIDs.put(fileID, templateURI.toString());
                break;
              }
            }

            --fileID;
          }
        }
      }

      JETContentOutlinePage contentOutlinePage = jetEditor.getContentOutlinePage();
      contentOutlinePage.setCompilerResult(jetCompilerResult, documentIDs);

      refreshBreadcrumbs();
    }

    private void refreshBreadcrumbs()
    {
      JETBreadcrumbViewer breadcrumbViewer = jetEditor.getBreadcrumbViewer();
      JETContentOutlinePage.Item currentInput = (JETContentOutlinePage.Item)breadcrumbViewer.getInput();
      JETContentOutlinePage.Item newCurrentInput = jetEditor.getContentOutlinePage().getSelectionForDocumentID(getDocumentID(currentFileID));
      if (currentInput != newCurrentInput && newCurrentInput != null)
      {
        breadcrumbViewer.setInput(newCurrentInput);
        jetEditor.getContentOutlinePage().setSelection(new StructuredSelection(newCurrentInput), true);
      }
      else
      {
        breadcrumbViewer.refresh(true);
        for (JETContentOutlinePage.Item item = currentInput; item != null; item = item.getParent())
        {
          breadcrumbViewer.update(item, null);
        }
      }
    }

    static class JavaSynchronizer implements IDocumentListener
    {
      private final JETEditor jetEditor;

      private boolean disabled;

      public JavaSynchronizer(JETEditor jetEditor)
      {
        this.jetEditor = jetEditor;
      }

      public void setEnableJavaSynchronization(boolean enable)
      {
        this.disabled = !enable;
      }

      public void documentChanged(DocumentEvent event)
      {
        if (!disabled)
        {
          int eventOffset = event.getOffset();
          JETTokenRegion tokenRegion = jetEditor.getTokenRegionAt(eventOffset);
          if (tokenRegion != null)
          {
            JETToken token = tokenRegion.getToken();
            if (token == JETScriptletRule.TOKEN || token == JETExpressionRule.TOKEN)
            {
              // Compute the location relative to the start of the Java in the region
              // because as we add characters, we might add beyond the currently corresponding region.
              tokenRegion.shrinkToJava(event.getDocument());
              int tokenJavaStart = tokenRegion.getOffset();
              Position correspondingJavaPosition = jetEditor.getCorrespondingJavaPosition(tokenJavaStart, 0);
              if (correspondingJavaPosition != null)
              {
                try
                {
                  ISourceViewer javaSourceViewer = jetEditor.getJavaSourceViewer();
                  int javaOffset = correspondingJavaPosition.getOffset() + eventOffset - tokenJavaStart;
                  int eventLength = event.getLength();
                  javaSourceViewer.getDocument().replace(javaOffset, eventLength, event.getText());
                  javaSourceViewer.setSelectedRange(javaOffset + 1 - eventLength, 0);
                }
                catch (BadLocationException exception)
                {
                }
              }
            }
          }
        }
      }

      public void documentAboutToBeChanged(DocumentEvent event)
      {
      }
    }
  }

  static class JETContentOutlinePage extends ContentOutlinePage
  {
    private final ItemManager itemManager = new ItemManager();

    private final LabelProvider labelProvider = new LabelProvider();

    private final ContentProvider contentProvider = new ContentProvider();

    private final ISelectionChangedListener jetSourceViewerSynchronizer = new ISelectionChangedListener()
      {
        public void selectionChanged(SelectionChangedEvent event)
        {
          if (treeViewer != null && !treeViewer.getControl().isFocusControl())
          {
            ITextSelection selection = (ITextSelection)event.getSelection();
            int offset = selection.getOffset();
            JETItem jetItem = jetEditor.getJETItem(offset, false);
            if (jetItem != null)
            {
              Item correspondingItem = itemManager.getRootItem().getCorrespondingItem(jetItem);
              if (correspondingItem != null)
              {
                setSelection(new StructuredSelection(correspondingItem), true);
              }
            }
          }
        }
      };

    private final JETEditor jetEditor;

    private TreeViewer treeViewer;

    public JETContentOutlinePage(JETEditor jetEditor)
    {
      this.jetEditor = jetEditor;
    }

    public StyledCellLabelProvider newLabelProvider()
    {
      return new LabelProvider()
        {
          @Override
          protected StyledString getStyledText(Object element)
          {
            StyledString styledText = super.getStyledText(element);
            if (element instanceof Item)
            {
              Item item = (Item)element;
              if (item.isBreadcumbSurrogate())
              {
                int fileID = itemManager.getFileID(item);
                if (jetEditor.getDocumentManager().isDirty(fileID))
                {
                  StyledString newStyledString = new StyledString();
                  newStyledString.append('*');
                  newStyledString.append(styledText);
                  styledText = newStyledString;
                }

                if (fileID == jetEditor.getFileID())
                {
                  styledText.setStyle(0, styledText.length(), Item.getFontStyler(SWT.BOLD));
                }
              }
            }
            return styledText;
          }
        };
    }

    public ITreeContentProvider getContentProvider()
    {
      return contentProvider;
    }

    public Item getSelectionForDocumentID(String documentID)
    {
      return itemManager.getSelectionForDocumentID(documentID);
    }

    public void setSelection(ISelection selection, boolean reveal)
    {
      if (treeViewer != null && !treeViewer.getControl().isFocusControl())
      {
        treeViewer.setSelection(selection, reveal);
      }
    }

    @Override
    public void createControl(Composite parent)
    {
      super.createControl(parent);
      treeViewer = getTreeViewer();
      treeViewer.setLabelProvider(labelProvider);
      treeViewer.setContentProvider(contentProvider);
      treeViewer.setInput(itemManager.getRootItem());
      jetEditor.getSelectionProvider().addSelectionChangedListener(jetSourceViewerSynchronizer);
      registerToolBarActions();
    }

    @SuppressWarnings("restriction")
    private void registerToolBarActions()
    {
      class ExpandAllAction extends Action
      {
        public ExpandAllAction()
        {
          super("", CodeGenUIPlugin.getPlugin().getImage("full/ctool16/ExpandAll.png"));
          setToolTipText("Expand all");
          setActionDefinitionId(ExpandAllHandler.COMMAND_ID);
        }

        @Override
        public void run()
        {
          try
          {
            treeViewer.getControl().setRedraw(false);
            treeViewer.expandAll();
          }
          finally
          {
            treeViewer.getControl().setRedraw(true);
          }
        }
      }

      IToolBarManager toolBarManager = getSite().getActionBars().getToolBarManager();
      toolBarManager.add(new ExpandAllAction());

      org.eclipse.jdt.internal.ui.actions.CollapseAllAction collapseAllAction = new org.eclipse.jdt.internal.ui.actions.CollapseAllAction(treeViewer);
      collapseAllAction.setActionDefinitionId(CollapseAllHandler.COMMAND_ID);
      toolBarManager.add(collapseAllAction);

    }

    @Override
    public void dispose()
    {
      treeViewer = null;
      ISelectionProvider selectionProvider = jetEditor.getSelectionProvider();
      if (selectionProvider != null)
      {
        selectionProvider.removeSelectionChangedListener(jetSourceViewerSynchronizer);
      }
      super.dispose();
    }

    public void setCompilerResult(JETCompilerResult jetCompilerResult, Map<Integer, String> documentIDs)
    {
      itemManager.setCompilerResult(jetCompilerResult, documentIDs);
      if (treeViewer != null)
      {
        treeViewer.refresh();
      }
    }

    public int getFileID(ISelection selection)
    {
      Item item = getSelectedItem(selection);
      return item == null ? 0 : itemManager.getFileID(item);
    }

    public Item getSelectedItem(ISelection selection)
    {
      IStructuredSelection structuredSelection = (IStructuredSelection)selection;
      return (Item)structuredSelection.getFirstElement();
    }

    protected JETItem getSelectedJETItem(ISelection selection)
    {
      Item selectedItem = getSelectedItem(selection);
      return selectedItem == null ? null : selectedItem.getJETItem();
    }

    public Region getRegion(ISelection selection)
    {
      JETItem selectedJETItem = getSelectedJETItem(selection);
      if (selectedJETItem != null)
      {
        int start = selectedJETItem.getStartOffset();
        int stop = selectedJETItem.getStopOffset();
        return new Region(start, stop - start);
      }
      else
      {
        return null;
      }
    }

    private static class LabelProvider extends StyledCellLabelProvider implements ILabelProvider
    {
      private static final Map<Font, Font[]> FONTS = new HashMap<Font, Font[]>();

      @Override
      public void update(ViewerCell cell)
      {
        Object element = cell.getElement();

        StyledString styledString = getStyledText(element);
        String newText = styledString.toString();

        StyleRange[] oldStyleRanges = cell.getStyleRanges();
        StyleRange[] newStyleRanges = isOwnerDrawEnabled() ? styledString.getStyleRanges() : null;

        if (!Arrays.equals(oldStyleRanges, newStyleRanges))
        {
          cell.setStyleRanges(newStyleRanges);
          if (cell.getText().equals(newText))
          {
            cell.setText("");
          }
        }

        cell.setText(newText);
        cell.setImage(getImage(element));

        super.update(cell);
      }

      private FontData[] getFontData(int style)
      {
        FontData[] fontDatas = getViewer().getControl().getFont().getFontData();
        for (int i = 0; i < fontDatas.length; i++)
        {
          fontDatas[i].setStyle(style);
        }
        return fontDatas;
      }

      private Font getFont(int fontStyle)
      {
        if (fontStyle > 0 && fontStyle <= (SWT.BOLD | SWT.ITALIC))
        {
          Font baseFont = getViewer().getControl().getFont();
          Font[] fonts = FONTS.get(baseFont);
          if (fonts == null)
          {
            fonts = new Font [SWT.BOLD | SWT.ITALIC];
            FONTS.put(baseFont, fonts);
          }

          Font font = fonts[fontStyle - 1];
          if (font == null)
          {
            font = new Font(baseFont.getDevice(), getFontData(fontStyle));
            fonts[fontStyle - 1] = font;
          }

          return font;
        }
        else
        {
          return null;
        }
      }

      @Override
      protected StyleRange prepareStyleRange(StyleRange styleRange, boolean applyColors)
      {
        StyleRange preparedStyledRange = super.prepareStyleRange(styleRange, applyColors);
        preparedStyledRange.font = getFont(styleRange.fontStyle);
        return preparedStyledRange;
      }

      public Image getImage(Object element)
      {
        if (element instanceof Item)
        {
          return ((Item)element).getImage();
        }
        return null;
      }

      protected StyledString getStyledText(Object element)
      {
        if (element instanceof Item)
        {
          return ((Item)element).getStyledText();
        }
        if (element instanceof JETItem)
        {
          return new StyledString(((JETItem)element).getText());
        }

        return new StyledString("Root");
      }

      public String getText(Object element)
      {
        return getStyledText(element).toString();
      }
    }

    private static class ContentProvider implements ITreeContentProvider
    {
      public Object[] getElements(Object inputElement)
      {
        return getChildren(inputElement);
      }

      public Object[] getChildren(Object parentElement)
      {
        return ((Item)parentElement).getChildren().toArray();
      }

      public Object getParent(Object element)
      {
        return ((Item)element).getParent();
      }

      public boolean hasChildren(Object element)
      {
        return getChildren(element).length > 0;
      }

      public void dispose()
      {
      }

      public void inputChanged(Viewer viewer, Object oldInput, Object newInput)
      {
      }
    }

    private static class ItemManager extends Item
    {
      private static final Pattern COMMENT_PATTERN = Pattern.compile("^[^\n\r]*\\[([^\\]\n\r]*)\\]");

      private final HashMap<String, Item> managedItems = new HashMap<String, Item>();

      private final HashMap<String, Integer> documementIDsToFileIDs = new HashMap<String, Integer>();

      public ItemManager()
      {
        super("~top", null, "top");
        Item rootItem = createItem(managedItems, "~root", null, "root");
        rootItem.setParent(this);
        documementIDsToFileIDs.put("~root", 0);
      }

      public Item getRootItem()
      {
        return getChildren().get(0);
      }

      public int getFileID(Item item)
      {
        if (item.jetItem != null)
        {
          return item.jetItem.getFileID();
        }
        else if ("orphan".equals(item.type))
        {
          return documementIDsToFileIDs.get(item.signature.substring(0, item.signature.lastIndexOf("~orphan")));
        }
        else
        {
          return 0;
        }
      }

      public Item getSelectionForDocumentID(String documentID)
      {
        Item result = managedItems.get(documentID + "~content");
        if (result == null)
        {
          result = managedItems.get(documentID + "~orphan");
          if (result == null)
          {
            result = managedItems.get(documentID);
          }
        }
        return result;
      }

      public void setCompilerResult(JETCompilerResult comilerResult, Map<Integer, String> documentIDs)
      {
        JETCompilationUnit compilerResult = comilerResult.getCompilationUnit();
        if (compilerResult != null)
        {
          Item newTopItem = createItems(compilerResult, documentIDs);
          reconcile(newTopItem);
          handleProblems(comilerResult);
        }
      }

      @Override
      protected void reconcile(Item newItem)
      {
        super.reconcile(newItem);
        managedItems.clear();
        rebuildMap(managedItems);
      }

      private Item createTopItem(Map<String, Item> managedItems)
      {
        Item topItem = createItem(managedItems, "~top", null, "top");
        Item rootItem = createItem(managedItems, "~root", null, "root");
        rootItem.setParent(topItem);
        return topItem;
      }

      protected Item createItems(JETCompilationUnit compilerResult, Map<Integer, String> documentIDs)
      {
        documementIDsToFileIDs.clear();

        Map<String, Item> newManagedItems = new HashMap<String, Item>();
        Item newTopItem = createTopItem(newManagedItems);
        Item newRootItem = newTopItem.getChildren().get(0);
        List<Item> items = new ArrayList<Item>();
        List<JETItem> jetItems = compilerResult.getItems();
        if (!jetItems.isEmpty())
        {
          newRootItem.setJetItem(jetItems.get(0));
          List<Item> resolvedIncludeSuccessItems = new ArrayList<Item>();
          List<Item> flatContentItems = new ArrayList<Item>();
          for (JETItem jetItem : jetItems)
          {
            if (jetItem instanceof JETDirectiveItem)
            {
              JETDirectiveItem jetDirectiveItem = (JETDirectiveItem)jetItem;
              JETSkeleton skeleton = (JETSkeleton)jetDirectiveItem.getData(JETDirectiveItem.SKELETON);
              if (skeleton != null)
              {
                if (jetDirectiveItem.getLength() != 0)
                {
                  String qualifiedClassName = skeleton.getQualifiedClassName();
                  Item directiveItem = createItem(newManagedItems, qualifiedClassName, jetItem, "jet");
                  directiveItem.setParent(newRootItem);
                  items.add(directiveItem);
                }
              }
              else
              {
                String resolvedInclude = (String)jetDirectiveItem.getData(JETDirectiveItem.RESOLVED_INCLUDE);
                if (resolvedInclude != null)
                {
                  Item includeItem = createItem(newManagedItems, resolvedInclude, jetItem, "include");
                  items.add(includeItem);
                  includeItem.setParent(newRootItem);
                  if (Boolean.TRUE.equals(jetDirectiveItem.getData(JETDirectiveItem.RESOLVED_INCLUDE_SUCCESS)))
                  {
                    resolvedIncludeSuccessItems.add(includeItem);

                    JETItem contentStartItem = (JETItem)jetDirectiveItem.getData(JETDirectiveItem.RESOLVED_INCLUDE_START);
                    Item item = createItem(newManagedItems, documentIDs.get(contentStartItem.getFileID()) + "~content", contentStartItem, "content");
                    item.setParent(includeItem);
                    flatContentItems.add(item);
                  }
                }
              }
            }
            else if (jetItem instanceof JETCommentItem)
            {
              Matcher matcher = COMMENT_PATTERN.matcher(jetItem.getText());
              if (matcher.find())
              {
                String signature = matcher.group(1);
                Item commentItem = createItem(newManagedItems, signature + "~comment", jetItem, "comment");
                items.add(commentItem);
                commentItem.setParent(newRootItem);
              }
            }
          }

          List<Item> originalItems = new ArrayList<Item>(items);
          List<Item> rootItems = refactorIncludes(items, jetItems, newManagedItems);
          refactorInclusion(resolvedIncludeSuccessItems, originalItems, rootItems);

          for (Item item : flatContentItems)
          {
            int fileID = item.getJETItem().getFileID();
            Item indexItem = createItem(newManagedItems, documentIDs.get(fileID) + "~root", item.getJETItem(), "root");
            indexItem.setParent(newRootItem.getParent());
          }

          for (Map.Entry<Integer, String> entry : documentIDs.entrySet())
          {
            int fileID = entry.getKey();
            String documentID = entry.getValue();
            documementIDsToFileIDs.put(documentID, fileID);
            if (fileID < 0)
            {
              Item indexItem = createItem(newManagedItems, documentID + "~orphan", null, "orphan");
              indexItem.setParent(newRootItem.getParent());
            }
          }
        }

        return newTopItem;
      }

      private void refactorInclusion(List<Item> resolvedIncludeSuccessItems, List<Item> items, List<Item> rootItems)
      {
        for (Item item : items)
        {
          JETItem jetItem = item.getJETItem();
          int fileID = jetItem.getFileID();
          if (fileID != 0)
          {
            if (item.getParent().getJETItem().getFileID() != fileID)
            {
              Item inclusionItem = resolvedIncludeSuccessItems.get(fileID - 1);
              item.setParent(inclusionItem);
              rootItems.remove(item);
            }
          }
        }
      }

      private List<Item> refactorIncludes(List<Item> items, List<JETItem> jetItems, Map<String, Item> managedItems)
      {
        List<Item> result = new ArrayList<Item>();
        while (!items.isEmpty())
        {
          Item item = items.remove(0);
          captureChildren(item, items, jetItems, managedItems);
          result.add(item);
        }
        return result;
      }

      private void captureChildren(Item item, List<Item> remainingItems, List<JETItem> jetItems, Map<String, Item> managedItems)
      {
        JETItem jetItem = item.getJETItem();
        if (jetItem instanceof JETDirectiveItem)
        {
          JETDirectiveItem parentJETItem = (JETDirectiveItem)jetItem;
          JETDirectiveItem start = (JETDirectiveItem)parentJETItem.getData(JETDirectiveItem.SECTION_START);
          JETDirectiveItem end = (JETDirectiveItem)parentJETItem.getData(JETDirectiveItem.SECTION_END);
          if (start != null && end != null)
          {
            String signature = item.getSignature();
            createItem(managedItems, signature + "~start", start, "start").setParent(item);

            int startIndex = jetItems.indexOf(start) + 1;
            int endIndex = jetItems.indexOf(end) - 1;
            for (int i = startIndex; i <= endIndex; ++i)
            {
              JETItem jetChildItem = jetItems.get(i);
              if (jetChildItem instanceof JETDirectiveItem || jetChildItem instanceof JETCommentItem)
              {
                for (Item managedItem : managedItems.values())
                {
                  if (managedItem.getJETItem() == jetChildItem && remainingItems.remove(managedItem))
                  {
                    // Recursively capture the children of nested includes.
                    managedItem.setParent(item);
                    captureChildren(managedItem, remainingItems, jetItems, managedItems);
                    break;
                  }
                }
              }
            }

            createItem(managedItems, signature + "~end", end, "end").setParent(item);
          }
        }
      }

      private Item createItem(Map<String, Item> managedItems, String signature, JETItem jetItem, String type)
      {
        String uniqueSignature = signature;
        for (Item item = managedItems.get(uniqueSignature); item != null; item = managedItems.get(uniqueSignature))
        {
          uniqueSignature += "_";
        }

        Item item = new Item(uniqueSignature, jetItem, type);
        managedItems.put(uniqueSignature, item);
        return item;
      }
    }

    private static class Item
    {
      private static final Map<String, Image> IMAGES = new HashMap<String, Image>();

      private static final Map<String, Image> ERROR_IMAGES = new HashMap<String, Image>();

      private static final Map<String, Image> WARNING_IMAGES = new HashMap<String, Image>();

      private static final Map<String, Image> INFO_IMAGES = new HashMap<String, Image>();

      private static final Map<Integer, Map<String, Image>> ALL_IMAGES = new HashMap<Integer, Map<String, Image>>();

      static
      {
        CodeGenUIPlugin plugin = CodeGenUIPlugin.getPlugin();
        IMAGES.put("start", plugin.getActualImage("full/obj16/DirectiveStart"));
        IMAGES.put("end", plugin.getActualImage("full/obj16/DirectiveEnd"));
        IMAGES.put("include", plugin.getActualImage("full/obj16/DirectiveInclude"));
        IMAGES.put("jet", plugin.getActualImage("full/obj16/JET"));
        IMAGES.put("skeleton", plugin.getActualImage("full/obj16/Skeleton"));
        IMAGES.put("nlstring", plugin.getActualImage("full/obj16/NLString"));
        IMAGES.put("comment", plugin.getActualImage("full/obj16/Comment.png"));

        Image content = plugin.getActualImage("full/obj16/IncludeContent");
        IMAGES.put("content", content);
        IMAGES.put("root", content);
        IMAGES.put("orphan", content);

        org.eclipse.jdt.ui.ISharedImages sharedImages = JavaUI.getSharedImages();
        IMAGES.put("package", sharedImages.getImage(org.eclipse.jdt.ui.ISharedImages.IMG_OBJS_PACKAGE));
        IMAGES.put("class", sharedImages.getImage(org.eclipse.jdt.ui.ISharedImages.IMG_OBJS_CLASS));
        IMAGES.put("import", sharedImages.getImage(org.eclipse.jdt.ui.ISharedImages.IMG_OBJS_IMPDECL));

        Point size = new Point(16, 16);
        for (Map.Entry<String, Image> entry : IMAGES.entrySet())
        {
          // It should use JavaElementImageDescriptor.WARNING but that's not available in Helios.
          int info = JavaElementImageDescriptor.WARNING;
          try
          {
            info = (Integer)JavaElementImageDescriptor.class.getField("INFO").get(null);
          }
          catch (Exception exception)
          {
            // Ignore;
          }

          Image image = new JavaElementImageDescriptor(ImageDescriptor.createFromImage(entry.getValue()), info, size).createImage();
          INFO_IMAGES.put(entry.getKey(), image);
        }

        for (Map.Entry<String, Image> entry : IMAGES.entrySet())
        {
          Image image = new JavaElementImageDescriptor(ImageDescriptor.createFromImage(entry.getValue()), JavaElementImageDescriptor.WARNING, size).createImage();
          WARNING_IMAGES.put(entry.getKey(), image);
        }

        for (Map.Entry<String, Image> entry : IMAGES.entrySet())
        {
          Image image = new JavaElementImageDescriptor(ImageDescriptor.createFromImage(entry.getValue()), JavaElementImageDescriptor.ERROR, size).createImage();
          ERROR_IMAGES.put(entry.getKey(), image);
        }

        ALL_IMAGES.put(-1, IMAGES);
        ALL_IMAGES.put(IMarker.SEVERITY_INFO, INFO_IMAGES);
        ALL_IMAGES.put(IMarker.SEVERITY_WARNING, WARNING_IMAGES);
        ALL_IMAGES.put(IMarker.SEVERITY_ERROR, ERROR_IMAGES);
      }

      private static final Styler STRIKE_OUT_STYLER = new Styler()
        {
          @Override
          public void applyStyles(TextStyle textStyle)
          {
            textStyle.strikeout = true;
          }
        };

      private static final Styler[] FONT_STYLERS = new Styler [SWT.BOLD | SWT.ITALIC];

      private static final Styler getFontStyler(final int fontStyle)
      {
        if (fontStyle > 0 && fontStyle < (SWT.BOLD | SWT.ITALIC))
        {
          Styler styler = FONT_STYLERS[fontStyle];
          if (styler == null)
          {
            styler = new Styler()
              {
                @Override
                public void applyStyles(TextStyle textStyle)
                {
                  ((StyleRange)textStyle).fontStyle = fontStyle;
                }
              };
            FONT_STYLERS[fontStyle] = styler;
          }
          return styler;
        }
        else
        {
          return null;
        }
      }

      private final List<Item> children = new ArrayList<Item>();

      private final String type;

      private final String signature;

      private Item parent;

      private JETItem jetItem;

      private int problemSeverity = -1;

      public Item(String signature, JETItem jetItem, String type)
      {
        this.signature = signature;
        this.jetItem = jetItem;
        this.type = type == null ? getDirectiveType(jetItem) : type;

        if ("jet".equals(type))
        {
          createJETChildren();
        }
      }

      public Item getCorrespondingItem(JETItem jetItem)
      {
        for (Item child : children)
        {
          Item result = child.getCorrespondingItem(jetItem);
          if (result != null)
          {
            return result;
          }
        }

        return this.jetItem == jetItem ? this : null;
      }

      public boolean isBreadcumbSurrogate()
      {
        return "root".equals(type) || "orphan".equals(type);
      }

      public int handleProblems(JETCompilerResult compilerResult)
      {
        int maxSeverity = -1;
        for (Item child : children)
        {
          maxSeverity = Math.max(maxSeverity, child.handleProblems(compilerResult));
        }

        if ("class".equals(type))
        {
          int overallSeverity = getJavaProblemSeverity(compilerResult.getJavaProblems());
          maxSeverity = Math.max(maxSeverity, overallSeverity);
        }
        else if ("content".equals(type) || "root".equals(type))
        {
          int overallSeverity = getProblemSeverity(compilerResult.getProblems(jetItem.getFileID()));
          maxSeverity = Math.max(maxSeverity, overallSeverity);
        }
        else if ("jet".equals(type) && Boolean.TRUE.equals(((JETDirectiveItem)jetItem).getData(JETDirectiveItem.MISSING_JET_DIRECTIVE)))
        {
          maxSeverity = IMarker.SEVERITY_ERROR;
        }
        else if ("orphan".equals(type))
        {
          maxSeverity = IMarker.SEVERITY_WARNING;
        }
        else if (jetItem != null)
        {
          int localSeverity = getProblemSeverity(compilerResult.getProblem(jetItem));
          maxSeverity = Math.max(maxSeverity, localSeverity);
        }

        this.problemSeverity = maxSeverity;

        return maxSeverity;
      }

      private static final Method INFO_METHOD;

      static
      {
        Method infoMethod = null;
        try
        {
          infoMethod = IProblem.class.getMethod("isInfo");
        }
        catch (Exception exception)
        {
          // Ignore.
        }
        INFO_METHOD = infoMethod;
      }

      private static boolean isInfo(IProblem problem)
      {
        try
        {
          return (Boolean)INFO_METHOD.invoke(problem);
        }
        catch (Exception exception)
        {
          return false;
        }
      }

      private int getJavaProblemSeverity(List<IProblem> problems)
      {
        int result = -1;
        for (IProblem problem : problems)
        {
          int severity = isInfo(problem) ? IMarker.SEVERITY_INFO : problem.isWarning() ? IMarker.SEVERITY_WARNING : problem.isError() ? IMarker.SEVERITY_ERROR : -1;
          result = Math.max(result, severity);
          if (result == IMarker.SEVERITY_ERROR)
          {
            return IMarker.SEVERITY_ERROR;
          }
        }
        return result;
      }

      private int getProblemSeverity(List<JETProblemAnnotation> problems)
      {
        int result = -1;
        for (JETProblemAnnotation problem : problems)
        {
          result = Math.max(result, problem.getSeverity());
          if (result == IMarker.SEVERITY_ERROR)
          {
            return IMarker.SEVERITY_ERROR;
          }
        }
        return result;
      }

      protected void rebuildMap(HashMap<String, Item> managedItems)
      {
        managedItems.put(signature, this);
        for (Item child : children)
        {
          child.rebuildMap(managedItems);
        }
      }

      protected void reconcile(Item newItem)
      {
        // Remove all the existing children.
        Item[] existingChildren = children.toArray(new Item [children.size()]);
        Map<String, Item> signatures = new HashMap<String, Item>();
        for (Item child : existingChildren)
        {
          child.setParent(null);
          signatures.put(child.getSignature(), child);
        }

        Item[] newChildren = newItem.children.toArray(new Item [newItem.children.size()]);
        for (Item newChild : newChildren)
        {
          Item correspondingOldChild = signatures.get(newChild.getSignature());
          if (correspondingOldChild == null)
          {
            // Add the new child.
            newChild.setParent(this);
          }
          else
          {
            // Add the old child back in, transfer the new JET item and recursively reconcile its children.
            correspondingOldChild.setParent(this);
            correspondingOldChild.jetItem = newChild.jetItem;
            correspondingOldChild.reconcile(newChild);
          }
        }
      }

      private void createJETChildren()
      {
        JETDirectiveItem jetDirectiveItem = (JETDirectiveItem)jetItem;
        if (!Boolean.TRUE.equals(jetDirectiveItem.getData(JETDirectiveItem.DUPLICATE_JET_DIRECTIVE)))
        {
          JETAttributeItem packageAttributeItem = jetDirectiveItem.getAttribute("package");
          Item rootItem = this;
          if (packageAttributeItem != null)
          {
            JETValueItem packageValueItem = packageAttributeItem.getValueToken().getValueItem();
            Item packageItem = new Item(signature + "~package", packageValueItem, "package");
            packageItem.setParent(this);
            rootItem = packageItem;
          }

          JETAttributeItem classAttributeItem = jetDirectiveItem.getAttribute("class");
          if (classAttributeItem != null)
          {
            JETValueItem classValueItem = classAttributeItem.getValueToken().getValueItem();
            Item classItem = new Item(rootItem.getSignature() + "~class", classValueItem, "class");
            classItem.setParent(rootItem);
            rootItem = classItem;
          }

          JETAttributeItem importsAttribute = jetDirectiveItem.getAttribute("imports");
          if (importsAttribute != null)
          {
            for (JETValueElementItem jetImportItem : importsAttribute.getValueToken().getValueItem().getElements())
            {
              String importValue = jetImportItem.getValue();
              Item importItem = new Item(rootItem.getSignature() + importValue + "~import", jetImportItem, "import");
              importItem.setParent(rootItem);
            }
          }

          JETAttributeItem skeletonAttribute = jetDirectiveItem.getAttribute("skeleton");
          if (skeletonAttribute != null)
          {
            JETValueItem skeletonValueItem = skeletonAttribute.getValueToken().getValueItem();
            Item skeletonItem = new Item(signature + "~skeleton", skeletonValueItem, "skeleton");
            skeletonItem.setParent(this);
          }

          JETAttributeItem nlString = jetDirectiveItem.getAttribute("nlString");
          if (nlString != null)
          {
            JETValueItem nlStringValue = nlString.getValueToken().getValueItem();
            Item skeletonItem = new Item(signature + "~nlstring", nlStringValue, "nlstring");
            skeletonItem.setParent(this);
          }
        }
      }

      protected void setJetItem(JETItem jetItem)
      {
        this.jetItem = jetItem;
      }

      public Item getParent()
      {
        return parent;
      }

      protected void setParent(Item parent)
      {
        if (this.parent != null)
        {
          this.parent.children.remove(this);
        }

        this.parent = parent;
        if (parent != null)
        {
          parent.children.add(this);
        }
      }

      public String getSignature()
      {
        return signature;
      }

      public JETItem getJETItem()
      {
        return jetItem;
      }

      public Image getImage()
      {
        return ALL_IMAGES.get(problemSeverity).get(type);
      }

      public List<Item> getChildren()
      {
        return children;
      }

      public StyledString getStyledText()
      {
        if ("jet".equals(type))
        {
          JETDirectiveItem jetDirectiveItem = (JETDirectiveItem)jetItem;
          JETSkeleton skeleton = (JETSkeleton)jetDirectiveItem.getData(JETDirectiveItem.SKELETON);
          String className = skeleton.getClassName();
          String packageName = skeleton.getPackageName();
          StyledString result = new StyledString(className);
          if (packageName.length() != 0)
          {
            result.append(" - " + packageName, StyledString.DECORATIONS_STYLER);
          }
          if (Boolean.TRUE.equals(jetDirectiveItem.getData(JETDirectiveItem.DUPLICATE_JET_DIRECTIVE)))
          {
            result.setStyle(0, result.length(), STRIKE_OUT_STYLER);
          }
          return result;
        }
        else if ("include".equals(type))
        {
          JETDirectiveItem jetDirectiveItem = (JETDirectiveItem)jetItem;
          boolean resolvedIncludeSuccess = Boolean.TRUE.equals(jetDirectiveItem.getData(JETDirectiveItem.RESOLVED_INCLUDE_SUCCESS));
          return new StyledString(
            jetDirectiveItem.getAttribute("file").getValueToken().getValueItem().getText(),
            resolvedIncludeSuccess
              ? jetDirectiveItem.isSkipped() ? STRIKE_OUT_STYLER : getFontStyler(SWT.BOLD) : getChildren().isEmpty() ? STRIKE_OUT_STYLER : getFontStyler(SWT.ITALIC));
        }
        else if ("content".equals(type) || "root".equals(type) || "orphan".equals(type))
        {
          if (jetItem == null && !"orphan".equals(type))
          {
            return new StyledString("Analyzing...");
          }
          else
          {
            JETMark start = jetItem == null ? null : jetItem.getStart();
            URI uri = URI.createURI(start == null ? signature.substring(0, signature.lastIndexOf("~orphan")) : start.getResolvedURI());
            String fileName = uri.lastSegment();
            StyledString result = new StyledString(fileName);

            URI baseURI = uri.trimSegments(1);
            String relativeLocation = baseURI.isPlatformResource() ? baseURI.toPlatformString(true) : baseURI.toString();

            if (start != null)
            {
              while (start.getParentMark() != null)
              {
                start = start.getParentMark();
              }
            }

            URI rootURI = URI.createURI(start == null ? parent.getChildren().get(0).jetItem.getStart().getResolvedURI() : start.getResolvedURI());
            if (rootURI.isPlatformResource() && uri.isPlatformResource())
            {
              URI deresolvedURI = uri.deresolve(rootURI, true, true, false);
              if (deresolvedURI.segmentCount() > 1 && !"..".equals(deresolvedURI.segment(0)))
              {
                relativeLocation = deresolvedURI.trimSegments(1).toString();
              }
            }

            result.append(" - " + relativeLocation, StyledString.DECORATIONS_STYLER);
            if ("content".equals(type) && ((JETDirectiveItem)parent.jetItem).isSkipped() || "orphan".equals(type))
            {
              result.setStyle(0, result.length(), STRIKE_OUT_STYLER);
            }

            return result;
          }
        }
        else if ("top".equals(type))
        {
          return new StyledString("top");
        }
        else if ("start".equals(type) || "end".equals(type))
        {
          JETDirectiveItem jetDirectiveItem = (JETDirectiveItem)jetItem;
          return new StyledString(jetItem.getText(), jetDirectiveItem.isSkipped() ? STRIKE_OUT_STYLER : null);
        }
        else if ("comment".equals(type))
        {
          return new StyledString(signature.substring(0, signature.lastIndexOf("~comment")));
        }

        if (jetItem instanceof JETValueItem)
        {
          return new StyledString(((JETValueItem)jetItem).getValue());
        }

        if (jetItem instanceof JETValueElementItem)
        {
          return new StyledString(((JETValueElementItem)jetItem).getValue());
        }

        return new StyledString(jetItem == null ? "" : jetItem.getText());
      }

      private static String getDirectiveType(JETItem jetItem)
      {
        if (jetItem instanceof JETDirectiveItem)
        {
          return ((JETDirectiveItem)jetItem).getNameItem().getText();
        }
        else
        {
          return null;
        }
      }
    }
  }

  static class ViewPortHandler implements IViewportListener, Runnable
  {
    private final JETEditor jetEditor;

    private final StyledText jetStyledText;

    private int topIndex;

    public ViewPortHandler(JETEditor jetEditor)
    {
      this.jetEditor = jetEditor;
      ISourceViewer jetSourceViewer = jetEditor.getSourceViewer();
      jetSourceViewer.addViewportListener(this);
      jetStyledText = jetSourceViewer.getTextWidget();
    }

    public void viewportChanged(int verticalOffset)
    {
      topIndex = jetStyledText.getTopIndex();
      jetStyledText.getDisplay().timerExec(200, this);
    }

    public void run()
    {
      if (!jetStyledText.isDisposed() && topIndex == jetStyledText.getTopIndex())
      {
        jetEditor.invalidateVisibleTextPresentation(false);
      }
    }
  }

  static class JavaHyperLink implements IHyperlink
  {
    private final JETEditor jetEditor;

    private final IRegion region;

    private final IHyperlink javaHyperlink;

    public JavaHyperLink(JETEditor jetEditor, IRegion region, IHyperlink javaHyperlink)
    {
      this.jetEditor = jetEditor;
      this.region = region;
      this.javaHyperlink = javaHyperlink;
    }

    public IRegion getHyperlinkRegion()
    {
      return region;
    }

    public String getTypeLabel()
    {
      return javaHyperlink.getTypeLabel();
    }

    public String getHyperlinkText()
    {
      return javaHyperlink.getHyperlinkText();
    }

    public void open()
    {
      jetEditor.markInNavigationHistory();
      javaHyperlink.open();
    }
  }

  static class JETHyperLink implements IHyperlink
  {
    private final IRegion region;

    private JETEditor jetEditor;

    private final int fileID;

    private final String locationURI;

    public JETHyperLink(JETEditor jetEditor, IRegion region, int fileID, String locationURI)
    {
      this.jetEditor = jetEditor;
      this.region = region;
      this.fileID = fileID;
      this.locationURI = locationURI;
    }

    public IRegion getHyperlinkRegion()
    {
      return region;
    }

    public String getTypeLabel()
    {
      return "Open";
    }

    public String getHyperlinkText()
    {
      URI uri = URI.createURI(locationURI);
      return "Open JET " + (uri.isPlatformResource() ? uri.toPlatformString(true) : uri.toString());
    }

    public void open()
    {
      jetEditor.markInNavigationHistory();
      if (fileID == -1)
      {
        JETEditor.open(jetEditor.getEditorSite(), locationURI);
      }
      else
      {
        jetEditor.getDocumentManager().setFileID(fileID);
        Position correspondingTemplatePoint = jetEditor.getCorrespondingTemplatePosition(region.getOffset(), region.getLength());
        if (correspondingTemplatePoint != null)
        {
          jetEditor.selectAndReveal(correspondingTemplatePoint.offset, correspondingTemplatePoint.length);
        }
        else
        {
          jetEditor.markInNavigationHistory();
        }
      }
    }
  }

  static class JETNavigationHyperLink implements IHyperlink
  {
    protected final JETEditor jetEditor;

    private final IRegion region;

    protected final IRegion targetRegion;

    public JETNavigationHyperLink(JETEditor jetEditor, IRegion region, IRegion targetRegion)
    {
      this.jetEditor = jetEditor;
      this.region = region;
      this.targetRegion = targetRegion;
    }

    public IRegion getHyperlinkRegion()
    {
      return region;
    }

    public String getTypeLabel()
    {
      return "Navigate";
    }

    public String getHyperlinkText()
    {
      return "Navigate in Template";
    }

    public void open()
    {
      jetEditor.markInNavigationHistory();
      jetEditor.selectAndReveal(targetRegion.getOffset(), targetRegion.getLength());
    }
  }

  static class SelectionSynchronizer
  {
    private final JETEditor jetEditor;

    private final JavaEditor javaEditor;

    private final ISourceViewer jetUnderlyingSourceViewer;

    private final ISourceViewer javaUnderlyingSourceViewer;

    private CaretAndSelectionListener jetToJava;

    private CaretAndSelectionListener javaToJET;

    public SelectionSynchronizer(JETEditor jetEditor, final JavaEditor javaEditor)
    {
      this.jetEditor = jetEditor;
      this.javaEditor = javaEditor;

      this.jetUnderlyingSourceViewer = jetEditor.getSourceViewer();
      this.javaUnderlyingSourceViewer = javaEditor.getJavaSourceViewer();

      jetToJava = new CaretAndSelectionListener(jetUnderlyingSourceViewer, javaUnderlyingSourceViewer);
      javaToJET = new CaretAndSelectionListener(javaUnderlyingSourceViewer, jetUnderlyingSourceViewer);
    }

    public void sync(ISourceViewer viewer)
    {
      (viewer == jetUnderlyingSourceViewer ? jetToJava : javaToJET).handle();
    }

    class CaretAndSelectionListener implements CaretListener, SelectionListener
    {
      private final ISourceViewer source;

      private final ISourceViewer target;

      public CaretAndSelectionListener(ISourceViewer source, ISourceViewer target)
      {
        this.source = source;
        this.target = target;
        StyledText textWidget = source.getTextWidget();
        textWidget.addCaretListener(this);
        textWidget.addSelectionListener(this);
      }

      public void widgetSelected(SelectionEvent event)
      {
        if (isEnabled(event))
        {
          if (event.widget == javaUnderlyingSourceViewer.getTextWidget() && javaEditor.isNavigating())
          {
            Point javaSelectedRange = source.getSelectedRange();
            int javaOffset = javaSelectedRange.x;
            int correspondingTemplateFileID = jetEditor.getCorrespondingTemplateFileID(javaOffset);
            if (correspondingTemplateFileID != -1 && correspondingTemplateFileID != jetEditor.getFileID())
            {
              jetEditor.getDocumentManager().setFileID(correspondingTemplateFileID);
            }
          }

          handle();
        }
      }

      public void widgetDefaultSelected(SelectionEvent event)
      {
        if (isEnabled(event))
        {
          handle();
        }
      }

      public void caretMoved(CaretEvent event)
      {
        if (isEnabled(event))
        {
          // A caret move event doesn't generate a selection event if the selection was empty and is also now empty.
          // Furthermore, the queried selection range is stale during the notification, so we must post an event check the range again.
          final StyledText styledText = (StyledText)event.widget;
          if (styledText.getSelectionRange().y == 0)
          {
            event.widget.getDisplay().asyncExec(new Runnable()
              {
                public void run()
                {
                  // If the range is still empty, we need to generate a notification so that the cursors can stay in sync.
                  Point selection = styledText.getSelectionRange();
                  if (selection.y == 0)
                  {
                    Event fakeEvent = new Event();
                    fakeEvent.x = fakeEvent.y = selection.x;
                    styledText.notifyListeners(SWT.Selection, fakeEvent);
                  }
                }
              });
          }
        }
      }

      protected boolean isEnabled(TypedEvent event)
      {
        if (((Control)event.widget).isFocusControl())
        {
          // Don't synchronize anything if the document contents are out of sync with the Java contents.
          return jetEditor.isJavaEditorInSync();
        }
        else
        {
          return event.widget == javaUnderlyingSourceViewer.getTextWidget() && javaEditor.isNavigating();
        }
      }

      protected void handle()
      {
        Point selectedRange = source.getSelectedRange();

        if (source == jetUnderlyingSourceViewer)
        {
          JETItem startItem = jetEditor.getJETItem(selectedRange.x, false);
          JETItem endItem = jetEditor.getJETItem(selectedRange.x + selectedRange.y, false);

          TrackedPosition jetPosition = jetEditor.getJETPosition();
          if (startItem == endItem && startItem instanceof JETJavaItem && !(startItem instanceof JETLiteralItem))
          {
            JETJavaItem jetJavaItem = (JETJavaItem)startItem;
            jetPosition.update(jetJavaItem);
            jetEditor.getJavaPosition().update(jetJavaItem);
          }
          else
          {
            int start = startItem == null ? selectedRange.x : startItem.getStartOffset();
            int end = endItem == null ? startItem == null ? selectedRange.x + selectedRange.y : startItem.getStopOffset() : endItem.getStopOffset();
            int length = end - start;

            if (start != 0 && startItem instanceof JETLiteralItem)
            {
              JETToken token = jetEditor.getTokenAt(start - 1);
              if (token == JETDirectiveRule.TOKEN || token == JETCommentRule.TOKEN || token == JETScriptletRule.TOKEN)
              {
                // These items all consume the line delimiter that immediately follows.
                IDocument document = jetUnderlyingSourceViewer.getDocument();
                try
                {
                  char character = document.getChar(start);
                  if (character == '\n' || character == '\r')
                  {
                    ++start;
                    --length;
                    if (character == '\r' && document.getChar(start) == '\n')
                    {
                      ++start;
                      --length;
                    }
                  }
                }
                catch (BadLocationException exception)
                {
                }
              }
            }

            if (endItem instanceof JETLiteralItem)
            {
              // Always exclude the final line delimiter.
              IDocument document = jetUnderlyingSourceViewer.getDocument();
              try
              {
                char character = document.getChar(start + length);
                if (character == '\n' || character == '\r')
                {
                  --length;
                  if (character == '\n' && document.getChar(start) == '\r')
                  {
                    --length;
                  }
                }
              }
              catch (BadLocationException exception)
              {
              }
            }

            jetPosition.update(start, length);
          }

          jetUnderlyingSourceViewer.setRangeIndication(jetPosition.getOffset(), jetPosition.getLength(), false);
        }

        Position correspondingPoint = source == jetUnderlyingSourceViewer
          ? jetEditor.getCorrespondingJavaPosition(selectedRange.x, selectedRange.y) : jetEditor.getCorrespondingTemplatePosition(selectedRange.x, selectedRange.y);
        if (correspondingPoint != null)
        {
          Position correspondingPointEnd = null;
          for (int i = 0; i < 100 && correspondingPointEnd == null && selectedRange.y > 0; ++i, --selectedRange.y)
          {
            int endOffset = selectedRange.x + selectedRange.y;
            correspondingPointEnd = source == jetUnderlyingSourceViewer
              ? jetEditor.getCorrespondingJavaPosition(endOffset, 0) : jetEditor.getCorrespondingTemplatePosition(endOffset, 0);
          }

          int length = correspondingPointEnd == null ? correspondingPoint.length : correspondingPointEnd.offset + correspondingPointEnd.length - correspondingPoint.offset;
          (target == jetUnderlyingSourceViewer ? jetEditor : javaEditor).selectAndReveal(correspondingPoint.offset, length);

          StyledText textWidget = target.getTextWidget();
          Rectangle bounds = textWidget.getCaret().getBounds();
          textWidget.redraw(bounds.x, bounds.y, bounds.width, bounds.height, false);
        }
      }
    }
  }

  @SuppressWarnings("restriction")
  static class DelegatingTextHover implements IJavaEditorTextHover, ITextHoverExtension, ITextHoverExtension2
  {
    private final org.eclipse.jdt.internal.ui.text.java.hover.JavadocHover javadocHover = new org.eclipse.jdt.internal.ui.text.java.hover.JavadocHover();

    private final JETEditor jetEditor;

    private DefaultTextHover defaultTextHover;

    private ISourceViewer javaTextViewer;

    public DelegatingTextHover(JETEditor jetEditor)
    {
      this.jetEditor = jetEditor;
    }

    public void setEditor(IEditorPart editor)
    {
      javadocHover.setEditor(editor);
      javaTextViewer = ((JavaEditor)editor).getJavaSourceViewer();
      defaultTextHover = new DefaultTextHover(jetEditor.getSourceViewer());
    }

    public IRegion getHoverRegion(ITextViewer textViewer, int offset)
    {
      return defaultTextHover.getHoverRegion(textViewer, offset);
    }

    @SuppressWarnings("deprecation")
    public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion)
    {
      IRegion javaRegion = toJava(hoverRegion);
      return javaRegion == null ? defaultTextHover.getHoverInfo(textViewer, hoverRegion) : javadocHover.getHoverInfo(javaTextViewer, javaRegion);
    }

    public Object getHoverInfo2(ITextViewer textViewer, IRegion hoverRegion)
    {
      IRegion javaRegion = toJava(hoverRegion);
      return javaRegion == null ? null : javadocHover.getHoverInfo2(javaTextViewer, javaRegion);
    }

    public IInformationControlCreator getHoverControlCreator()
    {
      return javadocHover.getHoverControlCreator();
    }

    private IRegion toJava(IRegion templateRegion)
    {
      Position correspondingJavaPoint = jetEditor.getCorrespondingJavaPosition(templateRegion.getOffset(), templateRegion.getLength());
      if (correspondingJavaPoint != null)
      {
        return new Region(correspondingJavaPoint.getOffset(), correspondingJavaPoint.getLength());
      }
      return null;
    }
  }

  static class CompletionProposalDelegate
    implements
      ICompletionProposal,
      ICompletionProposalExtension2,
      ICompletionProposalExtension3,
      ICompletionProposalExtension4,
      ICompletionProposalExtension5,
      ICompletionProposalExtension6
  {
    private final JETEditor jetEditor;

    private final ICompletionProposal delegate;

    private final ITextViewer delegateViewer;

    final IDocument delegateDocument;

    final TrackedPosition javaPosition;

    final TrackedPosition jetPosition;

    public CompletionProposalDelegate(ICompletionProposal delelgate, JETEditor jetEditor)
    {
      this.delegate = delelgate;
      this.jetEditor = jetEditor;
      this.delegateViewer = jetEditor.getJavaSourceViewer();
      this.delegateDocument = delegateViewer.getDocument();
      this.jetPosition = jetEditor.getJETPosition();
      this.javaPosition = jetEditor.getJavaPosition();
    }

    public void apply(IDocument document)
    {
      JavaDocumentTransaction javaDocumentTransaction = new CompletionTransaction(jetEditor);
      javaDocumentTransaction.modify(delegateDocument, new SneakyRunnable()
        {
          @Override
          public void execute() throws Exception
          {
            Method performChangeMethod = null;
            // We do this ugliness because directly applying the proposal will often want to open the Java editor and we definitely don't want that.
            for (Class<?> delegateClass = delegate.getClass(); delegateClass != null; delegateClass = delegateClass.getSuperclass())
            {
              try
              {
                performChangeMethod = delegateClass.getDeclaredMethod("performChange", IEditorPart.class, IDocument.class);
                performChangeMethod.setAccessible(true);
                break;
              }
              catch (Exception exception)
              {
              }
            }

            if (performChangeMethod != null)
            {
              performChangeMethod.invoke(delegate, jetEditor.getJavaEditor(), delegateDocument);
            }
            else
            {
              delegate.apply(delegateDocument);
            }
          }
        });

      jetEditor.openDialog("Proposal Failed", javaDocumentTransaction.getStatus());
    }

    public void apply(ITextViewer viewer, final char trigger, final int stateMask, int offset)
    {
      if (delegate instanceof ICompletionProposalExtension2)
      {
        JavaDocumentTransaction javaDocumentTransaction = new CompletionTransaction(jetEditor);
        javaDocumentTransaction.modify(delegateDocument, new Runnable()
          {
            public void run()
            {
              ((ICompletionProposalExtension2)delegate).apply(delegateViewer, trigger, stateMask, delegateViewer.getSelectedRange().x);
            }
          });

        jetEditor.openDialog("Proposal Failed", javaDocumentTransaction.getStatus());
      }
      else
      {
        apply(delegateDocument);
      }
    }

    public Point getSelection(IDocument document)
    {
      return null;
    }

    public String getAdditionalProposalInfo()
    {
      return delegate.getAdditionalProposalInfo();
    }

    public String getDisplayString()
    {
      return delegate.getDisplayString();
    }

    public Image getImage()
    {
      return delegate.getImage();
    }

    public IContextInformation getContextInformation()
    {
      return delegate.getContextInformation();
    }

    public StyledString getStyledDisplayString()
    {
      return delegate instanceof ICompletionProposalExtension6 ? ((ICompletionProposalExtension6)delegate).getStyledDisplayString() : new StyledString(getDisplayString());
    }

    public Object getAdditionalProposalInfo(IProgressMonitor monitor)
    {
      return delegate instanceof ICompletionProposalExtension5 ? ((ICompletionProposalExtension5)delegate).getAdditionalProposalInfo(monitor) : null;
    }

    public boolean isAutoInsertable()
    {
      return delegate instanceof ICompletionProposalExtension4 && ((ICompletionProposalExtension4)delegate).isAutoInsertable();
    }

    public IInformationControlCreator getInformationControlCreator()
    {
      return delegate instanceof ICompletionProposalExtension3 ? ((ICompletionProposalExtension3)delegate).getInformationControlCreator() : null;
    }

    private int jetToJava(int templateOffset)
    {
      return javaPosition.getOffset() + templateOffset - jetPosition.getOffset();
    }

    public CharSequence getPrefixCompletionText(IDocument document, int completionOffset)
    {
      return delegate instanceof ICompletionProposalExtension3
        ? ((ICompletionProposalExtension3)delegate).getPrefixCompletionText(delegateDocument, jetToJava(completionOffset)) : null;
    }

    public int getPrefixCompletionStart(IDocument document, int completionOffset)
    {
      return delegate instanceof ICompletionProposalExtension3
        ? ((ICompletionProposalExtension3)delegate).getPrefixCompletionStart(delegateDocument, jetToJava(completionOffset)) : null;
    }

    public void selected(ITextViewer viewer, boolean smartToggle)
    {
      if (delegate instanceof ICompletionProposalExtension2)
      {
        ((ICompletionProposalExtension2)delegate).selected(delegateViewer, smartToggle);
      }
    }

    public void unselected(ITextViewer viewer)
    {
      if (delegate instanceof ICompletionProposalExtension2)
      {
        ((ICompletionProposalExtension2)delegate).unselected(delegateViewer);
      }
    }

    public boolean validate(IDocument document, int offset, DocumentEvent event)
    {
      return delegate instanceof ICompletionProposalExtension2 && ((ICompletionProposalExtension2)delegate).validate(delegateDocument, jetToJava(offset), event);
    }

    class CompletionTransaction extends JavaDocumentTransaction
    {
      public CompletionTransaction(JETEditor jetEditor)
      {
        super(jetEditor, Collections.singletonList(jetEditor.getJETPosition()), true, false);
      }

      @Override
      protected void postCommit(List<TrackedPosition> jetPositions)
      {
        Point finalJavaSelectionRange = delegate.getSelection(delegateDocument);
        if (finalJavaSelectionRange != null)
        {
          int offset = finalJavaSelectionRange.x - javaPosition.getOffset();
          if (offset > 0)
          {
            jetEditor.getSourceViewer().setSelectedRange(jetPosition.getOffset() + offset, finalJavaSelectionRange.y);
          }
        }

        // Ensure that pop-up in Java view, if any, is dismissed.
        Event event = new Event();
        event.character = SWT.ESC;
        event.keyCode = SWT.ESC;
        jetEditor.getJavaSourceViewer().getTextWidget().notifyListeners(SWT.KeyDown, event);
      }
    }
  }

  static class JavaDocumentTransaction implements IDocumentListener
  {
    private static final Pattern IMPORT_PATTERN = Pattern.compile("import ([^;]+);");

    protected final JETEditor jetEditor;

    protected final List<TrackedPosition> jetPositions;

    private final boolean transformBraces;

    private final boolean ignoreOutOfScopeChanges;

    public JavaDocumentTransaction(JETEditor jetEditor, List<TrackedPosition> jetPositions, boolean transformBraces, boolean ignoreOutOfScopeChanges)
    {
      this.jetEditor = jetEditor;
      this.jetPositions = jetPositions;
      this.transformBraces = transformBraces;
      this.ignoreOutOfScopeChanges = ignoreOutOfScopeChanges;
    }

    public void modify(IDocument document, Runnable javaModifier)
    {
      start();
      jetEditor.getDocumentManager().setEnableJavaSynchronization(false);
      document.addDocumentListener(this);
      try
      {
        javaModifier.run();
        if (status == null)
        {
          commit(document);
        }
      }
      catch (Exception exception)
      {
        status = CodeGenUIPlugin.toStatus(IStatus.ERROR, exception);
      }
      finally
      {
        document.removeDocumentListener(this);
        jetEditor.getDocumentManager().setEnableJavaSynchronization(true);
        stop();
      }
    }

    protected void startDocumentRewriteSession(ISourceViewer sourceViewer, DocumentRewriteSessionType type, boolean rememberSelection)
    {
      if (rememberSelection)
      {
        try
        {
          Method rememberSelectionMethod = SourceViewer.class.getDeclaredMethod("rememberSelection");
          rememberSelectionMethod.setAccessible(true);
          rememberSelectionMethod.invoke(sourceViewer);
        }
        catch (Exception exception)
        {
          throw new RuntimeException(exception);
        }
      }

      ((IDocumentExtension4)sourceViewer.getDocument()).startRewriteSession(type);
    }

    protected void stopDocumentRewriteSession(ISourceViewer sourceViewer, boolean restoreSelection)
    {
      IDocumentExtension4 extension = (IDocumentExtension4)sourceViewer.getDocument();
      extension.stopRewriteSession(extension.getActiveRewriteSession());

      if (restoreSelection)
      {
        try
        {
          Method rememberSelectionMethod = SourceViewer.class.getDeclaredMethod("restoreSelection");
          rememberSelectionMethod.setAccessible(true);
          rememberSelectionMethod.invoke(sourceViewer);
        }
        catch (Exception exception)
        {
          throw new RuntimeException(exception);
        }
      }
    }

    protected void start()
    {
    }

    protected void stop()
    {
    }

    private List<String> imports = new ArrayList<String>();

    private IStatus status;

    public IStatus getStatus()
    {
      return status == null ? Status.OK_STATUS : status;
    }

    public void documentAboutToBeChanged(DocumentEvent event)
    {
    }

    public void documentChanged(DocumentEvent event)
    {
      if (status == null)
      {
        for (TrackedPosition jetPosition : jetPositions)
        {
          TrackedPosition javaPosition = jetPosition.getOpposite();
          if (javaPosition.overlapsWith(event.getOffset(), event.getLength()))
          {
            return;
          }
        }

        String replacement = event.getText();
        Matcher matcher = IMPORT_PATTERN.matcher(replacement);
        if (matcher.matches())
        {
          String newImport = matcher.group(1);
          imports.add(newImport);
        }
        else if (!ignoreOutOfScopeChanges && replacement.trim().length() != 0)
        {
          status = new Status(IStatus.ERROR, CodeGenUIPlugin.getPlugin().getBundle().getSymbolicName(), "Changes outside the scope of the current document occurred.");
        }
      }
    }

    protected void commit(IDocument delegateDocument) throws Exception
    {
      SourceViewer jetSourceViewer = jetEditor.getJETSourceViewer();
      IUndoManager undoManager = jetSourceViewer.getUndoManager();
      undoManager.beginCompoundChange();
      try
      {
        MultiTextEdit textEdit = new MultiTextEdit();
        for (TrackedPosition jetPosition : jetPositions)
        {
          TrackedPosition javaPosition = jetPosition.getOpposite();
          if (!javaPosition.isDeleted())
          {
            String finalJavaText = javaPosition.getText();

            String originalTemplateJavaText = jetPosition.getText();

            if (!originalTemplateJavaText.equals(finalJavaText))
            {
              int orginalTemplateJavaTextLength = jetPosition.getLength();
              int finalJavaTextLength = javaPosition.getLength();
              int start = 0;
              while (start < finalJavaTextLength && start < orginalTemplateJavaTextLength && finalJavaText.charAt(start) == originalTemplateJavaText.charAt(start))
              {
                ++start;
              }

              if (start != orginalTemplateJavaTextLength - 1 || orginalTemplateJavaTextLength != finalJavaTextLength)
              {
                int end = 0;
                while (finalJavaTextLength - end > 0 && orginalTemplateJavaTextLength - end > 0
                    && finalJavaText.charAt(finalJavaTextLength - end - 1) == originalTemplateJavaText.charAt(orginalTemplateJavaTextLength - end - 1))
                {
                  ++end;
                }

                int replacementOffset = jetPosition.getOffset() + start;
                int replacementLength = orginalTemplateJavaTextLength - end - start;
                while (replacementLength < 0)
                {
                  // The replacement length could be negative because the tail end of the actual Java replacement text could exactly match the text in the original string.
                  // And that part will have been trimmed from the replacement text we've computed for application to the template.
                  ++replacementLength;
                  --end;
                }

                int finalJavaTextEnd = finalJavaTextLength - end;
                String replacementText = finalJavaTextEnd < start ? "" : handleReplacement(finalJavaText.substring(start, finalJavaTextEnd));
                ReplaceEdit replaceEdit = new ReplaceEdit(replacementOffset, replacementLength, replacementText);
                textEdit.addChild(replaceEdit);
              }
            }
          }
        }

        int jetDirectiveFileID = -1;
        int importsOffset = -1;
        boolean needsImportsAttribute = false;
        StringBuilder newImports = new StringBuilder();
        if (!imports.isEmpty())
        {
          JETDirectiveItem jetJETDirectiveItem = jetEditor.getCompilationUnit().getJETJETDirectiveItem();
          jetDirectiveFileID = jetJETDirectiveItem.getFileID();
          JETAttributeItem imports = jetJETDirectiveItem.getAttribute("imports");
          for (String newImport : this.imports)
          {
            if (imports == null)
            {
              importsOffset = jetJETDirectiveItem.getAttributes().getStopOffset();
              needsImportsAttribute = true;
              if (newImports.length() != 0)
              {
                newImports.append(' ');
              }
            }
            else
            {
              JETTokenItem importValue = imports.getValueToken();
              if (importsOffset == -1)
              {
                JETValueItem valueItem = importValue.getValueItem();
                importsOffset = valueItem.getStopOffset();
                if (!valueItem.getElements().isEmpty())
                {
                  newImports.append(' ');
                }
              }
              else
              {
                newImports.append(' ');
              }
            }

            newImports.append(newImport);
          }
        }

        if (importsOffset != -1)
        {
          String insertion = needsImportsAttribute ? " imports=\"" + newImports + "\"" : newImports.toString();
          ReplaceEdit insertImportsEdit = new ReplaceEdit(importsOffset, 0, insertion);
          if (jetDirectiveFileID != jetEditor.getFileID())
          {
            IDocument jetDirectiveDocument = jetEditor.getDocumentManager().getDocument(jetDirectiveFileID);
            if (jetDirectiveDocument != null)
            {
              try
              {
                insertImportsEdit.apply(jetDirectiveDocument);
              }
              catch (Exception exception)
              {
                CodeGenUIPlugin.getPlugin().log(exception);
              }
            }
          }
          else
          {
            textEdit.addChild(insertImportsEdit);
          }
        }

        try
        {
          TextEdit[] children = textEdit.getChildren();
          if (children.length > 0)
          {
            startDocumentRewriteSession(jetSourceViewer, children.length > 50 ? DocumentRewriteSessionType.UNRESTRICTED : DocumentRewriteSessionType.UNRESTRICTED_SMALL, false);
            IDocument document = jetSourceViewer.getDocument();
            textEdit.apply(document);
          }

          postCommit(jetPositions);
        }
        finally
        {
          stopDocumentRewriteSession(jetSourceViewer, false);
        }
      }
      finally
      {
        undoManager.endCompoundChange();
      }
    }

    private static Pattern BRACE_COLLAPSE_PATTERN = Pattern.compile("\\s\\s+\\{");

    private static Pattern TAB_REPLACEMENT_PATTERN = Pattern.compile("\t");

    private static Pattern INDENT_FIX_PATTERN_ = Pattern.compile("(\r?\n)  ");

    protected String handleReplacement(String replacement)
    {
      if (transformBraces)
      {
        String result = BRACE_COLLAPSE_PATTERN.matcher(replacement).replaceAll(" {");
        result = TAB_REPLACEMENT_PATTERN.matcher(result).replaceAll("  ");
        result = INDENT_FIX_PATTERN_.matcher(result).replaceAll("$1");
        return result;
      }

      return replacement;
    }

    protected void postCommit(List<TrackedPosition> jetPositions)
    {
    }
  }

  static class DelegatingContentAssistProcessor implements IContentAssistProcessor
  {
    private static final Pattern IGNORED_PROPOSAL_PATTERN = Pattern.compile("^(((TEXT)?_([0-9]+))|nl|(NL(_[0-9]+)?)) : String");

    private final JETEditor jetEditor;

    private final JETContentAssistProcessor jetContentAssistProcessor;

    private IContentAssistProcessor javaContentAssistProcessor;

    private IContentAssistProcessor delegate;

    public DelegatingContentAssistProcessor(JETEditor jetEditor)
    {
      this.jetEditor = jetEditor;
      jetContentAssistProcessor = new JETContentAssistProcessor(jetEditor);
    }

    protected IContentAssistProcessor getJavaAssistProcessor()
    {
      if (javaContentAssistProcessor == null)
      {
        IContentAssistant contentAssist = jetEditor.getJavaEditor().getContentAssist();
        javaContentAssistProcessor = contentAssist.getContentAssistProcessor(IDocument.DEFAULT_CONTENT_TYPE);
      }
      return javaContentAssistProcessor;
    }

    public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset)
    {
      final JETToken token = jetEditor.getTokenAt(offset);
      if (token == JETContentRule.TOKEN || token == JETTrailingWhitespaceContentRule.TOKEN || token == JETDirectiveRule.TOKEN || token == null)
      {
        delegate = jetContentAssistProcessor;
        return jetContentAssistProcessor.computeCompletionProposals(viewer, offset);
      }
      else if (token == JETScriptletRule.TOKEN || token == JETExpressionRule.TOKEN)
      {
        delegate = getJavaAssistProcessor();

        final ISourceViewer javaSourceViewer = jetEditor.getJavaEditor().getJavaSourceViewer();
        final Point javaSelectionRange = javaSourceViewer.getSelectedRange();
        final int javaCaretOffset = javaSelectionRange.x + javaSelectionRange.y;

        ICompletionProposal[] proposals = delegate.computeCompletionProposals(javaSourceViewer, javaCaretOffset);
        List<ICompletionProposal> filteredProposals = new ArrayList<ICompletionProposal>();
        Set<String> duplicates = new HashSet<String>();

        for (final ICompletionProposal completionProposal : proposals)
        {
          String displayString = completionProposal.getDisplayString();
          if (duplicates.add(displayString) && !IGNORED_PROPOSAL_PATTERN.matcher(displayString).find())
          {
            filteredProposals.add(new CompletionProposalDelegate(completionProposal, jetEditor));
          }
        }

        return filteredProposals.toArray(new ICompletionProposal [filteredProposals.size()]);
      }
      else
      {
        return null;
      }
    }

    public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset)
    {
      return null;
    }

    public char[] getCompletionProposalAutoActivationCharacters()
    {
      JETToken token = jetEditor.getTokenAtSelection(0);
      return token == JETScriptletRule.TOKEN || token == JETExpressionRule.TOKEN ? getJavaAssistProcessor().getCompletionProposalAutoActivationCharacters() : null;
    }

    public char[] getContextInformationAutoActivationCharacters()
    {
      JETToken token = jetEditor.getTokenAtSelection(0);
      return token == JETScriptletRule.TOKEN || token == JETExpressionRule.TOKEN ? getJavaAssistProcessor().getContextInformationAutoActivationCharacters() : null;
    }

    public String getErrorMessage()
    {
      return delegate != null ? delegate.getErrorMessage() : null;
    }

    public IContextInformationValidator getContextInformationValidator()
    {
      return null;
    }
  }

  static class DelegatingQuickAssistProcessor implements IQuickAssistProcessor
  {
    private JETEditor jetEditor;

    public DelegatingQuickAssistProcessor(JETEditor jetEditor)
    {
      this.jetEditor = jetEditor;
    }

    @SuppressWarnings("restriction")
    protected IQuickAssistProcessor getJavaAssistProcessor(IQuickAssistInvocationContext invocationContext)
    {
      JavaEditor javaEditor = jetEditor.getJavaEditor();
      IQuickAssistAssistant quickAssistAssistant = javaEditor.getQuickAssistAssistant();
      if (invocationContext != null)
      {
        try
        {
          ArrayList<Annotation> resultingAnnotations = new ArrayList<Annotation>();
          org.eclipse.jdt.internal.ui.text.correction.JavaCorrectionAssistant.collectQuickFixableAnnotations(
            javaEditor,
            invocationContext.getOffset(),
            invocationContext.getLength() != 0,
            resultingAnnotations);
          Field fCurrentAnnotationsField = quickAssistAssistant.getClass().getDeclaredField("fCurrentAnnotations");
          fCurrentAnnotationsField.setAccessible(true);
          fCurrentAnnotationsField.set(quickAssistAssistant, resultingAnnotations.toArray(new Annotation [resultingAnnotations.size()]));
        }
        catch (Exception exception)
        {
        }
      }

      return quickAssistAssistant.getQuickAssistProcessor();
    }

    protected IQuickAssistInvocationContext getJavaInvocationContext(IQuickAssistInvocationContext invocationContext)
    {
      final JETToken token = jetEditor.getTokenAt(invocationContext.getOffset());
      if (token == JETScriptletRule.TOKEN || token == JETExpressionRule.TOKEN)
      {
        final ISourceViewer javaSourceViewer = jetEditor.getJavaEditor().getJavaSourceViewer();
        final Point javaSelectionRange = javaSourceViewer.getSelectedRange();
        return new TextInvocationContext(javaSourceViewer, javaSelectionRange.x, javaSelectionRange.y);
      }
      else
      {
        return null;
      }
    }

    public boolean canAssist(IQuickAssistInvocationContext invocationContext)
    {
      IQuickAssistInvocationContext javaInvocationContext = getJavaInvocationContext(invocationContext);
      return javaInvocationContext != null && getJavaAssistProcessor(javaInvocationContext).canAssist(javaInvocationContext);
    }

    public ICompletionProposal[] computeQuickAssistProposals(IQuickAssistInvocationContext invocationContext)
    {
      IQuickAssistInvocationContext javaInvocationContext = getJavaInvocationContext(invocationContext);
      if (javaInvocationContext != null)
      {
        ICompletionProposal[] quickAssistProposals = getJavaAssistProcessor(javaInvocationContext).computeQuickAssistProposals(javaInvocationContext);
        if (quickAssistProposals != null)
        {
          List<ICompletionProposal> delegatingProposals = new ArrayList<ICompletionProposal>(quickAssistProposals.length);
          for (ICompletionProposal completionProposal : quickAssistProposals)
          {
            delegatingProposals.add(new CompletionProposalDelegate(completionProposal, jetEditor));
          }
          return delegatingProposals.toArray(new ICompletionProposal [delegatingProposals.size()]);
        }
      }
      return null;
    }

    public String getErrorMessage()
    {
      return getJavaAssistProcessor(null).getErrorMessage();
    }

    public boolean canFix(Annotation annotation)
    {
      return getJavaAssistProcessor(null).canFix(annotation);
    }
  }

  static class JETContentAssistProcessor extends TemplateCompletionProcessor
  {
    private static final Map<String, JETTemplateContextType> JET_CONTEXT_TYPES = new HashMap<String, JETTemplateContextType>();

    private static final Map<JETToken, JETTemplateContextType> JET_TOKEN_CONTEXT_TYPES = new HashMap<JETToken, JETTemplateContextType>();

    private static Image JET_IMAGE = CodeGenUIPlugin.getPlugin().getActualImage("full/obj16/JET");

    private final JETEditor jetEditor;

    static
    {
      new JETContentContextType();
      new JETDirectiveContextType();
    }

    public JETContentAssistProcessor(JETEditor jetEditor)
    {
      this.jetEditor = jetEditor;
    }

    @Override
    protected Template[] getTemplates(String contextTypeId)
    {
      JETTemplateContextType jetTemplateContextType = JET_CONTEXT_TYPES.get(contextTypeId);
      return jetTemplateContextType == null ? null : jetTemplateContextType.getTemplates(jetEditor.getEditorInput());
    }

    @Override
    public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset)
    {
      try
      {
        return super.computeCompletionProposals(viewer, offset);
      }
      finally
      {
        for (JETTemplateContextType jetTemplateContextType : JET_CONTEXT_TYPES.values())
        {
          jetTemplateContextType.resetContext();
        }
      }
    }

    @Override
    protected String extractPrefix(ITextViewer viewer, int offset)
    {
      return "";
    }

    @Override
    protected ICompletionProposal createProposal(Template template, TemplateContext context, IRegion region, int relevance)
    {
      return new JETTemplateProposal(template, context, region, getImage(template), relevance);
    }

    @Override
    protected TemplateContextType getContextType(ITextViewer viewer, IRegion region)
    {
      JETTokenRegion tokenRegion = jetEditor.getTokenRegionAt(region.getOffset());

      // If we are right at the start, even in an empty file, treat it like we are in content.
      JETToken token = tokenRegion == null || tokenRegion.getOffset() == region.getOffset() ? JETContentRule.TOKEN : tokenRegion.getToken();
      JETTemplateContextType jetTemplateContextType = JET_TOKEN_CONTEXT_TYPES.get(token.getBaseToken());
      if (jetTemplateContextType != null)
      {
        jetTemplateContextType.setContext(jetEditor, region);
      }
      return jetTemplateContextType;
    }

    @Override
    protected Image getImage(Template template)
    {
      return JET_IMAGE;
    }

    private static class JETTemplateProposal extends TemplateProposal implements ICompletionProposalExtension6
    {
      private StyledString displayString;

      public JETTemplateProposal(Template template, TemplateContext context, IRegion region, Image image, int relevance)
      {
        super(template, context, region, image, relevance);
      }

      @Override
      public String getDisplayString()
      {
        return getStyledDisplayString().toString();
      }

      public StyledString getStyledDisplayString()
      {
        if (displayString == null)
        {
          String name = getTemplate().getName();
          String description = getTemplate().getDescription();
          displayString = new StyledString(name, StyledString.COUNTER_STYLER);
          displayString.append(" - ");
          displayString.append(description, StyledString.QUALIFIER_STYLER);
        }
        return displayString;
      }
    }

    static class JETTemplateContextType extends TemplateContextType
    {
      private JETEditor jetEditor;

      private IRegion region;

      public JETTemplateContextType(String id, JETToken token)
      {
        super(id);
        JET_CONTEXT_TYPES.put(id, this);
        JET_TOKEN_CONTEXT_TYPES.put(token, this);
      }

      public JETEditor getEditor()
      {
        return jetEditor;
      }

      public IRegion getRegion()
      {
        return region;
      }

      public void setContext(JETEditor jetEditor, IRegion region)
      {
        this.jetEditor = jetEditor;
        this.region = region;
      }

      public void resetContext()
      {
        jetEditor = null;
        region = null;
      }

      public Template[] getTemplates(IEditorInput input)
      {
        return null;
      }
    }

    static class JETContentContextType extends JETTemplateContextType
    {
      private static String ID = "org.eclipse.code.ui.jet.content";

      private static Pattern JET_DIRECTIVE_PATTERN = Pattern.compile("\\s*<%@\\s*jet\\s+.*%>", Pattern.DOTALL);

      private static final Template JET_DIRECTIVE_TEMPLATE = new Template(
        "<%@jet%>",
        "@jet directive to minimally specify the Java class name",
        ID,
        "<%@jet package=\"${java_package_name}\" class=\"${java_class_name}\" imports=\"${java_imports}\"%>\n",
        true);

      private static final Template JET_SCRIPTLET_TEMPLATE = new Template("<%%>", "scriptlet for specifying Java statement logic", JETContentContextType.ID, "<%${}%>", true);

      private static final Template JET_EXPRESSION_TEMPLATE = new Template("<%=%>", "expression for specifying Java expression logic", JETContentContextType.ID, "<%=${}%>", true);

      private static final Template JET_SIMPLE_INCLUDE_TEMPLATE = new Template(
        "<%@include%>",
        "@include directive for including a 'jetinc' file",
        ID,
        "<%@include file=\"${file}\"%>",
        true);

      private static final Template JET_OPTIONAL_INCLUDE_TEMPLATE = new Template(
        "<%@include fail=\"silent\"%>",
        "@include directive for including a 'jetinc' file, ignoring when it doesn't exist",
        ID,
        "<%@include file=\"${file}\" fail=\"silent\"%>",
        true);

      private static final Template JET_ALTERNATIVE_INCLUDE_TEMPLATE = new Template(
        "<%@include fail=\"alternative\"%>",
        "@include directive for including a 'jetinc' file, using the @start/@end block when it doesn't exist",
        ID,
        "<%@include file=\"${file}\" fail=\"alternative\"%>\n<%@start%>\n${}<%@end%>\n",
        true);

      private static final Template JET_COPYRIGHT_COMMENT_TEMPLATE = new Template(
        "<%--copyright--%>",
        "a comment at the start of the file for specifying the copyright",
        ID,
        "<%--\nCopyright " + System.getProperty("user.name") + " " + Calendar.getInstance().get(Calendar.YEAR) + "\n--%>\n",
        true);

      private static final Template JET_COMMENT_TEMPLATE = new Template("<%--%>", "comment for specifying excluded content", JETContentContextType.ID, "<%-${}-%>", true);

      private static final Template JET_COMMENT_TEMPLATE_WITH_TAG = new Template(
        "<%-[]-%>",
        "comment for specifying a tag in the content outline",
        JETContentContextType.ID,
        "<%-[${Tag}]-%>",
        true);

      public JETContentContextType()
      {
        super(ID, JETContentRule.TOKEN);
      }

      private Template convert(Template template, String lineDelimeter)
      {
        return new Template(
          template.getName(),
          template.getDescription(),
          template.getContextTypeId(),
          template.getPattern().replace("\n", lineDelimeter),
          template.isAutoInsertable());
      }

      @Override
      public Template[] getTemplates(IEditorInput input)
      {
        ITextViewer viewer = getEditor().getSourceViewer();
        IDocument document = viewer.getDocument();
        String lineDelimiter = TextUtilities.getDefaultLineDelimiter(document);

        List<Template> result = new ArrayList<Template>();
        if (viewer.getSelectedRange().x == 0)
        {
          result.add(convert(JET_COPYRIGHT_COMMENT_TEMPLATE, lineDelimiter));
        }

        boolean isJET = input == null || input.getName() == null || input.getName().endsWith("jet");
        if (isJET)
        {
          try
          {
            IRegion lineInformation = document.getLineInformation(0);
            String firstLine = document.get(lineInformation.getOffset(), lineInformation.getLength());
            if (!JET_DIRECTIVE_PATTERN.matcher(firstLine).matches())
            {
              JETCompilationUnit compilationUnit = getEditor().getCompilationUnit();
              if (compilationUnit == null || compilationUnit.getJETJETDirectiveItem() == null || compilationUnit.getJETJETDirectiveItem().getLength() == 0)
              {
                result.add(convert(JET_DIRECTIVE_TEMPLATE, lineDelimiter));
              }
            }
          }
          catch (BadLocationException exception)
          {
          }
        }

        result.add(convert(JET_SCRIPTLET_TEMPLATE, lineDelimiter));
        result.add(convert(JET_EXPRESSION_TEMPLATE, lineDelimiter));
        result.add(convert(JET_SIMPLE_INCLUDE_TEMPLATE, lineDelimiter));
        result.add(convert(JET_OPTIONAL_INCLUDE_TEMPLATE, lineDelimiter));
        result.add(convert(JET_ALTERNATIVE_INCLUDE_TEMPLATE, lineDelimiter));
        result.add(convert(JET_COMMENT_TEMPLATE, lineDelimiter));
        result.add(convert(JET_COMMENT_TEMPLATE_WITH_TAG, lineDelimiter));

        return result.toArray(new Template [result.size()]);
      }
    }

    private static class JETDirectiveContextType extends JETTemplateContextType
    {
      private static String ID = "org.eclipse.code.ui.jet.directive";

      private static final Map<String, List<Template>> ATTRIBUTE_TEMPLATES = new HashMap<String, List<Template>>();

      static
      {
        List<Template> jetTemplates = new ArrayList<Template>();
        jetTemplates.add(new Template("class", "specify the Java class name of the class", ID, "class=\"${java_class_name}\"", false));
        jetTemplates.add(new Template("package", "specify the Java package name of the class", ID, "package=\"${java_package_name}\"", false));
        jetTemplates.add(new Template("imports", "specify the Java imports for the class", ID, "imports=\"${java_imports}\"", false));
        jetTemplates.add(
          new Template("builder", "specify the builder to be used in the generate method of the class", ID, "builder=\"StringBuilder ${builder} = new StringBuilder()\"", false));
        jetTemplates.add(new Template("minimize", "whether to mimize the footprint of the literal constants of the class", ID, "minimize=\"${true}\"", false));
        jetTemplates.add(
          new Template("nlString", "specify the Java expression used to extract the default line delimiter of the class", ID, "nlString=\"${global_expression}\"", false));
        jetTemplates.add(new Template("skeleton", "specify a Java class to be used as the skeleton of the class", ID, "skeleton=\"${skeleton_java_file}\"", false));
        jetTemplates.add(new Template("version", "specify and arbtirary used version for the templates", ID, "version=\"${version}\"", false));
        ATTRIBUTE_TEMPLATES.put("jet", jetTemplates);

        List<Template> includeTemplates = new ArrayList<Template>();
        includeTemplates.add(new Template("file", "specify file to include in the template", ID, "file=\"${file}\"", false));
        includeTemplates.add(new Template("fail", "specify to ignore a missing file", ID, "fail=\"${silent}\"", false));
        ATTRIBUTE_TEMPLATES.put("include", includeTemplates);
      }

      public JETDirectiveContextType()
      {
        super(ID, JETDirectiveRule.TOKEN);
      }

      private Template convert(Template template, String prefix, String suffix)
      {
        return new Template(template.getName(), template.getDescription(), template.getContextTypeId(), prefix + template.getPattern() + suffix, template.isAutoInsertable());
      }

      @Override
      public Template[] getTemplates(IEditorInput input)
      {
        JETEditor jetEditor = getEditor();

        // Ignore the prefix.
        IRegion region = getRegion();
        int offset = region.getOffset() + region.getLength();
        JETTokenRegion tokenRegion = jetEditor.getTokenRegionAt(offset);
        try
        {
          List<Template> result = new ArrayList<Template>();
          int tokenRegionOffset = tokenRegion.getOffset();
          int tokenRegionLength = tokenRegion.getLength();
          String directive = jetEditor.getSourceViewer().getDocument().get(tokenRegionOffset, tokenRegionLength);
          JETDirectiveItem jetDirectiveItem = JETParser.parseDirective(directive);
          JETAttributeListItem attributes = jetDirectiveItem.getAttributes();
          int relativeOffset = offset - tokenRegionOffset;
          JETItem nameItem = jetDirectiveItem.getNameItem();
          String directiveName = nameItem.getText();
          List<Template> templates = ATTRIBUTE_TEMPLATES.get(directiveName);
          if (templates != null)
          {
            boolean valid = false;
            JETItem previousItem = nameItem;
            for (JETAttributeItem jetAttributeItem : attributes.getAttributes())
            {
              int previousStopOffset = previousItem.getStopOffset();
              int nextStartOffset = jetAttributeItem.getStartOffset();
              if (relativeOffset >= previousStopOffset && relativeOffset <= nextStartOffset)
              {
                valid = true;
                break;
              }
              else
              {
                previousItem = jetAttributeItem;
              }
            }

            if (!valid && relativeOffset >= attributes.getStopOffset() && relativeOffset <= tokenRegionLength - 2)
            {
              valid = true;
            }

            if (valid)
            {
              char characterBeforeInsertion = directive.charAt(relativeOffset - 1);
              char characterAfterInsertion = directive.charAt(relativeOffset);
              String prefix = Character.isWhitespace(characterBeforeInsertion) ? "" : " ";
              String suffix = Character.isWhitespace(characterAfterInsertion) || characterAfterInsertion == '%' ? "" : " ";
              for (Template template : templates)
              {
                if (jetDirectiveItem.getAttribute(template.getName()) == null)
                {
                  result.add(convert(template, prefix, suffix));
                }
              }
            }
          }

          return result.toArray(new Template [result.size()]);
        }
        catch (Exception exception)
        {
          return null;
        }
      }
    }
  }

  static class JETProblemAnnotation extends Annotation implements IAnnotationPresentation
  {
    private static final Image[] SEVERITY_IMAGES;

    private static final Image[] DISABLED_SEVERITY_IMAGES;

    static
    {
      ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
      SEVERITY_IMAGES = new Image []{
        sharedImages.getImage(ISharedImages.IMG_OBJS_INFO_TSK),
        sharedImages.getImage(ISharedImages.IMG_OBJS_WARN_TSK),
        sharedImages.getImage(ISharedImages.IMG_OBJS_ERROR_TSK) };
      DISABLED_SEVERITY_IMAGES = new Image []{
        new Image(SEVERITY_IMAGES[0].getDevice(), SEVERITY_IMAGES[0], SWT.IMAGE_GRAY),
        new Image(SEVERITY_IMAGES[1].getDevice(), SEVERITY_IMAGES[1], SWT.IMAGE_GRAY),
        new Image(SEVERITY_IMAGES[2].getDevice(), SEVERITY_IMAGES[2], SWT.IMAGE_GRAY) };
    }

    private static final String[] SEVERITY_TYPES = new String []{
      "org.eclipse.ui.workbench.texteditor.info",
      "org.eclipse.ui.workbench.texteditor.warning",
      "org.eclipse.ui.workbench.texteditor.error" };

    private final boolean disabled;

    private final int severity;

    private final Position position;

    public JETProblemAnnotation(int severity, String message, Position position)
    {
      super(SEVERITY_TYPES[severity], false, message);
      this.severity = severity;
      this.position = position;
      this.disabled = false;
    }

    public JETProblemAnnotation(MarkerAnnotation annotation, Position position)
    {
      super(SEVERITY_TYPES[getSeverity(annotation)], false, annotation.getText());
      severity = getSeverity(annotation);
      this.position = position;
      this.disabled = true;
    }

    public Position getPosition()
    {
      return position;
    }

    public int getSeverity()
    {
      return severity;
    }

    public int getLayer()
    {
      return 6 + severity + (disabled ? 1 : 0);
    }

    private Image getImage()
    {
      return (disabled ? DISABLED_SEVERITY_IMAGES : SEVERITY_IMAGES)[severity];
    }

    public void paint(GC gc, Canvas canvas, Rectangle bounds)
    {
      ImageUtilities.drawImage(getImage(), gc, canvas, bounds, SWT.CENTER, SWT.TOP);
    }

    private static int getSeverity(MarkerAnnotation markerAnnotation)
    {
      try
      {
        IMarker marker = markerAnnotation.getMarker();
        if (marker != null)
        {
          Object severity = marker.getAttribute(IMarker.SEVERITY);
          return severity instanceof Integer ? (Integer)severity : IMarker.SEVERITY_INFO;
        }
      }
      catch (CoreException exception)
      {
      }
      return IMarker.SEVERITY_ERROR;
    }
  }

  static class ColorManager implements IPropertyChangeListener
  {
    private static ColorManager INSTANCE = new ColorManager();

    private static final String PROPERTY_PREFIX = "org.eclipse.emf.codegen.ui.jet.";

    private final Set<JETToken> tokens = new LinkedHashSet<JETEditor.JETToken>();

    private final Map<Control, ISourceViewer> sourceViewers = new HashMap<Control, ISourceViewer>();

    private final Map<Color, Map<Color, Color>> blendedColors = new HashMap<Color, Map<Color, Color>>();

    private final Map<RGB, Color> colors = new HashMap<RGB, Color>();

    private final DisposeListener disposeListener = new DisposeListener()
      {
        public void widgetDisposed(DisposeEvent event)
        {
          sourceViewers.remove(event.widget);
          if (sourceViewers.isEmpty())
          {
            if (trace)
            {
              System.out.println("unhook color");
            }
            JFaceResources.getColorRegistry().removeListener(ColorManager.this);
          }
        }
      };

    private ColorManager()
    {
    }

    public Color getColor(int red, int green, int blue)
    {
      RGB rgb = new RGB(red, green, blue);
      Color color = colors.get(rgb);
      if (color == null)
      {
        color = new Color(Display.getDefault(), rgb);
        colors.put(rgb, color);
      }
      return color;
    }

    public Color getBlendedColor(Color foreground, Color background)
    {
      Map<Color, Color> blendedBackgroundColors = blendedColors.get(foreground);
      if (blendedBackgroundColors == null)
      {
        blendedBackgroundColors = new HashMap<Color, Color>();
        blendedColors.put(foreground, blendedBackgroundColors);
      }
      Color blendedColor = blendedBackgroundColors.get(background);
      if (blendedColor == null)
      {
        blendedColor = new Color(foreground.getDevice(), ColorUtil.blend(foreground.getRGB(), background.getRGB(), 50));
        blendedBackgroundColors.put(background, blendedColor);
      }
      return blendedColor;
    }

    public String getProperty(JETToken token)
    {
      return "org.eclipse.emf.codegen.ui.jet." + (token == null ? JETContentRule.TOKEN : token.getBaseToken()).getType() + "Background";
    }

    public void register(ISourceViewer sourceViewer)
    {
      if (sourceViewers.isEmpty())
      {
        JFaceResources.getColorRegistry().addListener(this);
      }

      StyledText control = sourceViewer.getTextWidget();
      sourceViewers.put(control, sourceViewer);
      control.addDisposeListener(disposeListener);
    }

    public void register(JETToken token)
    {
      tokens.add(token);
      update(token);
    }

    private void update(JETToken token)
    {
      String type = token.getBaseToken().getType();
      token.setData(new JETTextAttribute(getForeground(type), getBackground(type), token.getTextStyle(), token.getBorderStyle(), null, token));
    }

    private Color getForeground(String type)
    {
      return JFaceResources.getColorRegistry().get("org.eclipse.emf.codegen.ui.jet." + type + "Foreground");
    }

    private Color getBackground(String type)
    {
      return JFaceResources.getColorRegistry().get("org.eclipse.emf.codegen.ui.jet." + type + "Background");
    }

    public void propertyChange(PropertyChangeEvent event)
    {
      if (event.getProperty().startsWith(PROPERTY_PREFIX))
      {
        Display.getDefault().asyncExec(new Runnable()
          {
            public void run()
            {
              for (JETToken token : tokens)
              {
                update(token);
              }

              for (ISourceViewer sourceViewer : sourceViewers.values())
              {
                sourceViewer.invalidateTextPresentation();
              }
            }
          });
      }
    }

    private static class JETTextAttribute extends TextAttribute
    {
      private final JETToken token;

      private final int borderStyle;

      public JETTextAttribute(Color foreground, Color background, int textStyle, int borderStyle, Font font, JETToken token)
      {
        super(foreground, background, textStyle, font);
        this.token = token;
        this.borderStyle = borderStyle;
      }

      public JETToken getToken()
      {
        return token;
      }

      public int getBorderStyle()
      {
        return borderStyle;
      }

      @Override
      public boolean equals(Object object)
      {
        return object == this;
      }
    }
  }

  static class JETBracketMatcher extends DefaultCharacterPairMatcher
  {
    private final static char[] BRACKETS = { //
      '{',
      '}', //
      '(',
      ')', //
      '[',
      ']', //
      '<',
      '>', //
      '%',
      '%', //
      '=',
      '%', //
      '@',
      '%' //
    };

    private final JETEditor jetEditor;

    private int customAnchor;

    public JETBracketMatcher(JETEditor jetEditor)
    {
      super(BRACKETS);
      this.jetEditor = jetEditor;
    }

    private JETToken getToken(int offset)
    {
      return jetEditor.getTokenAt(offset);
    }

    private Point getTokenRange(int offset)
    {
      return jetEditor.getTokenRangeAt(offset);
    }

    @Override
    public IRegion match(IDocument document, int offset)
    {
      JETToken token = getToken(offset);
      if (token == null)
      {
        return null;
      }

      Point tokenRange;
      if (token.getBaseToken() == JETContentRule.TOKEN)
      {
        if (offset > 1)
        {
          token = getToken(offset - 1);
          if (token == null || token.getBaseToken() == JETContentRule.TOKEN)
          {
            return null;
          }

          tokenRange = getTokenRange(offset - 1);
        }
        else
        {
          return null;
        }
      }
      else
      {
        tokenRange = getTokenRange(offset);
      }

      for (;;)
      {
        try
        {
          // Trim of the preceding white space that's included in comments, scriptlets, and directives.
          if (document.getChar(tokenRange.x) == ' ')
          {
            ++tokenRange.x;
            --tokenRange.y;
          }
          else
          {
            break;
          }
        }
        catch (BadLocationException exception)
        {
          break;
        }
      }

      int effectiveOffset = offset;
      boolean isCloseBrace = false;
      if (token == JETScriptletRule.TOKEN && tokenRange.y == 5)
      {
        try
        {
          isCloseBrace = "<%}%>".equals(document.get(tokenRange.x, tokenRange.y));
          if (isCloseBrace)
          {
            effectiveOffset = tokenRange.x + 3;
          }
        }
        catch (BadLocationException exception)
        {
        }
      }

      if (!isCloseBrace)
      {
        IRegion jetMatch = token.match(tokenRange, offset);
        if (jetMatch != null)
        {
          customAnchor = offset > jetMatch.getOffset() + jetMatch.getLength() - 2 ? RIGHT : LEFT;
          return jetMatch;
        }
      }

      if (token == JETScriptletRule.TOKEN || token == JETExpressionRule.TOKEN)
      {
        JavaEditor javaEditor = jetEditor.getJavaEditor();
        if (javaEditor != null)
        {
          ISourceViewer javaSourceViewer = javaEditor.getJavaSourceViewer();
          IDocument javaDocument = javaSourceViewer.getDocument();
          DefaultCharacterPairMatcher javaBracketMatcher = javaEditor.getJavaBracketMatcher();
          if (javaBracketMatcher != null)
          {
            Position correspondingJavaPoint = jetEditor.getCorrespondingJavaPosition(effectiveOffset, 0);
            if (correspondingJavaPoint == null)
            {
              return null;
            }

            IRegion javaRegion = javaBracketMatcher.match(javaDocument, correspondingJavaPoint.offset);
            if (javaRegion == null)
            {
              return null;
            }

            Position correspondingTemplatePointStart = jetEditor.getCorrespondingTemplatePosition(javaRegion.getOffset(), 0);
            if (correspondingTemplatePointStart == null)
            {
              return null;
            }

            Position correspondingTemplatePositionEnd = jetEditor.getCorrespondingTemplatePosition(javaRegion.getOffset() + javaRegion.getLength() - 1, 0);
            if (correspondingTemplatePositionEnd == null)
            {
              return null;
            }

            customAnchor = javaBracketMatcher.getAnchor();
            if (customAnchor != -1)
            {
              return new Region(correspondingTemplatePointStart.offset, correspondingTemplatePositionEnd.offset - correspondingTemplatePointStart.offset + 1);
            }
          }
        }
      }

      customAnchor = -1;
      return null;
    }

    @Override
    public int getAnchor()
    {
      return customAnchor == -1 ? super.getAnchor() : customAnchor;
    }
  }

  static class JETSourceViewerConfiguration extends TextSourceViewerConfiguration
  {
    private final DelegatingContentAssistProcessor delegatingContentAssistProcessor;

    private final DelegatingQuickAssistProcessor delegatingQuickAssistProcessor;

    private final BufferedRuleBasedScanner scanner = new BufferedRuleBasedScanner(1000);

    private final CompletionListener completionListener;

    private final JETEditor jetEditor;

    public JETSourceViewerConfiguration(IPreferenceStore preferenceStore, JETEditor jetEditor)
    {
      super(preferenceStore);

      this.jetEditor = jetEditor;

      delegatingContentAssistProcessor = new DelegatingContentAssistProcessor(jetEditor);
      delegatingQuickAssistProcessor = new DelegatingQuickAssistProcessor(jetEditor);
      completionListener = new CompletionListener(jetEditor);

      scanner.setRules(
        new IRule []{
          new JETCommentRule(),
          new JETExpressionRule(),
          new JETDirectiveRule(),
          new JETScriptletRule(),
          new JETContentRule(),
          new JETTrailingWhitespaceContentRule(),
          new JETCommentErrorRule(),
          new JETExpressionErrorRule(),
          new JETDirectiveErrorRule(),
          new JETScriptletErrorRule() });
    }

    @Override
    public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceViewer)
    {
      PresentationReconciler reconciler = createPresentationReconciler();
      reconciler.setDocumentPartitioning(getConfiguredDocumentPartitioning(sourceViewer));

      DefaultDamagerRepairer damageRepairer = new JETDamageRepairer(jetEditor, scanner);
      reconciler.setDamager(damageRepairer, IDocument.DEFAULT_CONTENT_TYPE);
      reconciler.setRepairer(damageRepairer, IDocument.DEFAULT_CONTENT_TYPE);

      return reconciler;
    }

    protected PresentationReconciler createPresentationReconciler()
    {
      return new JETPresentationReconciler(jetEditor);
    }

    @Override
    @SuppressWarnings("restriction")
    public IAnnotationHover getAnnotationHover(ISourceViewer sourceViewer)
    {
      return new org.eclipse.jdt.internal.ui.text.HTMLAnnotationHover(false)
        {
          @Override
          protected boolean isIncluded(Annotation annotation)
          {
            return isShowInVerticalRuler(annotation);
          }
        };
    }

    @Override
    public IUndoManager getUndoManager(ISourceViewer sourceViewer)
    {
      int undoLevel = fPreferenceStore == null || !fPreferenceStore.contains(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_UNDO_HISTORY_SIZE)
        ? 25 : fPreferenceStore.getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_UNDO_HISTORY_SIZE);
      return new CompositeUndoManager(jetEditor, undoLevel);
    }

    @SuppressWarnings("restriction")
    @Override
    public IContentAssistant getContentAssistant(ISourceViewer sourceViewer)
    {
      ContentAssistant contentAssistant = new ContentAssistant();
      contentAssistant.setInformationControlCreator(getInformationControlCreator(sourceViewer));
      contentAssistant.setContentAssistProcessor(delegatingContentAssistProcessor, IDocument.DEFAULT_CONTENT_TYPE);
      contentAssistant.addCompletionListener(completionListener);

      org.eclipse.jdt.internal.ui.text.ContentAssistPreference.configure(contentAssistant, org.eclipse.jdt.internal.ui.JavaPlugin.getDefault().getPreferenceStore());

      contentAssistant.enableColoredLabels(true);
      return contentAssistant;
    }

    @SuppressWarnings("restriction")
    @Override
    public IQuickAssistAssistant getQuickAssistAssistant(ISourceViewer sourceViewer)
    {
      QuickAssistAssistant assistant = new QuickAssistAssistant();
      assistant.setQuickAssistProcessor(delegatingQuickAssistProcessor);

      try
      {
        // Not available until 3.7, i.e., Helios.
        assistant.getClass().getMethod("setRestoreCompletionProposalSize", IDialogSettings.class).invoke(
          assistant,
          CodeGenUIPlugin.getPlugin().getDialogSettingsSection("quick_assist_proposal_size"));
      }
      catch (Exception exception)
      {
        // Ignore.
      }

      assistant.setInformationControlCreator(new IInformationControlCreator()
        {
          public IInformationControl createInformationControl(Shell parent)
          {
            return new DefaultInformationControl(parent, org.eclipse.ui.internal.editors.text.EditorsPlugin.getAdditionalInfoAffordanceString());
          }
        });
      assistant.addCompletionListener(completionListener);
      return assistant;
    }

    @Override
    public IInformationControlCreator getInformationControlCreator(ISourceViewer sourceViewer)
    {
      return new IInformationControlCreator()
        {
          public IInformationControl createInformationControl(Shell parent)
          {
            return new DefaultInformationControl(parent, false);
          }
        };
    }

    @Override
    public ITextHover getTextHover(ISourceViewer sourceViewer, String contentType)
    {
      return jetEditor.delegatingTextHover;
    }

    @Override
    protected Map<String, IAdaptable> getHyperlinkDetectorTargets(ISourceViewer sourceViewer)
    {
      Map<String, IAdaptable> targets = super.getHyperlinkDetectorTargets(sourceViewer);
      targets.put("org.eclipse.emf.codegen.ui.jetTemplate", jetEditor);
      return targets;
    }

    static class CompletionListener implements ICompletionListener
    {
      private final JETEditor jetEditor;

      public CompletionListener(JETEditor jetEditor)
      {
        this.jetEditor = jetEditor;
      }

      public void assistSessionStarted(ContentAssistEvent event)
      {
        jetEditor.setShowingContentAssist(true);
      }

      public void assistSessionEnded(ContentAssistEvent event)
      {
        jetEditor.setShowingContentAssist(false);
      }

      public void selectionChanged(ICompletionProposal proposal, boolean smartToggle)
      {
      }
    }
  }

  static final class CompositeUndoManager implements IUndoManager, IUndoManagerExtension, ITextInputListener
  {
    private final JETEditor jetEditor;

    private final int undoLevel;

    private int compoundChange;

    private final Map<IDocument, SharedTextViewerUndoManager> managers = new HashMap<IDocument, SharedTextViewerUndoManager>();

    private final IUndoContext undoContext = new IUndoContext()
      {
        public boolean matches(IUndoContext context)
        {
          for (SharedTextViewerUndoManager undoManager : managers.values())
          {
            IUndoContext undoContext = undoManager.getUndoContext();
            if (undoContext.matches(context))
            {
              return true;
            }
          }
          return false;
        }

        public String getLabel()
        {
          return "Shared Undo Context";
        }
      };

    private ITextViewer textViewer;

    private IDocument document;

    public CompositeUndoManager(JETEditor jetEditor, int undoLevel)
    {
      this.jetEditor = jetEditor;
      this.undoLevel = undoLevel;
    }

    protected SharedTextViewerUndoManager getUndoManager()
    {
      return managers.get(document);
    }

    public IUndoContext getUndoContext()
    {
      return undoContext;
    }

    public void connect(ITextViewer textViewer)
    {
      if (this.textViewer == null)
      {
        textViewer.addTextInputListener(this);
      }

      this.textViewer = textViewer;

      inputDocumentAboutToBeChanged(this.document, textViewer.getDocument());
    }

    public void disconnect()
    {
      textViewer.removeTextInputListener(this);
      textViewer = null;
      document = null;

      for (SharedTextViewerUndoManager undoManager : managers.values())
      {
        undoManager.disconnect();
      }

      managers.clear();
    }

    public void reset()
    {
      // This is called when switching documents and we don't want to lose our undo history for each document so don't do anything.
    }

    public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput)
    {
      SharedTextViewerUndoManager undoManager = getUndoManager();
      if (undoManager != null)
      {
        undoManager.removeListeners();
      }
    }

    public void inputDocumentChanged(IDocument oldInput, IDocument newInput)
    {
      this.document = newInput;
      SharedTextViewerUndoManager undoManager = getUndoManager();
      if (undoManager == null)
      {
        undoManager = new SharedTextViewerUndoManager(undoLevel, jetEditor, textViewer, document);
        managers.put(document, undoManager);
      }
      undoManager.addListeners();
    }

    public void beginCompoundChange()
    {
      if (compoundChange++ == 0)
      {
        for (SharedTextViewerUndoManager sharedTextViewerUndoManager : managers.values())
        {
          sharedTextViewerUndoManager.beginCompoundChange();
        }
      }
    }

    public void endCompoundChange()
    {
      if (--compoundChange == 0)
      {
        for (SharedTextViewerUndoManager sharedTextViewerUndoManager : managers.values())
        {
          sharedTextViewerUndoManager.endCompoundChange();
        }
      }
    }

    public void setMaximalUndoLevel(int undoLevel)
    {
      getUndoManager().setMaximalUndoLevel(undoLevel);
    }

    public boolean undoable()
    {
      return getUndoManager().undoable();
    }

    public boolean redoable()
    {
      return getUndoManager().redoable();
    }

    public void undo()
    {
      getUndoManager().undo();
    }

    public void redo()
    {
      getUndoManager().redo();
    }
  }

  static class SharedTextViewerUndoManager implements IUndoManager, IUndoManagerExtension, IDocumentUndoListener, KeyListener, MouseListener
  {
    private final JETEditor jetEditor;

    private final ITextViewer textViewer;

    private final IDocumentUndoManager documentUndoManager;

    private final IDocument document;

    private int undoLevel;

    public SharedTextViewerUndoManager(int undoLevel, JETEditor jetEditor, ITextViewer textViewer, IDocument document)
    {
      this.undoLevel = undoLevel;
      this.jetEditor = jetEditor;
      this.textViewer = textViewer;
      this.document = document;

      DocumentUndoManagerRegistry.connect(document);
      documentUndoManager = DocumentUndoManagerRegistry.getDocumentUndoManager(document);
      documentUndoManager.connect(this);

      setMaximalUndoLevel(undoLevel);

      documentUndoManager.addDocumentUndoListener(this);
    }

    public void beginCompoundChange()
    {
      documentUndoManager.beginCompoundChange();
    }

    public void endCompoundChange()
    {
      documentUndoManager.endCompoundChange();
    }

    public void addListeners()
    {
      StyledText text = textViewer.getTextWidget();
      if (text != null)
      {
        text.addMouseListener(this);
        text.addKeyListener(this);
      }
    }

    public void removeListeners()
    {
      StyledText text = textViewer.getTextWidget();
      if (text != null)
      {
        text.removeMouseListener(this);
        text.removeKeyListener(this);
      }
    }

    public void setMaximalUndoLevel(int newUndoLevel)
    {
      undoLevel = Math.max(0, newUndoLevel);
      documentUndoManager.setMaximalUndoLevel(undoLevel);
    }

    public void connect(ITextViewer newTextViewer)
    {
      // This is never called because we connect during construction.
    }

    public void disconnect()
    {
      removeListeners();
      documentUndoManager.disconnect(this);
      DocumentUndoManagerRegistry.disconnect(document);
      documentUndoManager.removeDocumentUndoListener(this);
    }

    public void reset()
    {
      documentUndoManager.reset();
    }

    public boolean redoable()
    {
      return documentUndoManager.redoable();
    }

    public boolean undoable()
    {
      return documentUndoManager.undoable();
    }

    public void redo()
    {
      try
      {
        documentUndoManager.redo();
      }
      catch (ExecutionException ex)
      {
        openErrorDialog("Redo Failed", ex);
      }
    }

    public void undo()
    {
      try
      {
        documentUndoManager.undo();
      }
      catch (ExecutionException ex)
      {
        openErrorDialog("Undo Failed", ex);
      }
    }

    private void selectAndReveal(int offset, int length)
    {
      jetEditor.selectAndReveal(document, offset, length);
    }

    public IUndoContext getUndoContext()
    {
      return documentUndoManager.getUndoContext();
    }

    private void openErrorDialog(final String title, final Exception ex)
    {
      Shell shell = null;
      StyledText st = textViewer.getTextWidget();
      if (st != null && !st.isDisposed())
      {
        shell = st.getShell();
      }

      if (Display.getCurrent() != null)
      {
        MessageDialog.openError(shell, title, ex.getLocalizedMessage());
      }
      else
      {
        Display display;
        final Shell finalShell = shell;
        if (finalShell != null)
        {
          display = finalShell.getDisplay();
        }
        else
        {
          display = Display.getDefault();
        }
        display.syncExec(new Runnable()
          {
            public void run()
            {
              MessageDialog.openError(finalShell, title, ex.getLocalizedMessage());
            }
          });
      }
    }

    public void mouseDoubleClick(MouseEvent event)
    {
    }

    public void mouseUp(MouseEvent event)
    {
    }

    public void mouseDown(MouseEvent event)
    {
      if (event.button == 1)
      {
        documentUndoManager.commit();
      }
    }

    public void keyReleased(KeyEvent event)
    {
    }

    public void keyPressed(KeyEvent event)
    {
      switch (event.keyCode)
      {
        case SWT.ARROW_UP:
        case SWT.ARROW_DOWN:
        case SWT.ARROW_LEFT:
        case SWT.ARROW_RIGHT:
        {
          documentUndoManager.commit();
          break;
        }
      }
    }

    public void documentUndoNotification(DocumentUndoEvent event)
    {
      int eventType = event.getEventType();
      if ((eventType & DocumentUndoEvent.ABOUT_TO_UNDO) != 0 || (eventType & DocumentUndoEvent.ABOUT_TO_REDO) != 0)
      {
        if (event.isCompound())
        {
          ((ITextViewerExtension)textViewer).setRedraw(false);
        }
      }
      else if (((eventType & DocumentUndoEvent.UNDONE) != 0) || ((eventType & DocumentUndoEvent.REDONE) != 0))
      {
        if (event.isCompound())
        {
          ((ITextViewerExtension)textViewer).setRedraw(true);
        }

        StyledText widget = textViewer.getTextWidget();
        if (widget != null && !widget.isDisposed() && widget.isFocusControl())
        {
          selectAndReveal(event.getOffset(), event.getText() == null ? 0 : event.getText().length());
        }
      }
    }
  }

  static class JETDamageRepairer extends DefaultDamagerRepairer
  {
    private final JETEditor jetEditor;

    private int capacity = 8;

    private int size = 0;

    private int[] offsets = new int [capacity];

    private JETToken[] tokens = new JETToken [capacity];

    public JETDamageRepairer(JETEditor jetEditor, ITokenScanner scanner)
    {
      super(scanner);
      this.jetEditor = jetEditor;
    }

    private void ensureCapacity(int capacity)
    {
      if (this.capacity < capacity)
      {
        int newCapacity = capacity << 1;
        int[] newOffsets = new int [newCapacity];
        JETToken[] newTokens = new JETToken [newCapacity];
        System.arraycopy(offsets, 0, newOffsets, 0, this.capacity);
        System.arraycopy(tokens, 0, newTokens, 0, this.capacity);

        this.offsets = newOffsets;
        this.tokens = newTokens;
        this.capacity = newCapacity;
      }
    }

    private void add(int offset, int length, JETToken token)
    {
      ensureCapacity(size + 2);
      tokens[size] = token;
      offsets[size] = offset;
      tokens[++size] = null;
      offsets[size] = offset + length;
    }

    @Override
    public void createPresentation(TextPresentation presentation, ITypedRegion region)
    {
      try
      {
        super.createPresentation(presentation, region);
      }
      finally
      {
        int[] offsets = new int [size + 1];
        JETToken[] tokens = new JETToken [size + 1];

        System.arraycopy(this.offsets, 0, offsets, 0, size + 1);
        System.arraycopy(this.tokens, 0, tokens, 0, size + 1);

        jetEditor.setTokenData(offsets, tokens);

        size = 0;
        this.offsets[0] = 0;
        this.offsets[1] = 0;
      }
    }

    @Override
    protected void addRange(TextPresentation presentation, int offset, int length, TextAttribute textAttribute)
    {
      if (textAttribute instanceof ColorManager.JETTextAttribute)
      {
        JETEditor.ColorManager.JETTextAttribute jetTextAttribute = (ColorManager.JETTextAttribute)textAttribute;
        JETToken token = jetTextAttribute.getToken();
        add(offset, length, token);

        int style = textAttribute.getStyle();
        int fontStyle = style & (SWT.ITALIC | SWT.BOLD | SWT.NORMAL);
        StyleRange styleRange = new StyleRange(offset, length, textAttribute.getForeground(), textAttribute.getBackground(), fontStyle);
        styleRange.strikeout = (style & TextAttribute.STRIKETHROUGH) != 0;
        styleRange.underline = (style & TextAttribute.UNDERLINE) != 0;
        styleRange.font = textAttribute.getFont();
        styleRange.data = token;
        styleRange.borderStyle = jetTextAttribute.getBorderStyle();
        if (styleRange.borderStyle != SWT.NONE)
        {
          styleRange.borderColor = styleRange.foreground;
        }

        presentation.addStyleRange(styleRange);
      }
    }
  }

  static class JETTokenRegion
  {
    private final JETToken token;

    private int offset;

    private int length;

    public JETTokenRegion(int offset, int length, JETToken token)
    {
      this.offset = offset;
      this.length = length;
      this.token = token;
    }

    public JETToken getToken()
    {
      return token;
    }

    public void shrinkToJava(IDocument document)
    {
      if (token == JETScriptletRule.TOKEN)
      {
        int delta = 0;

        try
        {
          while (Character.isWhitespace(document.getChar(offset + delta)))
          {
            ++delta;
          }
        }
        catch (BadLocationException exception)
        {
          // Should not happen.
        }

        offset += delta + 2;
        length -= delta + 2;
      }
      else if (token == JETExpressionRule.TOKEN)
      {
        offset += 3;
        length -= 5;
      }
    }

    public void expand()
    {
      --offset;
      length += 2;
    }

    public int getOffset()
    {
      return offset;
    }

    public void setOffset(int offset)
    {
      this.offset = offset;
    }

    public int getLength()
    {
      return length;
    }

    public void setLength(int length)
    {
      this.length = length;
    }
  }

  static class JETPresentationReconciler extends PresentationReconciler
  {
    private final JETEditor jetEditor;

    private IDocument document;

    public JETPresentationReconciler(JETEditor jetEditor)
    {
      this.jetEditor = jetEditor;
    }

    @Override
    public TextPresentation createPresentation(IRegion damage, IDocument document)
    {
      if (document != this.document)
      {
        setDocument(document);
      }

      List<JETJavaItem> javaItems = null;
      List<JETRootItem> skippedItems = null;
      ITextViewerExtension5 javaViewer = null;
      StyledText javaStyledText = null;
      if (jetEditor.isJavaEditorInSync())
      {
        JETCompilationUnit compilerResult = jetEditor.getCompilationUnit();
        if (compilerResult != null)
        {
          ISourceViewer underlyingSourceViewer = jetEditor.getJavaEditor().getJavaSourceViewer();
          if (compilerResult.getJavaCompilationUnit().equals(underlyingSourceViewer.getDocument().get()))
          {
            int fileID = jetEditor.getFileID();
            int templateOffset = damage.getOffset();
            int templateLength = damage.getLength();
            javaItems = compilerResult.getJavaItems(fileID, templateOffset, templateLength);
            skippedItems = compilerResult.getSkippedItems(fileID, templateOffset, templateLength);
            javaStyledText = underlyingSourceViewer.getTextWidget();
            javaViewer = (ITextViewerExtension5)underlyingSourceViewer;
          }
        }
      }

      // Create the full presentation because we can only find tokens if we start from the start.
      // 
      Region fullRegion = new Region(0, document.getLength());
      TextPresentation fullTextPresentation = super.createPresentation(fullRegion, document);

      if (javaItems == null && damage.equals(fullRegion))
      {
        return fullTextPresentation;
      }

      // Filter these down to the ones within the actual damaged region because updating so many takes long.
      //
      TextPresentation textPresentation = new TextPresentation(damage, 1000);
      int offset = damage.getOffset();
      int end = offset + damage.getLength();
      for (Iterator<StyleRange> i = fullTextPresentation.getAllStyleRangeIterator(); i.hasNext();)
      {
        StyleRange styleRange = i.next();
        if (styleRange.start + styleRange.length >= offset && styleRange.start <= end)
        {
          if (javaItems == null)
          {
            textPresentation.addStyleRange(styleRange);
          }
          else
          {
            applySemanticStyle(textPresentation, styleRange, javaItems, skippedItems, javaStyledText, javaViewer);
          }
        }
      }

      return textPresentation;
    }

    private void applySemanticStyle(
      TextPresentation textPresentation,
      StyleRange styleRange,
      List<JETJavaItem> javaItems,
      List<JETRootItem> skippedItems,
      StyledText textWidget,
      ITextViewerExtension5 javaModelViewer)
    {
      IToken token = (IToken)styleRange.data;
      int start = styleRange.start;
      int end = styleRange.start + styleRange.length;
      for (JETRootItem jetRootItem : skippedItems)
      {
        if (jetRootItem.getStartOffset() < end && jetRootItem.getStopOffset() > start)
        {
          styleRange.strikeout = true;
          Color blendedColor = ColorManager.INSTANCE.getBlendedColor(styleRange.foreground, styleRange.background);
          styleRange.strikeoutColor = blendedColor;
          styleRange.foreground = blendedColor;
          break;
        }
      }

      Color defaultForeground = textWidget.getForeground();

      // Only do this work for scriptlets and expressions.
      if (token == JETScriptletRule.TOKEN || token == JETExpressionRule.TOKEN)
      {
        for (JETJavaItem javaItem : javaItems)
        {
          int delta = javaItem.getStartOffset() - start;
          if (delta >= 0 && javaItem.getStopOffset() <= end)
          {
            int javaOffset = javaItem.getJavaOffset();
            int javaLength = javaItem.getJavaLength();
            int javaWidgetOffset = javaModelViewer.modelOffset2WidgetOffset(javaOffset);

            StyleRange[] styleRanges = textWidget.getStyleRanges(javaWidgetOffset, javaLength);

            if (trace)
            {
              String text = javaItem.getText();
              String javaText = textWidget.getTextRange(javaWidgetOffset, javaLength);
              if (!text.equals(javaText))
              {
                if (trace)
                {
                  System.out.println("mismatched text when applying styles");
                }
                break;
              }
            }

            List<StyleRange> newStyleRanges = new ArrayList<StyleRange>();

            // Whatever text range appears before the start of the Java style range keeps the existing style.
            // This will definitely include the leading <%= or the leading <% and any pure whitespace before it.
            //
            StyleRange leadingNewStyleRanage = (StyleRange)styleRange.clone();
            leadingNewStyleRanage.length = delta;
            newStyleRanges.add(leadingNewStyleRanage);
            start += delta;

            int consumedLength = 0;
            for (StyleRange javaStyleRange : styleRanges)
            {
              StyleRange newStyleRange = (StyleRange)styleRange.clone();
              newStyleRange.start = start + consumedLength;
              newStyleRange.length = javaStyleRange.length;
              if (javaStyleRange.foreground == null || !javaStyleRange.foreground.equals(defaultForeground))
              {
                newStyleRange.foreground = javaStyleRange.foreground;
              }
              newStyleRange.foreground = newStyleRange.strikeout
                ? ColorManager.INSTANCE.getBlendedColor(newStyleRange.foreground, newStyleRange.background) : newStyleRange.foreground;
              newStyleRange.fontStyle = javaStyleRange.fontStyle;
              consumedLength += newStyleRange.length;
              newStyleRanges.add(newStyleRange);
            }

            // This will include the style range for the %>
            //
            StyleRange trailingNewStyleRange = (StyleRange)styleRange.clone();
            trailingNewStyleRange.start = start + consumedLength;
            trailingNewStyleRange.length = end - trailingNewStyleRange.start;
            newStyleRanges.add(trailingNewStyleRange);

            for (StyleRange newStyleRange : newStyleRanges)
            {
              textPresentation.addStyleRange(newStyleRange);
            }

            return;
          }
        }
      }

      // If we don't find a corresponding Java item, just handle it like normal.
      textPresentation.addStyleRange(styleRange);
    }

    protected void setDocument(IDocument document)
    {
      setDocumentToDamagers(document);
      setDocumentToRepairers(document);
      this.document = document;
    }
  }

  static class BracketInserter implements VerifyKeyListener
  {
    private final JETEditor jetEditor;

    private final ISelectionProvider jetSelectionProvider;

    public BracketInserter(JETEditor jetEditor)
    {
      this.jetEditor = jetEditor;
      jetSelectionProvider = jetEditor.getSelectionProvider();
    }

    private boolean edit(ITextViewer jetSourceViewer, int offset, int length, String replacement, int selectionOffset)
    {
      try
      {
        IDocument document = jetSourceViewer.getDocument();
        document.replace(offset, length, replacement);
        jetSourceViewer.setSelectedRange(selectionOffset, 0);
        return false;
      }
      catch (BadLocationException exception)
      {
        return true;
      }
    }

    public void verifyKey(VerifyEvent event)
    {
      if (event.doit && jetEditor.getInsertMode() == SMART_INSERT && !isMultilineSelection())
      {
        ISourceViewer jetSourceViewer = jetEditor.getSourceViewer();
        final Point selection = jetSourceViewer.getSelectedRange();
        final int offset = selection.x;
        final int length = selection.y;
        if (length == 0 && offset > 0)
        {
          if (event.character == '%')
          {
            JETToken tokenAtSelection = jetEditor.getTokenAtSelection(0);
            JETToken tokenBeforeSelection = jetEditor.getTokenAtSelection(-1);
            if (tokenAtSelection != null && tokenAtSelection.getBaseToken() == JETContentRule.TOKEN
                || tokenBeforeSelection != null && tokenBeforeSelection.getBaseToken() == JETContentRule.TOKEN)
            {
              String text = jetSourceViewer.getTextWidget().getTextRange(offset - 1, 1);
              if ("<".equals(text))
              {
                event.doit = edit(jetSourceViewer, offset, 0, "%%>", offset + 1);
              }
            }
          }
          else if (event.character == '-')
          {
            JETToken token = jetEditor.getTokenAt(offset);
            if (token == JETScriptletRule.TOKEN)
            {
              IDocument document = jetSourceViewer.getDocument();
              try
              {
                if ("<%%>".equals(document.get(offset - 2, 4)))
                {
                  event.doit = edit(jetSourceViewer, offset, 0, "--", offset + 1);
                }
              }
              catch (BadLocationException e)
              {
              }
            }
          }
          else if (event.character == '>')
          {
            JETToken tokenAtSelection = jetEditor.getTokenAtSelection(0);
            if (tokenAtSelection == JETScriptletRule.TOKEN)
            {
              IDocument document = jetSourceViewer.getDocument();
              try
              {
                char character = document.getChar(offset - 1);
                if (character == '%')
                {
                  int documentLength = document.getLength();
                  StringBuilder additionalWhiteSpace = new StringBuilder();
                  for (int i = offset; i < documentLength; ++i)
                  {
                    character = document.getChar(i);
                    if (!Character.isWhitespace(character))
                    {
                      // {
                      if (character != '}')
                      {
                        additionalWhiteSpace.setLength(0);
                      }
                      break;
                    }
                    additionalWhiteSpace.append(character);
                  }

                  event.doit = edit(jetSourceViewer, offset, additionalWhiteSpace.length(), ">" + additionalWhiteSpace + "<%", offset + 1);
                }
              }
              catch (BadLocationException e)
              {
              }
            }
          }
          else if (event.keyCode == 0x8)
          {
            JETTokenRegion tokenRegion = jetEditor.getTokenRegionAt(offset);
            if (tokenRegion != null)
            {
              JETToken token = tokenRegion.getToken();
              if (token == JETCommentRule.TOKEN)
              {
                if (offset - tokenRegion.getOffset() == 3 && tokenRegion.getLength() == 6)
                {
                  event.doit = edit(jetSourceViewer, offset - 1, 2, "", offset - 1);
                }
              }
              else if (token == JETScriptletRule.TOKEN)
              {
                if (offset - tokenRegion.getOffset() == 2 && tokenRegion.getLength() == 4)
                {
                  event.doit = edit(jetSourceViewer, offset - 1, 3, "", offset - 1);
                }
              }
            }
          }
        }
      }
    }

    private boolean isMultilineSelection()
    {
      ISelection selection = jetSelectionProvider.getSelection();
      if (selection instanceof ITextSelection)
      {
        ITextSelection ts = (ITextSelection)selection;
        return ts.getStartLine() != ts.getEndLine();
      }
      return false;
    }
  }

  static final class JETToken extends Token
  {
    private final String type;

    private final int textStyle;

    private final int borderStyle;

    private final JETToken baseToken;

    public JETToken(String type)
    {
      this(type, SWT.NONE, SWT.NONE);
    }

    public JETToken(String type, int textStyle, int borderStyle)
    {
      super(null);
      this.type = type;
      this.textStyle = textStyle;
      this.borderStyle = borderStyle;
      this.baseToken = this;
      ColorManager.INSTANCE.register(this);
    }

    public JETToken(JETToken baseToken, String type, int textStyle)
    {
      this(baseToken, type, textStyle, SWT.NONE);
    }

    public JETToken(JETToken baseToken, String type, int textStyle, int borderStyle)
    {
      super(null);
      this.type = type + '.' + baseToken.getType();
      this.textStyle = textStyle;
      this.baseToken = baseToken;
      this.borderStyle = borderStyle;
      ColorManager.INSTANCE.register(this);
    }

    public final String getType()
    {
      return type;
    }

    public final JETToken getBaseToken()
    {
      return baseToken;
    }

    public final int getTextStyle()
    {
      return textStyle;
    }

    public final int getBorderStyle()
    {
      return borderStyle;
    }

    public final void setData(TextAttribute textAttribute)
    {
      super.setData(textAttribute);
    }

    @Override
    public final void setData(Object data)
    {
      super.setData((TextAttribute)data);
    }

    @Override
    public final String toString()
    {
      return "JETToken:" + type;
    }

    public final IRegion match(Point tokenRange, int offset)
    {
      int prefixLength = -1;
      if (type.equals("scriptlet"))
      {
        prefixLength = 2;
      }
      else if (type.equals("expression") || type.equals("directive") || type.equals("comment"))
      {
        prefixLength = 3;
      }

      if (prefixLength != -1)
      {
        if (offset == tokenRange.x + tokenRange.y || offset == tokenRange.x + 1)
        {
          return new Region(tokenRange.x, tokenRange.y);
        }

        if (offset == tokenRange.x + tokenRange.y - 1 || offset == tokenRange.x + 2)
        {
          return new Region(tokenRange.x + 1, tokenRange.y - 2);
        }

        if (prefixLength == 3 && (offset == tokenRange.x + tokenRange.y - 1 || offset == tokenRange.x + 3))
        {
          return new Region(tokenRange.x + 2, tokenRange.y - 3);
        }
      }

      return null;
    }
  }

  static abstract class JETRule
  {
    private final JETToken successToken;

    private final char[] prefix;

    private int count;

    public JETRule(JETToken successToken, char[] prefix)
    {
      this.successToken = successToken;
      this.prefix = prefix;
    }

    protected int getCount()
    {
      return count;
    }

    public final JETToken getSuccessToken()
    {
      return successToken;
    }

    protected boolean scanForStart(ICharacterScanner scanner)
    {
      for (int i = 0; i < prefix.length; ++i)
      {
        if (prefix[i] != read(scanner))
        {
          return false;
        }
      }
      return true;
    }

    protected boolean scanForEnd(ICharacterScanner scanner)
    {
      for (int character = read(scanner); character != ICharacterScanner.EOF; character = read(scanner))
      {
        if (character == '%')
        {
          if (read(scanner) == '>')
          {
            return true;
          }

          unread(scanner);
        }
      }

      return false;
    }

    protected int read(ICharacterScanner scanner)
    {
      ++count;
      int read = scanner.read();

      if (trace)
      {
        if (read == ICharacterScanner.EOF)
        {
          System.out.print("'eof'");
        }
        else if (read == ' ')
        {
          System.out.print("~");
        }
        else if (read == '\n')
        {
          System.out.print("\\n");
        }
        else if (read == '\r')
        {
          System.out.print("\\r");
        }
        else
        {
          System.out.print((char)read);
        }
      }

      return read;
    }

    protected void unread(ICharacterScanner scanner)
    {
      --count;
      scanner.unread();
    }

    protected void unreadAll(ICharacterScanner scanner)
    {
      while (count-- > 0)
      {
        scanner.unread();
      }
      count = 0;
    }

    protected boolean isGreedy()
    {
      return true;
    }

    protected void skipLeading(ICharacterScanner scanner)
    {
      if (trace)
      {
        System.out.println("-----------");
      }

      if (isGreedy())
      {
        for (int read = read(scanner); read == ' ' || read == '\t'; read = read(scanner))
        {
        }

        unread(scanner);
      }
    }

    public final IToken evaluate(ICharacterScanner scanner)
    {
      try
      {
        return doEvaluate(scanner);
      }
      finally
      {
        count = 0;
      }
    }

    protected IToken doEvaluate(ICharacterScanner scanner)
    {
      skipLeading(scanner);
      if (scanForStart(scanner))
      {
        if (scanForEnd(scanner))
        {
          return getSuccessToken();
        }
      }

      unreadAll(scanner);

      return Token.UNDEFINED;
    }
  }

  static class JETDirectiveRule extends JETRule implements IRule
  {
    private static final char[] PREFIX = new char []{ '<', '%', '@' };

    public static final JETToken TOKEN = new JETToken("directive");

    @Override
    protected boolean isGreedy()
    {
      return false;
    }

    public JETDirectiveRule()
    {
      super(TOKEN, PREFIX);
    }
  }

  static class JETScriptletRule extends JETRule implements IRule
  {
    private static final char[] PREFIX = new char []{ '<', '%' };

    public static final JETToken TOKEN = new JETToken("scriptlet");

    public JETScriptletRule()
    {
      super(TOKEN, PREFIX);
    }

    @Override
    protected boolean scanForStart(ICharacterScanner scanner)
    {
      if (super.scanForStart(scanner))
      {
        int read = read(scanner);
        unread(scanner);
        if (read == '@' || read == '=' || read == '-')
        {
          return false;
        }
        else
        {
          return true;
        }
      }
      else
      {
        return false;
      }
    }
  }

  static class JETCommentRule extends JETRule implements IRule
  {
    private static final char[] PREFIX = new char []{ '<', '%', '-' };

    public static final JETToken TOKEN = new JETToken("comment");

    public JETCommentRule()
    {
      super(TOKEN, PREFIX);
    }

    @Override
    protected boolean scanForEnd(ICharacterScanner scanner)
    {
      for (int character = read(scanner); character != ICharacterScanner.EOF; character = read(scanner))
      {
        if (character == '-')
        {
          if (read(scanner) == '%')
          {
            if (read(scanner) == '>')
            {
              return true;
            }

            unread(scanner);
          }
          else
          {
            unread(scanner);
          }
        }
      }

      return false;
    }
  }

  static class JETExpressionRule extends JETRule implements IRule
  {
    private static final char[] PREFIX = new char []{ '<', '%', '=' };

    public static final JETToken TOKEN = new JETToken("expression");

    public JETExpressionRule()
    {
      super(TOKEN, PREFIX);
    }

    @Override
    protected boolean isGreedy()
    {
      return false;
    }
  }

  static class JETContentRule extends JETRule implements IRule
  {
    private static final char[] PREFIX = new char [0];

    public static final JETToken TOKEN = new JETToken("content");

    public JETContentRule()
    {
      super(TOKEN, PREFIX);
    }

    @Override
    protected IToken doEvaluate(ICharacterScanner scanner)
    {
      int trailingWhitespaceCount = 0;
      for (int read = read(scanner); read != ICharacterScanner.EOF; read = read(scanner))
      {
        if (read == '<')
        {
          if (read(scanner) == '%')
          {
            unread(scanner);
            unread(scanner);
            if (getCount() == 0)
            {
              return Token.UNDEFINED;
            }
            else
            {
              return getSuccessToken();
            }
          }
          else
          {
            unread(scanner);
          }
        }
        else if (read == '\n')
        {
          if (trailingWhitespaceCount != 0)
          {
            for (int i = 0; i <= trailingWhitespaceCount; ++i)
            {
              unread(scanner);
            }

            return getCount() == 0 ? Token.UNDEFINED : getSuccessToken();
          }
          else
          {
            return getSuccessToken();
          }
        }
        else if (read == '\r')
        {
          if (trailingWhitespaceCount != 0)
          {
            ++trailingWhitespaceCount;
          }
        }
        else if (read == ' ' || read == '\t')
        {
          ++trailingWhitespaceCount;
        }
        else
        {
          trailingWhitespaceCount = 0;
        }
      }

      if (getCount() == 1)
      {
        return Token.EOF;
      }
      else
      {
        if (trailingWhitespaceCount != 0)
        {
          for (int i = 0; i <= trailingWhitespaceCount; ++i)
          {
            unread(scanner);
          }
        }
        return getCount() == 0 ? Token.UNDEFINED : getSuccessToken();
      }
    }
  }

  static class JETTrailingWhitespaceContentRule extends JETRule implements IRule
  {
    private static final char[] PREFIX = new char [0];

    public static final JETToken TOKEN = new JETToken(JETContentRule.TOKEN, "trailing-whitespace-content", SWT.NONE, SWT.BORDER_SOLID);

    public JETTrailingWhitespaceContentRule()
    {
      super(TOKEN, PREFIX);
    }

    @Override
    protected IToken doEvaluate(ICharacterScanner scanner)
    {
      for (int read = read(scanner); read != ICharacterScanner.EOF; read = read(scanner))
      {
        if (read == '\n')
        {
          return getSuccessToken();
        }
        else if (read != ' ' && read != '\t' && read != '\r')
        {
          unreadAll(scanner);
          return Token.UNDEFINED;
        }
      }

      return getCount() == 1 ? Token.EOF : getSuccessToken();
    }
  }

  static class JETBaseErrorRule extends JETRule implements IRule
  {
    public JETBaseErrorRule(JETToken successToken, char[] prefix)
    {
      super(successToken, prefix);
    }

    @Override
    protected IToken doEvaluate(ICharacterScanner scanner)
    {
      if (scanForStart(scanner))
      {
        for (int read = read(scanner); read != ICharacterScanner.EOF; read = read(scanner))
        {
        }

        return getSuccessToken();
      }

      if (read(scanner) == ICharacterScanner.EOF)
      {
        return Token.EOF;
      }
      else
      {
        unreadAll(scanner);
        return Token.UNDEFINED;
      }
    }
  }

  static class JETScriptletErrorRule extends JETBaseErrorRule
  {
    public static final JETToken TOKEN = new JETToken(JETScriptletRule.TOKEN, "error", TextAttribute.STRIKETHROUGH);

    public JETScriptletErrorRule()
    {
      super(TOKEN, JETScriptletRule.PREFIX);
    }
  }

  static class JETExpressionErrorRule extends JETBaseErrorRule
  {
    public static final JETToken TOKEN = new JETToken(JETExpressionRule.TOKEN, "error", TextAttribute.STRIKETHROUGH);

    public JETExpressionErrorRule()
    {
      super(TOKEN, JETExpressionRule.PREFIX);
    }
  }

  static class JETCommentErrorRule extends JETBaseErrorRule
  {
    public static final JETToken TOKEN = new JETToken(JETCommentRule.TOKEN, "error", TextAttribute.STRIKETHROUGH);

    public JETCommentErrorRule()
    {
      super(TOKEN, JETCommentRule.PREFIX);
    }
  }

  static class JETDirectiveErrorRule extends JETBaseErrorRule
  {
    public static final JETToken TOKEN = new JETToken(JETDirectiveRule.TOKEN, "error", TextAttribute.STRIKETHROUGH);

    public JETDirectiveErrorRule()
    {
      super(TOKEN, JETDirectiveRule.PREFIX);
    }
  }

  @SuppressWarnings("restriction")
  static class JETBreadcrumbViewer extends org.eclipse.jdt.internal.ui.javaeditor.breadcrumb.BreadcrumbViewer
  {
    public JETBreadcrumbViewer(Composite parent, int style)
    {
      super(parent, style);

      addDoubleClickListener(new IDoubleClickListener()
        {
          public void doubleClick(DoubleClickEvent event)
          {
            Object element = ((IStructuredSelection)event.getSelection()).getFirstElement();
            Widget item = findItem(((ITreeContentProvider)getContentProvider()).getParent(element));
            if (item != null)
            {
              try
              {
                Method openDropDownMenuMethod = item.getClass().getDeclaredMethod("openDropDownMenu");
                openDropDownMenuMethod.setAccessible(true);
                openDropDownMenuMethod.invoke(item);
              }
              catch (Exception ex)
              {
                // Ignore.
              }
            }
          }
        });
    }

    @Override
    protected void configureDropDownViewer(TreeViewer viewer, Object input)
    {
    }

    @Override
    public Control getControl()
    {
      // Ensure the callers don't get warnings about restricted access.
      return super.getControl();
    }
  }

  static class JETTextSelectionNavigationLocation extends NavigationLocation
  {
    private static final String TAG_X = "x";

    private static final String TAG_Y = "y";

    private static final String TAG_DOCUMENT_ID = "document_id";

    private static final String TAG_INFO = "info";

    private static final String INFO_DELETED = "deleted";

    private static final String INFO_NOT_DELETED = "not_deleted";

    private static final String CATEGORY = "__navigation_" + JETTextSelectionNavigationLocation.class.hashCode();

    private static final IPositionUpdater POSITION_UPDATER = new DefaultPositionUpdater(CATEGORY);

    private Position position;

    private String documentID;

    private IDocument document;

    private Position savedPosition;

    public JETTextSelectionNavigationLocation(JETEditor jetEditor, boolean initialize)
    {
      super(jetEditor);

      if (initialize)
      {
        ITextSelection selection = (ITextSelection)jetEditor.getSelectionProvider().getSelection();
        IDocument document = getDocument(jetEditor);
        Position position = new Position(selection.getOffset(), selection.getLength());
        if (installOnDocument(document, position, CATEGORY, POSITION_UPDATER))
        {
          this.document = document;
          this.documentID = getDocumentID(jetEditor);
          Assert.isNotNull(this.documentID, "The document ID must be specified");
          this.position = position;
          if (!jetEditor.isDirty())
          {
            savedPosition = new Position(position.offset, position.length);
          }
        }
      }
    }

    public String getDocumentID()
    {
      return documentID;
    }

    private String getDocumentID(JETEditor jetEditor)
    {
      return jetEditor.getDocumentManager().getDocumentID();
    }

    private IDocument getDocument(JETEditor jetEditor)
    {
      return jetEditor.getSourceViewer().getDocument();
    }

    @Override
    public void dispose()
    {
      free();
      super.dispose();
    }

    private void free()
    {
      uninstallFromDocument(document, position, CATEGORY, POSITION_UPDATER);
      document = null;
      position = null;
      savedPosition = null;
      documentID = null;

    }

    @Override
    public void releaseState()
    {
      free();
      super.releaseState();
    }

    public boolean mergeInto(INavigationLocation location)
    {
      if (location == null || location.getClass() != getClass())
      {
        return false;
      }

      JETTextSelectionNavigationLocation jetLocation = (JETTextSelectionNavigationLocation)location;
      if (!jetLocation.documentID.equals(documentID))
      {
        return false;
      }

      if (position == null || position.isDeleted || document == null)
      {
        return true;
      }

      if (jetLocation.position == null || jetLocation.position.isDeleted)
      {
        uninstallFromDocument(document, position, CATEGORY, POSITION_UPDATER);
        jetLocation.document = document;
        jetLocation.position = position;
        jetLocation.savedPosition = savedPosition;
        jetLocation.documentID = documentID;
        return true;
      }

      if (jetLocation.document == document)
      {
        if (jetLocation.position.overlapsWith(position.offset, position.length) || position.offset + position.length == jetLocation.position.offset
            || jetLocation.position.offset + jetLocation.position.length == position.offset)
        {
          jetLocation.position.offset = position.offset;
          jetLocation.position.length = position.length;
          return true;
        }
      }

      return false;
    }

    public void restoreLocation()
    {
      if (position != null && !position.isDeleted)
      {
        IEditorPart part = getEditorPart();
        if (part instanceof JETEditor)
        {
          JETEditor editor = (JETEditor)getEditorPart();
          editor.selectAndReveal(this, position.offset, position.length);
        }
      }
    }

    public void saveState(IMemento memento)
    {
      if (savedPosition != null)
      {
        memento.putInteger(TAG_X, savedPosition.offset);
        memento.putInteger(TAG_Y, savedPosition.length);
        memento.putString(TAG_INFO, (savedPosition.isDeleted ? INFO_DELETED : INFO_NOT_DELETED));
        memento.putString(TAG_DOCUMENT_ID, documentID);
      }
    }

    public void restoreState(IMemento memento)
    {
      IEditorPart part = getEditorPart();
      if (part instanceof JETEditor)
      {
        documentID = memento.getString(TAG_DOCUMENT_ID);

        Integer offset = memento.getInteger(TAG_X);
        Integer length = memento.getInteger(TAG_Y);
        String deleted = memento.getString(TAG_INFO);

        if (offset != null && length != null)
        {
          Position position = new Position(offset.intValue(), length.intValue());
          this.position = position;
          if (deleted != null)
          {
            position.isDeleted = INFO_DELETED.equals(deleted) ? true : false;
          }
        }
      }
    }

    public void restoreDocument(JETEditor jetEditor, IDocument document)
    {
      this.document = document;
      if (installOnDocument(document, position, CATEGORY, POSITION_UPDATER))
      {
        if (!jetEditor.isDirty())
        {
          savedPosition = new Position(position.offset, position.length);
        }
      }
    }

    public void partSaved(IEditorPart part)
    {
      savedPosition = position == null || position.isDeleted() ? null : new Position(position.offset, position.length);
    }

    private boolean samePosition(JETEditor jetEditor)
    {
      if (position == null)
      {
        return true;
      }

      if (position.isDeleted)
      {
        return false;
      }

      if (!getDocumentID(jetEditor).equals(documentID))
      {
        return false;
      }

      ITextSelection textSelection = (ITextSelection)jetEditor.getSelectionProvider().getSelection();
      if (textSelection.getOffset() == position.offset && textSelection.getLength() == position.length)
      {
        String text = textSelection.getText();
        if (text != null)
        {
          try
          {
            return text.equals(document.get(position.offset, position.length));
          }
          catch (BadLocationException exception)
          {
          }
        }
      }

      return false;
    }

    public void update()
    {
      IEditorPart part = getEditorPart();
      if (part instanceof JETEditor)
      {
        JETEditor textEditor = (JETEditor)part;

        if (!samePosition(textEditor))
        {
          ITextSelection selection = (ITextSelection)textEditor.getSelectionProvider().getSelection();
          if (selection.getOffset() != 0 || selection.getLength() != 0)
          {
            position.offset = selection.getOffset();
            position.length = selection.getLength();
            position.isDeleted = false;

            if (!part.isDirty())
            {
              savedPosition = new Position(position.offset, position.length);
            }
          }
        }
      }
    }

    @Override
    public String toString()
    {
      return "Selection<" + position + ">";
    }
  }

  static class JETDynamicCompilerJob extends Job implements IDocumentListener
  {
    private final JETEditor jetEditor;

    private final IFile file;

    private volatile boolean disabled;

    public JETDynamicCompilerJob(JETEditor jetEditor, IFile file)
    {
      super("JET Dynamic Compiler");
      setSystem(true);
      this.jetEditor = jetEditor;
      this.file = file;
    }

    public void setDisabled(boolean disabled)
    {
      this.disabled = disabled;
      if (disabled)
      {
        cancel();
      }
      else
      {
        documentChanged(null);
      }
    }

    @Override
    protected IStatus run(IProgressMonitor monitor)
    {
      if (!disabled)
      {
        try
        {
          final WorkingCopyCompilationMonitor compilationMonitor = new WorkingCopyCompilationMonitor();
          JETCompileTemplateOperation jetCompileTemplateOperation = new JETCompileTemplateOperation(file, compilationMonitor)
            {
              @Override
              public void run(IProgressMonitor progressMonitor) throws CoreException
              {
                if (files.isEmpty())
                {
                  files.add(file);
                }

                super.run(progressMonitor);
              }
            };

          long startTime = System.currentTimeMillis();
          jetCompileTemplateOperation.run(monitor);
          if (!monitor.isCanceled())
          {
            final JETCompilationUnit compilerResult = compilationMonitor.getResult();
            if (compilerResult != null)
            {
              JETNature nature = jetEditor.getJETNature();

              IContainer sourceContainer = nature.getJavaSourceContainer();
              IProject project = sourceContainer.getProject();
              IJavaProject javaProject = JavaCore.create(project);

              String className = compilerResult.getClassName();
              final IFile javaFile = sourceContainer.getFile(new Path(className.replace('.', '/') + ".java"));

              ICompilationUnit compilationUnit = JavaCore.createCompilationUnitFrom(javaFile);
              LocalWorkingCopy localWorkingCopy = new LocalWorkingCopy(compilerResult.getJavaCompilationUnit());
              ICompilationUnit workingCopy = compilationUnit.getWorkingCopy(localWorkingCopy, null);

              ASTParser astParser = CodeGenUtil.EclipseUtil.newASTParser(true);
              String fileExtension = file.getFileExtension();
              if (fileExtension != null && fileExtension.endsWith("jet"))
              {
                astParser.setResolveBindings(true);
              }
              astParser.setProject(javaProject);

              ASTRequestor astRequestor = new ASTRequestor()
                {
                  @Override
                  public void acceptAST(ICompilationUnit sourceUnit, CompilationUnit compiledUnit)
                  {
                    IProblem[] problems = compiledUnit.getProblems();
                    JETCompilerResult jetCompilerResult = new JETCompilerResult(javaFile, compilerResult, Arrays.asList(problems), compilationMonitor.getException());
                    handleJavaResult(jetCompilerResult);
                  }
                };

              astParser.createASTs(new ICompilationUnit []{ workingCopy }, new String [0], astRequestor, null);
            }
            else if (compilationMonitor.getException() != null)
            {
              JETCompilerResult jetCompilerResult = new JETCompilerResult(compilationMonitor.getException());
              handleJavaResult(jetCompilerResult);
            }
          }

          long stop = System.currentTimeMillis();
          if (trace)
          {
            System.out.println("elapsed=" + (stop - startTime));
          }
        }
        catch (CoreException exception)
        {
          CodeGenUIPlugin.getPlugin().log(exception);
        }
      }

      return Status.OK_STATUS;
    }

    private void handleJavaResult(final JETCompilerResult jetCompilerResult)
    {
      if (!disabled)
      {
        jetEditor.handleCompilerResult(jetCompilerResult);
      }
    }

    public void documentAboutToBeChanged(DocumentEvent event)
    {
      if (!disabled)
      {
        cancel();
      }
    }

    public void documentChanged(DocumentEvent event)
    {
      if (!disabled)
      {
        jetEditor.resetExpectedModificationStamp();
        schedule(100);
      }
    }
  }

  static class JETCompilerResult
  {
    private final IFile javaFile;

    private final JETCompilationUnit compilationUnit;

    private final List<IProblem> javaProblems;

    private final JETException jetException;

    private final Map<Integer, List<JETProblemAnnotation>> problemAnnotations = new HashMap<Integer, List<JETProblemAnnotation>>();

    public JETCompilerResult(IFile javaFile, JETCompilationUnit compilationUnit, List<IProblem> javaProblems, JETException jetException)
    {
      this.javaFile = javaFile;
      this.compilationUnit = compilationUnit;
      this.javaProblems = javaProblems;
      this.jetException = jetException;
    }

    public JETCompilerResult(JETException jetException)
    {
      this.javaFile = null;
      this.compilationUnit = null;
      this.javaProblems = Collections.emptyList();
      this.jetException = jetException;
    }

    public IFile getJavaFile()
    {
      return javaFile;
    }

    public JETException getJETException()
    {
      return jetException;
    }

    public List<IProblem> getJavaProblems()
    {
      return javaProblems;
    }

    public JETCompilationUnit getCompilationUnit()
    {
      return compilationUnit;
    }

    public List<JETProblemAnnotation> getProblem(JETItem jetItem)
    {
      int fileID = jetItem.getFileID();
      List<JETProblemAnnotation> allProblems = getProblems(fileID);
      if (!allProblems.isEmpty())
      {
        List<JETProblemAnnotation> result = new ArrayList<JETProblemAnnotation>();
        int startOffset = jetItem.getStartOffset();
        int stopOffset = jetItem.getStopOffset();
        for (JETProblemAnnotation problem : allProblems)
        {
          Position position = problem.getPosition();
          if (stopOffset > position.offset && startOffset < position.offset + position.length)
          {
            result.add(problem);
          }
        }
        return result;
      }
      else
      {
        return Collections.emptyList();
      }
    }

    public List<JETProblemAnnotation> getProblems(int fileID)
    {
      if (fileID < 0)
      {
        JETProblemAnnotation jetOrphanProblemAnnotation = new JETProblemAnnotation(IMarker.SEVERITY_WARNING, "This file is no longer included by the template", new Position(0, 0));
        return Collections.singletonList(jetOrphanProblemAnnotation);
      }
      else
      {
        List<JETProblemAnnotation> jetProblemAnnotations = problemAnnotations.get(fileID);
        if (jetProblemAnnotations == null)
        {
          jetProblemAnnotations = new ArrayList<JETProblemAnnotation>();
          JETException jetException = getJETException();
          if (jetException != null)
          {
            JETProblemAnnotation jetExceptionProblemAnnotation = getExceptionProblem(fileID, jetException);
            if (jetExceptionProblemAnnotation != null)
            {
              jetProblemAnnotations.add(jetExceptionProblemAnnotation);
            }
          }

          JETCompilationUnit result = getCompilationUnit();
          if (compilationUnit != null)
          {
            List<JETException> jetProblems = result.getProblems();
            for (JETException jetProblem : jetProblems)
            {
              JETProblemAnnotation jetExceptionProblemAnnotation = getExceptionProblem(fileID, jetProblem);
              if (jetExceptionProblemAnnotation != null)
              {
                jetProblemAnnotations.add(jetExceptionProblemAnnotation);
              }
            }

            List<IProblem> javaProblems = getJavaProblems();
            for (IProblem problem : javaProblems)
            {
              int start = problem.getSourceStart();
              int end = problem.getSourceEnd() + 1;
              int[] correspondingTemplatePoint = result.getClosestCorrespondingTemplatePoint(fileID, start, end == 0 ? 0 : end - start);
              if (correspondingTemplatePoint != null)
              {
                String message = problem.getMessage();
                JETProblemAnnotation jetProblemAnnotation = new JETProblemAnnotation(
                  problem.isError() ? IMarker.SEVERITY_ERROR : problem.isWarning() ? IMarker.SEVERITY_WARNING : IMarker.SEVERITY_INFO,
                  message,
                  new Position(correspondingTemplatePoint[0], correspondingTemplatePoint[1]));
                jetProblemAnnotations.add(jetProblemAnnotation);
              }
            }
          }
        }

        problemAnnotations.put(fileID, jetProblemAnnotations);

        return jetProblemAnnotations;
      }
    }

    protected JETProblemAnnotation getExceptionProblem(int fileID, JETException exception)
    {
      int offset = -1;
      int length = 0;
      boolean locatedInThisFile = false;
      for (JETMark start = exception.getStart(); start != null; start = start.getParentMark())
      {
        if (start.getFileId() == fileID)
        {
          offset = start.getCursor();
          JETMark stop = exception.getStop();
          locatedInThisFile = stop.getFileId() == fileID;
          if (locatedInThisFile)
          {
            length = stop.getCursor() - offset;
          }

          break;
        }
      }

      if (offset == -1)
      {
        String message = exception.getLocalizedMessage();
        int severity = exception.getDiagnostic().getSeverity();
        int problemSeverity = severity == Diagnostic.INFO ? IMarker.SEVERITY_INFO : severity == Diagnostic.WARNING ? IMarker.SEVERITY_WARNING : IMarker.SEVERITY_ERROR;
        JETProblemAnnotation jetProblemAnnotation = new JETProblemAnnotation(problemSeverity, message, new Position(0, length));
        return jetProblemAnnotation;
      }
      else
      {
        String message = exception.getLocalizedMessage();
        if (locatedInThisFile)
        {
          int index = message.indexOf(" in ");
          if (index != -1)
          {
            message = message.substring(0, index);
          }
        }

        int severity = exception.getDiagnostic().getSeverity();
        int problemSeverity = severity == Diagnostic.INFO ? IMarker.SEVERITY_INFO : severity == Diagnostic.WARNING ? IMarker.SEVERITY_WARNING : IMarker.SEVERITY_ERROR;
        JETProblemAnnotation jetProblemAnnotation = new JETProblemAnnotation(problemSeverity, message, new Position(offset, length));
        return jetProblemAnnotation;
      }
    }
  }

  static class VisibleCaretHandler extends FocusAdapter implements PaintListener
  {
    private final StyledText styledText;

    private final StyledText otherStyledText;

    public VisibleCaretHandler(StyledText styledText, StyledText otherStyledText)
    {
      this.styledText = styledText;
      this.otherStyledText = otherStyledText;
      styledText.addFocusListener(this);
      otherStyledText.addFocusListener(this);
      styledText.addPaintListener(this);
    }

    @Override
    public void focusGained(FocusEvent event)
    {
      Rectangle caretBounds = getCaretBounds();
      styledText.redraw(caretBounds.x, caretBounds.y, caretBounds.width, caretBounds.height, false);
    }

    @Override
    public void focusLost(FocusEvent event)
    {
      Rectangle caretBounds = getCaretBounds();
      styledText.redraw(caretBounds.x, caretBounds.y, caretBounds.width, caretBounds.height, false);
    }

    public void paintControl(PaintEvent event)
    {
      if (!styledText.isFocusControl() && otherStyledText.isFocusControl())
      {
        Color background = event.gc.getBackground();
        event.gc.setBackground(ColorManager.INSTANCE.getBlendedColor(ColorManager.INSTANCE.getForeground(JETDirectiveRule.TOKEN.getType()), background));
        Rectangle caretBounds = getCaretBounds();
        event.gc.fillRectangle(caretBounds);
        event.gc.setBackground(background);
      }
    }

    private Rectangle getCaretBounds()
    {
      return styledText.getCaret().getBounds();
    }
  }

  static class SelectEnclosingJETElementAction extends Action
  {
    private JETEditor jetEditor;

    public SelectEnclosingJETElementAction(JETEditor jetEditor)
    {
      this.jetEditor = jetEditor;
    }

    @Override
    public void run()
    {
      ISourceViewer sourceViewer = jetEditor.getSourceViewer();
      Point selectedRange = sourceViewer.getSelectedRange();
      JETItem startItem = jetEditor.getJETItem(selectedRange.x, false);
      JETItem stopItem = jetEditor.getJETItem(selectedRange.x + selectedRange.y, false);

      if (startItem != null)
      {
        Point startRange = getRange(startItem);
        int offset = startRange.x;
        int end = startRange.x + startRange.y;
        if (stopItem != null)
        {
          Point stopRange = getRange(stopItem);
          end = stopRange.x + stopRange.y;
        }

        jetEditor.selectAndReveal(offset, end - offset);
      }
    }

    private Point getRange(JETItem jetItem)
    {
      if (jetItem instanceof JETScriptletItem || jetItem instanceof JETExpressionItem || jetItem instanceof JETCommentItem)
      {
        Point tokenRange = jetEditor.getTokenRangeAt(jetItem.getStartOffset());
        if (tokenRange != null)
        {
          return tokenRange;
        }
      }
      int start = jetItem.getStartOffset();
      int stop = jetItem.getStopOffset();
      return new Point(start, stop - start);
    }
  }

  static abstract class JETAbstractBaseAction extends Action
  {
    protected final JETEditor jetEditor;

    public JETAbstractBaseAction(JETEditor jetEditor, String text, ImageDescriptor imageDescriptor)
    {
      super(text, imageDescriptor);

      this.jetEditor = jetEditor;
    }

    @Override
    public void run()
    {
      SourceViewer jetSourceViewer = jetEditor.getJETSourceViewer();
      final IDocument jetDocument = jetSourceViewer.getDocument();

      final SourceViewer javaSourceViewer = jetEditor.getJavaSourceViewer();
      IDocument javaDocument = javaSourceViewer.getDocument();

      JETCompilationUnit compilationUnit = jetEditor.getCompilationUnit();
      List<JETJavaItem> javaItems = compilationUnit.getJavaItems(jetEditor.getFileID());
      List<TrackedPosition> jetPositions = new ArrayList<TrackedPosition>(javaItems.size());

      try
      {
        for (JETJavaItem jetJavaItem : javaItems)
        {
          if (jetJavaItem instanceof JETScriptletItem || jetJavaItem instanceof JETExpressionItem)
          {
            TrackedPosition jetPosition = new TrackedPosition(TrackedPosition.Type.JET);
            jetPosition.addToDocument(jetDocument);
            jetPosition.update(jetJavaItem);

            TrackedPosition javaPosition = new TrackedPosition(TrackedPosition.Type.JAVA);
            javaPosition.addToDocument(javaDocument);
            javaPosition.update(jetJavaItem);

            jetPosition.setOpposite(javaPosition);

            jetPositions.add(jetPosition);
          }
        }

        JavaDocumentTransaction javaDocumentTransaction = new JavaDocumentTransaction(jetEditor, jetPositions, isTransformBraces(), isIgnoreOutOfScopeChanges())
          {
            @Override
            protected void start()
            {
              DocumentRewriteSessionType documentRewriteSessionType = getDocumentRewriteSessionType();
              if (documentRewriteSessionType != null)
              {
                startDocumentRewriteSession(javaSourceViewer, documentRewriteSessionType, isRememberSelection());
              }

              JETAbstractBaseAction.this.start();
            }

            @Override
            protected void postCommit(List<TrackedPosition> jetPositions)
            {
              postProcess(jetDocument, jetPositions);
            }

            @Override
            protected void stop()
            {
              for (TrackedPosition jetPosition : jetPositions)
              {
                TrackedPosition javaPosition = jetPosition.getOpposite();
                jetPosition.dispose();
                javaPosition.dispose();
              }

              JETAbstractBaseAction.this.stop();

              DocumentRewriteSessionType documentRewriteSessionType = getDocumentRewriteSessionType();
              if (documentRewriteSessionType != null)
              {
                stopDocumentRewriteSession(javaSourceViewer, isRememberSelection());
              }
            }
          };

        javaDocumentTransaction.modify(javaDocument, new SneakyRunnable()
          {
            @Override
            public void execute() throws Exception
            {
              doRun();
            }
          });

        jetEditor.openDialog(getText().replace("&", "") + " Failure", javaDocumentTransaction.getStatus());
      }
      catch (Exception exception)
      {
        CodeGenUIPlugin.getPlugin().log(exception);
      }
    }

    protected DocumentRewriteSessionType getDocumentRewriteSessionType()
    {
      return null;
    }

    protected boolean isRememberSelection()
    {
      return getDocumentRewriteSessionType() != null;
    }

    protected boolean isTransformBraces()
    {
      return false;
    }

    boolean isIgnoreOutOfScopeChanges()
    {
      return false;
    }

    protected void start()
    {
    }

    protected abstract void doRun() throws Exception;

    protected void postProcess(IDocument jetDocument, List<TrackedPosition> jetPositions)
    {
    }

    protected void stop()
    {
    }
  }

  static class JETExtractLocalVariableAction extends JETAbstractBaseAction
  {
    private final IAction javaAction;

    public JETExtractLocalVariableAction(JETEditor jetEditor, IAction javaAction)
    {
      super(jetEditor, javaAction.getText(), javaAction.getImageDescriptor());

      this.javaAction = javaAction;

      setActionDefinitionId(javaAction.getActionDefinitionId());
    }

    @Override
    protected void doRun()
    {
      javaAction.run();
    }
  }

  static class JETRenameLocalVariableAction extends JETAbstractBaseAction
  {
    public JETRenameLocalVariableAction(JETEditor jetEditor)
    {
      super(jetEditor, "Re&name...", null);
      setActionDefinitionId(RENAME_COMMAND_ID);
    }

    @Override
    protected void doRun() throws Exception
    {
      IJavaElement[] selectedElements = jetEditor.getJavaEditor().getSelectedElements();
      if (selectedElements != null)
      {
        for (IJavaElement element : selectedElements)
        {
          if (element instanceof ILocalVariable)
          {
            ILocalVariable localVariable = (ILocalVariable)element;
            RenameSupport renameLocalVariableSupport = RenameSupport.create(localVariable, localVariable.getElementName(), RenameSupport.UPDATE_REFERENCES);
            renameLocalVariableSupport.openDialog(jetEditor.getSite().getShell());
            return;
          }
        }
      }

      throw new CoreException(new Status(IStatus.ERROR, CodeGenUIPlugin.getPlugin().getBundle().getSymbolicName(), "A local variable must be selected"));
    }
  }

  static class JETFormatAction extends JETAbstractBaseAction
  {
    public JETFormatAction(JETEditor jetEditor)
    {
      super(jetEditor, "&Format", null);
      setActionDefinitionId(FORMAT_COMMAND_ID);
    }

    @Override
    protected DocumentRewriteSessionType getDocumentRewriteSessionType()
    {
      return DocumentRewriteSessionType.UNRESTRICTED;
    }

    @Override
    boolean isIgnoreOutOfScopeChanges()
    {
      return true;
    }

    @Override
    protected void start()
    {
    }

    @Override
    protected void stop()
    {
      jetEditor.waitForCompilerResult(1000);
    }

    @Override
    protected void doRun() throws Exception
    {
      IJavaProject javaProject = JavaCore.create(jetEditor.getJETNature().getProject());
      Map<String, String> options = javaProject.getOptions(true);
      for (Map.Entry<String, String> entry : options.entrySet())
      {
        String key = entry.getKey();
        if (key.contains(".formatter.brace_position_for"))
        {
          entry.setValue(DefaultCodeFormatterConstants.END_OF_LINE);
        }
      }

      options.put(JavaCore.PLUGIN_ID + ".formatter.align_with_spaces", DefaultCodeFormatterConstants.TRUE);
      options.put(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR, JavaCore.SPACE);
      options.put(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE, "2");
      options.put(DefaultCodeFormatterConstants.FORMATTER_INDENTATION_SIZE, "2");
      options.put(DefaultCodeFormatterConstants.FORMATTER_INSERT_NEW_LINE_BEFORE_ELSE_IN_IF_STATEMENT, DefaultCodeFormatterConstants.FALSE);
      options.put(DefaultCodeFormatterConstants.FORMATTER_KEEP_ELSE_STATEMENT_ON_SAME_LINE, DefaultCodeFormatterConstants.TRUE);
      options.put(DefaultCodeFormatterConstants.FORMATTER_COMPACT_ELSE_IF, DefaultCodeFormatterConstants.TRUE);

      CodeFormatter codeFormatter = ToolFactory.createCodeFormatter(options);
      JavaEditor javaEditor = jetEditor.getJavaEditor();
      IDocument document = javaEditor.getJavaSourceViewer().getDocument();
      String source = document.get();
      TextEdit edit = ((CodeFormatter)codeFormatter).format(
        CodeFormatter.K_COMPILATION_UNIT | CodeFormatter.F_INCLUDE_COMMENTS,
        source,
        0,
        source.length(),
        0,
        TextUtilities.getDefaultLineDelimiter(document));
      if (edit != null)
      {
        edit.apply(document);
      }
    }

    @Override
    protected void postProcess(IDocument jetDocument, List<TrackedPosition> jetPositions)
    {
      IDocument javaDocument = jetEditor.getJavaSourceViewer().getDocument();

      MultiTextEdit multiTextEdit = new MultiTextEdit();
      List<JETRootItem> rootItems = JETParser.parseRootItems(jetDocument.get());
      for (JETRootItem jetRootItem : rootItems)
      {
        int start = jetRootItem.getStartOffset();
        int stop = jetRootItem.getStopOffset();
        if (jetRootItem instanceof JETScriptletItem)
        {
          JETScriptletItem jetScriptletItem = (JETScriptletItem)jetRootItem;
          String body = jetScriptletItem.getText();
          reformatScriptletBody(jetDocument, jetPositions, javaDocument, multiTextEdit, start, stop, body);
        }
        else if (jetRootItem instanceof JETDirectiveItem)
        {
          JETDirectiveItem jetDirectiveItem = (JETDirectiveItem)jetRootItem;
          String directive = jetDirectiveItem.getText();
          String directiveName = jetDirectiveItem.getNameItem().getText();
          if (!directiveName.isEmpty() && Character.isUpperCase(directiveName.codePointAt(0)))
          {
            multiTextEdit.addChild(new ReplaceEdit(start + 2, 0, " "));
            reformatScriptletBody(jetDocument, jetPositions, javaDocument, multiTextEdit, start + 2, stop - 2, directive.substring(2, directive.length() - 2));
          }
          else
          {
            reformatDirective(jetDocument, multiTextEdit, start, stop, directiveName, directive, jetDirectiveItem);
          }
        }
      }

      stripTrailingWhitespace(jetDocument, multiTextEdit);

      if (multiTextEdit.hasChildren())
      {
        try
        {
          multiTextEdit.apply(jetDocument);

          // As a second pass, to avoid overlapping edits, we correct the indentation of lines within the scriptlet bodies.
          multiTextEdit = new MultiTextEdit();
          rootItems = JETParser.parseRootItems(jetDocument.get());
          for (JETRootItem jetRootItem : rootItems)
          {
            if (jetRootItem instanceof JETScriptletItem)
            {
              int start = jetRootItem.getStartOffset();
              int stop = jetRootItem.getStopOffset();
              try
              {
                // Subsequent line starts within the scriptlet will simply have 2 spaces too much indentation.
                // Actually 4 because that's the generate method body's indentation,
                // but in order to line up with the start of the scriptlet, which has <% in it, it will have 2 too many.
                int lineOffset;
                for (int i = jetDocument.getLineOfOffset(start) + 1; (lineOffset = jetDocument.getLineOffset(i)) < stop; ++i)
                {
                  if (jetDocument.getChar(lineOffset) == ' ' && jetDocument.getChar(lineOffset + 1) == ' ')
                  {
                    multiTextEdit.addChild(new ReplaceEdit(lineOffset, 2, ""));
                  }
                }
              }
              catch (BadLocationException exception)
              {
              }
            }
          }

          if (multiTextEdit.hasChildren())
          {
            multiTextEdit.apply(jetDocument);
          }
        }
        catch (Exception exception)
        {
        }
      }
    }

    private static final Map<String, String[]> JET_DIRECTIVE_ORDERED_ATTRIBUTES = new HashMap<String, String[]>();

    static
    {
      JET_DIRECTIVE_ORDERED_ATTRIBUTES.put("jet", new String []{ "package", "class", "imports", "builder", "nlString", "minimize", "skeleton", "version" });
      JET_DIRECTIVE_ORDERED_ATTRIBUTES.put("include", new String []{ "file", "fail" });
    }

    private void reformatDirective(
      IDocument jetDocument,
      MultiTextEdit multiTextEdit,
      int start,
      int stop,
      String directiveName,
      String directive,
      JETDirectiveItem jetDirectiveItem)
    {
      StringBuilder result = new StringBuilder();
      result.append(directiveName);
      boolean isMultiLine = directive.contains("\n");
      String lineDelimiter = TextUtilities.getDefaultLineDelimiter(jetDocument);
      JETAttributeListItem attributes = jetDirectiveItem.getAttributes();
      List<JETAttributeItem> items = new ArrayList<JETAttributeItem>(attributes.getAttributes());
      String[] orderedAttributes = JET_DIRECTIVE_ORDERED_ATTRIBUTES.get(directiveName);
      if (orderedAttributes != null)
      {
        for (int i = orderedAttributes.length; --i >= 0;)
        {
          JETAttributeItem attribute = attributes.getAttribute(orderedAttributes[i]);
          if (items.remove(attribute))
          {
            items.add(0, attribute);
          }
        }
      }

      for (JETAttributeItem jetAttributeItem : items)
      {
        if (isMultiLine)
        {
          result.append(lineDelimiter);
          result.append("  ");
        }
        else if (result.length() != 0)
        {
          result.append(' ');
        }

        String nameText = jetAttributeItem.getNameToken().getText();
        result.append(nameText);
        result.append('=');

        JETTokenItem valueToken = jetAttributeItem.getValueToken();
        String valueText = valueToken.getText();
        if ("imports".equals(nameText))
        {
          char quote = valueText.length() == 0 ? '"' : valueText.charAt(0);

          boolean isMultiLineImport = valueText.contains("\n");

          if (isMultiLineImport)
          {
            result.append(lineDelimiter).append("    ");
          }

          result.append(quote);
          List<String> imports = sortImports(valueToken);
          int count = 0;
          for (String importedName : imports)
          {
            if (isMultiLineImport)
            {
              result.append(lineDelimiter).append("      ");
            }
            else if (count++ != 0)
            {
              result.append(' ');
            }

            result.append(importedName);
          }

          if (isMultiLineImport)
          {
            result.append(lineDelimiter).append("    ");
          }

          result.append(quote);
        }
        else
        {
          result.append(valueText);
        }
      }

      multiTextEdit.addChild(new ReplaceEdit(start + 3, stop - start - 5, result.toString()));
    }

    private static final Comparator<SegmentSequence> COMPARATOR = new Comparator<SegmentSequence>()
      {
        Comparator<String> comparator = CommonPlugin.INSTANCE.getComparator();

        public int compare(SegmentSequence o1, SegmentSequence o2)
        {
          int count1 = o1.segmentCount();
          int count2 = o2.segmentCount();

          for (int i = 0; i < count1 && i < count2; ++i)
          {
            int comparision = comparator.compare(o1.segment(i), o2.segment(i));
            if (comparision != 0)
            {
              return comparision;
            }
          }

          return count1 == count2 ? 0 : count1 < count2 ? -1 : 1;
        }
      };

    private List<String> sortImports(JETTokenItem imports)
    {
      Set<SegmentSequence> importedNames = new TreeSet<SegmentSequence>(COMPARATOR);
      for (JETValueElementItem jetValueElementItem : imports.getValueItem().getElements())
      {
        importedNames.add(SegmentSequence.create(".", jetValueElementItem.getText()));
      }

      List<String> result = new ArrayList<String>();
      for (SegmentSequence name : importedNames)
      {
        result.add(name.toString());
      }

      return result;
    }

    protected void stripTrailingWhitespace(IDocument jetDocument, MultiTextEdit multiTextEdit)
    {
      try
      {
        int numberOfLines = jetDocument.getNumberOfLines();
        for (int i = 0; i < numberOfLines; ++i)
        {
          int lineOffset = jetDocument.getLineOffset(i);
          int lineLength = jetDocument.getLineLength(i);
          String lineDelimiter = jetDocument.getLineDelimiter(i);
          int lineDelimiterLength = lineDelimiter == null ? 0 : lineDelimiter.length();
          int count = 0;
          for (int j = lineOffset + lineLength - lineDelimiterLength - 1; j >= 0; --j)
          {
            char character = jetDocument.getChar(j);
            if (character == ' ' || character == '\t')
            {
              ++count;
            }
            else
            {
              break;
            }
          }

          if (count > 0)
          {
            try
            {
              multiTextEdit.addChild(new ReplaceEdit(lineOffset + lineLength - lineDelimiterLength - count, count, ""));
            }
            catch (MalformedTreeException exception)
            {
              // These can and do happen because the directives are formatted to already eliminate trailing whitespace.
            }
          }
        }
      }
      catch (BadLocationException e)
      {
        // Can't happen.
      }
    }

    protected TrackedPosition getTrackedPosition(List<TrackedPosition> positions, int offset)
    {
      for (Position position : positions)
      {
        if (position.overlapsWith(offset, 0))
        {
          return (TrackedPosition)position;
        }
      }
      return null;
    }

    protected void reformatScriptletBody(
      IDocument jetDocument,
      List<TrackedPosition> positions,
      IDocument javaDocument,
      MultiTextEdit multiTextEdit,
      int start,
      int stop,
      String body)
    {
      // Trim any whitespace including the line feed at the end of the body.
      int trim = -1;
      int length = body.length();
      for (int i = length; --i >= 0;)
      {
        if (Character.isWhitespace(body.charAt(i)))
        {
          trim = i;
        }
        else
        {
          break;
        }
      }

      if (trim != -1)
      {
        multiTextEdit.addChild(new ReplaceEdit(start + trim, length - trim, ""));
      }

      // Compute the indentation for the scriptlet based on the indentation of the corresponding Java.
      TrackedPosition jetPosition = getTrackedPosition(positions, start);
      if (jetPosition != null)
      {
        TrackedPosition javaPosition = jetPosition.getOpposite();
        int jetIndentation = computeIndentation(jetDocument, jetPosition.offset - 2);
        if (jetIndentation != -1)
        {
          int javaIndentation = computeIndentation(javaDocument, javaPosition.offset);
          if (javaIndentation >= 4)
          {
            // The body of the generate method is expected to be at an indentation level of 4 spaces.
            int baseJavaIndentation = javaIndentation - 4;
            int delta = baseJavaIndentation - jetIndentation;
            if (delta > 0)
            {
              StringBuilder spaces = new StringBuilder();
              for (int i = 0; i < delta; ++i)
              {
                spaces.append(' ');
              }

              multiTextEdit.addChild(new ReplaceEdit(start - 2, 0, spaces.toString()));
            }
            else if (delta < 0)
            {
              multiTextEdit.addChild(new ReplaceEdit(start + delta - 2, -delta, ""));
            }
          }
        }
      }
    }

    protected int computeIndentation(IDocument document, int offset)
    {
      int indent = 0;
      for (int i = offset; --i >= 0;)
      {
        try
        {
          char character = document.getChar(i);
          if (character == ' ')
          {
            ++indent;
          }
          else if (character == '\n')
          {
            break;
          }
          else
          {
            return -1;
          }
        }
        catch (BadLocationException exception)
        {
        }
      }

      return indent;
    }
  }

  static class WorkingCopyCompilationMonitor extends JETCompileTemplateOperation.JETCompilationMonitor
  {
    @Override
    public InputStream openInputStream(String locationURI) throws JETException
    {
      URI uri = URI.createURI(locationURI);
      if (uri.isPlatformResource())
      {
        IPath fullPath = new Path(uri.toPlatformString(true));
        ITextFileBuffer buffer = ITextFileBufferManager.DEFAULT.getTextFileBuffer(fullPath, LocationKind.IFILE);
        if (buffer != null)
        {
          IDocument document = buffer.getDocument();
          String contents = document.get();
          try
          {
            return new ByteArrayInputStream(contents.getBytes("UTF-8"));
          }
          catch (UnsupportedEncodingException exception)
          {
            // UTF-8 is always supported.
          }
        }
      }

      return super.openInputStream(locationURI);
    }
  }

  static class LocalWorkingCopy extends WorkingCopyOwner
  {
    private String contents;

    public LocalWorkingCopy(String contents)
    {
      this.contents = contents;
    }

    @Override
    public IBuffer createBuffer(final ICompilationUnit workingCopy)
    {
      return new IBuffer()
        {
          public IOpenable getOwner()
          {
            return workingCopy;
          }

          public String getText(int offset, int length) throws IndexOutOfBoundsException
          {
            return contents.substring(offset, offset + length);
          }

          public int getLength()
          {
            return contents.length();
          }

          public String getContents()
          {
            return contents;
          }

          public char[] getCharacters()
          {
            return contents.toCharArray();
          }

          public char getChar(int position)
          {
            return contents.charAt(position);
          }

          public boolean isReadOnly()
          {
            return true;
          }

          public boolean isClosed()
          {
            return false;
          }

          public boolean hasUnsavedChanges()
          {
            return false;
          }

          public IResource getUnderlyingResource()
          {
            return null;
          }

          public void close()
          {
          }

          public void save(IProgressMonitor progress, boolean force) throws JavaModelException
          {
            throw new UnsupportedOperationException();
          }

          public void setContents(String contents)
          {
            throw new UnsupportedOperationException();
          }

          public void setContents(char[] contents)
          {
            throw new UnsupportedOperationException();
          }

          public void replace(int position, int length, String text)
          {
            throw new UnsupportedOperationException();
          }

          public void replace(int position, int length, char[] text)
          {
            throw new UnsupportedOperationException();
          }

          public void append(String text)
          {
            throw new UnsupportedOperationException();
          }

          public void append(char[] text)
          {
            throw new UnsupportedOperationException();
          }

          public void addBufferChangedListener(IBufferChangedListener listener)
          {
          }

          public void removeBufferChangedListener(IBufferChangedListener listener)
          {
          }
        };
    }
  }

  static final class StorageEditorInput extends PlatformObject implements IStorageEditorInput, IPathEditorInput, IURIEditorInput
  {
    private final URLStorage storage;

    public StorageEditorInput(URI uri)
    {
      this(uri, null, null);
    }

    public StorageEditorInput(URI uri, String contents, String encoding)
    {
      storage = new URLStorage(uri, contents, encoding == null ? "UTF-8" : encoding);
    }

    @Override
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public Object getAdapter(Class adapter)
    {
      return storage.getAdapter(adapter);
    }

    public boolean exists()
    {
      return storage.exists();
    }

    public ImageDescriptor getImageDescriptor()
    {
      IEditorRegistry registry = PlatformUI.getWorkbench().getEditorRegistry();
      return registry.getImageDescriptor(storage.uri.fileExtension());
    }

    public String getName()
    {
      return storage.uri.lastSegment();
    }

    public IPersistableElement getPersistable()
    {
      return null;
    }

    public String getToolTipText()
    {
      return storage.uri.toString();
    }

    public IEncodedStorage getStorage() throws CoreException
    {
      return storage;
    }

    public java.net.URI getURI()
    {
      try
      {
        URI resolvedURI = JETNature.resolve(storage.uri);
        return new java.net.URI(resolvedURI.toString());
      }
      catch (URISyntaxException exception)
      {
        throw new RuntimeException(exception);
      }
    }

    public IPath getPath()
    {
      IPath path = storage.getFullPath();
      return path == null ? new Path("") : path;
    }

    public IFile getFile()
    {
      return (IFile)storage.getAdapter(IFile.class);
    }

    @Override
    public String toString()
    {
      return storage.toString();
    }

    @Override
    public int hashCode()
    {
      return storage.hashCode();
    }

    @Override
    public boolean equals(Object that)
    {
      if (this == that)
      {
        return true;
      }
      else if (that == null || that.getClass() != StorageEditorInput.class)
      {
        return false;
      }
      else
      {
        StorageEditorInput other = (StorageEditorInput)that;
        return storage == null ? other.storage == null : storage.equals(other.storage);
      }
    }

    private static final class URLStorage extends PlatformObject implements IEncodedStorage
    {
      private final URI uri;

      private final String contents;

      private final String encoding;

      public URLStorage(URI uri, String contents, String encoding)
      {
        this.uri = uri;
        this.contents = contents;
        this.encoding = encoding;
      }

      @Override
      @SuppressWarnings({ "unchecked", "rawtypes" })
      public Object getAdapter(Class adapter)
      {
        if (adapter == IFile.class)
        {
          IFile file = toFile(uri);
          if (file != null)
          {
            return file;
          }
        }

        return super.getAdapter(adapter);
      }

      public boolean exists()
      {
        try
        {
          if (contents == null)
          {
            getContents().close();
          }
          return true;
        }
        catch (Exception exception)
        {
          return false;
        }
      }

      public InputStream getContents() throws CoreException
      {
        try
        {
          if (contents == null)
          {
            return new URL(uri.toString()).openStream();
          }
          else
          {
            return new ByteArrayInputStream(contents.getBytes(encoding == null ? "UTF-8" : encoding));
          }
        }
        catch (Exception exception)
        {
          throw new CoreException(new Status(IStatus.ERROR, CodeGenPlugin.ID, exception.getLocalizedMessage(), exception));
        }
      }

      public IPath getFullPath()
      {
        if (uri.isPlatformResource())
        {
          return new Path(uri.toPlatformString(true));
        }

        if (uri.isFile())
        {
          return new Path(uri.toFileString());
        }

        return null;
      }

      public String getName()
      {
        return uri.lastSegment();
      }

      public boolean isReadOnly()
      {
        return true;
      }

      public String getCharset() throws CoreException
      {
        return encoding;
      }

      @Override
      public int hashCode()
      {
        return uri == null ? 0 : uri.hashCode();
      }

      @Override
      public boolean equals(Object that)
      {
        if (this == that)
        {
          return true;
        }
        else if (that == null || that.getClass() != URLStorage.class)
        {
          return false;
        }
        else
        {
          URLStorage other = (URLStorage)that;
          return uri == null ? other.uri == null : uri.equals(other.uri);
        }
      }

      @Override
      public String toString()
      {
        return String.valueOf(uri);
      }
    }
  }

  static class JavaEditorInput extends FileEditorInput
  {
    private boolean isConnected;

    private boolean isWorkingCopy;

    private ICompilationUnit workingCopy;

    public JavaEditorInput(IFile file, String content)
    {
      super(file);
      try
      {
        IPath path = file.getFullPath();
        FileBuffers.getTextFileBufferManager().connect(path, LocationKind.IFILE, new NullProgressMonitor());
        isConnected = true;
        ITextFileBuffer textFileBuffer = FileBuffers.getTextFileBufferManager().getTextFileBuffer(path, LocationKind.IFILE);
        if (textFileBuffer instanceof IStateValidationSupport)
        {
          ((IStateValidationSupport)textFileBuffer).validationStateChanged(true, Status.OK_STATUS);
        }
        IDocument document = textFileBuffer.getDocument();
        document.set(content);
      }
      catch (CoreException exception)
      {
        CodeGenUIPlugin.getPlugin().log(exception);
      }
    }

    public synchronized ITypeRoot getWorkingCopy(ITypeRoot inputJavaElement)
    {
      if (!isWorkingCopy && inputJavaElement instanceof ICompilationUnit)
      {
        try
        {
          ICompilationUnit workingCopy = (ICompilationUnit)inputJavaElement;
          workingCopy.becomeWorkingCopy(new NullProgressMonitor());
          this.workingCopy = workingCopy;
          isWorkingCopy = true;
        }
        catch (JavaModelException exception)
        {
          isWorkingCopy = true;
          CodeGenUIPlugin.getPlugin().log(exception);
        }
      }

      return inputJavaElement;
    }

    public void releaseWorkingCopy()
    {
      if (workingCopy != null)
      {
        try
        {
          workingCopy.discardWorkingCopy();
        }
        catch (JavaModelException exception)
        {
          CodeGenUIPlugin.getPlugin().log(exception);
        }
      }
    }

    public void release()
    {
      if (isConnected)
      {
        IPath path = getFile().getFullPath();
        try
        {
          FileBuffers.getTextFileBufferManager().disconnect(path, LocationKind.IFILE, new NullProgressMonitor());
        }
        catch (CoreException exception)
        {
          CodeGenUIPlugin.getPlugin().log(exception);
        }
      }
    }
  }

  @SuppressWarnings("restriction")
  static final class JavaEditor extends org.eclipse.jdt.internal.ui.javaeditor.CompilationUnitEditor
  {
    static final Map<String, String> SELECTION_ACTIONS = new LinkedHashMap<String, String>();

    static
    {
      SELECTION_ACTIONS.put(org.eclipse.jdt.internal.ui.javaeditor.selectionactions.StructureSelectionAction.ENCLOSING, IJavaEditorActionDefinitionIds.SELECT_ENCLOSING);
      SELECTION_ACTIONS.put(org.eclipse.jdt.internal.ui.javaeditor.selectionactions.StructureSelectionAction.NEXT, IJavaEditorActionDefinitionIds.SELECT_NEXT);
      SELECTION_ACTIONS.put(org.eclipse.jdt.internal.ui.javaeditor.selectionactions.StructureSelectionAction.PREVIOUS, IJavaEditorActionDefinitionIds.SELECT_PREVIOUS);
      SELECTION_ACTIONS.put(org.eclipse.jdt.internal.ui.javaeditor.selectionactions.StructureSelectionAction.HISTORY, IJavaEditorActionDefinitionIds.SELECT_LAST);
    }

    private final JETEditor jetEditor;

    private boolean navigating;

    private IAction openDeclarationAction;

    private DefaultCharacterPairMatcher bracketMatcher;

    public JavaEditor(JETEditor jetEditor)
    {
      this.jetEditor = jetEditor;
      setDocumentProvider(new StatusFilteringForwardingDocumentProvider(getDocumentProvider()));
    }

    SourceViewer getJavaSourceViewer()
    {
      return (SourceViewer)getSourceViewer();
    }

    @Override
    public IAction getAction(String actionID)
    {
      if ("OpenEditor".equals(actionID))
      {
        return getOpenDeclarationAction();
      }

      final IAction action = super.getAction(actionID);
      String actionDefinitionID = SELECTION_ACTIONS.get(actionID);
      if (actionDefinitionID != null)
      {
        return new Action(action.getText(), action.getStyle())
          {
            @Override
            public void run()
            {
              try
              {
                navigating = true;
                action.run();
                jetEditor.selectionSynchronizer.sync(getJavaSourceViewer());
              }
              finally
              {
                navigating = false;
              }
            }
          };
      }
      else if (EXTRACT_LOCAL_VARIABLE_ACTION_ID.equals(actionID))
      {
        return new JETExtractLocalVariableAction(jetEditor, action);
      }
      else
      {
        return action;
      }
    }

    IJavaElement[] getSelectedElements()
    {
      ITypeRoot inputJavaElement = getInputJavaElement();
      Point selectedRange = getJavaSourceViewer().getSelectedRange();
      try
      {
        return inputJavaElement.codeSelect(selectedRange.x, selectedRange.y);
      }
      catch (JavaModelException e)
      {
        return null;
      }
    }

    DefaultCharacterPairMatcher getJavaBracketMatcher()
    {
      if (bracketMatcher == null)
      {
        try
        {
          Field field = org.eclipse.jdt.internal.ui.javaeditor.JavaEditor.class.getDeclaredField("fBracketMatcher");
          field.setAccessible(true);
          bracketMatcher = (DefaultCharacterPairMatcher)field.get(this);
        }
        catch (Exception exception)
        {
        }
      }

      return bracketMatcher;
    }

    IHyperlinkDetector[] getHyperlinkDetectors()
    {
      IHyperlinkDetector[] hyperlinkDetectors = getSourceViewerConfiguration().getHyperlinkDetectors(getSourceViewer());
      return hyperlinkDetectors;
    }

    boolean isNavigating()
    {
      return navigating;
    }

    IAction getOpenDeclarationAction()
    {
      if (openDeclarationAction == null)
      {
        openDeclarationAction = new OpenAction(this)
          {
            @Override
            public void run(Object[] elements)
            {
              IFileEditorInput editorInput = (IFileEditorInput)getEditorInput();
              IPath editorInputPath = editorInput.getFile().getFullPath();
              for (Object element : elements)
              {
                if (element instanceof IJavaElement)
                {
                  IPath path = ((IJavaElement)element).getPath();
                  if (editorInputPath.equals(path))
                  {
                    try
                    {
                      navigating = true;
                      JavaUI.revealInEditor(JavaEditor.this, (IJavaElement)element);

                      ISourceViewer javaSourceViewer = JavaEditor.this.getJavaSourceViewer();
                      javaSourceViewer.getTextWidget().notifyListeners(SWT.Selection, new Event());
                    }
                    finally
                    {
                      navigating = false;
                    }
                    return;
                  }
                }
              }

              super.run(elements);
            }
          };
      }
      return openDeclarationAction;
    }

    IAction getGotoMatchingBracketAction()
    {
      final IAction gotoMatchingBracketAction = getAction(org.eclipse.jdt.internal.ui.javaeditor.GotoMatchingBracketAction.GOTO_MATCHING_BRACKET);
      gotoMatchingBracketAction.setActionDefinitionId(IJavaEditorActionDefinitionIds.GOTO_MATCHING_BRACKET);
      return new Action(gotoMatchingBracketAction.getText(), gotoMatchingBracketAction.getStyle())
        {
          @Override
          public void run()
          {
            JETBracketMatcher jetBracketMatcher = jetEditor.getJetBracketMatcher();
            ISourceViewer jetSourceViewer = jetEditor.getSourceViewer();
            Point selectedRange = jetSourceViewer.getSelectedRange();
            IRegion jetMatch = jetBracketMatcher.match(jetSourceViewer.getDocument(), selectedRange.x);
            if (jetMatch == null)
            {
              try
              {
                navigating = true;
                gotoMatchingBracketAction.run();
              }
              finally
              {
                navigating = false;
              }
            }
            else
            {
              int offset = jetMatch.getOffset();
              int length = jetMatch.getLength();
              int targetOffset = (ICharacterPairMatcher.RIGHT == jetBracketMatcher.getAnchor()) ? offset + 1 : offset + length;
              jetEditor.selectAndReveal(targetOffset, 0);
            }
          }
        };
    }

    IContentAssistant getContentAssist()
    {
      try
      {
        Field field = SourceViewer.class.getDeclaredField("fContentAssistant");
        field.setAccessible(true);
        return (IContentAssistant)field.get(getSourceViewer());
      }
      catch (Exception exception)
      {
        throw new RuntimeException(exception);
      }
    }

    IQuickAssistAssistant getQuickAssistAssistant()
    {
      try
      {
        Field field = SourceViewer.class.getDeclaredField("fQuickAssistAssistant");
        field.setAccessible(true);
        return (IQuickAssistAssistant)field.get(getSourceViewer());
      }
      catch (Exception exception)
      {
        throw new RuntimeException(exception);
      }
    }

    IHyperlink[] detectHyperlinks(ITextViewer textViewer, IRegion region, boolean canShowMultipleHyperlinks)
    {
      JETCompilationUnit compilerResult = jetEditor.getCompilationUnit();
      if (compilerResult != null)
      {
        IRegion javaWordRegion = org.eclipse.jdt.internal.ui.text.JavaWordFinder.findWord(textViewer.getDocument(), region.getOffset());
        final int correspondingTemplateFileID = compilerResult.getCorrespondingTemplateFileID(region.getOffset());
        if (correspondingTemplateFileID != -1 && correspondingTemplateFileID != jetEditor.getFileID())
        {
          JETHyperLink jetHyperLink = new JETHyperLink(
            jetEditor,
            javaWordRegion == null || javaWordRegion.getLength() == 0 ? new Region(region.getOffset(), 1) : javaWordRegion,
            correspondingTemplateFileID,
            toURI(jetEditor.getDocumentManager().getEditorInput(correspondingTemplateFileID)).toString())
            {
              @Override
              public String getTypeLabel()
              {
                return "Open JET";
              }
            };
          return new IHyperlink []{ jetHyperLink };
        }
      }
      return null;
    }

    @Override
    protected void markInNavigationHistory()
    {
      // Don't maintain history for the Java file itself.
    }

    @Override
    protected ITypeRoot getInputJavaElement()
    {
      ITypeRoot inputJavaElement = super.getInputJavaElement();
      IEditorInput editorInput = getEditorInput();
      if (editorInput instanceof JavaEditorInput)
      {
        return ((JavaEditorInput)editorInput).getWorkingCopy(inputJavaElement);
      }
      else
      {
        return inputJavaElement;
      }
    }

    @Override
    public void dispose()
    {
      super.dispose();

      IEditorInput editorInput = getEditorInput();
      if (editorInput instanceof JavaEditorInput)
      {
        ((JavaEditorInput)editorInput).releaseWorkingCopy();
      }
    }

    void setInput(IFile javaFile, String compilationUnit)
    {
      IFile effectiveJavaFile = javaFile;
      boolean javaFileExists = javaFile.exists();
      if (!javaFileExists)
      {
        // Ensure that the file is contained by an actual existing project or folder.
        IContainer parent = javaFile.getParent();
        while (!parent.exists())
        {
          parent = parent.getParent();
        }

        if (parent.getType() == IResource.ROOT)
        {
          parent = jetEditor.getEditorInputFile().getParent();
        }

        effectiveJavaFile = parent.getFile(new Path(javaFile.getName()));
      }

      IEditorInput newInput = javaFileExists ? new FileEditorInput(effectiveJavaFile) : new JavaEditorInput(effectiveJavaFile, compilationUnit);
      IEditorInput oldInput = getEditorInput();
      if (!newInput.equals(oldInput))
      {
        setInput(newInput);
        getJavaSourceViewer().setEditable(false);
        if (oldInput instanceof JavaEditorInput)
        {
          ((JavaEditorInput)oldInput).releaseWorkingCopy();
        }
      }

      jetEditor.getJavaPosition().addToDocument(getJavaSourceViewer().getDocument());

      if (newInput instanceof JavaEditorInput)
      {
        ((JavaEditorInput)newInput).release();
      }
    }

    @Override
    public void init(IEditorSite site, IEditorInput input) throws PartInitException
    {
      super.init(new DelegatingEditorSite(site), input);

      IPerspectiveDescriptor perspective = getSite().getPage().getPerspective();
      String id = EDITOR_SHOW_BREADCRUMB + "." + perspective.getId(); //$NON-NLS-1$
      IPreferenceStore preferenceStore = getPreferenceStore();
      PreferenceStore localPreferenceStore = new PreferenceStore();
      localPreferenceStore.setValue(PreferenceConstants.EDITOR_FOLDING_ENABLED, false);
      setPreferenceStore(new ChainedPreferenceStore(new IPreferenceStore []{ localPreferenceStore, preferenceStore }));
      localPreferenceStore.setValue(id, true);
    }

    @Override
    public void createPartControl(Composite parent)
    {
      super.createPartControl(parent);

      new VisibleCaretHandler(getSourceViewer().getTextWidget(), jetEditor.getSourceViewer().getTextWidget());

      // The Java editor does semantic highlighting on a background thread that updates the Java viewer at some arbitrary point in time later.
      ((ITextViewerExtension4)getSourceViewer()).addTextPresentationListener(new ITextPresentationListener()
        {
          private Color getBackground(JETJavaRange jetRange)
          {
            JETItem jetItem = jetRange.getJETItem();
            if (jetItem instanceof JETScriptletItem)
            {
              return ColorManager.INSTANCE.getBackground(JETScriptletRule.TOKEN.getType());
            }
            else if (jetItem instanceof JETExpressionItem)
            {
              return ColorManager.INSTANCE.getBackground(JETExpressionRule.TOKEN.getType());
            }
            else if (jetItem instanceof JETLiteralItem)
            {
              return ColorManager.INSTANCE.getBackground(JETContentRule.TOKEN.getType());
            }
            else if (jetItem instanceof JETSubItem && jetItem.getParent() instanceof JETCommentItem)
            {
              return ColorManager.INSTANCE.getBackground(JETCommentRule.TOKEN.getType());
            }
            else
            {
              return ColorManager.INSTANCE.getBackground(JETDirectiveRule.TOKEN.getType());
            }
          }

          public void applyTextPresentation(TextPresentation textPresentation)
          {
            // Start two parallel iterators on the JET ranges and the style ranges.
            List<JETJavaRange> ranges = jetEditor.getRanges();
            if (!ranges.isEmpty())
            {
              Iterator<StyleRange> i = textPresentation.getAllStyleRangeIterator();
              if (i.hasNext())
              {
                // Accumulate altered style ranges.
                List<StyleRange> newStyleRanges = new ArrayList<StyleRange>();

                // Initialize the current starting point for each scanned JET range and style range.
                Iterator<JETJavaRange> r = ranges.iterator();
                JETJavaRange range = r.next();
                StyleRange styleRange = i.next();

                LOOP: while (true)
                {
                  // Skip any empty ranges, e.g., the ones from an empty scriptlet.
                  int rangeJavaLength = range.getJavaLength();
                  while (rangeJavaLength == 0)
                  {
                    if (!r.hasNext())
                    {
                      newStyleRanges.add(styleRange);
                      break LOOP;
                    }

                    range = r.next();
                    rangeJavaLength = range.getJavaLength();
                  }

                  int javaOffset = styleRange.start;
                  int rangeJavaOffset = range.getJavaOffset();

                  // Consume ranges until we hit one that overlaps with the current style range.
                  while (rangeJavaOffset + rangeJavaLength <= javaOffset)
                  {
                    if (!r.hasNext())
                    {
                      newStyleRanges.add(styleRange);
                      break LOOP;
                    }

                    range = r.next();
                    rangeJavaOffset = range.getJavaOffset();
                    rangeJavaLength = range.getJavaLength();
                  }

                  // Consume style ranges until we hit one that overlaps with the current range.
                  while (javaOffset + styleRange.length <= rangeJavaOffset)
                  {
                    newStyleRanges.add(styleRange);
                    if (i.hasNext())
                    {
                      styleRange = i.next();
                      javaOffset = styleRange.start;
                    }
                    else
                    {
                      break LOOP;
                    }
                  }

                  // Trim off any unstyled prefix from the style range.
                  int delta = rangeJavaOffset - javaOffset;
                  if (delta > 0)
                  {
                    // The prefix has normal style so we clone the range and reduce the length.
                    StyleRange newStyleRange = (StyleRange)styleRange.clone();
                    newStyleRange.length = delta;
                    newStyleRanges.add(newStyleRange);

                    // If we've consumed the whole style range...
                    if (styleRange.length == delta)
                    {
                      if (i.hasNext())
                      {
                        // If there are style ranges left, continue the loop.
                        styleRange = i.next();
                        continue;
                      }
                      else
                      {
                        // Otherwise we're done.
                        break;
                      }
                    }

                    // Advance the style range.
                    javaOffset = styleRange.start += delta;
                    styleRange.length -= delta;
                  }

                  // Consume the styled part of the style range.
                  int lengthDelta = styleRange.length - rangeJavaLength;
                  if (lengthDelta > 0)
                  {
                    // The new style range completely consumes the range.
                    StyleRange newStyleRange = (StyleRange)styleRange.clone();
                    newStyleRange.length = rangeJavaLength;
                    newStyleRange.background = getBackground(range);
                    newStyleRanges.add(newStyleRange);

                    // Advance the style range by the consumed length.
                    styleRange.start += rangeJavaLength;
                    styleRange.length = lengthDelta;

                    // Advance the range if there are any left.
                    if (!r.hasNext())
                    {
                      // If not, consume the remaining portion of the style range and terminate.
                      newStyleRanges.add(styleRange);
                      break LOOP;
                    }
                    else
                    {
                      range = r.next();
                    }
                  }
                  else
                  {
                    // The style range is completely consumed by the range.
                    styleRange.background = getBackground(range);
                    newStyleRanges.add(styleRange);

                    // If we're out of style ranges, terminate.
                    if (!i.hasNext())
                    {
                      break;
                    }
                    else
                    {
                      // Otherwise, advance the range to the remaining portion and advanced to the next style range.
                      range = range.subrange(styleRange.length);
                      styleRange = i.next();
                    }
                  }
                }

                // Consume any remaining unaltered style ranges.
                while (i.hasNext())
                {
                  newStyleRanges.add(i.next());
                }

                textPresentation.clear();
                for (StyleRange newStyleRange : newStyleRanges)
                {
                  textPresentation.addStyleRange(newStyleRange);
                }
              }
            }

            if (jetEditor.isJavaEditorInSync())
            {
              jetEditor.invalidateVisibleTextPresentation(true);
            }
          }
        });

      try
      {
        // Disable the Java editor from also setting the last edit location.
        Field textListenerField = AbstractTextEditor.class.getDeclaredField("fTextListener");
        textListenerField.setAccessible(true);
        Object textListener = textListenerField.get(this);
        Field runnableField = textListener.getClass().getDeclaredField("fRunnable");
        runnableField.setAccessible(true);
        runnableField.set(textListener, new Runnable()
          {
            public void run()
            {
            }
          });
      }
      catch (Exception exception)
      {
        // If that doesn't work, it's not tragic, just annoying.
      }

      ((IPostSelectionProvider)getSelectionProvider()).addSelectionChangedListener(new JavadocUpdater(jetEditor));
    }

    private static final class JavadocUpdater implements ISelectionChangedListener
    {
      private final JETEditor jetEditor;

      public JavadocUpdater(JETEditor jetEditor)
      {
        this.jetEditor = jetEditor;
      }

      public void selectionChanged(SelectionChangedEvent event)
      {
        JavaEditor javaEditor = jetEditor.getJavaEditor();
        ISelectionProvider selectionProvider = javaEditor.getSelectionProvider();
        ISelectionValidator selectionValidator = (ISelectionValidator)selectionProvider;
        if (selectionValidator.isValid(event.getSelection()))
        {
          IWorkbenchPage page = jetEditor.getSite().getPage();
          IViewPart javadocView = page.findView(JavaUI.ID_JAVADOC_VIEW);
          if (javadocView != null)
          {
            IViewPart[] viewStack = page.getViewStack(javadocView);
            if (viewStack != null && viewStack.length > 0 && viewStack[0] == javadocView)
            {
              ((ISelectionListener)javadocView).selectionChanged(javaEditor, selectionProvider.getSelection());
            }
          }
        }
      }
    }

    private static final class StatusFilteringForwardingDocumentProvider extends ForwardingDocumentProvider
    {
      public StatusFilteringForwardingDocumentProvider(IDocumentProvider parentProvider)
      {
        super(IJavaPartitions.JAVA_PARTITIONING, new IDocumentSetupParticipant()
          {
            public void setup(IDocument document)
            {
            }
          }, parentProvider);
      }

      @Override
      public IStatus getStatus(Object element)
      {
        return Status.OK_STATUS;
      }
    }

    private static class DelegatingEditorSite implements IEditorSite
    {
      private final IEditorSite editorSite;

      private SubActionBars subActionBars;

      public DelegatingEditorSite(IEditorSite editorSite)
      {
        this.editorSite = editorSite;
        subActionBars = new SubActionBars(getEditorSite().getActionBars());
      }

      public IEditorSite getEditorSite()
      {
        return editorSite;
      }

      public IEditorActionBarContributor getActionBarContributor()
      {
        return getEditorSite().getActionBarContributor();
      }

      public String getId()
      {
        return JavaUI.ID_CU_EDITOR;
      }

      @SuppressWarnings("rawtypes")
      public boolean hasService(Class api)
      {
        return false;
      }

      @SuppressWarnings({ "unchecked", "rawtypes" })
      public Object getService(Class api)
      {
        return null;
      }

      public String getPluginId()
      {
        return getEditorSite().getPluginId();
      }

      @SuppressWarnings({ "unchecked", "rawtypes" })
      public Object getAdapter(Class adapter)
      {
        return getEditorSite().getAdapter(adapter);
      }

      public IWorkbenchPage getPage()
      {
        return getEditorSite().getPage();
      }

      public ISelectionProvider getSelectionProvider()
      {
        return getEditorSite().getSelectionProvider();
      }

      public Shell getShell()
      {
        return getEditorSite().getShell();
      }

      public IActionBars getActionBars()
      {
        return subActionBars;
      }

      public IWorkbenchPart getPart()
      {
        return getEditorSite().getPart();
      }

      public IWorkbenchWindow getWorkbenchWindow()
      {
        return getEditorSite().getWorkbenchWindow();
      }

      public void setSelectionProvider(ISelectionProvider provider)
      {
        getEditorSite().setSelectionProvider(provider);
      }

      public String getRegisteredName()
      {
        return getEditorSite().getRegisteredName();
      }

      public void registerContextMenu(String menuId, MenuManager menuManager, ISelectionProvider selectionProvider)
      {
      }

      public void registerContextMenu(MenuManager menuManager, ISelectionProvider selectionProvider, boolean includeEditorInput)
      {
      }

      public void registerContextMenu(MenuManager menuManager, ISelectionProvider selectionProvider)
      {
      }

      public void registerContextMenu(String menuId, MenuManager menuManager, ISelectionProvider selectionProvider, boolean includeEditorInput)
      {
      }

      @SuppressWarnings("deprecation")
      public org.eclipse.ui.IKeyBindingService getKeyBindingService()
      {
        return new DelegatingKeyBindingService();
      }

      @SuppressWarnings("deprecation")
      private static class DelegatingKeyBindingService implements org.eclipse.ui.IKeyBindingService
      {
        public String[] getScopes()
        {
          return new String [0];
        }

        public void registerAction(IAction action)
        {
        }

        public void setScopes(String[] scopes)
        {
        }

        public void unregisterAction(IAction action)
        {
        }
      }
    }
  }

  static abstract class SneakyRunnable implements Runnable
  {
    @SuppressWarnings("unchecked")
    private static <E extends Throwable> void sneakyThrow(Throwable throwable) throws E
    {
      throw (E)throwable;
    }

    public final void run()
    {
      try
      {
        execute();
      }
      catch (Throwable throwable)
      {
        SneakyRunnable.<RuntimeException> sneakyThrow(throwable);
      }
    }

    protected abstract void execute() throws Throwable;
  }

  static IEditorPart open(IEditorSite site, String locationURI)
  {
    IFile file = toFile(URI.createURI(locationURI));
    if (file != null && file.exists())
    {
      try
      {
        String editorID = site.getWorkbenchWindow().getWorkbench().getEditorRegistry().getDefaultEditor(file.getName()).getId();
        return site.getPage().openEditor(new FileEditorInput(file), editorID);
      }
      catch (PartInitException exception)
      {
        CodeGenUIPlugin.write(exception);
      }
    }

    return null;
  }

  static IFile toFile(URI uri)
  {
    return uri.isPlatformResource() ? ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(uri.toPlatformString(true))) : null;
  }

  static URI toPlatformResourceURI(IEditorInput input)
  {
    return toURI(toFile(input));
  }

  static IFile toFile(IEditorInput input)
  {
    if (input instanceof StorageEditorInput)
    {
      return ((StorageEditorInput)input).getFile();
    }
    else
    {
      return input instanceof IAdaptable ? (IFile)((IAdaptable)input).getAdapter(IFile.class) : null;
    }
  }

  static URI toURI(IResource resource)
  {
    return resource == null ? null : URI.createPlatformResourceURI(resource.getFullPath().toString(), true);
  }

  static IRegion toRegion(JETItem jetItem)
  {
    int start = jetItem.getStartOffset();
    int stop = jetItem.getStopOffset();
    return new Region(start, stop - start);
  }

  static IEditorInput toEditorInput(URI uri)
  {
    if (uri != null && uri.isPlatformResource())
    {
      IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(uri.toPlatformString(true)));
      if (file.isAccessible())
      {
        return new FileEditorInput(file);
      }
    }

    return new StorageEditorInput(JETNature.resolve(uri));
  }

  static IFile toFile(IPath path)
  {
    return ResourcesPlugin.getWorkspace().getRoot().getFile(path);
  }

  static URI toURI(IEditorInput input)
  {
    if (input instanceof IFileEditorInput)
    {
      return toURI(((IFileEditorInput)input).getFile());
    }
    else if (input instanceof StorageEditorInput)
    {
      return URI.createURI(((StorageEditorInput)input).getURI().toString());
    }
    else if (input instanceof IURIEditorInput)
    {
      return URI.createURI(((IURIEditorInput)input).getURI().toString());
    }
    else
    {
      return null;
    }
  }

  static boolean installOnDocument(IDocument document, Position position, String category, IPositionUpdater positionUpdater)
  {
    if (document != null && position != null)
    {
      if (!document.containsPositionCategory(category))
      {
        document.addPositionCategory(category);
        document.addPositionUpdater(positionUpdater);
      }

      try
      {
        document.addPosition(category, position);
        return true;
      }
      catch (Exception excetpion)
      {
      }
    }

    return false;
  }

  static boolean uninstallFromDocument(IDocument document, Position position, String category, IPositionUpdater positionUpdater)
  {
    if (document != null && position != null)
    {
      try
      {
        document.removePosition(category, position);

        Position[] positions = document.getPositions(category);
        if (positions == null || positions.length == 0)
        {
          document.removePositionCategory(category);
          document.removePositionUpdater(positionUpdater);
        }

        return true;
      }
      catch (BadPositionCategoryException exception)
      {
      }
    }

    return false;
  }
}
