<commit_before>package ua.com.fielden.platform.swing.model;

import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;

import javax.swing.Action;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JOptionPane;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import ua.com.fielden.platform.dao.IMasterDetailsDao;
import ua.com.fielden.platform.entity.AbstractEntity;
import ua.com.fielden.platform.entity.query.fluent.fetch;
import ua.com.fielden.platform.error.Result;
import ua.com.fielden.platform.swing.actions.Command;
import ua.com.fielden.platform.swing.components.NotificationLayer.MessageType;
import ua.com.fielden.platform.swing.components.bind.development.Binder;
import ua.com.fielden.platform.swing.components.blocking.BlockingIndefiniteProgressPane;
import ua.com.fielden.platform.swing.dialogs.DialogWithDetails;
import ua.com.fielden.platform.swing.egi.models.PropertyTableModel;
import ua.com.fielden.platform.swing.ei.editors.development.ILightweightPropertyBinder;

import com.jidesoft.grid.TableModelWrapperUtils;

/**
 * This is a CRUD UI model representing one-to-many master/details association. It has the following properties:
 * <ul>
 * <li>All instances of details entities are kept in {@link PropertyTableModel}.
 * <li>Only one instance can be edited or saved at the time.
 * <li>A details instance becomes a managed entity (thus get's associated with editors) upon selection of a row representing this instance in EGI associated with model's table
 * model.
 * <li>After instantiation, model should be provided with an instance of the associated view in order to be able to provided some feedback to users.
 * </ul>
 *
 * @author <NAME>
 *
 * @param <M>
 * @param <D>
 * @param <C>
 */
public abstract class UmDetailsWithCrudMany<M extends AbstractEntity<?>, D extends AbstractEntity<?>, C extends IMasterDetailsDao<M, D>> extends UmDetailsWithCrud<M, D, C> {
    /** Table model representing all instances of details entities. */
    private final PropertyTableModel<D> tableModel;

    /** Listener responsible for an action happening upon row change in the grid of details entities. */
    private final ListSelectionListener onRowSelect;
    // a helper state to assist in correct handling of the programmatically enforced row change even, which occurs upon cancel action.
    // its value should be changed strictly on EDT in order to ensure thread safety
    private boolean isModifying = false;

    protected UmDetailsWithCrudMany(final M entity, final C companion, final ILightweightPropertyBinder<D> propertyBinder, final fetch<D> fm, final PropertyTableModel<D> tableModel, final boolean lazy) {
	super(entity, companion, propertyBinder, fm, lazy);
	this.tableModel = tableModel;
	onRowSelect = new ListSelectionListener() {
	    private int oldIndex = -1;

	    @Override
	    public void valueChanged(final ListSelectionEvent e) {
		if (!e.getValueIsAdjusting()) {
		    final int actualIndex = TableModelWrapperUtils.getActualRowAt(getTableModel(), ((DefaultListSelectionModel) e.getSource()).getMaxSelectionIndex());

		    if (canClose() != null && isModifying) {
			if (getView() != null) {
			    getView().notify(whyCannotClose(), MessageType.WARNING);
			} else {
			    new DialogWithDetails(null, "Entity selection", new IllegalStateException(whyCannotClose())).setVisible(true);
			}
			getTableModel().selectRow(oldIndex);
		    } else {
			final D selectedEntity = getTableModel().instance(actualIndex);
			if (selectedEntity == null) {
			    return;
			}
			oldIndex = actualIndex;
			if (actualIndex >= 0) {
			    setManagedEntity(selectedEntity);
			}
			setState(UmState.VIEW);
		    }
		}
	    }
	};

    }

    /** A convenient method for loading details. Could potentially be overridden, but unlikely there should be a reason for that. */
    protected List<D> loadDetails() {
	return getCompanion().findDetails(getEntity(), getFetchModel(), Integer.MAX_VALUE).data();
    }

    //////////////////////////////////////////////////////////////
    /////////////////////// INITIALISATION ///////////////////////
    //////////////////////////////////////////////////////////////
    /** A field for temporal holding of details entities retrieved upon initialisation. */
    private final List<D> tmpDetails = new ArrayList<D>();

    @Override
    protected void preInit(final BlockingIndefiniteProgressPane blockingPane) {
	blockingPane.setText("Loading...");
    }

    @Override
    protected void doInit(final BlockingIndefiniteProgressPane blockingPane) {
	tmpDetails.clear();
	tmpDetails.addAll(loadDetails());
    }

    @Override
    protected void postInit(final BlockingIndefiniteProgressPane blockingPane) {
	setEditors(buildEditors(getEntity(), getCompanion(), getPropertyBinder()));
	getView().buildUi();

	getTableModel().clearInstances();
	// weird cast occurs here because it is not possible to create array with elements of type D
	((PropertyTableModel) getTableModel()).addInstances(tmpDetails.toArray(new AbstractEntity[] {}));
	tmpDetails.clear();
	getTableModel().fireTableDataChanged();

	if (getTableModel().instances().size() > 0) {
	    final int row = getTableModel().getRowOf(getManagedEntity());
	    getTableModel().selectRow(row >= 0 ? row : 0);
	} else {
	    setManagedEntity(determineManagedEntity(getTableModel().instances()));
	}

	setState(UmState.VIEW);
    }

    /**
     * A convenient method for determining what details entity should be managed.
     *
     * @param data
     * @return
     */
    protected D determineManagedEntity(final List<D> data) {
	// if there are no indirect charges associated with a work order then create a new one
	return data.size() > 0 ? data.get(0) : newEntity(getEntity().getEntityFactory());
    }

    //////////////////////////////////////////////////////////////
    ////////////////////////// ACTIONS ///////////////////////////
    //////////////////////////////////////////////////////////////

    /**
     * Determines whether managed entity should be restored by setting its properties to the original values.
     */
    protected boolean shouldRestore(final D managedEntity) {
	return getManagedEntity().isPersisted() && getManagedEntity().isDirty();
    }

    @Override
    protected Action createCancelAction() {
	final Command<D> action = new Command<D>("Cancel") {
	    private static final long serialVersionUID = 1L;

	    @Override
	    protected boolean preAction() {
		notifyActionStageChange(ActionStage.CANCEL_PRE_ACTION);
		getSaveAction().setEnabled(false);
		if (super.preAction()) {
		    disableEditors(true);
		    return true;
		} else {
		    return false;
		}
	    }

	    @Override<commit_msg>Modified the contract for refetching entity as part of details UI model -- enforced refectching when entity was not dirty, but invalid.<commit_after>package ua.com.fielden.platform.swing.model;

import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;

import javax.swing.Action;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JOptionPane;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import ua.com.fielden.platform.dao.IMasterDetailsDao;
import ua.com.fielden.platform.entity.AbstractEntity;
import ua.com.fielden.platform.entity.query.fluent.fetch;
import ua.com.fielden.platform.error.Result;
import ua.com.fielden.platform.swing.actions.Command;
import ua.com.fielden.platform.swing.components.NotificationLayer.MessageType;
import ua.com.fielden.platform.swing.components.bind.development.Binder;
import ua.com.fielden.platform.swing.components.blocking.BlockingIndefiniteProgressPane;
import ua.com.fielden.platform.swing.dialogs.DialogWithDetails;
import ua.com.fielden.platform.swing.egi.models.PropertyTableModel;
import ua.com.fielden.platform.swing.ei.editors.development.ILightweightPropertyBinder;

import com.jidesoft.grid.TableModelWrapperUtils;

/**
 * This is a CRUD UI model representing one-to-many master/details association. It has the following properties:
 * <ul>
 * <li>All instances of details entities are kept in {@link PropertyTableModel}.
 * <li>Only one instance can be edited or saved at the time.
 * <li>A details instance becomes a managed entity (thus get's associated with editors) upon selection of a row representing this instance in EGI associated with model's table
 * model.
 * <li>After instantiation, model should be provided with an instance of the associated view in order to be able to provided some feedback to users.
 * </ul>
 *
 * @author <NAME>
 *
 * @param <M>
 * @param <D>
 * @param <C>
 */
public abstract class UmDetailsWithCrudMany<M extends AbstractEntity<?>, D extends AbstractEntity<?>, C extends IMasterDetailsDao<M, D>> extends UmDetailsWithCrud<M, D, C> {
    /** Table model representing all instances of details entities. */
    private final PropertyTableModel<D> tableModel;

    /** Listener responsible for an action happening upon row change in the grid of details entities. */
    private final ListSelectionListener onRowSelect;
    // a helper state to assist in correct handling of the programmatically enforced row change even, which occurs upon cancel action.
    // its value should be changed strictly on EDT in order to ensure thread safety
    private boolean isModifying = false;

    protected UmDetailsWithCrudMany(final M entity, final C companion, final ILightweightPropertyBinder<D> propertyBinder, final fetch<D> fm, final PropertyTableModel<D> tableModel, final boolean lazy) {
	super(entity, companion, propertyBinder, fm, lazy);
	this.tableModel = tableModel;
	onRowSelect = new ListSelectionListener() {
	    private int oldIndex = -1;

	    @Override
	    public void valueChanged(final ListSelectionEvent e) {
		if (!e.getValueIsAdjusting()) {
		    final int actualIndex = TableModelWrapperUtils.getActualRowAt(getTableModel(), ((DefaultListSelectionModel) e.getSource()).getMaxSelectionIndex());

		    if (canClose() != null && isModifying) {
			if (getView() != null) {
			    getView().notify(whyCannotClose(), MessageType.WARNING);
			} else {
			    new DialogWithDetails(null, "Entity selection", new IllegalStateException(whyCannotClose())).setVisible(true);
			}
			getTableModel().selectRow(oldIndex);
		    } else {
			final D selectedEntity = getTableModel().instance(actualIndex);
			if (selectedEntity == null) {
			    return;
			}
			oldIndex = actualIndex;
			if (actualIndex >= 0) {
			    setManagedEntity(selectedEntity);
			}
			setState(UmState.VIEW);
		    }
		}
	    }
	};

    }

    /** A convenient method for loading details. Could potentially be overridden, but unlikely there should be a reason for that. */
    protected List<D> loadDetails() {
	return getCompanion().findDetails(getEntity(), getFetchModel(), Integer.MAX_VALUE).data();
    }

    //////////////////////////////////////////////////////////////
    /////////////////////// INITIALISATION ///////////////////////
    //////////////////////////////////////////////////////////////
    /** A field for temporal holding of details entities retrieved upon initialisation. */
    private final List<D> tmpDetails = new ArrayList<D>();

    @Override
    protected void preInit(final BlockingIndefiniteProgressPane blockingPane) {
	blockingPane.setText("Loading...");
    }

    @Override
    protected void doInit(final BlockingIndefiniteProgressPane blockingPane) {
	tmpDetails.clear();
	tmpDetails.addAll(loadDetails());
    }

    @Override
    protected void postInit(final BlockingIndefiniteProgressPane blockingPane) {
	setEditors(buildEditors(getEntity(), getCompanion(), getPropertyBinder()));
	getView().buildUi();

	getTableModel().clearInstances();
	// weird cast occurs here because it is not possible to create array with elements of type D
	((PropertyTableModel) getTableModel()).addInstances(tmpDetails.toArray(new AbstractEntity[] {}));
	tmpDetails.clear();
	getTableModel().fireTableDataChanged();

	if (getTableModel().instances().size() > 0) {
	    final int row = getTableModel().getRowOf(getManagedEntity());
	    getTableModel().selectRow(row >= 0 ? row : 0);
	} else {
	    setManagedEntity(determineManagedEntity(getTableModel().instances()));
	}

	setState(UmState.VIEW);
    }

    /**
     * A convenient method for determining what details entity should be managed.
     *
     * @param data
     * @return
     */
    protected D determineManagedEntity(final List<D> data) {
	// if there are no indirect charges associated with a work order then create a new one
	return data.size() > 0 ? data.get(0) : newEntity(getEntity().getEntityFactory());
    }

    //////////////////////////////////////////////////////////////
    ////////////////////////// ACTIONS ///////////////////////////
    //////////////////////////////////////////////////////////////

    /**
     * Determines whether managed entity should be restored by setting its properties to the original values.
     */
    protected boolean shouldRestore(final D managedEntity) {
	return getManagedEntity().isPersisted() && (getManagedEntity().isDirty() || !getManagedEntity().isValid().isSuccessful());
    }

    @Override
    protected Action createCancelAction() {
	final Command<D> action = new Command<D>("Cancel") {
	    private static final long serialVersionUID = 1L;

	    @Override
	    protected boolean preAction() {
		notifyActionStageChange(ActionStage.CANCEL_PRE_ACTION);
		getSaveAction().setEnabled(false);
		if (super.preAction()) {
		    disableEditors(true);
		    return true;
		} else {
		    return false;
		}
	    }

	    @Override
{'fraction_non_alphanumeric': 0.14806461001164145, 'fraction_numerical': 0.0011641443538998836, 'mean_word_length': 3.334594765058341, 'pattern_counts': {'":': 0, '<': 53, '<?xml version=': 0, '>': 61, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5111268', 'n_tokens_mistral': 3819, 'n_tokens_neox': 3602, 'n_words': 1034}