/**
   * Returns a valid instance selection for the current selection of the tree viewer. Returns <code>
   * null</code> if there is none.
   *
   * @return a valid instance selection for the current selection of the tree viewer or <code>null
   *     </code>
   */
  private InstanceSelection getValidSelection() {
    ISelection viewerSelection = treeViewer.getSelection();
    if (!(viewerSelection instanceof ITreeSelection) || viewerSelection.isEmpty()) return null;

    ITreeSelection treeSelection = (ITreeSelection) treeViewer.getSelection();
    TreePath firstPath = treeSelection.getPaths()[0]; // XXX use all paths
    // instead of first
    // only?

    InstanceValidationMessage firstMessage;
    Iterator<InstanceValidationMessage> restIter;

    if (firstPath.getLastSegment() instanceof InstanceValidationMessage) {
      firstMessage = (InstanceValidationMessage) firstPath.getLastSegment();
      restIter = Iterators.emptyIterator();
    } else {
      Collection<InstanceValidationMessage> messages =
          contentProvider.getMessages(treeSelection.getPaths()[0]);
      if (messages.isEmpty()) return null; // shouldn't happen, but doesn't really matter
      restIter = messages.iterator();
      firstMessage = restIter.next();
    }

    InstanceService is = PlatformUI.getWorkbench().getService(InstanceService.class);
    // check first message for valid instance reference
    if (firstMessage.getInstanceReference() == null
        || is.getInstance(firstMessage.getInstanceReference()) == null) return null;

    Set<InstanceReference> references = new HashSet<InstanceReference>();
    references.add(firstMessage.getInstanceReference());
    while (restIter.hasNext()) references.add(restIter.next().getInstanceReference());

    return new DefaultInstanceSelection(references.toArray());
  }
    /** @see Widget#dispose() */
    @Override
    public void dispose() {
      SchemaService ss = PlatformUI.getWorkbench().getService(SchemaService.class);
      InstanceService is = PlatformUI.getWorkbench().getService(InstanceService.class);

      ss.removeSchemaServiceListener(schemaListener);
      is.removeListener(instanceListener);

      refreshImage.dispose();

      listeners.clear();

      super.dispose();
    }
    /** Update the feature types selection */
    protected void updateTypesSelection() {
      SchemaSpaceID space = getSchemaSpace();

      TypeDefinition lastSelected = null;
      ISelection lastSelection = typeDefinitions.getSelection();
      if (!lastSelection.isEmpty() && lastSelection instanceof IStructuredSelection) {
        lastSelected = (TypeDefinition) ((IStructuredSelection) lastSelection).getFirstElement();
      }

      DataSet dataset = (space == SchemaSpaceID.SOURCE) ? (DataSet.SOURCE) : (DataSet.TRANSFORMED);
      InstanceService is = PlatformUI.getWorkbench().getService(InstanceService.class);

      // get instance types
      List<TypeDefinition> filteredTypes =
          new ArrayList<TypeDefinition>(is.getInstanceTypes(dataset));

      if (filteredTypes.isEmpty()) {
        // if there are no instances present, show all types
        SchemaService ss = PlatformUI.getWorkbench().getService(SchemaService.class);
        filteredTypes =
            new ArrayList<TypeDefinition>(ss.getSchemas(space).getMappingRelevantTypes());
      }

      typeDefinitions.setInput(filteredTypes);

      // select the previously selected type if possible
      TypeDefinition typeToSelect =
          (filteredTypes.contains(lastSelected)) ? (lastSelected) : (null);

      // fallback selection
      if (typeToSelect == null && !filteredTypes.isEmpty()) {
        typeToSelect = filteredTypes.iterator().next();
      }

      if (typeToSelect != null) {
        typeDefinitions.setSelection(new StructuredSelection(typeToSelect));
      }

      boolean enabled = !filteredTypes.isEmpty();
      typeDefinitions.getControl().setEnabled(enabled);
      count.getControl().setEnabled(enabled);

      layout(true, true);

      updateSelection();
    }
    /** Update the selection */
    protected void updateSelection() {
      if (!typeDefinitions.getSelection().isEmpty()) {
        TypeDefinition type =
            (TypeDefinition)
                ((IStructuredSelection) typeDefinitions.getSelection()).getFirstElement();

        filterField.setType(type);

        SchemaSpaceID space = getSchemaSpace();

        Integer max = (Integer) ((IStructuredSelection) count.getSelection()).getFirstElement();

        InstanceService is = PlatformUI.getWorkbench().getService(InstanceService.class);

        List<Instance> instanceList = new ArrayList<Instance>();
        DataSet dataset =
            (space == SchemaSpaceID.SOURCE) ? (DataSet.SOURCE) : (DataSet.TRANSFORMED);

        Filter filter = null;
        String filterExpression = filterField.getFilterExpression();
        /*
         * Custom filter handling.
         *
         * FIXME Ultimately this should be done by the filter field
         * instead, which should be able to handle all kinds of
         * registered filters (e.g. also Groovy).
         */
        if (filterExpression.startsWith("id:")) {
          // XXX meta ID "hack"
          String metaFilter = filterExpression.substring("id:".length());
          String[] values = metaFilter.split(",");

          filter =
              new MetaFilter(
                  type, InstanceMetadata.METADATA_ID, new HashSet<>(Arrays.asList(values)));
        } else if (filterExpression.startsWith("source:")) {
          // XXX meta source ID "hack"
          String metaFilter = filterExpression.substring("source:".length());
          String[] values = metaFilter.split(",");

          filter =
              new MetaFilter(
                  type, InstanceMetadata.METADATA_SOURCEID, new HashSet<>(Arrays.asList(values)));
        } else {
          filter = filterField.getFilter();
        }

        InstanceCollection instances = is.getInstances(dataset);
        if (filter != null) {
          instances = instances.select(filter);
        }

        ResourceIterator<Instance> it = instances.iterator();
        try {
          int num = 0;
          while (it.hasNext() && num < max) {
            Instance instance = it.next();
            if (instance.getDefinition().equals(type)) {
              instanceList.add(instance);
              num++;
            }
          }
        } finally {
          it.close();
        }

        selection = instanceList;
        selectedType = type;
      } else {
        selection = null;
        selectedType = null;

        filterField.setType(null);
      }

      for (InstanceSelectionListener listener : listeners) {
        listener.selectionChanged(selectedType, selection);
      }
    }
    /** @see Composite#Composite(Composite, int) */
    public InstanceSelectorControl(Composite parent, int style) {
      super(parent, style);

      refreshImage =
          DataViewPlugin.getImageDescriptor("icons/refresh.gif").createImage(); // $NON-NLS-1$

      GridLayout layout = new GridLayout((spaceID == null) ? (4) : (3), false);
      layout.marginHeight = 2;
      layout.marginWidth = 3;
      setLayout(layout);

      // schema type selector
      if (spaceID == null) {
        schemaSpaces = new ComboViewer(this, SWT.READ_ONLY);
        schemaSpaces.setLabelProvider(
            new LabelProvider() {

              @Override
              public String getText(Object element) {
                if (element instanceof SchemaSpaceID) {
                  switch ((SchemaSpaceID) element) {
                    case SOURCE:
                      return Messages.InstanceServiceFeatureSelector_SourceReturnText;
                    case TARGET:
                      return Messages.InstanceServiceFeatureSelector_TargetReturnText;
                    default:
                      return Messages.InstanceServiceFeatureSelector_defaultReturnText;
                  }
                } else {
                  return super.getText(element);
                }
              }
            });
        schemaSpaces.setContentProvider(ArrayContentProvider.getInstance());
        schemaSpaces.setInput(new Object[] {SchemaSpaceID.SOURCE, SchemaSpaceID.TARGET});
        schemaSpaces.setSelection(new StructuredSelection(SchemaSpaceID.SOURCE));
      } else {
        schemaSpaces = null;
      }

      // feature type selector
      typeDefinitions = new ComboViewer(this, SWT.READ_ONLY);
      typeDefinitions.setContentProvider(ArrayContentProvider.getInstance());
      typeDefinitions.setComparator(new DefinitionComparator());
      typeDefinitions.setLabelProvider(new DefinitionLabelProvider(null));
      typeDefinitions.addSelectionChangedListener(
          new ISelectionChangedListener() {

            @Override
            public void selectionChanged(SelectionChangedEvent event) {
              updateSelection();
            }
          });

      // filter field
      filterField =
          new CQLFilterField(
              (selectedType == null) ? (null) : (selectedType), this, SWT.NONE, spaceID);
      filterField.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
      filterField.addListener(
          new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
              if (evt.getPropertyName().equals(TypeFilterField.PROPERTY_FILTER)) {
                updateSelection();
              }
            }
          });

      // refresh button
      /*
       * XXX disabled for now - Button refresh = new Button(this,
       * SWT.PUSH); refresh.setImage(refreshImage);
       * refresh.setToolTipText("Refresh"); refresh.setLayoutData(new
       * GridData(SWT.CENTER, SWT.CENTER, false, false));
       * refresh.addSelectionListener(new SelectionAdapter() {
       *
       * @Override public void widgetSelected(SelectionEvent e) {
       * updateSelection(); }
       *
       * });
       */

      // max count selector
      count = new ComboViewer(this, SWT.READ_ONLY);
      count.setContentProvider(ArrayContentProvider.getInstance());
      count.setInput(
          new Integer[] {
            Integer.valueOf(1),
            Integer.valueOf(2),
            Integer.valueOf(3),
            Integer.valueOf(4),
            Integer.valueOf(5)
          });
      count.setSelection(new StructuredSelection(Integer.valueOf(2)));
      count.addSelectionChangedListener(
          new ISelectionChangedListener() {

            @Override
            public void selectionChanged(SelectionChangedEvent event) {
              updateSelection();
            }
          });

      updateTypesSelection();

      if (schemaSpaces != null) {
        schemaSpaces.addSelectionChangedListener(
            new ISelectionChangedListener() {

              @Override
              public void selectionChanged(SelectionChangedEvent event) {
                updateTypesSelection();
              }
            });
      }

      // service listeners
      SchemaService ss = PlatformUI.getWorkbench().getService(SchemaService.class);
      ss.addSchemaServiceListener(
          schemaListener =
              new SchemaServiceListener() {

                @Override
                public void schemaAdded(SchemaSpaceID spaceID, Schema schema) {
                  final Display display = PlatformUI.getWorkbench().getDisplay();
                  display.syncExec(
                      new Runnable() {

                        @Override
                        public void run() {
                          updateTypesSelection();
                        }
                      });
                }

                @Override
                public void schemasCleared(SchemaSpaceID spaceID) {
                  final Display display = PlatformUI.getWorkbench().getDisplay();
                  display.syncExec(
                      new Runnable() {

                        @Override
                        public void run() {
                          updateTypesSelection();
                        }
                      });
                }

                @Override
                public void mappableTypesChanged(
                    SchemaSpaceID spaceID, Collection<? extends TypeDefinition> types) {
                  final Display display = PlatformUI.getWorkbench().getDisplay();
                  display.syncExec(
                      new Runnable() {

                        @Override
                        public void run() {
                          updateTypesSelection();
                        }
                      });
                }
              });

      InstanceService is = PlatformUI.getWorkbench().getService(InstanceService.class);
      is.addListener(
          instanceListener =
              new InstanceServiceAdapter() {

                @Override
                public void datasetChanged(DataSet dataSet) {
                  final Display display = PlatformUI.getWorkbench().getDisplay();
                  display.syncExec(
                      new Runnable() {

                        @Override
                        public void run() {
                          updateTypesSelection();
                        }
                      });
                }
              });
    }