/** Sets the <code>key</code> editor's current value to <code>value</code>. */
 public void setAttribute(String key, Object value) {
   final IAttributeEditor editor = editors.get(key);
   if (editor != null) {
     editor.setValue(value);
   }
 }
  /** Create internal GUI. */
  private void createComponents(Map<String, Object> currentValues) {
    /*
     * Sort alphabetically by label.
     */
    final Locale locale = Locale.getDefault();
    final Map<String, String> labels = Maps.newHashMap();
    for (Map.Entry<String, AttributeDescriptor> entry : attributeDescriptors.entrySet()) {
      labels.put(entry.getKey(), getLabel(entry.getValue()).toLowerCase(locale));
    }

    final Collator collator = Collator.getInstance(locale);
    final List<String> sortedKeys = Lists.newArrayList(labels.keySet());
    Collections.sort(
        sortedKeys,
        new Comparator<String>() {
          public int compare(String a, String b) {
            return collator.compare(labels.get(a), labels.get(b));
          }
        });

    /*
     * Create editors and inquire about their layout needs.
     */
    editors = Maps.newHashMap();
    final Map<String, AttributeEditorInfo> editorInfos = Maps.newHashMap();

    int maxColumns = 1;
    for (String key : sortedKeys) {
      final AttributeDescriptor descriptor = attributeDescriptors.get(key);

      IAttributeEditor editor = null;
      try {
        editor = EditorFactory.getEditorFor(this.componentClazz, descriptor);
        final AttributeEditorInfo info =
            editor.init(bindable, descriptor, globalEventsProvider, currentValues);

        editorInfos.put(key, info);
        maxColumns = Math.max(maxColumns, info.columns);
      } catch (EditorNotFoundException ex) {
        Utils.logError(
            "No editor for attribute: " + descriptor.key + " (class: " + descriptor.type + ")",
            false);

        /*
         * Skip editor.
         */
        editor = null;
      }

      editors.put(key, editor);
    }

    /*
     * Prepare the layout for this editor.
     */
    final GridLayout layout = GUIFactory.zeroMarginGridLayout();
    layout.makeColumnsEqualWidth = false;

    layout.numColumns = maxColumns;
    this.setLayout(layout);

    /*
     * Create visual components for editors.
     */
    final GridDataFactory labelFactory = GridDataFactory.fillDefaults().span(maxColumns, 1);

    boolean firstEditor = true;
    for (String key : sortedKeys) {
      final AttributeDescriptor descriptor = attributeDescriptors.get(key);
      final IAttributeEditor editor = editors.get(key);
      final AttributeEditorInfo editorInfo = editorInfos.get(key);

      if (editor == null) {
        // Skip attributes without the editor.
        continue;
      }

      final Object defaultValue;
      if (currentValues != null && currentValues.get(key) != null) {
        defaultValue = currentValues.get(key);
      } else {
        defaultValue = attributeDescriptors.get(key).defaultValue;
      }

      // Add label to editors that do not have it.
      if (!editorInfo.displaysOwnLabel) {
        final Label label = new Label(this, SWT.LEAD);
        final GridData gd = labelFactory.create();
        if (!firstEditor) {
          gd.verticalIndent = SPACE_BEFORE_LABEL;
        }
        label.setLayoutData(gd);

        label.setText(getLabel(descriptor) + (descriptor.requiredAttribute ? " (required)" : ""));

        /*
         * Add validation overlay.
         */
        addValidationOverlay(descriptor, editor, defaultValue, label);

        AttributeInfoTooltip.attach(label, descriptor);
      }

      // Add the editor, if available.
      editor.createEditor(this, maxColumns);

      // Set the default value for the editor.
      editor.setValue(defaultValue);
      editors.put(editor.getAttributeKey(), editor);

      /*
       * Forward events from this editor to all our listeners.
       */
      editor.addAttributeListener(forwardListener);

      firstEditor = false;
    }
  }