/*******************************************************************************
 * Copyright (c) 2000, 2015 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Steven Spungin <steven@spungin.tv> - Bug 401439
 *     Lars Vogel <Lars.Vogel@vogella.com> - Bug 475844, 475689
 *******************************************************************************/
package org.eclipse.jface.viewers;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.internal.InternalPolicy;
import org.eclipse.jface.util.Policy;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.widgets.Control;

/**
 * A content viewer is a model-based adapter on a widget which accesses its
 * model by means of a content provider and a label provider.
 * <p>
 * A viewer's model consists of elements, represented by objects.
 * A viewer defines and implements generic infrastructure for handling model
 * input, updates, and selections in terms of elements.
 * Input is obtained by querying an <code>IContentProvider</code> which returns
 * elements. The elements themselves are not displayed directly.  They are
 * mapped to labels, containing text and/or an image, using the viewer's
 * <code>ILabelProvider</code>.
 * </p>
 * <p>
 * Implementing a concrete content viewer typically involves the following steps:
 * <ul>
 * <li>
 * create SWT controls for viewer (in constructor) (optional)
 * </li>
 * <li>
 * initialize SWT controls from input (inputChanged)
 * </li>
 * <li>
 * define viewer-specific update methods
 * </li>
 * <li>
 * support selections (<code>setSelection</code>, <code>getSelection</code>)
 * </ul>
 * </p>
 */
public abstract class ContentViewer extends Viewer {

    /**
     * This viewer's content provider, or <code>null</code> if none.
     */
    private IContentProvider contentProvider = null;

    /**
     * This viewer's input, or <code>null</code> if none.
     * The viewer's input provides the "model" for the viewer's content.
     */
    private Object input = null;

    /**
     * This viewer's label provider. Initially <code>null</code>, but
     * lazily initialized (to a <code>SimpleLabelProvider</code>).
     */
    private IBaseLabelProvider labelProvider = null;

    /**
     * This viewer's label provider listener.
     * Note: Having a viewer register a label provider listener with
     * a label provider avoids having to define public methods
     * for internal events.
     */
    private final ILabelProviderListener labelProviderListener = new ILabelProviderListener() {
    	private boolean logWhenDisposed = true; // initially true, set to false

        @Override
		public void labelProviderChanged(LabelProviderChangedEvent event) {
        	Control control = getControl();
        	if (control == null || control.isDisposed()) {
    			if (logWhenDisposed) {
    				String message = "Ignored labelProviderChanged notification because control is diposed." + //$NON-NLS-1$
    						" This indicates a potential memory leak."; //$NON-NLS-1$
    				if (!InternalPolicy.DEBUG_LOG_LABEL_PROVIDER_NOTIFICATIONS_WHEN_DISPOSED) {
    					// stop logging after the first
    					logWhenDisposed = false;
    					message += " This is only logged once per viewer instance," + //$NON-NLS-1$
    							" but similar calls will still be ignored."; //$NON-NLS-1$
    				}
    				Policy.getLog().log(
    						new Status(IStatus.WARNING, Policy.JFACE, message,
    								new RuntimeException()));
    			}
        		return;
        	}
        	ContentViewer.this.handleLabelProviderChanged(event);
        }
    };

    /**
     * Creates a content viewer with no input, no content provider, and a
     * default label provider.
     */
    protected ContentViewer() {
    }

    /**
     * Returns the content provider used by this viewer,
     * or <code>null</code> if this view does not yet have a content
     * provider.
     * <p>
     * The <code>ContentViewer</code> implementation of this method returns the content
     * provider recorded is an internal state variable.
     * Overriding this method is generally not required;
     * however, if overriding in a subclass,
     * <code>super.getContentProvider</code> must be invoked.
     * </p>
     *
     * @return the content provider, or <code>null</code> if none
     */
    public IContentProvider getContentProvider() {
        return contentProvider;
    }

    /**
     * The <code>ContentViewer</code> implementation of this <code>IInputProvider</code>
     * method returns the current input of this viewer, or <code>null</code>
     * if none. The viewer's input provides the "model" for the viewer's
     * content.
     */
    @Override
	public Object getInput() {
        return input;
    }

    /**
     * Returns the label provider used by this viewer.
     * <p>
     * The <code>ContentViewer</code> implementation of this method returns the label
     * provider recorded in an internal state variable; if none has been
     * set (with <code>setLabelProvider</code>) a default label provider
     * will be created, remembered, and returned.
     * Overriding this method is generally not required;
     * however, if overriding in a subclass,
     * <code>super.getLabelProvider</code> must be invoked.
     * </p>
     *
     * @return a label provider
     */
    public IBaseLabelProvider getLabelProvider() {
        if (labelProvider == null) {
			labelProvider = new LabelProvider();
		}
        return labelProvider;
    }

    /**
     * Handles a dispose event on this viewer's control.
     * <p>
     * The <code>ContentViewer</code> implementation of this method disposes of this
     * viewer's label provider and content provider (if it has one).
     * Subclasses should override this method to perform any additional
     * cleanup of resources; however, overriding methods must invoke
     * <code>super.handleDispose</code>.
     * </p>
     *
     * @param event a dispose event
     */
    protected void handleDispose(DisposeEvent event) {
		if (contentProvider != null) {
			try {
				contentProvider.inputChanged(this, getInput(), null);
			} catch (Exception e) {
				// ignore exception
				String message = "Exception while calling ContentProvider.inputChanged from ContentViewer.handleDispose"; //$NON-NLS-1$
				message += " (" + contentProvider.getClass().getName() + ")"; //$NON-NLS-1$//$NON-NLS-2$
				Policy.getLog().log(new Status(IStatus.WARNING, Policy.JFACE, message, e));
			}
			contentProvider.dispose();
			contentProvider = null;
		}
		if (labelProvider != null) {
			labelProvider.removeListener(labelProviderListener);
			labelProvider.dispose();
			labelProvider = null;
		}
		input = null;
    }

    /**
     * Handles a label provider changed event.
     * <p>
     * The <code>ContentViewer</code> implementation of this method calls <code>labelProviderChanged()</code>
     * to cause a complete refresh of the viewer.
     * Subclasses may reimplement or extend.
     * </p>
     * @param event the change event
     */
    protected void handleLabelProviderChanged(LabelProviderChangedEvent event) {
        labelProviderChanged();
    }

    /**
     * Adds event listener hooks to the given control.
     * <p>
     * All subclasses must call this method when their control is
     * first established.
     * </p>
     * <p>
     * The <code>ContentViewer</code> implementation of this method hooks
     * dispose events for the given control.
     * Subclasses may override if they need to add other control hooks;
     * however, <code>super.hookControl</code> must be invoked.
     * </p>
     *
     * @param control the control
     */
    protected void hookControl(Control control) {
		control.addDisposeListener(this::handleDispose);
    }

    /**
     * Notifies that the label provider has changed.
     * <p>
     * The <code>ContentViewer</code> implementation of this method calls <code>refresh()</code>.
     * Subclasses may reimplement or extend.
     * </p>
     */
    protected void labelProviderChanged() {
        refresh();
    }

    /**
     * Sets the content provider used by this viewer.
     * <p>
     * The <code>ContentViewer</code> implementation of this method records the
     * content provider in an internal state variable.
     * Overriding this method is generally not required;
     * however, if overriding in a subclass,
     * <code>super.setContentProvider</code> must be invoked.
     * </p>
     *
     * @param contentProvider the content provider
     * @see #getContentProvider
     */
    public void setContentProvider(IContentProvider contentProvider) {
        Assert.isNotNull(contentProvider);
        IContentProvider oldContentProvider = this.contentProvider;
        this.contentProvider = contentProvider;
        if (oldContentProvider != null) {
            Object currentInput = getInput();
            oldContentProvider.inputChanged(this, currentInput, null);
            oldContentProvider.dispose();
            contentProvider.inputChanged(this, null, currentInput);
            refresh();
        }
    }

    /**
     * The <code>ContentViewer</code> implementation of this <code>Viewer</code>
     * method invokes <code>inputChanged</code> on the content provider and then the
     * <code>inputChanged</code> hook method. This method fails if this viewer does
     * not have a content provider. Subclassers are advised to override
     * <code>inputChanged</code> rather than this method, but may extend this method
     * if required.
     */
    @Override
	public void setInput(Object input) {
    	Control control = getControl();
		if (control == null || control.isDisposed()) {
			throw new IllegalStateException(
					"Need an underlying widget to be able to set the input." + //$NON-NLS-1$
							"(Has the widget been disposed?)"); //$NON-NLS-1$
		}
		Assert.isTrue(getContentProvider() != null,
				"Instances of ContentViewer must have a content provider assigned before the setInput method is called."); //$NON-NLS-1$

        Object oldInput = getInput();
        contentProvider.inputChanged(this, oldInput, input);
        this.input = input;

        // call input hook
        inputChanged(this.input, oldInput);
    }

    /**
     * Sets the label provider for this viewer.
     * <p>
     * The <code>ContentViewer</code> implementation of this method ensures that the
     * given label provider is connected to this viewer and the
     * former label provider is disconnected from this viewer.
     * Overriding this method is generally not required;
     * however, if overriding in a subclass,
     * <code>super.setLabelProvider</code> must be invoked.
     * </p>
     *
     * @param labelProvider the label provider, or <code>null</code> if none
     */
    public void setLabelProvider(IBaseLabelProvider labelProvider) {
        IBaseLabelProvider oldProvider = this.labelProvider;
        // If it hasn't changed, do nothing.
        // This also ensures that the provider is not disposed
        // if set a second time.
        if (labelProvider == oldProvider) {
            return;
        }
        if (oldProvider != null) {
            oldProvider.removeListener(this.labelProviderListener);
        }
        this.labelProvider = labelProvider;
        if (labelProvider != null) {
            labelProvider.addListener(this.labelProviderListener);
        }
        refresh();

        // Dispose old provider after refresh, so that items never refer to stale images.
        if (oldProvider != null) {
    		internalDisposeLabelProvider(oldProvider);
        }
    }

	/**
	 * @param oldProvider
	 *
	 * @since 3.4
	 */
	void internalDisposeLabelProvider(IBaseLabelProvider oldProvider) {
		oldProvider.dispose();
	}
}
