/*
* Copyright (c) 2012 Data Harmonisation Panel
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution. If not, see <http://www.gnu.org/licenses/>.
*
* Contributors:
* HUMBOLDT EU Integrated Project #030962
* Data Harmonisation Panel <http://www.dhpanel.eu>
*/
package eu.esdihumboldt.hale.ui.io;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.jface.dialogs.IPageChangingListener;
import org.eclipse.jface.dialogs.PageChangingEvent;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.wizard.IWizardPage;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.ui.PlatformUI;
import com.google.common.base.Objects;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimap;
import de.fhg.igd.eclipse.util.extension.ExtensionObjectFactoryCollection;
import de.fhg.igd.eclipse.util.extension.FactoryFilter;
import de.fhg.igd.slf4jplus.ALogger;
import de.fhg.igd.slf4jplus.ALoggerFactory;
import de.fhg.igd.slf4jplus.ATransaction;
import eu.esdihumboldt.hale.common.core.io.CachingImportProvider;
import eu.esdihumboldt.hale.common.core.io.IOAdvisor;
import eu.esdihumboldt.hale.common.core.io.IOProvider;
import eu.esdihumboldt.hale.common.core.io.IOProviderConfigurationException;
import eu.esdihumboldt.hale.common.core.io.ImportProvider;
import eu.esdihumboldt.hale.common.core.io.ProgressMonitorIndicator;
import eu.esdihumboldt.hale.common.core.io.Value;
import eu.esdihumboldt.hale.common.core.io.extension.IOProviderDescriptor;
import eu.esdihumboldt.hale.common.core.io.extension.IOProviderExtension;
import eu.esdihumboldt.hale.common.core.io.project.model.IOConfiguration;
import eu.esdihumboldt.hale.common.core.io.project.model.Project;
import eu.esdihumboldt.hale.common.core.io.report.IOReport;
import eu.esdihumboldt.hale.common.core.io.report.IOReporter;
import eu.esdihumboldt.hale.common.core.io.report.impl.IOMessageImpl;
import eu.esdihumboldt.hale.ui.io.action.ActionUI;
import eu.esdihumboldt.hale.ui.io.action.ActionUIExtension;
import eu.esdihumboldt.hale.ui.io.config.AbstractConfigurationPage;
import eu.esdihumboldt.hale.ui.io.config.ConfigurationPageExtension;
import eu.esdihumboldt.hale.ui.service.project.ProjectService;
import eu.esdihumboldt.hale.ui.service.report.ReportService;
/**
* Abstract I/O wizard based on {@link IOProvider} descriptors
*
* @param
* <P>
* the {@link IOProvider} type used in the wizard
*
* @author Simon Templer
* @partner 01 / Fraunhofer Institute for Computer Graphics Research
*/
public abstract class IOWizard<P extends IOProvider> extends Wizard
implements IPageChangingListener {
private static final ALogger log = ALoggerFactory.getLogger(IOWizard.class);
private final Set<IOWizardListener<P, ? extends IOWizard<P>>> listeners = new HashSet<IOWizardListener<P, ? extends IOWizard<P>>>();
private final Class<P> providerType;
private P provider;
private IOProviderDescriptor descriptor;
private IOAdvisor<P> advisor;
private String actionId;
private IContentType contentType;
private Multimap<String, AbstractConfigurationPage<? extends P, ? extends IOWizard<P>>> configPages;
private final List<IWizardPage> mainPages = new ArrayList<IWizardPage>();
/**
* Create an I/O wizard
*
* @param providerType the I/O provider type
*/
public IOWizard(Class<P> providerType) {
super();
this.providerType = providerType;
// create possible configuration pages
configPages = createConfigurationPages(getFactories());
setNeedsProgressMonitor(true);
}
/**
* Get the I/O advisor
*
* @return the advisor
*/
protected IOAdvisor<P> getAdvisor() {
return advisor;
}
/**
* Get the action identifier
*
* @return the action ID
*/
public String getActionId() {
return actionId;
}
/**
* Set the I/O advisor
*
* @param advisor the advisor to set
* @param actionId the action identifier, <code>null</code> if it has none
*/
public void setAdvisor(IOAdvisor<P> advisor, String actionId) {
this.advisor = advisor;
this.actionId = actionId;
// recreate possible configuration pages now that advisor is set
configPages = createConfigurationPages(getFactories());
}
/**
* Get the provider IDs mapped to configuration pages.
*
* @param factories the provider descriptors
* @return provider IDs mapped to configuration pages
*/
protected ListMultimap<String, AbstractConfigurationPage<? extends P, ? extends IOWizard<P>>> createConfigurationPages(
Collection<IOProviderDescriptor> factories) {
return ConfigurationPageExtension.getInstance().getConfigurationPages(getFactories());
}
/**
* @see Wizard#addPages()
*/
@Override
public void addPages() {
super.addPages();
// add configuration pages
for (AbstractConfigurationPage<? extends P, ? extends IOWizard<P>> page : new HashSet<>(
configPages.values())) {
addPage(page);
}
if (getContainer() instanceof WizardDialog) {
((WizardDialog) getContainer()).addPageChangingListener(this);
}
else {
throw new RuntimeException("Only WizardDialog as container supported");
}
}
/**
* @see IPageChangingListener#handlePageChanging(PageChangingEvent)
*/
@Override
public void handlePageChanging(PageChangingEvent event) {
if (getProvider() == null) {
return;
}
if (event.getCurrentPage() instanceof IWizardPage
&& event.getTargetPage() == getNextPage((IWizardPage) event.getCurrentPage())) {
// only do automatic configuration when proceeding to next page
if (event.getCurrentPage() instanceof IOWizardPage<?, ?>) {
@SuppressWarnings("unchecked")
IOWizardPage<P, ?> page = (IOWizardPage<P, ?>) event.getCurrentPage();
event.doit = validatePage(page);
// TODO error message?!
}
}
}
/**
* @see Wizard#dispose()
*/
@Override
public void dispose() {
if (getContainer() instanceof WizardDialog) {
((WizardDialog) getContainer()).removePageChangingListener(this);
}
super.dispose();
}
/**
* @see Wizard#addPage(IWizardPage)
*/
@Override
public void addPage(IWizardPage page) {
// collect main pages
if (!configPages.containsValue(page)) {
mainPages.add(page);
}
super.addPage(page);
}
/**
* Get the list of configuration pages for the currently selected provider
* factory <code>null</code> if there are none.
*
* @return the configuration pages for the current provider
*/
protected List<AbstractConfigurationPage<? extends P, ? extends IOWizard<P>>> getConfigurationPages() {
if (descriptor == null) {
return null;
}
// get the provider id
String id = descriptor.getIdentifier();
List<AbstractConfigurationPage<? extends P, ? extends IOWizard<P>>> result = new ArrayList<AbstractConfigurationPage<? extends P, ? extends IOWizard<P>>>(
configPages.get(id));
return (result.size() > 0 ? result : null);
}
/**
* @see Wizard#canFinish()
*/
@Override
public boolean canFinish() {
// check if main pages are complete
for (int i = 0; i < mainPages.size(); i++) {
if (!(mainPages.get(i)).isPageComplete()) {
return false;
}
}
// check if configuration pages are complete
List<AbstractConfigurationPage<? extends P, ? extends IOWizard<P>>> confPages = getConfigurationPages();
if (confPages != null) {
for (int i = 0; i < confPages.size(); i++) {
if (!(confPages.get(i)).isPageComplete()) {
return false;
}
}
}
return true;
}
/**
* @see Wizard#getNextPage(IWizardPage)
*/
@Override
public IWizardPage getNextPage(IWizardPage page) {
// get main index
int mainIndex = mainPages.indexOf(page);
if (mainIndex >= 0) {
// current page is one of the main pages
if (mainIndex < mainPages.size() - 1) {
// next main page
return mainPages.get(mainIndex + 1);
}
else {
// first configuration page
List<AbstractConfigurationPage<? extends P, ? extends IOWizard<P>>> confPages = getConfigurationPages();
if (confPages != null && confPages.size() > 0) {
return confPages.get(0);
}
}
}
else {
// current page is a configuration page
List<AbstractConfigurationPage<? extends P, ? extends IOWizard<P>>> confPages = getConfigurationPages();
// return the next configuration page
if (confPages != null) {
for (int i = 0; i < confPages.size() - 1; ++i) {
if (confPages.get(i) == page) {
return confPages.get(i + 1);
}
}
}
}
return null;
}
/**
* @see Wizard#getPageCount()
*/
@Override
public int getPageCount() {
int count = mainPages.size();
List<AbstractConfigurationPage<? extends P, ? extends IOWizard<P>>> confPages = getConfigurationPages();
if (confPages != null) {
count += confPages.size();
}
return count;
}
/**
* @see Wizard#getPreviousPage(IWizardPage)
*/
@Override
public IWizardPage getPreviousPage(IWizardPage page) {
// get main index
int mainIndex = mainPages.indexOf(page);
if (mainIndex >= 0) {
// current page is one of the main pages
if (mainIndex > 0) {
// previous main page
return mainPages.get(mainIndex - 1);
}
}
else {
// current page is a configuration page
List<AbstractConfigurationPage<? extends P, ? extends IOWizard<P>>> confPages = getConfigurationPages();
if (confPages != null) {
if (confPages.size() > 0 && confPages.get(0) == page) {
if (mainPages.isEmpty()) {
return null;
}
else {
// return last main page
return mainPages.get(mainPages.size() - 1);
}
}
// return the previous configuration page
for (int i = 1; i < confPages.size(); ++i) {
if (confPages.get(i) == page) {
return confPages.get(i - 1);
}
}
}
}
return null;
}
/**
* @see Wizard#getStartingPage()
*/
@Override
public IWizardPage getStartingPage() {
if (!mainPages.isEmpty()) {
return mainPages.get(0);
}
else {
List<AbstractConfigurationPage<? extends P, ? extends IOWizard<P>>> cps = getConfigurationPages();
if (cps != null && !cps.isEmpty()) {
return cps.get(0);
}
else {
// TODO provide an empty completed page instead?
throw new IllegalStateException("No starting page to display for wizard");
}
}
}
/**
* Get the available provider descriptors. To filter or sort them you can
* override this method.
*
* @return the available provider descriptors
*/
public List<IOProviderDescriptor> getFactories() {
// FIXME rename method
return IOProviderExtension.getInstance()
.getFactories(new FactoryFilter<IOProvider, IOProviderDescriptor>() {
@Override
public boolean acceptFactory(IOProviderDescriptor factory) {
// accept all factories that provide a compatible I/O
// provider
return providerType.isAssignableFrom(factory.getProviderType());
}
@Override
public boolean acceptCollection(
ExtensionObjectFactoryCollection<IOProvider, IOProviderDescriptor> collection) {
return true;
}
});
}
/**
* Get the provider assigned to the wizard. It will be <code>null</code> if
* no page assigned a provider factory to the wizard yet.
*
* @return the I/O provider
*/
@SuppressWarnings("unchecked")
public P getProvider() {
if (provider == null && descriptor != null) {
try {
provider = (P) descriptor.createExtensionObject();
} catch (Exception e) {
throw new IllegalStateException("Could not instantiate I/O provider", e);
}
advisor.prepareProvider(provider);
}
return provider;
}
/**
* Assign an I/O provider factory to the wizard
*
* @param descriptor the provider factory to set
*/
public void setProviderFactory(IOProviderDescriptor descriptor) {
/*
* The following must be done even if the descriptor seems the same, as
* the descriptor might be a preset and thus influence the configuration
* pages.
*/
// disable old configuration pages
List<AbstractConfigurationPage<? extends P, ? extends IOWizard<P>>> pages = getConfigurationPages();
if (pages != null) {
for (AbstractConfigurationPage<? extends P, ? extends IOWizard<P>> page : pages) {
page.disable();
}
}
this.descriptor = descriptor;
// reset provider
provider = null;
// enable new configuration pages
pages = getConfigurationPages();
if (pages != null) {
for (AbstractConfigurationPage<? extends P, ? extends IOWizard<P>> page : pages) {
page.enable();
}
}
// force button update
try {
getContainer().updateButtons();
} catch (NullPointerException e) {
// ignore - buttons may not have been initialized yet
}
fireProviderFactoryChanged(descriptor);
}
/**
* Get the content type assigned to the wizard
*
* @return the content type, may be <code>null</code>
*/
public IContentType getContentType() {
return contentType;
}
/**
* Assign a content type to the wizard
*
* @param contentType the content type to set
*/
public void setContentType(IContentType contentType) {
if (Objects.equal(contentType, this.contentType))
return;
this.contentType = contentType;
fireContentTypeChanged(contentType);
}
/**
* Get the provider descriptor assigned to the wizard. It will be
* <code>null</code> if no page assigned a provider factory to the wizard
* yet.
*
* @return the I/O provider factory
*/
public IOProviderDescriptor getProviderFactory() {
return descriptor;
}
/**
* @see Wizard#performFinish()
*
* @return <code>true</code> if executing the I/O provider was successful
*/
@Override
public boolean performFinish() {
if (getProvider() == null) {
return false;
}
if (!applyConfiguration()) {
return false;
}
// create default report
IOReporter defReport = provider.createReporter();
// validate and execute provider
try {
// validate configuration
provider.validate();
ProjectService ps = PlatformUI.getWorkbench().getService(ProjectService.class);
URI projectLoc = ps.getLoadLocation() == null ? null : ps.getLoadLocation();
boolean isProjectResource = false;
if (actionId != null) {
// XXX instead move project resource to action?
ActionUI factory = ActionUIExtension.getInstance().findActionUI(actionId);
isProjectResource = factory.isProjectResource();
}
// prevent loading of duplicate resources
if (isProjectResource && provider instanceof ImportProvider
&& !getProviderFactory().allowDuplicateResource()) {
String currentResource = ((ImportProvider) provider).getSource().getLocation()
.toString();
URI currentAbsolute = URI.create(currentResource);
if (projectLoc != null && !currentAbsolute.isAbsolute()) {
currentAbsolute = projectLoc.resolve(currentAbsolute);
}
for (IOConfiguration conf : ((Project) ps.getProjectInfo()).getResources()) {
Value otherResourceValue = conf.getProviderConfiguration()
.get(ImportProvider.PARAM_SOURCE);
if (otherResourceValue == null) {
continue;
}
String otherResource = otherResourceValue.as(String.class);
URI otherAbsolute = URI.create(otherResource);
if (projectLoc != null && !otherAbsolute.isAbsolute()) {
otherAbsolute = projectLoc.resolve(otherAbsolute);
}
String action = conf.getActionId();
// resource is already loaded into the project
if (currentAbsolute.equals(otherAbsolute) && Objects.equal(actionId, action)) {
// check if the resource is loaded with a provider that
// allows duplicates
boolean allowDuplicate = false;
IOProviderDescriptor providerFactory = IOProviderExtension.getInstance()
.getFactory(conf.getProviderId());
if (providerFactory != null) {
allowDuplicate = providerFactory.allowDuplicateResource();
}
if (!allowDuplicate) {
log.userError(
"Resource is already loaded. Loading duplicate resources is aborted!");
return false;
}
}
}
}
// enable provider internal caching
if (isProjectResource && provider instanceof CachingImportProvider) {
((CachingImportProvider) provider).setProvideCache();
}
IOReport report = execute(provider, defReport);
if (report != null) {
// add report to report server
ReportService repService = PlatformUI.getWorkbench()
.getService(ReportService.class);
repService.addReport(report);
// show message to user
if (report.isSuccess()) {
// no message, we rely on the report being shown/processed
// let advisor handle results
try {
getContainer().run(true, false, new IRunnableWithProgress() {
@Override
public void run(IProgressMonitor monitor)
throws InvocationTargetException, InterruptedException {
monitor.beginTask("Completing operation...",
IProgressMonitor.UNKNOWN);
try {
advisor.handleResults(getProvider());
} finally {
monitor.done();
}
}
});
} catch (InvocationTargetException e) {
log.userError(
"Error processing results:\n" + e.getCause().getLocalizedMessage(),
e.getCause());
return false;
} catch (Exception e) {
log.userError("Error processing results:\n" + e.getLocalizedMessage(), e);
return false;
}
// add to project service if necessary
if (isProjectResource)
ps.rememberIO(actionId, getProviderFactory().getIdentifier(), provider);
return true;
}
else {
// error message
log.userError(report.getSummary() + "\nPlease see the report for details.");
return false;
}
}
else
return true;
} catch (IOProviderConfigurationException e) {
// user feedback
log.userError(
"Validation of the provider configuration failed:\n" + e.getLocalizedMessage(),
e);
return false;
}
}
/**
* Apply configuration of main pages, configuration pages and the wizard.
*
* @return <code>true</code> if validation was successful,
* <code>false</code> otherwise
*/
protected boolean applyConfiguration() {
// process main pages
for (int i = 0; i < mainPages.size(); i++) {
// validating is still necessary as it is not guaranteed to be up to
// date by handlePageChanging
boolean valid = validatePage(mainPages.get(i));
if (!valid) {
// TODO error message?!
return false;
}
}
// check if configuration pages are complete
List<AbstractConfigurationPage<? extends P, ? extends IOWizard<P>>> confPages = getConfigurationPages();
if (confPages != null) {
for (int i = 0; i < confPages.size(); i++) {
// validating is still necessary as it is not guaranteed to be
// up to date by handlePageChanging
boolean valid = validatePage(confPages.get(i));
if (!valid) {
// TODO error message?!
return false;
}
}
}
// process wizard
updateConfiguration(provider);
return true;
}
/**
* Execute the given provider
*
* @param provider the I/O provider
* @param defaultReporter the default reporter that is used if the provider
* doesn't supply a report
* @return the execution report, if null it will not give feedback to the
* user and the advisor's handleResult method won't be called either
*/
protected IOReport execute(final IOProvider provider, final IOReporter defaultReporter) {
// execute provider
final AtomicReference<IOReport> report = new AtomicReference<IOReport>(defaultReporter);
defaultReporter.setSuccess(false);
try {
getContainer().run(true, provider.isCancelable(), new IRunnableWithProgress() {
@Override
public void run(IProgressMonitor monitor)
throws InvocationTargetException, InterruptedException {
ATransaction trans = log.begin(defaultReporter.getTaskName());
try {
IOReport result = provider.execute(new ProgressMonitorIndicator(monitor));
if (result != null) {
report.set(result);
}
else {
defaultReporter.setSuccess(true);
}
} catch (Throwable e) {
defaultReporter.error(new IOMessageImpl(e.getLocalizedMessage(), e));
} finally {
trans.end();
}
}
});
} catch (Throwable e) {
defaultReporter.error(new IOMessageImpl(e.getLocalizedMessage(), e));
}
return report.get();
}
/**
* Update the provider configuration. This will be called just before the
* I/O provider is executed.
*
* @param provider the I/O provider
*/
protected void updateConfiguration(P provider) {
// set the content type
provider.setContentType(getContentType());
// let advisor update configuration
advisor.updateConfiguration(provider);
}
/**
* Validate the given page and update the I/O provider
*
* @param page the wizard page to validate
* @return if the page is valid and updating the I/O provider was successful
*/
@SuppressWarnings("unchecked")
protected boolean validatePage(IWizardPage page) {
if (page instanceof IOWizardPage<?, ?>) {
return ((IOWizardPage<P, ?>) page).updateConfiguration(getProvider());
}
else {
return true;
}
}
/**
* Get the supported I/O provider type, usually an interface.
*
* @return the supported I/O provider type
*/
public Class<P> getProviderType() {
return providerType;
}
/**
* Adds an {@link IOWizardListener}
*
* @param listener the listener to add
*/
public void addIOWizardListener(IOWizardListener<P, ? extends IOWizard<P>> listener) {
synchronized (listeners) {
listeners.add(listener);
}
}
/**
* Removes an {@link IOWizardListener}
*
* @param listener the listener to remove
*/
public void removeIOWizardListener(IOWizardListener<P, ? extends IOWizard<P>> listener) {
synchronized (listeners) {
listeners.remove(listener);
}
}
private void fireProviderFactoryChanged(IOProviderDescriptor providerFactory) {
synchronized (listeners) {
for (IOWizardListener<P, ? extends IOWizard<P>> listener : listeners) {
listener.providerDescriptorChanged(providerFactory);
}
}
}
private void fireContentTypeChanged(IContentType contentType) {
synchronized (listeners) {
for (IOWizardListener<P, ? extends IOWizard<P>> listener : listeners) {
listener.contentTypeChanged(contentType);
}
}
}
}