org.eclipse.oomph.setup.ui.wizards.VariablePage.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.oomph.setup.ui.wizards.VariablePage.java

Source

/*
 * Copyright (c) 2014-2016 Eike Stepper (Berlin, Germany) and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Eike Stepper - initial API and implementation
 *    Ericsson AB (Julian Enoch) - Bug 434525 - Allow prompted variables to be pre-populated
 */
package org.eclipse.oomph.setup.ui.wizards;

import org.eclipse.oomph.base.Annotation;
import org.eclipse.oomph.base.util.BaseUtil;
import org.eclipse.oomph.internal.setup.SetupPrompter;
import org.eclipse.oomph.internal.ui.AccessUtil;
import org.eclipse.oomph.preferences.util.PreferencesUtil;
import org.eclipse.oomph.setup.AnnotationConstants;
import org.eclipse.oomph.setup.Installation;
import org.eclipse.oomph.setup.SetupTask;
import org.eclipse.oomph.setup.SetupTaskContext;
import org.eclipse.oomph.setup.Trigger;
import org.eclipse.oomph.setup.User;
import org.eclipse.oomph.setup.VariableChoice;
import org.eclipse.oomph.setup.VariableTask;
import org.eclipse.oomph.setup.Workspace;
import org.eclipse.oomph.setup.internal.core.SetupContext;
import org.eclipse.oomph.setup.internal.core.SetupTaskPerformer;
import org.eclipse.oomph.setup.internal.core.util.Authenticator;
import org.eclipse.oomph.setup.internal.core.util.SetupCoreUtil;
import org.eclipse.oomph.setup.ui.PropertyField;
import org.eclipse.oomph.setup.ui.PropertyField.AuthenticatedField;
import org.eclipse.oomph.setup.ui.PropertyField.ValueListener;
import org.eclipse.oomph.setup.ui.SetupUIPlugin;
import org.eclipse.oomph.setup.ui.wizards.SetupWizard.IndexLoader;
import org.eclipse.oomph.setup.util.StringExpander;
import org.eclipse.oomph.ui.ButtonBar;
import org.eclipse.oomph.ui.ErrorDialog;
import org.eclipse.oomph.ui.UICallback;
import org.eclipse.oomph.ui.UIUtil;
import org.eclipse.oomph.util.CollectionUtil;
import org.eclipse.oomph.util.OS;
import org.eclipse.oomph.util.StringUtil;
import org.eclipse.oomph.util.UserCallback;

import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;

import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Eike Stepper
 */
public class VariablePage extends SetupWizardPage implements SetupPrompter {
    private static final String BASIC_DESCRIPTION = "Enter values for the required variables.";

    private static final String AUGMENTED_DESCRIPTION = BASIC_DESCRIPTION
            + "  Bold variables may conditionally affect the set of required variables.";

    private static final String SETUP_TASK_ANALYSIS_TITLE = "Setup Task Analysis";

    private static final URI INSTALLATION_ID_URI = URI.createURI("#~installation.id");

    private static final URI WORKSPACE_ID_URI = URI.createURI("#~workspace.id");

    private Composite composite;

    private ScrolledComposite scrolledComposite;

    private final FieldHolderManager manager = new FieldHolderManager();

    private final Set<String> unusedVariables = new HashSet<String>();

    private boolean prompted;

    private boolean fullPrompt;

    private boolean updating;

    private Set<SetupTaskPerformer> incompletePerformers = new LinkedHashSet<SetupTaskPerformer>();

    private Set<SetupTaskPerformer> allPromptedPerfomers = new LinkedHashSet<SetupTaskPerformer>();

    private SetupTaskPerformer performer;

    private Control focusControl;

    private SetupContext originalContext;

    private boolean save = true;

    private boolean defaultsSet;

    private UserCallback userCallback;

    private final Validator validator = new Validator();

    private FocusListener focusListener = new FocusAdapter() {
        @Override
        public void focusGained(FocusEvent e) {
            focusControl = (Control) e.widget;
        }
    };

    private PerformerCreationJob performerCreationJob;

    private long delay;

    public VariablePage() {
        super("VariablePage");
        setTitle("Variables");
        setDescription(BASIC_DESCRIPTION);
    }

    @Override
    protected Control createUI(Composite parent) {
        Composite mainComposite = new Composite(parent, SWT.NONE);
        mainComposite.setLayout(UIUtil.createGridLayout(1));
        mainComposite.setLayoutData(new GridData(GridData.FILL_BOTH));

        GridLayout outerLayout = (GridLayout) parent.getParent().getLayout();
        outerLayout.marginLeft = outerLayout.marginWidth;
        outerLayout.marginWidth = 0;

        scrolledComposite = new ScrolledComposite(mainComposite, SWT.VERTICAL);
        scrolledComposite.setExpandHorizontal(true);
        scrolledComposite.setExpandVertical(true);
        scrolledComposite.setShowFocusedControl(true);
        scrolledComposite.setLayoutData(new GridData(GridData.FILL_BOTH));

        GridLayout layout = UIUtil.createGridLayout(3);
        layout.horizontalSpacing = 10;
        layout.verticalSpacing = 10;
        layout.marginRight = outerLayout.marginLeft;

        composite = new Composite(scrolledComposite, SWT.NONE);
        composite.setLayout(layout);
        scrolledComposite.setContent(composite);
        composite.setLayoutData(new GridData(GridData.FILL_BOTH));

        ControlAdapter resizeListener = new ControlAdapter() {
            @Override
            public void controlResized(ControlEvent event) {
                Point size = composite.computeSize(scrolledComposite.getClientArea().width, SWT.DEFAULT);
                scrolledComposite.setMinSize(size);
            }
        };

        scrolledComposite.addControlListener(resizeListener);
        composite.addControlListener(resizeListener);
        composite.notifyListeners(SWT.Resize, new Event());

        return mainComposite;
    }

    @Override
    protected void createCheckButtons(ButtonBar buttonBar) {
        final Button fullPromptButton = buttonBar.addCheckButton("Show all variables", "", false, "fullPrompt");
        fullPrompt = fullPromptButton.getSelection();
        fullPromptButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                fullPrompt = fullPromptButton.getSelection();
                validator.schedule(false);
            }
        });

        AccessUtil.setKey(fullPromptButton, "showAll");
    }

    private synchronized boolean updateFields() {
        unusedVariables.clear();

        for (FieldHolder fieldHolder : manager) {
            fieldHolder.clear();
        }

        Set<SetupTaskPerformer> performers = new LinkedHashSet<SetupTaskPerformer>();
        if (incompletePerformers.isEmpty()) {
            if (performer != null) {
                performers.add(performer);
            }

            performers.addAll(allPromptedPerfomers);
        } else {
            performers.addAll(incompletePerformers);
            performers.addAll(allPromptedPerfomers);
        }

        Set<String> usedVariables = new HashSet<String>();
        for (FieldHolder fieldHolder : manager) {
            String value = fieldHolder.getValue();
            if (!StringUtil.isEmpty(value)) {
                usedVariables.addAll(SetupTaskPerformer.getVariables(value));
            }
        }

        for (SetupTaskPerformer setupTaskPerformer : performers) {
            List<VariableTask> variables = setupTaskPerformer.getUnresolvedVariables();
            for (VariableTask variable : variables) {
                VariableTask ruleVariable = setupTaskPerformer.getRuleVariable(variable);
                if (ruleVariable == null) {
                    if (variable.getAnnotation(AnnotationConstants.ANNOTATION_UNDECLARED_VARIABLE) != null) {
                        String name = variable.getName();
                        if (!usedVariables.contains(name)) {
                            Trigger trigger = getTrigger();
                            boolean isUsedInActualTriggeredTask = false;
                            for (SetupTask setupTask : setupTaskPerformer.getTriggeredSetupTasks()) {
                                if (setupTask.getTriggers().contains(trigger)
                                        && setupTaskPerformer.isVariableUsed(name, setupTask, true)) {
                                    isUsedInActualTriggeredTask = true;
                                    break;
                                }
                            }

                            if (!isUsedInActualTriggeredTask) {
                                unusedVariables.add(name);
                                continue;
                            }
                        }
                    }

                    manager.getFieldHolder(variable, true, false);
                } else {
                    FieldHolder fieldHolder = manager.getFieldHolder(ruleVariable, true, false);
                    fieldHolder.add(variable);
                    manager.associate(variable, fieldHolder);
                }
            }
        }

        boolean setDefault = false;
        for (FieldHolder fieldHolder : manager) {
            if (StringUtil.isEmpty(fieldHolder.getValue())) {
                String initialValue = null;
                String initialDefaultValue = null;
                for (VariableTask variable : fieldHolder.getVariables()) {
                    if (initialValue == null) {
                        String value = variable.getValue();
                        if (!StringUtil.isEmpty(value)) {
                            initialValue = value;

                            // Check the choices for ones that specify "match choice" annotations.
                            for (VariableChoice choice : variable.getChoices()) {
                                Annotation annotation = choice
                                        .getAnnotation(AnnotationConstants.ANNOTATION_MATCH_CHOICE);
                                if (annotation != null) {
                                    String choiceValue = choice.getValue();
                                    if (choiceValue != null) {
                                        // Expand the choice into a pattern where the variables expand to ".*" and the rest of the value is quoted as literal.
                                        StringBuffer result = new StringBuffer("\\Q");
                                        Matcher matcher = StringExpander.STRING_EXPANSION_PATTERN
                                                .matcher(choiceValue);
                                        while (matcher.find()) {
                                            matcher.appendReplacement(result, "\\\\E.*\\\\Q");
                                        }

                                        matcher.appendTail(result);

                                        try {
                                            // If the pattern matches, use the value of the choice as the initial value.
                                            Pattern pattern = Pattern.compile(result.toString());
                                            if (pattern.matcher(value).matches()) {
                                                initialValue = choiceValue;
                                                setDefault = true;
                                                break;
                                            }
                                        } catch (Throwable throwable) {
                                            // Ignore.
                                        }
                                    }
                                }
                            }
                        }
                    }

                    if (initialDefaultValue == null) {
                        String defaultValue = variable.getDefaultValue();
                        if (!StringUtil.isEmpty(defaultValue)) {
                            initialDefaultValue = defaultValue;
                        }
                    }
                }

                if (!StringUtil.isEmpty(initialValue)) {
                    fieldHolder.setValue(initialValue);
                } else if (!StringUtil.isEmpty(initialDefaultValue)) {
                    setDefault = true;
                    fieldHolder.setValue(initialDefaultValue);
                } else {
                    String defaultValue = fieldHolder.getDefaultValue();
                    if (!StringUtil.isEmpty(defaultValue)) {
                        setDefault = true;
                        fieldHolder.setValue(defaultValue);
                    }
                }
            }
        }

        try {
            for (FieldHolder fieldHolder : manager) {
                updating = true;
                fieldHolder.update();
            }
        } finally {
            updating = false;
        }

        for (FieldHolder fieldHolder : manager) {
            fieldHolder.recordInitialValue();
        }

        // Determine the URIs of all the variables actually being used.
        Set<URI> uris = new HashSet<URI>();
        if (performer != null) {
            for (VariableTask variable : performer.getUnresolvedVariables()) {
                uris.add(manager.getURI(variable));
                VariableTask ruleVariable = performer.getRuleVariable(variable);
                if (ruleVariable != null) {
                    uris.add(manager.getURI(ruleVariable));
                }
            }
        }

        manager.cleanup(uris);

        ScrolledComposite parent = (ScrolledComposite) composite.getParent();
        Point origin = parent.getOrigin();
        parent.setRedraw(false);

        List<SetupTaskPerformer> allPerformers = new ArrayList<SetupTaskPerformer>(allPromptedPerfomers);
        if (performer != null) {
            allPerformers.add(0, performer);
        }

        // Determine an appropriate field order.
        manager.reorder(allPerformers);

        parent.pack();
        parent.getParent().layout();
        parent.setOrigin(origin);
        parent.setRedraw(true);

        FieldHolder firstField = null;
        FieldHolder firstEmptyField = null;
        for (FieldHolder fieldHolder : manager) {
            if (!fieldHolder.isDisposed()) {
                if (firstField == null) {
                    firstField = fieldHolder;
                }

                if (firstEmptyField == null && StringUtil.isEmpty(fieldHolder.getValue())) {
                    firstEmptyField = fieldHolder;
                }
            }
        }

        if (focusControl != null && !focusControl.isDisposed()) {
            focusControl.setFocus();
            parent.showControl(focusControl);
        } else {
            FieldHolder field = firstEmptyField;
            if (field == null) {
                field = firstField;
            }

            if (field != null) {
                field.setFocus();
                parent.showControl(field.getControl());
            }
        }

        for (FieldHolder fieldHolder : manager) {
            if (!fieldHolder.isDisposed()) {
                Control control = fieldHolder.getControl();
                PropertyField field = fieldHolder.field;
                Label label = field.getLabel();
                Control helper = field.getHelper();
                for (VariableTask variable : fieldHolder.getVariables()) {
                    String name = variable.getName();
                    if (name.startsWith("@<id>")) {
                        name = name.substring(name.indexOf("name: ") + 6, name.indexOf(')'));
                    }

                    AccessUtil.setKey(label, name + ".label");
                    AccessUtil.setKey(control, name + ".control");
                    AccessUtil.setKey(helper, name + ".helper");

                    break;
                }
            }
        }

        if (setDefault) {
            defaultsSet = true;
        }

        boolean hasFilterProperties = false;
        for (FieldHolder fieldHolder : manager) {
            if (fieldHolder.isFilterProperty()) {
                hasFilterProperties = true;
                break;
            }
        }

        setDescription(hasFilterProperties ? AUGMENTED_DESCRIPTION : BASIC_DESCRIPTION);

        if (isPageComplete() ? setDefault : firstEmptyField == null || setDefault) {
            // If the page isn't complete but there are no empty fields, then the last change introduced a new field.
            // So we should validate again to be sure there really needs to be more information prompted from the user.
            return true;
        }

        return false;
    }

    private void validate() {
        try {
            performer = null;
            incompletePerformers.clear();
            allPromptedPerfomers.clear();

            setButtonState(IDialogConstants.NEXT_ID, false);

            performerCreationJob = new PerformerCreationJob(SETUP_TASK_ANALYSIS_TITLE) {
                @Override
                protected SetupTaskPerformer createPerformer() throws Exception {
                    return VariablePage.this.createPerformer(VariablePage.this, fullPrompt);
                }

                @Override
                protected Dialog createDialog() {
                    return createDialog(getShell(), SETUP_TASK_ANALYSIS_TITLE, null,
                            "Analyzing the needed setup tasks has taken more than "
                                    + (System.currentTimeMillis() - getStart()) / 1000
                                    + " seconds.  The Next button will be disabled, though animated, until it completes.  You may continue to modify the values of the variables.",
                            MessageDialog.INFORMATION, new String[] { IDialogConstants.OK_LABEL }, 0);
                }

                @Override
                protected void handleDialogResult(int result) {
                    if (result == 0) {
                        setDelay(Integer.MAX_VALUE);
                    } else {
                        setDelay(2 * getDelay());
                    }
                }
            };

            if (delay != 0) {
                performerCreationJob.setDelay(delay);
            }

            performerCreationJob.create();
            delay = performerCreationJob.getDelay();

            Throwable throwable = performerCreationJob.getThrowable();
            if (throwable != null) {
                if (throwable instanceof OperationCanceledException) {
                    performerCreationJob = null;
                    return;
                }

                throw throwable;
            }

            performer = performerCreationJob.getPerformer();

            UIUtil.asyncExec(getControl(), new Runnable() {
                public void run() {
                    performerCreationJob = null;
                    if (updateFields()) {
                        validate();
                    }
                }
            });

            if (performer == null) {
                setPageComplete(false);
            } else {
                setPageComplete(true);

                if (!prompted) {
                    prompted = true;
                    gotoNextPage();
                }
            }
        } catch (Throwable t) {
            performerCreationJob = null;
            SetupUIPlugin.INSTANCE.log(t);
            ErrorDialog.open(t);
        }
    }

    private void clearSpecialFieldHolders() {
        clearSpecialFieldHolders(INSTALLATION_ID_URI);
        clearSpecialFieldHolders(WORKSPACE_ID_URI);
    }

    private void clearSpecialFieldHolders(URI uri) {
        FieldHolderRecord fieldHolderRecord = manager.getFieldHolderRecord(uri);
        if (fieldHolderRecord != null) {
            FieldHolder fieldHolder = fieldHolderRecord.getFieldHolder();
            if (fieldHolder != null && !fieldHolder.isDirty()) {
                fieldHolder.clearValue();
            }
        }
    }

    @Override
    public void enterPage(boolean forward) {
        if (forward) {
            if (userCallback == null) {
                Shell shell = getShell();
                userCallback = new UICallback(shell, shell.getText());
            }

            clearSpecialFieldHolders();
        }

        performer = getWizard().getPerformer();
        if (performer != null && forward) {
            performer.setPrompter(this);
            setPageComplete(true);
            gotoNextPage();
        } else {
            if (!forward) {
                getWizard().setSetupContext(originalContext);
                originalContext = null;
            }

            IndexLoader indexLoader = getWizard().getIndexLoader();
            if (indexLoader != null) {
                indexLoader.awaitIndexLoad();
            }

            setPageComplete(false);
            validate();
            if (forward && getPreviousPage() == null) {
                UIUtil.asyncExec(getControl(), new Runnable() {
                    public void run() {
                        if (isPageComplete() && !defaultsSet) {
                            gotoNextPage();
                        }
                    }
                });
            }
        }
    }

    @Override
    public void leavePage(boolean forward) {
        if (forward) {
            originalContext = getWizard().getSetupContext();

            final List<VariableTask> unresolvedVariables = performer.getUnresolvedVariables();
            for (FieldHolder fieldHolder : manager) {
                unresolvedVariables.addAll(fieldHolder.getVariables());
            }

            final ResourceSet resourceSet = SetupCoreUtil.createResourceSet();

            User user = getUser();
            final User copiedUser = EcoreUtil.copy(user);
            URI userResourceURI = user.eResource().getURI();
            Resource userResource = resourceSet.createResource(userResourceURI);
            userResource.getContents().add(copiedUser);

            Installation installation = performer.getInstallation();
            Resource installationResource = installation.eResource();
            URI installationResourceURI = installationResource.getURI();
            installationResource.setURI(URI.createFileURI(new File(performer.getProductConfigurationLocation(),
                    "org.eclipse.oomph.setup/installation.setup").toString()));

            Workspace workspace = performer.getWorkspace();
            Resource workspaceResource = null;
            URI workspaceResourceURI = null;
            if (workspace != null) {
                workspaceResource = workspace.eResource();
                workspaceResourceURI = workspaceResource.getURI();
                workspaceResource.setURI(URI.createFileURI(new File(performer.getWorkspaceLocation(),
                        ".metadata/.plugins/org.eclipse.oomph.setup/workspace.setup").toString()));
            }

            Installation copiedInstallation = EcoreUtil.copy(installation);
            URI copiedInstallationResourceURI = installation.eResource().getURI();
            Resource copiedInstallationResource = resourceSet.createResource(copiedInstallationResourceURI);
            copiedInstallationResource.getContents().add(copiedInstallation);

            Workspace copiedWorkspace = EcoreUtil.copy(workspace);
            if (workspace != null) {
                URI copiedWorkspaceResourceURI = workspace.eResource().getURI();
                Resource copiedWorkspaceResource = resourceSet.createResource(copiedWorkspaceResourceURI);
                copiedWorkspaceResource.getContents().add(copiedWorkspace);
            }

            performer.recordVariables(copiedInstallation, copiedWorkspace, copiedUser);

            unresolvedVariables.clear();

            getWizard().setSetupContext(SetupContext.create(copiedInstallation, copiedWorkspace, copiedUser));
            setPerformer(performer);

            if (save) {
                BaseUtil.saveEObject(copiedUser);

                performer.savePasswords();
            }

            installationResource.setURI(installationResourceURI);
            if (workspaceResource != null) {
                workspaceResource.setURI(workspaceResourceURI);
            }
        } else {
            originalContext = null;

            setPerformer(null);

            validator.cancel();
        }
    }

    public synchronized String getValue(VariableTask variable) {
        FieldHolder fieldHolder = manager.getFieldHolder(variable, false, true);
        if (fieldHolder != null && (updating || fieldHolder.isDirty())) {
            String value = fieldHolder.getValue();
            if (!"".equals(value)) {
                return value;
            }
        }

        if (updating && performer != null) {
            Object value = performer.getMap().get(variable.getName());
            if (value != null) {
                return value.toString();
            }
        }

        return null;
    }

    public OS getOS() {
        return getWizard().getOS();
    }

    public String getVMPath() {
        return getWizard().getVMPath();
    }

    public synchronized boolean promptVariables(List<? extends SetupTaskContext> contexts) {
        prompted = true;

        @SuppressWarnings("unchecked")
        List<SetupTaskPerformer> performers = (List<SetupTaskPerformer>) contexts;
        allPromptedPerfomers.addAll(performers);
        for (SetupTaskPerformer performer : performers) {
            boolean resolvedAll = true;
            List<VariableTask> unresolvedVariables = performer.getUnresolvedVariables();
            for (VariableTask variable : unresolvedVariables) {
                FieldHolder fieldHolder = manager.getFieldHolder(variable, false, true);
                if (fieldHolder != null) {
                    String value = fieldHolder.getValue();
                    if (!"".equals(value)) {
                        variable.setValue(value);
                    } else {
                        resolvedAll = false;
                    }
                } else if (unusedVariables.contains(variable.getName())) {
                    variable.setValue(" ");
                } else {
                    resolvedAll = false;
                }
            }

            if (!resolvedAll) {
                incompletePerformers.add(performer);
            }
        }

        boolean isComplete = incompletePerformers.isEmpty();
        return isComplete;
    }

    public UserCallback getUserCallback() {
        return userCallback;
    }

    /**
     * @author Ed Merks
     */
    private final class FieldHolder implements ValueListener {
        private final Set<VariableTask> variables = new LinkedHashSet<VariableTask>();

        private PropertyField field;

        private String initialValue;

        private boolean filterProperty;

        public FieldHolder(VariableTask variable) {
            field = PropertyField.createField(variable);
            field.fill(composite);
            field.addValueListener(this);
            field.getControl().addFocusListener(focusListener);
            variables.add(variable);
        }

        public boolean isDisposed() {
            return field == null;
        }

        private Control getControl() {
            if (field == null) {
                return null;
            }

            Control control = field.getControl();
            Control parent = control.getParent();
            if (parent == composite) {
                return control;
            }

            return null;
        }

        public void setFocus() {
            if (field == null) {
                throw new IllegalStateException("Can't set the value of a disposed field");
            }

            field.setFocus();
        }

        public String getValue() {
            return field == null ? initialValue : field.getValue();
        }

        public String getDefaultValue() {
            return field == null ? null : field.getDefaultValue();
        }

        public void clearValue() {
            initialValue = "";

            if (field != null) {
                field.setValue("", false, false);
            }
        }

        public void setValue(String value) {
            if (field == null) {
                throw new IllegalStateException("Can't set the value of a disposed field");
            }

            initialValue = null;

            field.setValue(value, false);
        }

        public Set<VariableTask> getVariables() {
            return Collections.unmodifiableSet(variables);
        }

        public void clear() {
            variables.clear();

            if (field instanceof AuthenticatedField) {
                AuthenticatedField authenticatedField = (AuthenticatedField) field;
                authenticatedField.clear();
            }
        }

        public void add(VariableTask variable) {
            if (variables.add(variable)) {
                String value = field.getValue();
                if (!"".equals(value)) {
                    variable.setValue(value);
                }
            }
        }

        public void setFilterProperty(boolean filterProperty) {
            this.filterProperty = filterProperty;
            if (field != null) {
                field.setBold(filterProperty);
            }
        }

        public boolean isFilterProperty() {
            return filterProperty;
        }

        public void update() {
            if (field instanceof AuthenticatedField) {
                AuthenticatedField authenticatedField = (AuthenticatedField) field;
                String value = field.getValue();
                Set<Authenticator> allAuthenticators = new LinkedHashSet<Authenticator>();
                for (VariableTask variable : variables) {
                    if (!StringUtil.isEmpty(value)) {
                        variable.setValue(value);
                    }

                    Set<? extends Authenticator> authenticators = SetupTaskPerformer.getAuthenticators(variable);
                    if (authenticators != null) {
                        allAuthenticators.addAll(authenticators);
                    }
                }

                if (!allAuthenticators.isEmpty()) {
                    for (Iterator<Authenticator> it = allAuthenticators.iterator(); it.hasNext();) {
                        Authenticator authenticator = it.next();
                        if (authenticator.isFiltered()) {
                            it.remove();
                        }
                    }

                    authenticatedField.addAll(allAuthenticators);

                    if (allAuthenticators.isEmpty()) {
                        dispose(PreferencesUtil.encrypt(" "));
                    }
                }
            }
        }

        public void valueChanged(String oldValue, String newValue) throws Exception {
            synchronized (VariablePage.this) {
                for (VariableTask variable : variables) {
                    variable.setValue(newValue);
                }
            }

            validator.schedule(true);
        }

        public void recordInitialValue() {
            if (initialValue == null && field != null) {
                initialValue = field.getValue();
            }
        }

        public boolean isDirty() {
            return field != null && initialValue != null && !initialValue.equals(field.getValue());
        }

        public void dispose(String value) {
            if (field != null) {
                field.dispose();
                field = null;
            }

            if (StringUtil.isEmpty(initialValue)) {
                initialValue = value;
            }
        }

        public void dispose() {
            if (field != null) {
                field.dispose();
                field = null;
            }
        }

        @Override
        public String toString() {
            return field == null ? "<disposed>" : field.toString();
        }
    }

    /**
     * @author Ed Merks
     */
    private static class FieldHolderRecord {
        private FieldHolder fieldHolder;

        private final Set<URI> variableURIs = new HashSet<URI>();

        public FieldHolderRecord() {
        }

        public FieldHolder getFieldHolder() {
            return fieldHolder;
        }

        public void setFieldHolder(FieldHolder fieldHolder) {
            this.fieldHolder = fieldHolder;
        }

        public Set<URI> getVariableURIs() {
            return variableURIs;
        }

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

    /**
     * @author Ed Merks
     */
    private class FieldHolderManager implements Iterable<FieldHolder> {
        private final EList<FieldHolderRecord> fields = new BasicEList<FieldHolderRecord>();

        public Iterator<FieldHolder> iterator() {
            final Iterator<FieldHolderRecord> iterator = fields.iterator();
            return new Iterator<FieldHolder>() {
                public boolean hasNext() {
                    return iterator.hasNext();
                }

                public FieldHolder next() {
                    return iterator.next().getFieldHolder();
                }

                public void remove() {
                    throw new UnsupportedOperationException();
                }
            };
        }

        public void reorder(List<SetupTaskPerformer> allPerformers) {
            List<Control> controls = new ArrayList<Control>();
            Map<FieldHolderRecord, Set<FieldHolderRecord>> ruleUses = new LinkedHashMap<FieldHolderRecord, Set<FieldHolderRecord>>();
            LOOP: for (FieldHolderRecord fieldHolderRecord : fields) {
                FieldHolder fieldHolder = fieldHolderRecord.getFieldHolder();
                Control control = fieldHolder.getControl();
                if (control != null) {
                    controls.add(control);
                    for (VariableTask variable : fieldHolder.getVariables()) {
                        for (SetupTaskPerformer performer : allPerformers) {
                            EAttribute eAttribute = performer.getAttributeRuleVariableData(variable);
                            if (eAttribute != null) {
                                CollectionUtil.addAll(ruleUses, fieldHolderRecord,
                                        Collections.<FieldHolderRecord>emptySet());
                                continue LOOP;
                            }
                        }
                    }

                    for (VariableTask variable : fieldHolder.getVariables()) {
                        for (SetupTaskPerformer performer : allPerformers) {
                            VariableTask dependantVariable = performer.getRuleVariableData(variable);
                            if (dependantVariable != null) {
                                VariableTask ruleVariable = performer.getRuleVariable(dependantVariable);
                                if (ruleVariable != null) {
                                    FieldHolderRecord dependantFieldHolderRecord = getFieldHolderRecord(
                                            getURI(ruleVariable));
                                    if (dependantFieldHolderRecord != null) {
                                        CollectionUtil.add(ruleUses, dependantFieldHolderRecord, fieldHolderRecord);
                                        continue LOOP;
                                    }
                                }
                            }
                        }
                    }
                }
            }

            int fieldsSize = fields.size();
            if (fieldsSize > 1) {
                int index = 0;
                int maxPosition = fieldsSize - 1;
                for (Map.Entry<FieldHolderRecord, Set<FieldHolderRecord>> entry : ruleUses.entrySet()) {
                    FieldHolderRecord fieldHolderRecord = entry.getKey();
                    fields.move(index++, fieldHolderRecord);
                    Set<FieldHolderRecord> fieldHolderRecords = entry.getValue();
                    for (FieldHolderRecord dependantFieldHolderRecord : fieldHolderRecords) {
                        fields.move(Math.min(index++, maxPosition), dependantFieldHolderRecord);
                    }
                }

                for (int i = 0; i < fields.size(); ++i) {
                    FieldHolderRecord fieldHolderRecord = fields.get(i);
                    for (VariableTask variable : fieldHolderRecord.getFieldHolder().getVariables()) {
                        boolean isFilterProperty = false;
                        for (SetupTaskPerformer performer : allPerformers) {
                            if (performer.isFilterProperty(variable)) {
                                isFilterProperty = true;
                                break;
                            }
                        }

                        fieldHolderRecord.getFieldHolder().setFilterProperty(isFilterProperty);
                    }
                }
            }

            int size = controls.size();
            if (size > 1) {
                List<Control> children = Arrays.asList(composite.getChildren());

                int controlOffset = 0;
                for (Control child : children) {
                    if (controls.contains(child)) {
                        break;
                    }

                    ++controlOffset;
                }

                Control target = children.get(PropertyField.NUM_COLUMNS - 1);
                int count = 0;
                for (FieldHolder fieldHolder : this) {
                    Control control = fieldHolder.getControl();
                    if (control != null) {
                        int index = children.indexOf(control) - controlOffset;
                        Control newTarget = null;
                        for (int j = PropertyField.NUM_COLUMNS - 1; j >= 0; --j) {
                            Control child = children.get(index + j);
                            if (newTarget == null) {
                                newTarget = child;
                                if (index == count) {
                                    break;
                                }
                            }

                            child.moveBelow(target);
                        }

                        target = newTarget;
                        count += PropertyField.NUM_COLUMNS;
                    }
                }
            }
        }

        public void cleanup(Set<URI> uris) {
            LOOP: for (FieldHolderRecord fieldHolderRecord : fields) {
                for (URI uri : fieldHolderRecord.getVariableURIs()) {
                    if (uris.contains(uri)) {
                        continue LOOP;
                    }
                }

                FieldHolder fieldHolder = fieldHolderRecord.getFieldHolder();
                if (fieldHolder.getVariables().isEmpty() && !fieldHolder.isDirty()) {
                    fieldHolder.dispose();
                }
            }
        }

        public URI getURI(VariableTask variable) {
            String name = variable.getName();
            if (variable.getAnnotation(AnnotationConstants.ANNOTATION_GLOBAL_VARIABLE) != null) {
                return URI.createURI("#" + name);
            }

            Resource resource = variable.eResource();
            URI uri;
            if (resource == null) {
                uri = URI.createURI("#");
            } else {
                EObject eObject = resource.getContents().get(0);
                if (eObject instanceof Installation || eObject instanceof Workspace) {
                    uri = URI.createURI(resource.getURI().lastSegment())
                            .appendFragment(resource.getURIFragment(variable));
                } else {
                    uri = EcoreUtil.getURI(variable);
                }
            }

            uri = uri.appendFragment(uri.fragment() + "~" + name);
            return uri;
        }

        private FieldHolderRecord getFieldHolderRecord(URI uri) {
            for (FieldHolderRecord fieldHolderRecord : fields) {
                if (fieldHolderRecord.getVariableURIs().contains(uri)) {
                    return fieldHolderRecord;
                }
            }

            return null;
        }

        public void associate(VariableTask variable, FieldHolder fieldHolder) {
            URI uri = getURI(variable);
            for (FieldHolderRecord fieldHolderRecord : fields) {
                if (fieldHolderRecord.getFieldHolder() == fieldHolder) {
                    fieldHolderRecord.getVariableURIs().add(uri);
                    break;
                }
            }
        }

        public FieldHolder getFieldHolder(VariableTask variable, boolean demandCreate, boolean includeDisposed) {
            URI uri = getURI(variable);
            FieldHolderRecord fieldHolderRecord = getFieldHolderRecord(uri);
            FieldHolder fieldHolder = null;
            if (fieldHolderRecord == null) {
                if (!demandCreate) {
                    return null;
                }

                fieldHolderRecord = new FieldHolderRecord();
                fieldHolderRecord.getVariableURIs().add(uri);
                fields.add(fieldHolderRecord);
            } else {
                fieldHolder = fieldHolderRecord.getFieldHolder();
                if (fieldHolder.isDisposed()) {
                    if (includeDisposed) {
                        return fieldHolder;
                    }

                    if (!demandCreate) {
                        return null;
                    }

                    fieldHolder = null;
                }
            }

            if (fieldHolder == null) {
                fieldHolder = new FieldHolder(variable);
                fieldHolderRecord.setFieldHolder(fieldHolder);
            } else if (!updating) {
                fieldHolder.add(variable);
            }

            return fieldHolder;
        }

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

    /**
     * A class for delayed validation.
     * @author Ed Merks
     */
    private class Validator implements Runnable {
        private boolean canceled;

        private boolean dispatched;

        private boolean redispatch;

        private boolean clearSpecialFieldHolders;

        public void run() {
            if (!canceled && !getShell().isDisposed()) {
                dispatched = false;
                if (redispatch || performerCreationJob != null) {
                    if (performerCreationJob != null) {
                        performerCreationJob.cancel();
                    }

                    schedule(clearSpecialFieldHolders);
                } else {
                    if (clearSpecialFieldHolders) {
                        clearSpecialFieldHolders();
                        clearSpecialFieldHolders = false;
                    }

                    validate();
                }
            }
        }

        public void schedule(boolean clearSpecialFieldHolders) {
            canceled = false;

            if (clearSpecialFieldHolders) {
                this.clearSpecialFieldHolders = true;
            }

            if (dispatched) {
                redispatch = true;
            } else {
                dispatched = true;
                redispatch = false;

                UIUtil.timerExec(350, this);
            }
        }

        public void cancel() {
            canceled = true;
            dispatched = false;
            redispatch = false;
            clearSpecialFieldHolders = false;

            if (performerCreationJob != null) {
                performerCreationJob.cancel();
            }
        }
    }
}