/******************************************************************************* * Copyright (c) 2014, 2015 Association for Decentralized Information Management * in Industry THTH ry. * 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: * Semantum Oy - initial API and implementation *******************************************************************************/ package org.simantics.modeling.ui.wizard; import gnu.trove.map.hash.THashMap; import gnu.trove.set.hash.THashSet; import gnu.trove.set.hash.TIntHashSet; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Map; import java.util.Set; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.layout.RowLayoutFactory; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.window.Window; import org.eclipse.jface.wizard.Wizard; import org.eclipse.jface.wizard.WizardPage; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CCombo; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Point; 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.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.List; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import org.simantics.Simantics; import org.simantics.databoard.Bindings; import org.simantics.databoard.util.URIStringUtils; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.WriteGraph; import org.simantics.db.common.NamedResource; import org.simantics.db.common.request.ObjectsWithType; import org.simantics.db.common.request.UnaryRead; import org.simantics.db.common.request.UniqueRead; import org.simantics.db.common.request.WriteResultRequest; import org.simantics.db.common.utils.Logger; import org.simantics.db.common.utils.Versions; import org.simantics.db.exception.DatabaseException; import org.simantics.diagram.stubs.DiagramResource; import org.simantics.layer0.Layer0; import org.simantics.modeling.MigrateModel; import org.simantics.modeling.MigrateModel.MigrationOperation; import org.simantics.modeling.ModelingResources; import org.simantics.modeling.UserComponentMigration; import org.simantics.modeling.ui.Activator; import org.simantics.structural.stubs.StructuralResource2; import org.simantics.ui.workbench.dialogs.ResourceSelectionDialog3; import org.simantics.utils.datastructures.Pair; import org.simantics.utils.datastructures.Triple; import org.simantics.utils.strings.AlphanumComparator; /** * @author Antti Villberg */ public class MigrateWizard extends Wizard { final String initial; MigratePage migratePage; IEclipsePreferences prefnode; public MigrateWizard(String initial) { this.initial = initial; setWindowTitle("Perform migration"); setNeedsProgressMonitor(true); setForcePreviousAndNextButtons(false); setDialogSettings(Activator.getDefault().getDialogSettings()); prefnode = InstanceScope.INSTANCE.getNode( "org.simantics.modeling.ui.wizard.MigrateWizard" ); } @Override public void addPages() { migratePage = new MigratePage(initial); addPage(migratePage); } @Override public boolean canFinish() { // if (model.spec.name.isEmpty()) // return false; return true; } @Override public boolean performFinish() { int locationIndex = migratePage.locations.getSelectionIndex(); if(locationIndex == -1) return true; if(migratePage.model == null || migratePage.model.instances.isEmpty()) return true; int[] sel = migratePage.instances.getSelectionIndices(); TIntHashSet sels = new TIntHashSet(); for(int i : sel) sels.add(i); Collection ops = migratePage.model.sortedShownInstances; int index = 0; final ArrayList result = new ArrayList(); for(MigrationOperation op : ops) { if(sels.contains(index)) result.add(op); index++; } if(result.isEmpty()) return true; try { final String report = Simantics.getSession().syncRequest(new WriteResultRequest() { @Override public String perform(WriteGraph graph) throws DatabaseException { graph.markUndoPoint(); String report = UserComponentMigration.doMigration(graph, result); UserComponentMigration.doPostMigration(graph, result); return report; } }); class InfoMessageDialog extends MessageDialog { public InfoMessageDialog(Shell shell) { super(shell, "Migration report", null, "", MessageDialog.INFORMATION, new String[] { "Continue" }, 0); } @Override protected boolean isResizable() { return true; } @Override protected Point getInitialSize() { return new Point(800, 500); } @Override protected Control createCustomArea(Composite composite) { GridLayoutFactory.fillDefaults().applyTo(composite); Text text = new Text(composite, SWT.MULTI | SWT.V_SCROLL | SWT.READ_ONLY | SWT.BORDER); text.setText(report); GridDataFactory.fillDefaults().grab(true, true).applyTo(text); return composite; } } InfoMessageDialog md = new InfoMessageDialog(Display.getCurrent().getActiveShell()); md.open(); } catch (DatabaseException e) { Logger.defaultLogError(e); } return true; } abstract static class SelectionAdapter implements SelectionListener { @Override public void widgetDefaultSelected(SelectionEvent e) { widgetSelected(e); } } class MigratePage extends WizardPage { String initial; MigrateModel model; Text source; Button browseSource; Text target; Button browseTarget; Label symbolsLabel; CCombo symbols; Label instanceLabel; Label locationsLabel; CCombo locations; Composite buttonBar; Label instancesLabel; List instances; /** * ID of the location that has been previously selected by the user. * Used to prevent the location from being reset every time the user * makes a change in any of the other selections. * * See first field of {@link MigrateModel#instances}. */ String previouslySelectedLocationId = null; public MigratePage(String initial) { super("Perform migration", "Perform migration", null); this.initial = initial; } @Override public void createControl(Composite parent) { Composite container = new Composite(parent, SWT.NONE); { GridLayout layout = new GridLayout(); layout.horizontalSpacing = 20; layout.verticalSpacing = 10; layout.numColumns = 10; container.setLayout(layout); } new Label(container, SWT.NONE).setText("&Source:"); source = new Text(container, SWT.BORDER); source.setText(initial); source.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { refreshModel(); refreshInstances(); } }); GridDataFactory.fillDefaults().grab(true, false).span(8, 1).applyTo(source); browseSource = new Button(container, SWT.NONE); browseSource.setText("&Browse"); GridDataFactory.fillDefaults().grab(false, false).applyTo(browseSource); browseSource.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e_) { try { Map> map = Simantics.getSession().syncRequest(new BrowseSourceContentRequest(target.getText())); String uri = queryTargetSelection("Select Source Type", map); if (uri != null) source.setText(uri); } catch (DatabaseException e) { Logger.defaultLogError(e); } } }); new Label(container, SWT.NONE).setText("&Target:"); target = new Text(container, SWT.BORDER); target.setText(initial); target.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { refreshSymbols(); refreshModel(); refreshInstances(); } }); GridDataFactory.fillDefaults().grab(true, false).span(8, 1).applyTo(target); browseTarget = new Button(container, SWT.NONE); browseTarget.setText("B&rowse"); GridDataFactory.fillDefaults().grab(false, false).applyTo(browseTarget); browseTarget.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e_) { try { Map> map = Simantics.getSession().syncRequest(new BrowseTargetContentRequest(source.getText())); String uri = queryTargetSelection("Select Target Type", map); if (uri != null) target.setText(uri); } catch (DatabaseException e) { Logger.defaultLogError(e); } } }); symbolsLabel = new Label(container, SWT.NONE); symbolsLabel.setText("Target &symbol:"); GridDataFactory.fillDefaults().applyTo(symbolsLabel); symbols = new CCombo(container, SWT.BORDER | SWT.READ_ONLY); symbols.setVisibleItemCount(10); symbols.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { refreshModel(); refreshInstances(); } }); GridDataFactory.fillDefaults().grab(true, false).span(9, 1).applyTo(symbols); instanceLabel = new Label(container, SWT.NONE); GridDataFactory.fillDefaults().grab(true, false).span(10, 1).applyTo(instanceLabel); instanceLabel.setText(""); locationsLabel = new Label(container, SWT.NONE); locationsLabel.setText("&Locations:"); locations = new CCombo(container, SWT.BORDER | SWT.READ_ONLY); locations.setVisibleItemCount(25); locations.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { int index = locations.getSelectionIndex(); if (index >= 0 && index < model.instances.size()) { previouslySelectedLocationId = model.instances.get(index).first; } refreshInstances(); } }); GridDataFactory.fillDefaults().grab(true, false).span(9, 1).applyTo(locations); instancesLabel = new Label(container, SWT.NONE); instancesLabel.setText("&Select instances to migrate:"); GridDataFactory.fillDefaults().grab(true, false).span(10, 1).applyTo(instancesLabel); buttonBar = new Composite(container, SWT.NONE); RowLayoutFactory.fillDefaults().type(SWT.HORIZONTAL).applyTo(buttonBar); GridDataFactory.fillDefaults().grab(true, false).span(10, 1).applyTo(buttonBar); Button selectAll = new Button(buttonBar, SWT.PUSH); selectAll.setText("Select &All"); Button selectNone = new Button(buttonBar, SWT.PUSH); selectNone.setText("Select &None"); selectAll.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { instances.selectAll(); } }); selectNone.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { instances.deselectAll(); } }); instances = new List(container, SWT.MULTI | SWT.WRAP | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); GridDataFactory.fillDefaults().grab(true, true).span(10, 1).applyTo(instances); instances.addListener(SWT.Modify, validateListener); refreshSymbols(); refreshModel(); refreshInstances(); setControl(container); validatePage(); } String queryTargetSelection(String title, Map> map) { Shell shell = source.getShell(); ResourceSelectionDialog3 dialog = new ResourceSelectionDialog3(shell, map, title, false) { @Override protected IDialogSettings getBaseDialogSettings() { return Activator.getDefault().getDialogSettings(); } }; if (dialog.open() == Window.OK) return (String) dialog.getFirstResult(); return null; } void refreshSymbols() { try { final String uri = target.getText(); Collection syms = Simantics.getSession().syncRequest(new UniqueRead>() { @Override public Collection perform(ReadGraph graph) throws DatabaseException { Layer0 L0 = Layer0.getInstance(graph); DiagramResource DIA = DiagramResource.getInstance(graph); ModelingResources MOD = ModelingResources.getInstance(graph); Set result = new THashSet<>(); Resource componentType = graph.getResource(uri); Set potentialSymbols = new THashSet<>(); potentialSymbols.addAll(graph.syncRequest(new ObjectsWithType(componentType, L0.ConsistsOf, DIA.ElementClass))); potentialSymbols.addAll(graph.getObjects(componentType, MOD.ComponentTypeToSymbol)); for(Resource symbol : potentialSymbols) { if(!graph.hasStatement(symbol, MOD.SymbolToComponentType, componentType)) continue; String name = graph.getRelatedValue(symbol, L0.HasName, Bindings.STRING); if(graph.hasStatement(symbol, MOD.SymbolToComponentType, componentType)) result.add(new NamedResource(name, symbol)); } return new ArrayList(result); } }); symbols.removeAll(); symbols.add(""); for(NamedResource nr : syms) { symbols.add(nr.getName()); } symbols.setData(syms); symbols.select(0); if(syms.isEmpty()) { symbolsLabel.setVisible(false); symbols.setVisible(false); GridDataFactory.fillDefaults().exclude(true).applyTo(symbolsLabel); GridDataFactory.fillDefaults().exclude(true).grab(true, false).span(9, 1).applyTo(symbols); } else { symbolsLabel.setVisible(true); symbols.setVisible(true); GridDataFactory.fillDefaults().applyTo(symbolsLabel); GridDataFactory.fillDefaults().grab(true, false).span(9, 1).applyTo(symbols); } symbols.getParent().layout(); } catch (DatabaseException e) { Logger.defaultLogError(e); } } void refreshModel() { final String sourceText = source.getText(); final String targetText = target.getText(); int symbolIndex = symbols.getSelectionIndex(); Resource symbol = null; if(symbolIndex > 0) { @SuppressWarnings("unchecked") java.util.List nrs = (java.util.List)symbols.getData(); symbol = nrs.get(symbolIndex-1).getResource(); } final Resource finalSymbol = symbol; try { model = Simantics.getSession().syncRequest(new UniqueRead() { @Override public MigrateModel perform(ReadGraph graph) throws DatabaseException { Layer0 L0 = Layer0.getInstance(graph); StructuralResource2 STR = StructuralResource2.getInstance(graph); Resource source = graph.getPossibleResource(sourceText); if(source == null) return model; Resource target = graph.getPossibleResource(targetText); if(target == null) return model; if(graph.isInstanceOf(source, STR.ComponentType)) { return UserComponentMigration.getComponentTypeModel(graph, source, target, finalSymbol); } else if(graph.isInstanceOf(source, L0.SharedOntology)) { return UserComponentMigration.getSharedOntologyModel(graph, source, target); } else { return null; } } }); } catch (DatabaseException e) { Logger.defaultLogError(e); } instances.removeAll(); locations.removeAll(); if (model == null) return; int preSelect = -1, i = 0; for (Triple> r : model.instances) { locations.add(r.second.getName() + " (" + r.third.size() + " instances)"); if (r.first.equals(previouslySelectedLocationId)) preSelect = i; if (preSelect < 0 && model.activeModels.contains(r.second.getResource())) preSelect = i; ++i; } if (locations.getItemCount() == 0) { locations.add(""); locations.select(0); } else { locations.select(preSelect > -1 ? preSelect : 0); } } void refreshInstances() { int toMigrate = 0; for(Triple> pair : model.instances) { toMigrate += pair.third.size(); } if(model.instanceCount == 0) instanceLabel.setText("No instances were found."); else if(model.instanceCount == 1) { if(toMigrate == 1) { instanceLabel.setText("1 migratable instance found."); } else { instanceLabel.setText("1 instance found, but it cannot be migrated with current settings."); } } else { if(toMigrate < model.instanceCount) { if(toMigrate == 0) { instanceLabel.setText(model.instanceCount + " instances were found. None of them can be migrated with current settings."); } else { instanceLabel.setText(model.instanceCount + " instances were found. " + (model.instanceCount-toMigrate) + " of them cannot be migrated with current settings."); } } else { instanceLabel.setText(model.instanceCount + " migratable instances found. "); } } instances.removeAll(); if (model == null) return; if(toMigrate == 0) { locationsLabel.setVisible(false); locations.setVisible(false); GridDataFactory.fillDefaults().exclude(true).applyTo(locationsLabel); GridDataFactory.fillDefaults().exclude(true).grab(true, false).span(9, 1).applyTo(locations); } else { locationsLabel.setVisible(true); locations.setVisible(true); GridDataFactory.fillDefaults().applyTo(locationsLabel); GridDataFactory.fillDefaults().grab(true, false).span(9, 1).applyTo(locations); } if(!model.instances.isEmpty()) { int locationIndex = locations.getSelectionIndex(); if(locationIndex == -1) return; model.sortedShownInstances = new ArrayList(); for(MigrationOperation o : model.instances.get(locationIndex).third) model.sortedShownInstances.add(o); Collections.sort(model.sortedShownInstances, MIGRATION_OP_COMPARATOR); for(MigrationOperation o : model.sortedShownInstances) { String uri = o.toString(); uri = uri.replace("http://Projects/Development%20Project/", ""); uri = URIStringUtils.unescape(uri); instances.add(uri); } } if(model.sortedShownInstances.isEmpty()) { instancesLabel.setVisible(false); instances.setVisible(false); buttonBar.setVisible(false); GridDataFactory.fillDefaults().exclude(true).grab(true, false).span(9, 1).applyTo(instancesLabel); GridDataFactory.fillDefaults().exclude(true).grab(true, true).span(10, 1).applyTo(instances); GridDataFactory.fillDefaults().exclude(true).grab(true, false).span(10, 1).applyTo(buttonBar); } else { GridDataFactory.fillDefaults().grab(true, false).span(9, 1).applyTo(instancesLabel); GridDataFactory.fillDefaults().grab(true, true).span(10, 1).applyTo(instances); GridDataFactory.fillDefaults().grab(true, false).span(10, 1).applyTo(buttonBar); instancesLabel.setVisible(true); instances.setVisible(true); buttonBar.setVisible(true); } locations.getParent().layout(); } Listener validateListener = new Listener() { @Override public void handleEvent(Event event) { switch (event.type) { case SWT.Modify: validatePage(); case SWT.Selection: validatePage(); break; case SWT.FocusIn: { if (event.widget instanceof Text) { Text t = (Text) event.widget; t.selectAll(); } break; } } } }; private void validatePage() { setMessage(null); setErrorMessage(null); setPageComplete(true); } } private static final Comparator MIGRATION_OP_COMPARATOR = new Comparator() { @Override public int compare(MigrationOperation o1, MigrationOperation o2) { return AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(o1.instanceToMigrate.getName(), o2.instanceToMigrate.getName()); } }; private static abstract class BrowseContentRequest extends UnaryRead>> { public BrowseContentRequest(String uri) { super(uri); } @Override public Map> perform(ReadGraph graph) throws DatabaseException { Resource root = graph.getPossibleResource(parameter); if (root == null) return null; Map> result = new THashMap<>(); Collection infos = getVersions(graph, root); for (NamedResource info : infos) result.put(graph.getURI(info.getResource()), Pair.make(Versions.getStandardPathNameString(graph, info.getResource()), null)); return result; } protected abstract Collection getVersions(ReadGraph graph, Resource root) throws DatabaseException; } private static class BrowseSourceContentRequest extends BrowseContentRequest { public BrowseSourceContentRequest(String uri) { super(uri); } @Override protected Collection getVersions(ReadGraph graph, Resource root) throws DatabaseException { return Versions.getOlderVersions(graph, root); } } private static class BrowseTargetContentRequest extends BrowseContentRequest { public BrowseTargetContentRequest(String uri) { super(uri); } @Override protected Collection getVersions(ReadGraph graph, Resource root) throws DatabaseException { return Versions.getNewerVersions(graph, root); } } }