Java tutorial
/* * Copyright (C) 2015-2016, the @autors. All rights reserved. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 3.0 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.polymap.p4.data.importer; import static java.util.Arrays.stream; import static java.util.stream.Collectors.toMap; import static org.polymap.core.runtime.UIThreadExecutor.asyncFast; import java.util.ArrayList; import java.util.Comparator; import java.util.EventObject; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import java.lang.reflect.Field; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.google.common.collect.ImmutableList; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.core.runtime.jobs.IJobChangeEvent; import org.eclipse.core.runtime.jobs.JobChangeAdapter; import org.polymap.core.runtime.SubMonitor; import org.polymap.core.runtime.UIJob; import org.polymap.core.runtime.UIThreadExecutor; import org.polymap.core.runtime.config.Configurable; import org.polymap.core.runtime.event.EventHandler; import org.polymap.core.runtime.event.EventManager; import org.polymap.core.ui.UIUtils; import org.polymap.rhei.batik.toolkit.IPanelToolkit; import org.polymap.p4.data.importer.ImporterFactory.ImporterBuilder; import org.polymap.p4.data.importer.ImporterPrompt.Severity; /** * Provides the execution context of an {@link Importer}. It handles inbound context * variables, allows to find importers that are able to execute within this context, * executes one of them and provides outbound variables for the next level driver. * * @author <a href="http://www.polymap.de">Falko Brutigam</a> */ public class ImporterContext extends Configurable { private static final Log log = LogFactory.getLog(ImporterContext.class); private Importer importer; private ImporterSite site; private Map<Class, Object> contextIn = new HashMap(); private Map<Class, Object> contextOut = new HashMap(); /** Ordered prompts of the importer. Keep insert order. */ private Map<String, ImporterPrompt> prompts = new LinkedHashMap(); private UIJob verifier; private ImporterMonitor verifierMonitor; private Composite resultViewerParent; private IPanelToolkit resultViewerTk; /** * Creates a starting context without importer and no context set. */ public ImporterContext() { } /** * Creates a context for the given {@link Importer}. The importer gets initialized by * this ctor. * * @throws Exception If {@link Importer#init(ImporterSite, IProgressMonitor)} * throws Exception. */ public ImporterContext(Importer importer, Object[] contextIn, IProgressMonitor monitor) throws Exception { assert importer != null && contextIn != null; this.importer = importer; this.contextIn = stream(contextIn).collect(toMap(o -> o.getClass(), o -> o)); injectContextIn(importer, this.contextIn); site = new ImporterSite() { @Override public ImporterContext context() { return ImporterContext.this; } @Override public ImporterPrompt newPrompt(String id) { assert prompts != null : "newPrompt() called before createPrompts()!"; assert !prompts.containsKey(id); ImporterPrompt result = new ImporterPrompt() { @Override public ImporterContext context() { return ImporterContext.this; } }; prompts.put(id, result); EventManager.instance().publish(new ContextChangeEvent(ImporterContext.this)); return result; } @Override public Optional<ImporterPrompt> prompt(String id) { return Optional.ofNullable(prompts.get(id)); } }; monitor.beginTask("Initialize importer", 10); // init SubMonitor.on(monitor, 1).complete(submon -> importer.init(site, submon)); assert importer.site() == site; // create prompts SubMonitor.on(monitor, 1).complete(submon -> importer.createPrompts(submon)); // verify promptChanged(null); //SubMonitor.on( monitor, 8 ).complete( submon -> importer.verify( submon ) ); // listen to prompt changes EventManager.instance().subscribe(this, ev -> ev instanceof ConfigChangeEvent && prompts.containsValue(ev.getSource())); } /** * One of my prompts has been changed. */ @EventHandler(delay = 100) protected void promptChanged(List<ConfigChangeEvent> evs) { // cancel current verifier Job if (verifier != null) { log.info("Cancel VERIFIER!"); verifier.cancel(); verifier.getThread().interrupt(); verifier = null; } // new Job verifierMonitor = new ImporterMonitor(); verifier = new UIJob("Progress import") { @Override protected void runWithException(IProgressMonitor monitor) throws Exception { verifierMonitor.beginTask("Verifying data", IProgressMonitor.UNKNOWN); importer.verify(verifierMonitor); verifierMonitor.done(); if (site().ok.get()) { // check if all prompts also ok for (ImporterPrompt prompt : prompts.values()) { if (prompt.severity.isPresent() && prompt.severity.get().equals(Severity.REQUIRED) && !prompt.ok.get()) { // on first false, all are false site().ok.set(false); break; } } } } }; verifier.addJobChangeListener(new JobChangeAdapter() { private UIJob myVerifier = verifier; @Override public void done(IJobChangeEvent ev) { if (verifier == myVerifier) { verifier = null; } } }); if (resultViewerParent != null) { asyncFast(() -> updateResultViewer(resultViewerParent, resultViewerTk)); } verifier.scheduleWithUIUpdate(); // FIXME totally weird hack: the above scheduleWithUIUpdate() did a // syncFast() in previous version (which was wrong anyway); this is now gone // but this code here seems to need it to display prompts properly UIThreadExecutor.syncFast(() -> log.info("SYNC")); } public Importer importer() { return importer; } public ImporterSite site() { return site; } /** * For the root ImportContext without an importer this add the given value to * {@link #contextOut} in order to have in handed to factories in * {@link #findNext(IProgressMonitor)} */ public void addContextOut(Object o) { contextOut.put(o.getClass(), o); EventManager.instance().syncPublish(new ContextChangeEvent(this)); } /** * List of {@link Importer}s that are able to handle the current context. * * @param monitor * @throws IllegalAccessException * @throws InstantiationException */ public List<ImporterContext> findNext(IProgressMonitor monitor) throws Exception { List<ImporterExtension> exts = ImporterExtension.all(); monitor.beginTask("Check importers", exts.size() * 10); List<ImporterContext> result = new ArrayList(); for (ImporterExtension ext : exts) { ImporterFactory factory = ext.createFactory(); injectContextIn(factory, contextOut); SubProgressMonitor submon = new SubProgressMonitor(monitor, 10); factory.createImporters(new ImporterBuilder() { @Override public void newImporter(Importer newImporter, Object... newContextIn) throws Exception { result.add(new ImporterContext(newImporter, newContextIn, submon)); } }); submon.done(); } return result; } /** * Get or initially create prompts. * * @param monitor * @throws Exception */ public List<ImporterPrompt> prompts(IProgressMonitor monitor) throws Exception { // if (prompts == null) { // prompts = new HashMap(); // importer.createPrompts( monitor ); // } return ImmutableList.copyOf(prompts.values()); } protected Optional<Severity> maxNotOkPromptSeverity() { return prompts == null ? Optional.empty() : prompts.values().stream().filter(prompt -> !prompt.ok.get()).map(prompt -> prompt.severity.get()) .max(Comparator.comparing(s -> s.ordinal())); } /** * User has clicked this context in the list and wants to see the contents. */ public void updateResultViewer(Composite parent, IPanelToolkit tk) { resultViewerParent = parent; resultViewerTk = tk; JobChangeAdapter updateUI = new JobChangeAdapter() { @Override public void done(IJobChangeEvent ev) { if (ev == null || ev.getResult().isOK()) { asyncFast(() -> { if (!parent.isDisposed()) { UIUtils.disposeChildren(parent); parent.setLayout(new FillLayout(SWT.VERTICAL)); importer.createResultViewer(parent, tk); parent.layout(true); } }); } } }; if (verifier != null) { UIUtils.disposeChildren(parent); verifierMonitor.createContents(parent, tk); parent.layout(true); verifier.addJobChangeListenerWithContext(updateUI); } else { updateUI.done(null); } } public void createPromptViewer(Composite parent, ImporterPrompt prompt, IPanelToolkit tk) { assert parent.getLayout() instanceof FillLayout; prompt.extendedUI.ifPresent(uibuilder -> uibuilder.createContents(prompt, parent, tk)); } public Map<Class, Object> execute(IProgressMonitor monitor) throws Exception { importer.execute(monitor); // collect contextOut contextOut.clear(); Class cl = importer.getClass(); while (cl != null) { for (Field f : cl.getDeclaredFields()) { ContextOut a = f.getAnnotation(ContextOut.class); if (a != null) { try { f.setAccessible(true); Object value = f.get(importer); Object previous = contextOut.put(f.getType(), value); if (previous != null) { throw new IllegalStateException( "ContextOut already contains a value for the given type: " + value + " -- " + previous); } } catch (Exception e) { throw new RuntimeException(e); } } } cl = cl.getSuperclass(); } return contextOut; } protected void injectContextIn(Object obj, Map<Class, Object> values) { Class cl = obj.getClass(); while (cl != null) { for (Field f : cl.getDeclaredFields()) { ContextIn a = f.getAnnotation(ContextIn.class); if (a != null) { try { f.setAccessible(true); Object value = values.get(f.getType()); // check direct match first if (value != null) { f.set(obj, value); } // check for assignable types else { List<Object> assignable = values.values().stream() .filter(v -> v != null && f.getType().isAssignableFrom(v.getClass())) .collect(Collectors.toList()); assert assignable.size() <= 1 : "more than 1 value could be assigned to field " + f.getName() + " in " + cl.getName(); if (assignable.size() > 0) { f.set(obj, assignable.get(0)); } } } catch (Exception e) { throw new RuntimeException(e); } } } cl = cl.getSuperclass(); } } /** * Fired when {@link ImporterContext#addContextIn(Object)} is called after a file * was uploaded or anything else has been added to the context. Also fired when a * prompt has been added by calling {@link ImporterSite#newPrompt(String)} . */ class ContextChangeEvent extends EventObject { protected ContextChangeEvent(ImporterContext source) { super(source); } @Override public ImporterContext getSource() { return (ImporterContext) super.getSource(); } } }