/**
 * Copyright (c) 2010 Ed Merks 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
 * 
 * Contributors: 
 *   Ed Merks - Initial API and implementation
 */
package org.eclipse.emf.edit.ui;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EventObject;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.emf.common.command.AbstractCommand;
import org.eclipse.emf.common.command.BasicCommandStack;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CommandStack;
import org.eclipse.emf.common.command.CommandStackListener;
import org.eclipse.emf.common.command.CompoundCommand;
import org.eclipse.emf.common.command.UnexecutableCommand;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.common.util.BasicDiagnostic;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.Callback;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.common.util.UniqueEList;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EPackage.Registry;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.URIConverter;
import org.eclipse.emf.ecore.resource.URIService;
import org.eclipse.emf.ecore.resource.URIServiceAsync;
import org.eclipse.emf.ecore.resource.URIServiceCallback;
import org.eclipse.emf.ecore.resource.impl.BinaryResourceImpl;
import org.eclipse.emf.ecore.resource.impl.ResourceFactoryImpl;
import org.eclipse.emf.ecore.util.Diagnostician;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.edit.command.CopyToClipboardCommand;
import org.eclipse.emf.edit.command.CreateChildCommand;
import org.eclipse.emf.edit.command.CutToClipboardCommand;
import org.eclipse.emf.edit.command.DeleteCommand;
import org.eclipse.emf.edit.command.MoveCommand;
import org.eclipse.emf.edit.command.PasteFromClipboardCommand;
import org.eclipse.emf.edit.command.RemoveCommand;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.edit.provider.AdapterFactoryItemDelegator;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.edit.provider.ComposedImage;
import org.eclipse.emf.edit.provider.IChangeNotifier;
import org.eclipse.emf.edit.provider.IItemLabelProvider;
import org.eclipse.emf.edit.provider.IItemPropertyDescriptor;
import org.eclipse.emf.edit.provider.IItemPropertySource;
import org.eclipse.emf.edit.provider.INotifyChangedListener;
import org.eclipse.emf.edit.provider.ItemProvider;
import org.eclipse.emf.edit.provider.ReflectiveItemProviderAdapterFactory;
import org.eclipse.emf.edit.provider.resource.ResourceItemProviderAdapterFactory;

import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.cell.client.Cell;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.cellview.client.CellTree;
import com.google.gwt.user.cellview.client.TreeNode;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.SplitLayoutPanel;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Tree;
import com.google.gwt.user.client.ui.TreeItem;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.datepicker.client.DateBox;
import com.google.gwt.view.client.HasData;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.SelectionChangeEvent;
import com.google.gwt.view.client.SingleSelectionModel;
import com.google.gwt.view.client.TreeViewModel;

public abstract class EditorEntryPoint implements EntryPoint
{
  public static abstract class CommandHandler implements ClickHandler
  {
    protected Command command;
    protected EditingDomain editingDomain;
    protected Button button;

    public CommandHandler(EditingDomain editingDomain, Button button)
    {
      this.editingDomain = editingDomain;
      this.button = button;
      button.addClickHandler(this);
      button.setEnabled(false);
    }

    public void setSelection(Object value)
    {
      command = createCommand(value);
      button.setEnabled(command.canExecute());
    }

    public abstract Command createCommand(Object value);

    public void onClick(ClickEvent event)
    {
      editingDomain.getCommandStack().execute(command);
    }
  }

  public void onModuleLoad()
  {
    ComposedAdapterFactory composedAdapterFactory = new ComposedAdapterFactory();
    composedAdapterFactory.addAdapterFactory(new ResourceItemProviderAdapterFactory());
    configureItemProviderAdapterFactories(composedAdapterFactory);
    composedAdapterFactory.addAdapterFactory(new ReflectiveItemProviderAdapterFactory());

    final BasicCommandStack commandStack = new BasicCommandStack();
    final AdapterFactoryEditingDomain editingDomain = new AdapterFactoryEditingDomain(composedAdapterFactory, commandStack);

    final ResourceSet resourceSet = editingDomain.getResourceSet();
    resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put
      (Resource.Factory.Registry.DEFAULT_EXTENSION,
       new ResourceFactoryImpl()
       {
         @Override
         public Resource createResource(URI uri)
         {
           return new BinaryResourceImpl(uri);
         }
       });

    final URIServiceAsync uriService = GWT.create(URIService.class);
    URIServiceCallback uriServiceCallback = new URIServiceCallback(uriService);
    resourceSet.getURIConverter().getURIHandlers().add(uriServiceCallback);

    final List<Resource> originalResources = new ArrayList<Resource>();

    Resource listingResource =
      resourceSet.getResource
        (URI.createURI("datastore:/"),
         new Callback<Resource>()
         {
           public void onFailure(Throwable caught)
           {
             System.err.println("Failed");
             caught.printStackTrace();
           }

           public void onSuccess(Resource result)
           {
             EAnnotation eAnnotation = (EAnnotation)result.getContents().get(0);
             for (Map.Entry<String, String> entry : eAnnotation.getDetails())
             {
               resourceSet.createResource(URI.createURI(entry.getKey()));
             }
             originalResources.clear();
             originalResources.addAll(resourceSet.getResources());
           }
         });
    resourceSet.getResources().remove(listingResource);

    registerPackages(resourceSet.getPackageRegistry());

    final AdapterFactoryItemDelegator itemDelegator = new AdapterFactoryItemDelegator(composedAdapterFactory);

    DockLayoutPanel mainPanel = new DockLayoutPanel(Unit.PX);
    mainPanel.setStyleName("Main", true);
    mainPanel.setHeight("100%");

    FlowPanel headerPanel = new FlowPanel();
    headerPanel.setStyleName("Header", true);

    final Label header = new Label(getApplicationTitle());
    header.setStyleName("HeaderLabel", true);

    headerPanel.add(header);

    mainPanel.addNorth(headerPanel, 56.0);

    DockLayoutPanel bodyPanel = new DockLayoutPanel(Unit.PX);
    bodyPanel.setStyleName("Body", true);
    bodyPanel.setHeight("100%");

    mainPanel.add(bodyPanel);

    FlowPanel toolBar = new FlowPanel();
    toolBar.setStyleName("ToolBar", true);

    SplitLayoutPanel contentPanel = new SplitLayoutPanel();
    contentPanel.setStyleName("Content", true);

    DockLayoutPanel propertiesPanel = new DockLayoutPanel(Unit.PX);
    propertiesPanel.setStyleName("Properties", true);
    propertiesPanel.setHeight("100%");

    FlowPanel propertiesTitlePanel = new FlowPanel();
    propertiesTitlePanel.setStyleName("PropertiesTitle", true);

    final Label title = new Label("Properties");
    title.setStyleName("PropertiesTitleLabel", true);

    propertiesTitlePanel.add(title);
    
    propertiesPanel.addNorth(propertiesTitlePanel, 28.0);
    
    final Grid properties = new Grid();
    properties.setWidth("100%");
    ScrollPanel propertiesScrollPanel = new ScrollPanel();
    propertiesScrollPanel.add(properties);

    propertiesPanel.add(propertiesScrollPanel);

    final Button createButton = new Button();
    createButton.setText("Create...");
    createButton.setStyleName("ToolBarButtonLeftMargin");

    toolBar.add(createButton);

    final Button deleteButton = new Button();
    deleteButton.setText("Delete");
    final CommandHandler deleteCommandHandler =
      new CommandHandler(editingDomain, deleteButton)
      {
        @Override
        public Command createCommand(Object value)
        {
          if (value instanceof Resource)
          {
            final Resource resource = (Resource)value;
            final Command deleteContentsCommand = DeleteCommand.create(editingDomain, resource.getContents());
            final RemoveCommand removeResourceCommand = new RemoveCommand(editingDomain, resourceSet.getResources(), resource);
            return
              new CompoundCommand()
              {
                @Override
                protected boolean prepare()
                {
                  return true;
                }

                @Override
                public void execute()
                {
                  appendAndExecute(deleteContentsCommand);
                  appendAndExecute(removeResourceCommand);
                }
              };
          }
          else
          {
            return DeleteCommand.create(editingDomain, value);
          }
        }
      };
    deleteButton.setStyleName("ToolBarButton");

    toolBar.add(deleteButton);

    final Button validateButton = new Button();
    validateButton.setText("Validate");
    validateButton.setStyleName("ToolBarButtonLeftMargin");

    toolBar.add(validateButton);

    final Button saveButton = new Button();
    saveButton.setText("Save");
    saveButton.setStyleName("ToolBarButton");

    toolBar.add(saveButton);

    final Button undoButton = new Button();
    undoButton.setText("Undo");
    undoButton.setStyleName("ToolBarButtonLeftMargin");

    toolBar.add(undoButton);

    final Button redoButton = new Button();
    redoButton.setText("Redo");
    redoButton.setStyleName("ToolBarButton");

    toolBar.add(redoButton);

    final Button cutButton = new Button();
    cutButton.setText("Cut");
    final CommandHandler cutCommandHandler =
      new CommandHandler(editingDomain, cutButton)
      {
        @Override
        public Command createCommand(Object value)
        {
          return CutToClipboardCommand.create(editingDomain, value);
        }
      };
    cutButton.setStyleName("ToolBarButtonLeftMargin");

    toolBar.add(cutButton);

    final Button copyButton = new Button();
    copyButton.setText("Copy");
    final CommandHandler copyCommandHandler =
      new CommandHandler(editingDomain, copyButton)
      {
        @Override
        public Command createCommand(Object value)
        {
          return value instanceof EObject ? CopyToClipboardCommand.create(editingDomain, value) : UnexecutableCommand.INSTANCE;
        }
      };
    copyButton.setStyleName("ToolBarButton");

    toolBar.add(copyButton);

    final Button pasteButton = new Button();
    pasteButton.setText("Paste");
    final CommandHandler pasteCommandHandler =
      new CommandHandler(editingDomain, pasteButton)
      {
        @Override
        public Command createCommand(Object value)
        {
          return PasteFromClipboardCommand.create(editingDomain, value, null);
        }
      };
    pasteButton.setStyleName("ToolBarButton");

    toolBar.add(pasteButton);

    final Button upButton = new Button();
    upButton.setText("Up ^");
    final CommandHandler upCommandHandler =
      new CommandHandler(editingDomain, upButton)
      {
        @Override
        public Command createCommand(Object value)
        {
          Object owner = editingDomain.getParent(value);
          if (owner != null)
          {
            List<?> children = new ArrayList<Object>(editingDomain.getChildren(owner));
            int index = children.indexOf(value);
            if (index != -1)
            {
              return MoveCommand.create(editingDomain, owner, null, value, index - 1);
            }
          }
          return UnexecutableCommand.INSTANCE;
        }
      };
    upButton.setStyleName("ToolBarButtonLeftMargin");

    toolBar.add(upButton);

    final Button downButton = new Button();
    downButton.setText("Down v");
    final CommandHandler downCommandHandler =
      new CommandHandler(editingDomain, downButton)
      {
        @Override
        public Command createCommand(Object value)
        {
          Object owner = editingDomain.getParent(value);
          if (owner != null)
          {
            List<?> children = new ArrayList<Object>(editingDomain.getChildren(owner));
            int index = children.indexOf(value);
            if (index != -1)
            {
              return MoveCommand.create(editingDomain, owner, null, value, index + 1);
            }
          }
          return UnexecutableCommand.INSTANCE;
        }
      };
    downButton.setStyleName("ToolBarButton");

    toolBar.add(downButton);

    final Button navigateButton = new Button();
    navigateButton.setText("Navigate");
    navigateButton.setEnabled(false);
    navigateButton.setStyleName("ToolBarButtonLeftMargin");

    toolBar.add(navigateButton);

    final SingleSelectionModel<Object> treeSelection = new SingleSelectionModel<Object>();
    final Collection<Runnable> propertyUpdater = new HashSet<Runnable>();

    TreeViewModel treeViewModel =
      new TreeViewModel()
      {
        public <T> NodeInfo<?> getNodeInfo(final T value)
        {
          final ListDataProvider<Object> abstractListViewAdapter =
            new ListDataProvider<Object>()
            {
              int size;
              class NodeAdapter extends AdapterImpl implements INotifyChangedListener
              {
                Set<Object> children = new HashSet<Object>();

                @Override
                public void notifyChanged(Notification msg)
                {
                  if (msg.getEventType() != Notification.REMOVING_ADAPTER)
                  {
                    update();
                    if (!(msg.getNotifier() instanceof EObject))
                    {
                      for (Runnable runnable : propertyUpdater)
                      {
                        runnable.run();
                      }
                    }
                  }
                }

                public void listenTo(Object target)
                {
                  if (target instanceof Notifier)
                  {
                    ((Notifier)target).eAdapters().add(this);
                  }
                  else if (target instanceof IChangeNotifier)
                  {
                    ((IChangeNotifier)target).addListener(this);
                  }
                }
                public void stopListeningTo(Object target)
                {
                  if (target instanceof Notifier)
                  {
                    ((Notifier)target).eAdapters().remove(this);
                  }
                  else if (target instanceof IChangeNotifier)
                  {
                    ((IChangeNotifier)target).removeListener(this);
                  }
                }

                public void listenTo(List<Object> children)
                {
                  // TODO
                  // I don't know how to update just one child. :-(

                  boolean isListeningToValue = !this.children.isEmpty();
                  Set<Object> oldChildren = new HashSet<Object>(this.children);
                  for (Object child : children)
                  {
                    if (this.children.contains(child))
                    {
                      oldChildren.remove(child);
                    }
                    else
                    {
                      this.children.add(child);
                      listenTo(child);
                    }
                  }
                  for (Object child : oldChildren)
                  {
                    stopListeningTo(child);
                    this.children.remove(child);
                  }
                  if (isListeningToValue)
                  {
                    if (children.isEmpty())
                    {
                      stopListeningTo(value);
                    }
                  }
                  else if (!children.isEmpty())
                  {
                    listenTo(value);
                  }
                }
              }

              protected NodeAdapter nodeAdapter = new NodeAdapter();

              @Override
              protected void onRangeChanged(HasData<Object> display)
              {
                if (value instanceof Resource)
                {
                  Resource resource = (Resource)value;
                  if (!resource.isLoaded())
                  {
                    try
                    {
                      resource.load(resourceSet.getLoadOptions());
                    }
                    catch (IOException e)
                    {
                      // Ignore.
                    }
                  }
                }
                update();
              }

              protected void update()
              {
                Collection<?> children = itemDelegator.getChildren(value);
                ArrayList<Object> childrenList = new ArrayList<Object>(children);
                nodeAdapter.listenTo(childrenList);
                int size = children.size();
                if (this.size < size)
                {
                  updateRowCount(size, true);
                  this.size = size;
                }
                else
                {
                  // Pad with dummy objects.
                  for (int i = size; i < this.size; ++i)
                  {
                    childrenList.add(new Object());
                  }
                }
                updateRowData(0, childrenList);
                if (this.size > size)
                {
                  updateRowCount(size, true);
                  this.size = size;
                }
              }
            };

          Cell<Object> cell =
            new AbstractCell<Object>()
            {
              @Override
              public void render(Context context, Object value, SafeHtmlBuilder safeHtmlBuilder)
              {
                StringBuilder sb = new StringBuilder();
                Object image = itemDelegator.getImage(value);
                if (image instanceof ImageResource)
                {
                  ImageResource imageResource = (ImageResource)image;
                  sb.append("<div style='position:relative;padding-left:");
                  sb.append(imageResource.getWidth() + 6);
                  sb.append("px;'>");
                  sb.append("<div style=\"position:absolute;left:0px;top:0px;height:100%;");
                  sb.append("width:").append(imageResource.getWidth()).append("px;");
                  sb.append("background:url('").append(imageResource.getSafeUri()).append("') ");
                  sb.append("no-repeat scroll center center transparent;");
                  sb.append("\"></div>");
                  sb.append(itemDelegator.getText(value));
                  sb.append("</div>");
                }
                else if (image instanceof ComposedImage)
                {
                  ComposedImage composedImage = (ComposedImage)image;
                  List<ComposedImage.Size> sizes = new ArrayList<ComposedImage.Size>();
                  List<Object> images = new ArrayList<Object>(composedImage.getImages());
                  List<ImageData> nestedImagesData = new ArrayList<ImageData>();
                  for (Object nestedImage : images)
                  {
                    ImageData nestedImageData = getImageData(nestedImage);
                    ComposedImage.Size size = new ComposedImage.Size();
                    size.height = nestedImageData.height;
                    size.width = nestedImageData.width;
                    sizes.add(size);
                    nestedImagesData.add(nestedImageData);
                  }
                  ComposedImage.Size size = composedImage.getSize(sizes);
                  sb.append("<div style='position:relative;padding-left:");
                  sb.append(size.width + 6);
                  sb.append("px;'>");
                  List<ComposedImage.Point> drawPoints = composedImage.getDrawPoints(size);
                  int i = 0;
                  for (ComposedImage.Point drawPoint : drawPoints)
                  {
                    ImageResource nestedImage = (ImageResource)images.get(i++);
                    sb.append("<div style=\"position:absolute;left:").append(drawPoint.x).append("px;top:").append(drawPoint.y).append("px;height:100%;");
                    sb.append("width:").append(nestedImage.getWidth()).append("px;");
                    sb.append("background:url('").append(nestedImage.getSafeUri()).append("') ");
                    sb.append("no-repeat scroll center center transparent;");
                    sb.append("\"></div>");
                  }
                  sb.append(itemDelegator.getText(value));
                  sb.append("</div>");
                }
                else
                {
                  sb.append(itemDelegator.getText(value));
                }
                safeHtmlBuilder.appendHtmlConstant(sb.toString());
              }
            };
          return new DefaultNodeInfo<Object>(abstractListViewAdapter, cell, treeSelection, null);
        }

        public boolean isLeaf(Object value)
        {
          return !itemDelegator.hasChildren(value);
        }
      };

    treeSelection.setSelected(resourceSet, true);
    final MyCellTree cellTree = new MyCellTree(treeViewModel, new ItemProvider(Collections.singleton(resourceSet)));
    ScrollPanel treeScrollPanel = new ScrollPanel();
    treeScrollPanel.add(cellTree);

    class NavigationListener
    {
      Object selection;

      {
        navigateButton.addClickHandler
         (new ClickHandler()
          {
            public void onClick(ClickEvent event)
            {
              final Object oldSelection = treeSelection.getSelectedObject();
              Object newSelection = selection;
              cellTree.expand(editingDomain.getTreePath(editingDomain.getParent(newSelection)));
              treeSelection.setSelected(newSelection, true);
              Scheduler.get().scheduleDeferred
                (new Scheduler.ScheduledCommand()
                  {
                    public void execute()
                    {
                      setSelection(oldSelection);
                    }
                  });
            }
          });
      }

      public void listenTo(final ListBox listBox, final List<?> values)
      {
        listBox.addFocusHandler
          (new FocusHandler()
           {
             protected HandlerRegistration changeHandlerRegistration;

             {
               listBox.addBlurHandler
                 (new BlurHandler()
                  {
                    public void onBlur(BlurEvent event)
                    {
                      if (changeHandlerRegistration != null)
                      {
                        changeHandlerRegistration.removeHandler();
                        changeHandlerRegistration = null;
                      }
                    }
                  });
             }

             public void onFocus(FocusEvent event)
             {
               updateSelection();
               changeHandlerRegistration =
                 listBox.addChangeHandler
                   (new ChangeHandler()
                    {
                      public void onChange(ChangeEvent event)
                      {
                        updateSelection();
                      }
                    });
             }

             void updateSelection()
             {
               int selectedIndex = listBox.getSelectedIndex();
               setSelection(selectedIndex == -1 || selectedIndex >= values.size() ? null : values.get(selectedIndex));
             }
           });
      }

      protected void setSelection(Object object)
      {
        if (object != selection)
        {
          selection = object;
          navigateButton.setEnabled(selection != null);
        }
      }
    }
    final NavigationListener navigationListener = new NavigationListener();

    treeSelection.addSelectionChangeHandler
      (new SelectionChangeEvent.Handler()
       {
         protected Object oldSelection;

         public void onSelectionChange(SelectionChangeEvent event)
         {
           final Object object = treeSelection.getSelectedObject();

           deleteCommandHandler.setSelection(object);
           cutCommandHandler.setSelection(object);
           pasteCommandHandler.setSelection(object);
           copyCommandHandler.setSelection(object);
           upCommandHandler.setSelection(object);
           downCommandHandler.setSelection(object);
           createButton.setEnabled
             (object instanceof Resource ||
                object instanceof ResourceSet ||
                !editingDomain.getNewChildDescriptors(object, null).isEmpty());

           if (oldSelection == object)
           {
             for (Runnable runnable : propertyUpdater)
             {
               runnable.run();
             }
           }
           else
           {
             navigationListener.setSelection(null);

             oldSelection = object;

             List<IItemPropertyDescriptor> propertyDescriptors = itemDelegator.getPropertyDescriptors(object);
             if (propertyDescriptors != null)
             {
               properties.clear();
               propertyUpdater.clear();
               int size = propertyDescriptors.size();
               properties.resize(size, 2);
               properties.getColumnFormatter().setWidth(0, "10%");
               properties.getColumnFormatter().setWidth(1, "90%");
               for (int i = 0; i < size; ++i)
               {
                 final IItemPropertyDescriptor propertyDescriptor = propertyDescriptors.get(i);
                 properties.setText(i, 0, propertyDescriptor.getDisplayName(object));
                 Widget widget = null;
                 final IItemLabelProvider itemLabelProvider = propertyDescriptor.getLabelProvider(object);
                 final Object feature = propertyDescriptor.getFeature(object);
                 if (feature instanceof EReference[])
                 {
                   final ItemPropertyDescriptorFeatureEditor dialog =
                     new ItemPropertyDescriptorFeatureEditor(object, true, propertyDescriptor)
                     {
                       @Override
                       protected void hook(ListBox listBox, List<?> values)
                       {
                         navigationListener.listenTo(listBox, values);
                       }
                     };
                   dialog.update();
                   widget = dialog;
                   propertyUpdater.add
                     (new Runnable()
                      {
                        public void run()
                        {
                          dialog.update();
                        }
                      });
                 }
                 else if (feature instanceof EStructuralFeature)
                 {
                   final EStructuralFeature eStructureFeature = (EStructuralFeature)feature;
                   final EClassifier eType = eStructureFeature.getEType();
                   final Collection<?> choiceOfValues = propertyDescriptor.getChoiceOfValues(object);
                   if (choiceOfValues != null)
                   {
                     final List<?> listOfValues = new ArrayList<Object>(propertyDescriptor.getChoiceOfValues(object));
                     if (propertyDescriptor.isMany(object))
                     {
                       boolean valid = true;
                       for (Object choice : choiceOfValues)
                       {
                         if (!eType.isInstance(choice))
                         {
                           valid = false;
                           break;
                         }
                       }

                       if (valid)
                       {
                         final ItemPropertyDescriptorFeatureEditor dialog =
                           new ItemPropertyDescriptorFeatureEditor(object, true, propertyDescriptor)
                           {
                             @Override
                             protected void hook(ListBox listBox, List<?> values)
                             {
                               navigationListener.listenTo(listBox, values);
                             }
                           };
                         dialog.update();
                         widget = dialog;
                         propertyUpdater.add
                           (new Runnable()
                            {
                              public void run()
                              {
                                dialog.update();
                              }
                            });
                       }
                     }
                     if (widget == null)
                     {
                       final ListBox listBox = new ListBox();
                       widget = listBox;
                       listBox.setVisibleItemCount(1);
                       Runnable runnable =
                         new Runnable()
                         {
                           public void run()
                           {
                             listBox.clear();
                             navigationListener.listenTo(listBox, listOfValues);
                             Object propertyValue = itemDelegator.getEditableValue(propertyDescriptor.getPropertyValue(object));
                             for (Object value : choiceOfValues)
                             {
                               listBox.addItem(itemLabelProvider.getText(value));
                               if (value == null ? propertyValue == null : value.equals(propertyValue))
                               {
                                 listBox.setSelectedIndex(listBox.getItemCount() - 1);
                               }
                             }
                           }
                         };
                       listBox.addChangeHandler
                         (new ChangeHandler()
                          {
                            public void onChange(ChangeEvent event)
                            {
                              Object value = listOfValues.get(listBox.getSelectedIndex());
                              propertyDescriptor.setPropertyValue(object, value);
                            }
                          });
                       runnable.run();
                       propertyUpdater.add(runnable);
                     }
                   }
                   else if (eType instanceof EDataType)
                   {
                     final EDataType eDataType = (EDataType)eType;
                     if (eDataType.isSerializable())
                     {
                       if (propertyDescriptor.isMany(object))
                       {
                         final ItemPropertyDescriptorFeatureEditor dialog  =
                           new ItemPropertyDescriptorFeatureEditor(object, propertyDescriptor)
                           {
                             @Override
                             protected void hook(ListBox listBox, List<?> values)
                             {
                               navigationListener.listenTo(listBox, values);
                             }
                           };
                         dialog.update();
                         widget = dialog;
                         propertyUpdater.add
                           (new Runnable()
                            {
                              public void run()
                              {
                                dialog.update();
                              }
                            });
                       }
                       else
                       {
                         if (eDataType.getInstanceClass() == Date.class)
                         {
                           final DateBox dateBox = new DateBox();
                           widget = dateBox;
                           Runnable runnable =
                             new Runnable()
                             {
                               public void run()
                               {
                                 Object propertyValue = itemDelegator.getEditableValue(propertyDescriptor.getPropertyValue(object));
                                 dateBox.setValue((Date)propertyValue);
                               }
                             };
                           dateBox.addValueChangeHandler
                             (new ValueChangeHandler<Date>()
                              {
                                public void onValueChange(ValueChangeEvent<Date> event)
                                {
                                  propertyDescriptor.setPropertyValue(object, event.getValue());
                                }
                              });
                           runnable.run();
                           propertyUpdater.add(runnable);

                         }
                         else if (eDataType.getInstanceClass() == Boolean.class || eDataType.getInstanceClass() == boolean.class)
                         {
                           final CheckBox checkBox = new CheckBox();
                           widget = checkBox;
                           Runnable runnable =
                             new Runnable()
                             {
                               public void run()
                               {
                                 Object propertyValue = itemDelegator.getEditableValue(propertyDescriptor.getPropertyValue(object));
                                 checkBox.setValue(Boolean.TRUE.equals(propertyValue));
                               }
                             };
                           checkBox.addValueChangeHandler
                             (new ValueChangeHandler<Boolean>()
                              {
                                public void onValueChange(ValueChangeEvent<Boolean> event)
                                {
                                  propertyDescriptor.setPropertyValue(object, event.getValue());
                                }
                              });
                           runnable.run();
                           propertyUpdater.add(runnable);
                         }
                         else if (propertyDescriptor.isMultiLine(object))
                         {
                           final TextArea textArea = new TextArea();
                           widget = textArea;
                           Runnable runnable =
                             new Runnable()
                             {
                               public void run()
                               {
                                 Object propertyValue = itemDelegator.getEditableValue(propertyDescriptor.getPropertyValue(object));
                                 textArea.setText(EcoreUtil.convertToString(eDataType, propertyValue));
                               }
                             };
                           textArea.addValueChangeHandler
                             (new ValueChangeHandler<String>()
                              {
                                public void onValueChange(ValueChangeEvent<String> event)
                                {
                                  propertyDescriptor.setPropertyValue(object, EcoreUtil.createFromString(eDataType, event.getValue()));
                                }
                              });
                           runnable.run();
                           propertyUpdater.add(runnable);
                         }
                         else
                         {
                           final TextBox textBox = new TextBox();
                           widget = textBox;
                           textBox.addValueChangeHandler
                             (new ValueChangeHandler<String>()
                              {
                                public void onValueChange(ValueChangeEvent<String> event)
                                {
                                  String value = event.getValue();
                                  propertyDescriptor.setPropertyValue(object, EcoreUtil.createFromString(eDataType, value));
                                  // TOD0
                                  // commandStack.execute(SetCommand.create(editingDomain, object, feature, EcoreUtil.createFromString(eDataType, value)));
                                }
                              });
                           Runnable runnable =
                             new Runnable()
                             {
                               public void run()
                               {
                                 Object propertyValue = itemDelegator.getEditableValue(propertyDescriptor.getPropertyValue(object));
                                 String stringValue = EcoreUtil.convertToString(eDataType, propertyValue);
                                 if (!textBox.getText().equals(stringValue))
                                 {
                                   textBox.setText(stringValue);
                                 }
                               }
                             };
                           runnable.run();
                           propertyUpdater.add(runnable);
                         }
                       }
                     }
                   }
                 }
                 else
                 {
                   final TextBox textBox = new TextBox();
                   widget = textBox;
                   textBox.addValueChangeHandler
                     (new ValueChangeHandler<String>()
                      {
                        public void onValueChange(ValueChangeEvent<String> event)
                        {
                          String value = event.getValue();
                          propertyDescriptor.setPropertyValue(object, value);
                        }
                      });
                   Runnable runnable =
                     new Runnable()
                     {
                       public void run()
                       {
                         Object propertyValue = itemDelegator.getEditableValue(propertyDescriptor.getPropertyValue(object));
                         String stringValue =
                           propertyValue == null ?
                             null :
                             propertyDescriptor.getLabelProvider(object).getText(propertyValue);
                         if (!textBox.getText().equals(stringValue))
                         {
                           textBox.setText(stringValue);
                         }
                       }
                     };
                   runnable.run();
                   propertyUpdater.add(runnable);
                 }
                 if (widget != null)
                 {
                   widget.setWidth("95%");
                   properties.setWidget(i, 1, widget);
                   if (!propertyDescriptor.canSetProperty(object) && widget instanceof FocusWidget)
                   {
                     ((FocusWidget)widget).setEnabled(false);

                   }
                 }
               }
             }
           }
         }
       });

    contentPanel.addWest(treeScrollPanel, 400);
    contentPanel.add(propertiesPanel);

    bodyPanel.addNorth(toolBar, 28.0);
    bodyPanel.add(contentPanel);

    RootPanel.get("main").add(mainPanel);

    createButton.addClickHandler
     (new ClickHandler()
      {
        public void onClick(ClickEvent event)
        {
          Object selection= treeSelection.getSelectedObject();
          if (selection instanceof ResourceSet)
          {
            final DialogBox dialogBox = new DialogBox();
            dialogBox.setText("Create Resource");
            final Button okButton = new Button("OK");
            final Button cancelButton = new Button("Cancel");
            VerticalPanel verticalPanel = new VerticalPanel();
            Grid grid = new Grid();
            grid.setWidth("50em");
            grid.resize(3, 2);
            grid.getColumnFormatter().setWidth(0, "15%");
            grid.getColumnFormatter().setWidth(1, "85%");
            Label uriLabel = new Label();
            uriLabel.setText("URI");
            final TextBox uriText = new TextBox();
            uriText.setWidth("90%");
            int count = 1;
            while (resourceSet.getResource(URI.createURI("datastore:/resource" + count), false) != null)
            {
              ++count;
            }
            uriText.setValue("datastore:/resource" + count);
            uriText.selectAll();
            final Label message = new Label();
            uriText.addValueChangeHandler
              (new ValueChangeHandler<String>()
                {
                  public void onValueChange(ValueChangeEvent<String> event)
                  {
                    String value = event.getValue();
                    try
                    {
                      if (resourceSet.getResource(URI.createURI(value), false) != null)
                      {
                        message.setText("This URI a duplicate");
                        okButton.setEnabled(false);
                      }
                      else
                      {
                        message.setText("");
                        okButton.setEnabled(true);
                      }
                    }
                    catch (RuntimeException exception)
                    {
                      message.setText("This URI is not a well formed");
                      okButton.setEnabled(false);
                    }
                  }
                });
            grid.setWidget(0, 0, uriLabel);
            grid.setWidget(0, 1, uriText);
            verticalPanel.add(grid);
            grid.setWidget(1, 1, message);
            grid.setWidget(2, 0, okButton);
            grid.setWidget(2, 1, cancelButton);
            dialogBox.setWidget(verticalPanel);
            dialogBox.show();
            uriText.setFocus(true);
            // dialogBox.setWidth("50em");
            okButton.addClickHandler
              (new ClickHandler()
               {
                 public void onClick(ClickEvent event)
                 {
                   dialogBox.hide();
                   commandStack.execute
                     (new AbstractCommand()
                      {
                        Collection<?> affectedObjects;
                        Resource resource;

                        @Override
                        protected boolean prepare()
                        {
                          return true;
                        }

                        public void redo()
                        {
                          resourceSet.getResources().add(resource);
                          affectedObjects = Collections.singleton(resource);
                        }

                        @Override
                        public void undo()
                        {
                          resourceSet.getResources().remove(resource);
                          affectedObjects = Collections.singleton(resourceSet);
                        }

                        public void execute()
                        {
                          resource = resourceSet.createResource(URI.createURI(uriText.getValue()));
                          resource.getContents().clear();
                          affectedObjects = Collections.singleton(resource);
                        }

                        @Override
                        public Collection<?> getAffectedObjects()
                        {
                          return affectedObjects;
                        }
                      });
                 }
               });
            cancelButton.addClickHandler
              (new ClickHandler()
               {
                 public void onClick(ClickEvent event)
                 {
                   dialogBox.hide();
                 }
               });
          }
          else if (selection instanceof Resource)
          {
            final Resource resource = (Resource)selection;
            final DialogBox dialogBox = new DialogBox();
            dialogBox.setText("Create Object");
            final ListBox listBox = new ListBox();
            listBox.setVisibleItemCount(1);
            Registry packageRegistry = resourceSet.getPackageRegistry();
            for (String nsURI : packageRegistry.keySet())
            {
              EPackage ePackage = packageRegistry.getEPackage(nsURI);
              for (EClassifier eClassifier : ePackage.getEClassifiers())
              {
                if (eClassifier instanceof EClass)
                {
                  EClass eClass = (EClass)eClassifier;
                  if (!eClass.isAbstract())
                  {
                    EObject eObject = EcoreUtil.create(eClass);
                    listBox.addItem("New " + itemDelegator.getText(eObject), EcoreUtil.getURI(eClass).toString());
                  }
                }
              }
            }
            final Button okButton = new Button("OK");
            final Button cancelButton = new Button("Cancel");
            VerticalPanel verticalPanel = new VerticalPanel();
            Grid grid = new Grid();
            grid.setWidth("50em");
            grid.resize(2, 2);
            grid.getColumnFormatter().setWidth(0, "15%");
            grid.getColumnFormatter().setWidth(1, "85%");
            Label classLabel = new Label();
            classLabel.setText("Class");
            grid.setWidget(0, 0, classLabel);
            grid.setWidget(0, 1, listBox);
            verticalPanel.add(grid);
            // verticalPanel.add(okButton);
            grid.setWidget(1, 0, okButton);
            grid.setWidget(1, 1, cancelButton);
            dialogBox.setWidget(verticalPanel);
            dialogBox.show();
            listBox.setFocus(true);
            // dialogBox.setWidth("50em");
            okButton.addClickHandler
              (new ClickHandler()
               {
                 public void onClick(ClickEvent event)
                 {
                   dialogBox.hide();
                   commandStack.execute
                     (new AbstractCommand()
                      {
                        EObject eObject;
                        Collection<?> affectedObjects;

                        @Override
                        protected boolean prepare()
                        {
                          return true;
                        }

                        public void redo()
                        {
                          resource.getContents().add(eObject);
                          affectedObjects = Collections.singleton(eObject);
                        }

                        @Override
                        public void undo()
                        {
                          resource.getContents().remove(eObject);
                          affectedObjects = Collections.singleton(resource);
                        }

                        public void execute()
                        {
                          eObject = EcoreUtil.create((EClass)resourceSet.getEObject(URI.createURI(listBox.getValue(listBox.getSelectedIndex())), false));
                          resource.getContents().add(eObject);
                          affectedObjects = Collections.singleton(eObject);
                        }

                        @Override
                        public Collection<?> getAffectedObjects()
                        {
                          return affectedObjects;
                        }
                      });
                 }
               });
            cancelButton.addClickHandler
              (new ClickHandler()
               {
                 public void onClick(ClickEvent event)
                 {
                   dialogBox.hide();
                 }
               });

          }
          else
          {
            Collection<?> newChildDescriptors = editingDomain.getNewChildDescriptors(selection, null);
            final DialogBox dialogBox = new DialogBox();
            dialogBox.setText("Create Object");
            final ListBox listBox = new ListBox();
            final List<Command> commands = new ArrayList<Command>();
            listBox.setVisibleItemCount(1);
            for (Object descriptor : newChildDescriptors)
            {
              Command command = CreateChildCommand.create(editingDomain, selection, descriptor, Collections.singleton(selection));
              commands.add(command);
              listBox.addItem(command.getLabel());
            }
            final Button okButton = new Button("OK");
            final Button cancelButton = new Button("Cancel");
            VerticalPanel verticalPanel = new VerticalPanel();
            Grid grid = new Grid();
            grid.setWidth("50em");
            grid.resize(2, 2);
            grid.getColumnFormatter().setWidth(0, "15%");
            grid.getColumnFormatter().setWidth(1, "85%");
            Label classLabel = new Label();
            classLabel.setText("Class");
            grid.setWidget(0, 0, classLabel);
            grid.setWidget(0, 1, listBox);
            verticalPanel.add(grid);
            // verticalPanel.add(okButton);
            grid.setWidget(1, 0, okButton);
            grid.setWidget(1, 1, cancelButton);
            dialogBox.setWidget(verticalPanel);
            dialogBox.show();
            listBox.setFocus(true);
            // dialogBox.setWidth("50em");
            okButton.addClickHandler
              (new ClickHandler()
               {
                 public void onClick(ClickEvent event)
                 {
                   dialogBox.hide();
                   commandStack.execute(commands.get(listBox.getSelectedIndex()));
                 }
               });
            cancelButton.addClickHandler
              (new ClickHandler()
               {
                 public void onClick(ClickEvent event)
                 {
                   dialogBox.hide();
                 }
               });
          }
        }
      });

    saveButton.addClickHandler
     (new ClickHandler()
      {
        public void onClick(ClickEvent event)
        {
          EList<Resource> resources = resourceSet.getResources();
          for (Resource resource : resources)
          {
            try
            {
              if (resource.isLoaded())
              {
                Map<String, Object> options = null;
                if (originalResources.contains(resource))
                {
                  options = new HashMap<String, Object>();
                  options.put(URIConverter.OPTION_UPDATE_ONLY_IF_TIME_STAMP_MATCHES, resource.getTimeStamp());
                }
                resource.save
                  (options,
                   new Callback<Resource>()
                   {
                     public void onFailure(Throwable caught)
                     {
                       final DialogBox dialogBox = new DialogBox();
                       dialogBox.setText("Save conflict");
                       final Button okButton = new Button("OK");
                       final Tree tree = new Tree();
                       tree.addItem(SafeHtmlUtils.fromString(caught.getLocalizedMessage()));
                       Grid grid = new Grid();
                       grid.setWidth("150em");
                       grid.resize(2, 1);
                       grid.setWidget(0, 0, tree);
                       grid.setWidget(1, 0, okButton);
                       dialogBox.setWidget(grid);
                       dialogBox.show();
                       okButton.addClickHandler
                         (new ClickHandler()
                          {
                            public void onClick(ClickEvent event)
                            {
                              dialogBox.hide();
                            }
                          });
                     }

                     public void onSuccess(Resource result)
                     {
                       // TODO Auto-generated method stub
                     }
                   });
              }
            }
            catch (IOException exception)
            {
              EMFEditUIPlugin.INSTANCE.log(exception);
            }
          }
          for (Resource resource : originalResources)
          {
            if (!resources.contains(resource))
            {
              Map<String, Object> options = null;
              if (resource.isLoaded())
              {
                options = new HashMap<String, Object>();
                options.put(URIConverter.OPTION_UPDATE_ONLY_IF_TIME_STAMP_MATCHES, resource.getTimeStamp());
              }
              resourceSet.getURIConverter().delete
                (resource.getURI(),
                  options,
                  new Callback<Map<?, ?>>()
                  {
                    public void onFailure(Throwable caught)
                    {
                       final DialogBox dialogBox = new DialogBox();
                       dialogBox.setText("Delete conflict");
                       final Button okButton = new Button("OK");
                       final Tree tree = new Tree();
                       tree.addItem(SafeHtmlUtils.fromString(caught.getLocalizedMessage()));
                       Grid grid = new Grid();
                       grid.setWidth("150em");
                       grid.resize(2, 1);
                       grid.setWidget(0, 0, tree);
                       grid.setWidget(1, 0, okButton);
                       dialogBox.setWidget(grid);
                       dialogBox.show();
                       okButton.addClickHandler
                         (new ClickHandler()
                          {
                            public void onClick(ClickEvent event)
                            {
                              dialogBox.hide();
                            }
                          });
                    }

                    public void onSuccess(Map<?, ?> result)
                    {
                      // TODO Auto-generated method stub
                    }
                  });
            }
          }
          originalResources.clear();
          originalResources.addAll(resources);
          commandStack.saveIsDone();
          saveButton.setEnabled(false);
        }
      });

    validateButton.addClickHandler
     (new ClickHandler()
      {
        protected TreeItem createTreeItems(Diagnostic diagnostic)
        {
          TreeItem treeItem = new TreeItem(SafeHtmlUtils.fromString(diagnostic.getMessage()));
          List<?> data = diagnostic.getData();
          if (!data.isEmpty())
          {
            treeItem.setUserObject(data.get(0));
          }
          for (Diagnostic child : diagnostic.getChildren())
          {
            treeItem.addItem(createTreeItems(child));
          }
          return treeItem;
        }

        public void onClick(ClickEvent event)
        {
          Diagnostician diagnostician =
            new Diagnostician()
            {
              @Override
              public String getObjectLabel(EObject eObject)
              {
                return !eObject.eIsProxy() ? itemDelegator.getText(eObject) : super.getObjectLabel(eObject);
              }
            };
          Map<Object, Object> context = diagnostician.createDefaultContext();
          EList<Resource> resources = resourceSet.getResources();
          BasicDiagnostic diagnostics = new BasicDiagnostic();
          for (Resource resource : resources)
          {
            if (resource.isLoaded())
            {
              for (EObject eObject : resource.getContents())
              {
                diagnostician.validate(eObject, diagnostics, context);
              }
            }
          }
          final DialogBox dialogBox = new DialogBox();
          dialogBox.setText("Problems");
          final Button okButton = new Button("OK");
          final Tree tree = new Tree();
          if (diagnostics.getSeverity() == Diagnostic.OK)
          {
            tree.addItem(SafeHtmlUtils.fromString("No problems detected"));
          }
          else
          {
            for (Diagnostic child : diagnostics.getChildren())
            {
              tree.addItem(createTreeItems(child));
            }
          }
          Grid grid = new Grid();
          grid.setWidth("150em");
          grid.resize(2, 1);
          grid.setWidget(0, 0, tree);
          grid.setWidget(1, 0, okButton);
          dialogBox.setWidget(grid);
          dialogBox.show();
          okButton.addClickHandler
            (new ClickHandler()
             {
               public void onClick(ClickEvent event)
               {
                 TreeItem treeItem = tree.getSelectedItem();
                 if (treeItem != null)
                 {
                   Object newSelection = treeItem.getUserObject();
                   if (newSelection != null)
                   {
                     cellTree.expand(editingDomain.getTreePath(editingDomain.getParent(newSelection)));
                     treeSelection.setSelected(newSelection, true);
                   }
                 }
                 dialogBox.hide();
               }
             });
        }
      });
    saveButton.setEnabled(false);
    undoButton.setEnabled(false);
    redoButton.setEnabled(false);

    commandStack.addCommandStackListener
      (new CommandStackListener()
       {
         public void commandStackChanged(EventObject event)
         {
           saveButton.setEnabled(commandStack.isSaveNeeded());
           undoButton.setEnabled(commandStack.canUndo());
           redoButton.setEnabled(commandStack.canRedo());

           Command mostRecentCommand = ((CommandStack)event.getSource()).getMostRecentCommand();
           if (mostRecentCommand != null)
           {
             Collection<?> affectedObjects = mostRecentCommand.getAffectedObjects();
             if (!affectedObjects.isEmpty())
             {
               Object newSelection = affectedObjects.iterator().next();
               cellTree.expand(editingDomain.getTreePath(editingDomain.getParent(newSelection)));
               if (treeSelection.getSelectedObject() == newSelection)
               {
                 for (Runnable runnable : propertyUpdater)
                 {
                   runnable.run();
                 }
               }
               else
               {
                 treeSelection.setSelected(newSelection, true);
               }
             }
           }
         }
       });

    undoButton.addClickHandler
     (new ClickHandler()
      {
        public void onClick(ClickEvent event)
        {
          commandStack.undo();
        }
      });

    redoButton.addClickHandler
     (new ClickHandler()
      {
        public void onClick(ClickEvent event)
        {
          commandStack.redo();
        }
      });
  }

  protected abstract void registerPackages(EPackage.Registry ePackageRegistry);

  protected void configureItemProviderAdapterFactories(ComposedAdapterFactory adapterFactory)
  {
    // Do nothing.
  }

  protected abstract String getApplicationTitle();

  public static class ImageData
  {
    public int width;
    public int height;
  }

  protected ImageData getImageData(Object image)
  {
    ImageData imageData = new ImageData();
    if (image instanceof ImageResource)
    {
      ImageResource imageResource = (ImageResource)image;
      imageData.height = imageResource.getHeight();
      imageData.width = imageResource.getWidth();
    }
    else if (image instanceof ComposedImage)
    {
      ComposedImage composedImage = (ComposedImage)image;
      List<ComposedImage.Size> sizes = new ArrayList<ComposedImage.Size>();
      List<Object> images = new ArrayList<Object>(composedImage.getImages());
      List<ImageData> nestedImagesData = new ArrayList<ImageData>();
      for (Object nestedImage : images)
      {
        ImageData nestedImageData = getImageData(nestedImage);
        ComposedImage.Size size = new ComposedImage.Size();
        size.height = nestedImageData.height;
        size.width = nestedImageData.width;
        sizes.add(size);
        nestedImagesData.add(nestedImageData);
      }
    }
    return imageData;
  }

  public abstract static class FeatureEditor extends Composite
  {
    protected Object object;
    protected boolean isSortChoices;
    protected List<?> values;
    protected List<?> choiceOfValues;
    protected ListBox valueBox;
    protected ListBox choiceBox;
    protected EDataType eDataType;
    protected TextBox textBox;

    protected FeatureEditor(Object object, boolean isSortChoices, final EDataType eDataType)
    {
      this.object = object;
      this.isSortChoices = isSortChoices;
      this.eDataType = eDataType;

      Grid grid = new Grid();
      grid.resize(1, 3);

      grid.getColumnFormatter().setWidth(0, "40%");
      grid.getColumnFormatter().setWidth(1, "20%");
      grid.getColumnFormatter().setWidth(2, "40%");


      valueBox = new ListBox();
      valueBox.setMultipleSelect(true);
      valueBox.setVisibleItemCount(4);
      valueBox.setWidth("100%");
      grid.setWidget(0, 0, valueBox);
      
      Grid buttonGrid = new Grid();
      buttonGrid.resize(2, 2);
      Button upButton = new Button("Up ^");
      buttonGrid.setWidget(0, 0, upButton);
      Button downButton = new Button("Down v");
      buttonGrid.setWidget(1, 0, downButton);
      Button addButton = new Button("< Add");
      buttonGrid.setWidget(0, 1, addButton);
      Button removeButton = new Button("Remove >");
      buttonGrid.setWidget(1, 1, removeButton);
      grid.setWidget(0, 1, buttonGrid);

      if (eDataType != null)
      {
        textBox = new TextBox();
        textBox.setWidth("100%");
        grid.setWidget(0, 2, textBox);

        /*
        textBox.addValueChangeHandler
          (new ValueChangeHandler<String>()
           {
             public void onValueChange(ValueChangeEvent<String> event)
             {
               List<Object> result =  isUnique() ? new UniqueEList<Object>(values) : new ArrayList<Object>(values);
               try
               {
                 Object value = EcoreUtil.createFromString(eDataType, event.getValue());
                 result.add(value);
                 setValue(result);
               }
               catch (RuntimeException exception)
               {
                 // Ignore for now
               }
             }
           });
        */
      }
      else
      {
        choiceBox = new ListBox();
        choiceBox.setMultipleSelect(true);
        choiceBox.setVisibleItemCount(4);
        choiceBox.setWidth("100%");
        grid.setWidget(0, 2, choiceBox);
      }

      addButton.addClickHandler
       (new ClickHandler()
        {
          public void onClick(ClickEvent event)
          {
            List<Object> result =  isUnique() ? new UniqueEList<Object>(values) : new ArrayList<Object>(values);
            if (eDataType != null)
            {
              try
              {
                Object value = EcoreUtil.createFromString(eDataType, textBox.getValue());
                result.add(value);
              }
              catch (RuntimeException exception)
              {
                // Ignore for now
              }
            }
            else
            {
              for (int i = 0, size = choiceBox.getItemCount(); i < size; ++i)
              {
                if (choiceBox.isItemSelected(i))
                {
                  result.add(choiceOfValues.get(i));
                }
              }
            }
            setValue(result);
          }
        });

      removeButton.addClickHandler
       (new ClickHandler()
        {
          public void onClick(ClickEvent event)
          {
            List<Object> result = isUnique() ? new UniqueEList<Object>(values) : new ArrayList<Object>(values);
            for (int i = 0, size = valueBox.getItemCount(); i < size; ++i)
            {
              if (valueBox.isItemSelected(i))
              {
                result.remove(values.get(i));
              }
            }
            setValue(result);
          }
        });
      upButton.addClickHandler
       (new ClickHandler()
        {
          public void onClick(ClickEvent event)
          {
            EList<Object> result = new BasicEList<Object>(values);
            for (int i = 0, size = valueBox.getItemCount(); i < size; ++i)
            {
              if (valueBox.isItemSelected(i))
              {
                if (i != 0)
                {
                  result.move(i, i - 1);
                }
              }
            }
            setValue(result);
          }
        });
      downButton.addClickHandler
       (new ClickHandler()
        {
          public void onClick(ClickEvent event)
          {
            EList<Object> result = new BasicEList<Object>(values);
            for (int i = 0, size = valueBox.getItemCount(); i < size; ++i)
            {
              if (valueBox.isItemSelected(i))
              {
                if (i + 1 < size)
                {
                  result.move(i, i + 1);
                }
              }
            }
            setValue(result);
          }
        });
      initWidget(grid);
    }

    protected void hook(ListBox listBox, List<?> values)
    {
      // Do nothing.

    }

    public void update()
    {
      IItemLabelProvider labelProvider = getLabelProvider();
      List<?> oldValues = values;
      values = new ArrayList<Object>(getValues());

      List<Object> oldValueSelection = new ArrayList<Object>();
      int valueBoxItemCount = valueBox.getItemCount();
      if (valueBoxItemCount != 0)
      {
        for (int i = 0; i < valueBoxItemCount; ++i)
        {
          if (valueBox.isItemSelected(i))
          {
            oldValueSelection.add(oldValues.get(i));
          }
        }
        valueBox.clear();
      }

      for (Object value : values)
      {
        valueBox.addItem(labelProvider.getText(value));
        if (oldValueSelection.contains(value) || oldValues != null && !oldValues.contains(value))
        {
          valueBox.setItemSelected(valueBox.getItemCount() - 1, true);
        }
      }

      hook(valueBox, values);

      if (eDataType != null)
      {
        textBox.setValue("");
      }
      else
      {
        List<Object> oldChoiceSelection = new ArrayList<Object>();
        int choiceBoxItemCount = choiceBox.getItemCount();
        if (choiceBoxItemCount != 0)
        {
          for (int i = 0; i < choiceBoxItemCount; ++i)
          {
            if (choiceBox.isItemSelected(i))
            {
              oldChoiceSelection.add(choiceOfValues.get(i));
            }
          }
          choiceBox.clear();
        }

        Collection<?> basicChoices = getChoiceOfValues();
        if (basicChoices == null)
        {
          choiceOfValues = null;
        }
        else
        {
          choiceOfValues = new ArrayList<Object>(basicChoices);
          if (isSortChoices)
          {
            createItems(choiceOfValues, labelProvider, null, true);
          }
          for (Object value : choiceOfValues)
          {
            choiceBox.addItem(labelProvider.getText(value));
            if (oldChoiceSelection.contains(value) || oldValues != null && oldValues.contains(value) && !values.contains(value))
            {
              choiceBox.setItemSelected(choiceBox.getItemCount() - 1, true);
            }
          }
          hook(choiceBox, choiceOfValues);
        }
      }
    }

    protected abstract IItemLabelProvider getLabelProvider();
    protected abstract List<?> getValues();
    protected abstract Collection<?> getChoiceOfValues();
    protected abstract boolean isMultiLine();
    protected abstract boolean isUnique();
    protected abstract void setValue(Object value);
  }

  public static class ItemPropertyDescriptorFeatureEditor extends FeatureEditor
  {
    protected IItemPropertyDescriptor propertyDescriptor;

    public ItemPropertyDescriptorFeatureEditor(Object object, IItemPropertyDescriptor propertyDescriptor)
    {
      super(object, false, ((EAttribute)propertyDescriptor.getFeature(object)).getEAttributeType());
      this.propertyDescriptor = propertyDescriptor;
    }

    public ItemPropertyDescriptorFeatureEditor(Object object, boolean isSortChoices, IItemPropertyDescriptor propertyDescriptor)
    {
      super(object, isSortChoices, null);
      this.propertyDescriptor = propertyDescriptor;
    }

    @Override
    protected IItemLabelProvider getLabelProvider()
    {
      return propertyDescriptor.getLabelProvider(object);
    }

    @Override
    protected List<?> getValues()
    {
      Object propertyValue = propertyDescriptor.getPropertyValue(object);
      if (propertyValue instanceof IItemPropertySource)
      {
        propertyValue = ((IItemPropertySource)propertyValue).getEditableValue(propertyValue);
      }
      return (List<?>)propertyValue;
    }

    @Override
    protected Collection<?> getChoiceOfValues()
    {
      return propertyDescriptor.getChoiceOfValues(object);
    }

    @Override
    protected boolean isMultiLine()
    {
      return propertyDescriptor.isMultiLine(object);
    }

    @Override
    protected boolean isUnique()
    {
      Object feature = propertyDescriptor.getFeature(object);
      return !(feature instanceof EStructuralFeature) || ((EStructuralFeature)feature).isUnique();
    }

    @Override
    protected void setValue(Object value)
    {
      propertyDescriptor.setPropertyValue(object, value);
    }
  }

  private static class StringPositionPair implements Comparable<StringPositionPair>
  {
    public String key;
    public int position;

    StringPositionPair(String key, int position)
    {
      this.key = key;
      this.position = position;
    }

    public int compareTo(StringPositionPair object)
    {
      if (object == this || key == object.key)
      {
        return 0;
      }
      else if (key == null)
      {
        return -1;
      }
      else
      {
        return key.compareTo(object.key);
      }
    }
  }

  public static <T> String[] createItems(List<T> list, IItemLabelProvider labelProvider, String filter, boolean sorted)
  {
    String[] result;

    if (filter != null && filter.length() > 0)
    {
      sorted = true;
    }

    // If there are objects to populate...
    //
    if (list != null && list.size() > 0)
    {
      if (sorted)
      {
        List<T> unsortedList = new ArrayList<T>(list.size());
        if (filter != null && filter.length() > 0)
        {
          for (int i = 0; i < list.size(); i++)
          {
            if (select(filter, labelProvider.getText(list.get(i))))
            {
              unsortedList.add(list.get(i));
            }
          }
        }
        else
        {
          unsortedList.addAll(list);
        }
        list.clear();

        StringPositionPair[] pairs = new StringPositionPair [unsortedList.size()];

        for (int i = 0, size = unsortedList.size(); i < size; ++i)
        {
          Object object = unsortedList.get(i);
          pairs[i] = new StringPositionPair(labelProvider.getText(object), i);
        }

        Arrays.sort(pairs);

        // Create a new array.
        //
        result = new String [unsortedList.size()];
        // Fill in the result array with labels and re-populate the original list in order.
        //
        for (int i = 0, size = unsortedList.size(); i < size; ++i)
        {
          result[i] = pairs[i].key;
          list.add(unsortedList.get(pairs[i].position));
        }
      }
      else
      {
        // Create a new array.
        //
        result = new String [list.size()];
        // Fill in the array with labels.
        //
        for (int i = 0, size = list.size(); i < size; ++i)
        {
          Object object = list.get(i);
          result[i] = labelProvider.getText(object);
        }
      }
    }
    else
    {
      result = new String [] { "" };
    }

    return result;
  }

  public static boolean select(String filter, String labelValue)
  {
    if (filter != null && filter.length() > 0)
    {
      if (filter.length() > labelValue.length())
      {
        return false;
      }
      for (int i = 0; i < filter.length(); i++)
      {
        if (Character.toLowerCase(filter.charAt(i)) != Character.toLowerCase(labelValue.charAt(i)))
        {
          return false;
        }
      }
    }
    return true;
  }

  static class MyCellTree extends CellTree
  {
    public <T> MyCellTree(TreeViewModel viewModel, T rootValue)
    {
      super(viewModel, rootValue, (Resources)GWT.create(CellTree.BasicResources.class));
    }
 
    public void expand(List<?> path)
    {
      TreeNode node = getRootTreeNode();
      if (node != null)
      {
        LOOP:
        for (int i = 0, size = path.size(); i < size; ++i)
        {
          Object segment = path.get(i);
          for (int j = 0, count = node.getChildCount(); j < count; ++j)
          {
            Object childValue = node.getChildValue(j);
            if (childValue.equals(segment))
            {
              node = node.setChildOpen(j, true);
              continue LOOP;
            }
          }
          break;
        }
      }
    }
  }
  
}
