diff options
Diffstat (limited to 'plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal')
14 files changed, 2280 insertions, 0 deletions
diff --git a/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/Activator.java b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/Activator.java new file mode 100644 index 00000000000..9e14a6b2fea --- /dev/null +++ b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/Activator.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2011, 2014 Mia-Software, CEA, and others. + * 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: + * Nicolas Bros (Mia-Software) - Bug 366567 - [Releng] Tool to update rmaps + * Christian W. Damus (CEA) - Add support for updating Oomph setup models + * + *******************************************************************************/ +package org.eclipse.papyrus.releng.tools.internal; + +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.osgi.framework.BundleContext; + +/** The activator class controls the plug-in life cycle */ +public class Activator extends AbstractUIPlugin { + + // The plug-in ID + public static final String PLUGIN_ID = "org.eclipse.papyrus.releng.tools"; //$NON-NLS-1$ + + // The shared instance + private static Activator plugin; + + @Override + public void start(final BundleContext context) throws Exception { + super.start(context); + Activator.plugin = this; + } + + @Override + public void stop(final BundleContext context) throws Exception { + Activator.plugin = null; + super.stop(context); + } + + /** + * Returns the shared instance + * + * @return the shared instance + */ + public static Activator getDefault() { + return Activator.plugin; + } +} diff --git a/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/Messages.java b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/Messages.java new file mode 100644 index 00000000000..8d75c212858 --- /dev/null +++ b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/Messages.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2011 Mia-Software. + * 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: + * Nicolas Bros (Mia-Software) - Bug 366567 - [Releng] Tool to update rmaps + *******************************************************************************/ +package org.eclipse.papyrus.releng.tools.internal; + +import org.eclipse.osgi.util.NLS; + +public class Messages extends NLS { + private static final String BUNDLE_NAME = "org.eclipse.papyrus.releng.tools.internal.messages"; //$NON-NLS-1$ + public static String UpdateRMapAction_mapWasUpdatedTitle; + public static String UpdateRMapAction_chooseBuildModel; + public static String UpdateRMapAction_chooseBuildModelLong; + public static String UpdateRMapAction_error; + public static String UpdateRMapAction_mapWasUpdated; + public static String UpdateRMapAction_noBuildModelFound; + public static String UpdateRMapAction_noBuildModelFoundLong; + static { + // initialize resource bundle + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } + + private Messages() { + } +} diff --git a/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/handler/AddSetupRepositoryUpdateAnnotationHandler.java b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/handler/AddSetupRepositoryUpdateAnnotationHandler.java new file mode 100644 index 00000000000..54f5c963804 --- /dev/null +++ b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/handler/AddSetupRepositoryUpdateAnnotationHandler.java @@ -0,0 +1,422 @@ +/***************************************************************************** + * Copyright (c) 2014, 2015 CEA LIST, Christian W. Damus, and others. + * + * 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: + * CEA LIST - Initial API and implementation + * Christian W. Damus - Support updating of multiple selected files + * + *****************************************************************************/ + +package org.eclipse.papyrus.releng.tools.internal.handler; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.eclipse.cbi.p2repo.aggregator.Aggregation; +import org.eclipse.cbi.p2repo.aggregator.AggregatorPackage; +import org.eclipse.cbi.p2repo.aggregator.Contribution; +import org.eclipse.cbi.p2repo.aggregator.MappedRepository; +import org.eclipse.cbi.p2repo.aggregator.transformer.TransformationManager; +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.emf.edit.command.ChangeCommand; +import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain; +import org.eclipse.emf.edit.domain.EditingDomain; +import org.eclipse.jface.dialogs.DialogSettings; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.window.Window; +import org.eclipse.oomph.base.Annotation; +import org.eclipse.oomph.base.BaseFactory; +import org.eclipse.oomph.p2.Repository; +import org.eclipse.oomph.p2.RepositoryList; +import org.eclipse.oomph.targlets.Targlet; +import org.eclipse.papyrus.releng.tools.internal.Activator; +import org.eclipse.papyrus.releng.tools.internal.popup.actions.OomphSetupUpdater; +import org.eclipse.papyrus.releng.tools.internal.popup.actions.UpdateDependencies; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.ISources; +import org.eclipse.ui.dialogs.FilteredItemsSelectionDialog; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * @author damus + * + */ +public class AddSetupRepositoryUpdateAnnotationHandler extends AbstractHandler { + + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + Collection<? extends Repository> repositories = getSelectedRepositories(event); + + if (!repositories.isEmpty()) { + Shell activeShell = HandlerUtil.getActiveShell(event); + + try { + IFile aggrFile = UpdateDependencies.chooseAggregationBuildFile(UpdateDependencies.findAggregationBuildFiles(), activeShell); + if (aggrFile != null) { + ResourceSet rset = new ResourceSetImpl(); + + List<IAggregationElementProxy> elements = loadContributionsAndRepositories(rset, URI.createPlatformResourceURI(aggrFile.getFullPath().toString(), true)); + + Map<Repository, IAggregationElementProxy> updates = new HashMap<>(); + for (Repository repo : repositories) { + FilteredItemsSelectionDialog dlg = createSelectionDialog(activeShell, repo, elements); + if (dlg.open() == Window.OK) { + IAggregationElementProxy selected = (IAggregationElementProxy) dlg.getFirstResult(); + if (selected != null) { + updates.put(repo, selected); + } + } + } + + if (!updates.isEmpty()) { + createOrUpdateAnnotations(updates); + } + } + } catch (CoreException e) { + throw new ExecutionException("Failed to set the repository update annotation.", e); + } + } + + return null; + } + + @Override + public void setEnabled(Object evaluationContext) { + setBaseEnabled(!getSelectedRepositories(evaluationContext).isEmpty()); + } + + private Collection<Repository> getSelectedRepositories(Object context) { + List<Repository> result = new ArrayList<>(); + + Object variable = null; + if (context instanceof ExecutionEvent) { + variable = HandlerUtil.getCurrentSelection((ExecutionEvent) context); + } else { + variable = HandlerUtil.getVariable(context, ISources.ACTIVE_CURRENT_SELECTION_NAME); + } + + if (variable instanceof IStructuredSelection) { + for (Iterator<?> iter = ((IStructuredSelection) variable).iterator(); iter.hasNext();) { + Object selected = iter.next(); + if (selected instanceof Repository) { + Repository repo = (Repository) selected; + if ((repo.eContainer() instanceof RepositoryList) && (repo.eContainer().eContainer() instanceof Targlet)) { + result.add(repo); + } + } + } + } + + return result; + } + + protected List<IAggregationElementProxy> loadContributionsAndRepositories(ResourceSet rset, URI uri) throws CoreException { + // Ensure that the Aggregator model is loaded because the model plug-in doesn't register it + AggregatorPackage.eINSTANCE.eClass(); + + List<IAggregationElementProxy> result = new ArrayList<>(); + Resource resource; + + try { + resource = rset.getResource(uri, true); + } catch (Exception e) { + try { + TransformationManager transformationManager = new TransformationManager(uri); + resource = transformationManager.transformResource(true); + } catch (Exception e1) { + throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Error loading b3aggr. Make sure you have the latest version of B3 installed: " + e.getLocalizedMessage(), e1)); //$NON-NLS-1$ + } + } + + if (resource.getContents().size() == 0) { + throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "The b3aggr resource is empty")); //$NON-NLS-1$ + } + + Aggregation aggregation = (Aggregation) EcoreUtil.getObjectByType(resource.getContents(), AggregatorPackage.Literals.AGGREGATION); + if (aggregation != null) { + List<Contribution> contributions = aggregation.getAllContributions(true); + for (Contribution next : contributions) { + List<MappedRepository> repositories = next.getRepositories(true); + switch (repositories.size()) { + case 0: + break; + case 1: + result.add(new ContributionProxy(next)); // Add the contribution, itself + break; + default: + // User must select a specific repository + int index = 0; + for (MappedRepository repo : repositories) { + result.add(new RepositoryProxy(next, repo, index++)); + } + break; + } + } + } + + return result; + } + + protected void createOrUpdateAnnotations(final Map<Repository, IAggregationElementProxy> selection) { + EObject context = EcoreUtil.getRootContainer(selection.keySet().iterator().next()); + EditingDomain domain = AdapterFactoryEditingDomain.getEditingDomainFor(context); + if (domain == null) { + doCreateOrUpdateAnnotation(selection); + } else { + domain.getCommandStack().execute(new ChangeCommand(context) { + { + setLabel("Set Update Annotation"); //$NON-NLS-1$ + } + + @Override + protected void doExecute() { + doCreateOrUpdateAnnotation(selection); + } + }); + } + } + + protected void doCreateOrUpdateAnnotation(Map<Repository, IAggregationElementProxy> selection) { + for (Map.Entry<Repository, IAggregationElementProxy> next : selection.entrySet()) { + Repository repo = next.getKey(); + IAggregationElementProxy aggr = next.getValue(); + + Annotation annotation = repo.getAnnotation(OomphSetupUpdater.ANNOTATION_SOURCE); + if (annotation == null) { + annotation = BaseFactory.eINSTANCE.createAnnotation(OomphSetupUpdater.ANNOTATION_SOURCE); + repo.getAnnotations().add(annotation); + } + + annotation.getDetails().put(OomphSetupUpdater.UPDATE_KEY, aggr.getUpdateSpec()); + + // And, while we're at it, update the repository + repo.setURL(aggr.getRepositoryURL()); + } + } + + protected FilteredItemsSelectionDialog createSelectionDialog(Shell parentShell, Repository repo, final Collection<? extends IAggregationElementProxy> contents) { + FilteredItemsSelectionDialog result = new FilteredItemsSelectionDialog(parentShell) { + + @Override + protected IStatus validateItem(Object item) { + return Status.OK_STATUS; + } + + @SuppressWarnings("rawtypes") + @Override + protected Comparator getItemsComparator() { + return new Comparator() { + @Override + public int compare(Object o1, Object o2) { + if ((o1.getClass() != o2.getClass()) || (o1 instanceof ContributionProxy)) { + String label1 = ((IAggregationElementProxy) o1).getLabel(); + String label2 = ((IAggregationElementProxy) o2).getLabel(); + return label1.compareTo(label2); + } else { + RepositoryProxy repo1 = (RepositoryProxy) o1; + RepositoryProxy repo2 = (RepositoryProxy) o2; + if (repo1.getContribution() == repo2.getContribution()) { + // Use the ordered in which they are defined in the contribution + return repo1.getIndex() - repo2.getIndex(); + } else { + // Sort by contribution + String label1 = repo1.getContribution().getLabel(); + String label2 = repo2.getContribution().getLabel(); + return label1.compareTo(label2); + } + } + } + }; + } + + @Override + public String getElementName(Object item) { + return ((IAggregationElementProxy) item).getContribution().getLabel(); + } + + @Override + protected IDialogSettings getDialogSettings() { + return DialogSettings.getOrCreateSection(Activator.getDefault().getDialogSettings(), "AddSetupRepositoryUpdateAnnotation"); //$NON-NLS-1$ + } + + @Override + protected void fillContentProvider(AbstractContentProvider contentProvider, ItemsFilter itemsFilter, IProgressMonitor progressMonitor) throws CoreException { + for (Object next : contents) { + contentProvider.add(next, itemsFilter); + } + if (progressMonitor != null) { + progressMonitor.done(); + } + } + + @Override + protected ItemsFilter createFilter() { + return new ItemsFilter() { + + @Override + public boolean matchItem(Object item) { + return matches(((IAggregationElementProxy) item).getLabel()); + } + + @Override + public boolean isConsistentItem(Object item) { + return true; // The aggregation model is not editable in this context + } + }; + } + + @Override + protected Control createExtendedContentArea(Composite parent) { + return null; + } + }; + + + result.setTitle("Select Aggregation Component"); + result.setMessage(String.format("Select an aggregation component or repository for \"%s\".", repo.getURL())); + + result.setListLabelProvider(new LabelProvider() { + @Override + public String getText(Object element) { + return (element == null) ? "" : ((IAggregationElementProxy) element).getLabel(); //$NON-NLS-1$ + } + }); + + result.setDetailsLabelProvider(new LabelProvider() { + @Override + public String getText(Object element) { + return ((IAggregationElementProxy) element).getDetails(); + } + }); + + result.setInitialPattern("**", FilteredItemsSelectionDialog.FULL_SELECTION); //$NON-NLS-1$ + + return result; + } + + protected interface IAggregationElementProxy { + Contribution getContribution(); + + String getLabel(); + + String getDetails(); + + String getUpdateSpec(); + + String getRepositoryURL(); + } + + protected static final class ContributionProxy implements IAggregationElementProxy { + private final Contribution contribution; + + public ContributionProxy(Contribution contribution) { + this.contribution = contribution; + } + + @Override + public Contribution getContribution() { + return contribution; + } + + @Override + public String getLabel() { + String result = contribution.getLabel(); + return (result == null) ? "" : result; + } + + @Override + public String getDetails() { + if ((contribution.getDescription() != null) && (contribution.getDescription().length() > 0)) { + return String.format("%s - %s", contribution.getLabel(), contribution.getDescription()); + } else { + return contribution.getLabel(); + } + } + + @Override + public String getUpdateSpec() { + return contribution.getLabel(); + } + + @Override + public String getRepositoryURL() { + return contribution.getRepositories(true).get(0).getLocation(); + } + } + + protected static final class RepositoryProxy implements IAggregationElementProxy { + private final Contribution contribution; + private final MappedRepository repository; + private final int index; + + public RepositoryProxy(Contribution contribution, MappedRepository repository, int index) { + this.contribution = contribution; + this.repository = repository; + this.index = index; + } + + @Override + public Contribution getContribution() { + return contribution; + } + + public MappedRepository getRepository() { + return repository; + } + + public int getIndex() { + return index; + } + + @Override + public String getLabel() { + return String.format("%s - %s", contribution.getLabel(), repository.getLocation()); + } + + @Override + public String getDetails() { + if ((contribution.getDescription() != null) && (contribution.getDescription().length() > 0)) { + return String.format("%s (%s) - %s", contribution.getLabel(), contribution.getDescription(), repository.getLocation()); + } else { + return getLabel(); + } + } + + @Override + public String getUpdateSpec() { + return String.format("%s:%s", contribution.getLabel(), index); + } + + @Override + public String getRepositoryURL() { + return repository.getLocation(); + } + } +} diff --git a/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/handler/UpdateDependenciesHandler.java b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/handler/UpdateDependenciesHandler.java new file mode 100644 index 00000000000..ddec2c33d3a --- /dev/null +++ b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/handler/UpdateDependenciesHandler.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2015 Christian W. Damus and others. + * 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: + * Nicolas Bros (Mia-Software) - Bug 366567 - [Releng] Tool to update rmaps + * Camille Letavernier (CEA LIST) - camille.letavernier@cea.fr - Generalize to handle POMs + * Christian W. Damus (CEA) - Add support for updating Oomph setup models + * Christian W. Damus - Support updating of multiple selected files + * + *******************************************************************************/ +package org.eclipse.papyrus.releng.tools.internal.handler; + +import java.util.List; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.resources.IFile; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.papyrus.releng.tools.internal.popup.actions.UpdateDependencies; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.handlers.HandlerUtil; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; + + +public class UpdateDependenciesHandler extends AbstractHandler { + + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + ISelection selection = HandlerUtil.getCurrentSelection(event); + Shell activeShell = HandlerUtil.getActiveShell(event); + + if (selection instanceof IStructuredSelection) { + IStructuredSelection structuredSelection = (IStructuredSelection) selection; + List<IFile> filesToUpdate = ImmutableList.copyOf(Iterables.filter(structuredSelection.toList(), IFile.class)); + new UpdateDependencies().updateDependencies(filesToUpdate, activeShell); + } + + return null; + } + +} diff --git a/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/messages.properties b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/messages.properties new file mode 100644 index 00000000000..8708cdc00ed --- /dev/null +++ b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/messages.properties @@ -0,0 +1,18 @@ +############################################################################### +# Copyright (c) 2011, 2015 Mia-Software, Christian W. Damus, and others. +# 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: +# Nicolas Bros (Mia-Software) - Bug 366567 - [Releng] Tool to update rmaps +# Christian W. Damus - Support updating of multiple selected files +############################################################################### +UpdateRMapAction_mapWasUpdatedTitle=Papyrus Releng Tools +UpdateRMapAction_chooseBuildModel=Choose build model +UpdateRMapAction_chooseBuildModelLong=Select the build model with which to update dependencies: +UpdateRMapAction_error=Error +UpdateRMapAction_mapWasUpdated=The dependencies in {0}\nwere updated from {1}. +UpdateRMapAction_noBuildModelFound=No build model found +UpdateRMapAction_noBuildModelFoundLong=No build model was found in your workspace.\nPlease checkout a build project from git://git.eclipse.org/gitroot/simrel/org.eclipse.simrel.build.git diff --git a/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/DependencyUpdater.java b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/DependencyUpdater.java new file mode 100644 index 00000000000..a2e3e1c5478 --- /dev/null +++ b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/DependencyUpdater.java @@ -0,0 +1,437 @@ +/******************************************************************************* + * Copyright (c) 2011, 2016 Mia-Software, CEA LIST, Christian W. Damus, and others. + * 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: + * Nicolas Bros (Mia-Software) - Bug 366567 - [Releng] Tool to update rmaps + * Camille Letavernier (CEA LIST) - Generalize to support POMs + * Christian W. Damus (CEA) - Add support for updating Oomph setup models + * Christian W. Damus - Support updating of multiple selected files + * Christian W. Damus - Ignore equivalent URL prefixes in detecting suspicious updates + * + *******************************************************************************/ +package org.eclipse.papyrus.releng.tools.internal.popup.actions; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.cbi.p2repo.aggregator.Contribution; +import org.eclipse.cbi.p2repo.aggregator.MappedRepository; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Status; +import org.eclipse.emf.common.util.EList; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.window.Window; +import org.eclipse.osgi.util.NLS; +import org.eclipse.papyrus.releng.tools.internal.Activator; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.dialogs.ListSelectionDialog; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +/** + * Generic DependencyUpdater + * + * This class will read a B3 Aggregator model (e.g. from Simrel) to find up-to-date P2 repositories. + * + * Subclasses will then be able to update the relevant <T> element (e.g. XML Dom Node, EMF EObject...) + * with the new repository location + * + * The matching is typically done via comments using the following format: updateFrom(repositoryLabel, index) + * It is up to subclasses to retrieve these comments in their model (XML Document, EMF Model) + * + * @param <T> + */ +public abstract class DependencyUpdater<T> { + + private final Pattern commentPattern = Pattern.compile("updateFrom\\s*\\(\\s*\"(.*?)\"\\s*,\\s*(\\d+)\\s*\\)"); //$NON-NLS-1$ + + private final List<LocationUpdateStrategy> locationUpdateStrategies = ImmutableList.of( + new MilestoneLocationStrategy(), + new EMFLocationStrategy()); + + public DependencyUpdater() { + super(); + } + + protected static final String PREFIX = "http://download.eclipse.org/"; //$NON-NLS-1$ + + public abstract boolean canUpdate(IFile file); + + protected abstract List<T> getNodesToUpdate(IFile file) throws CoreException; + + public void updateDocument(final Shell parentShell, final IFile mapFile, final EList<Contribution> contributions, final Map<Object, Object> context) throws CoreException { + try { + List<T> nodesToUpdate = getNodesToUpdate(mapFile); + List<UpdateItem<T>> updates = Lists.newArrayList(); + for (T uri : nodesToUpdate) { + String comment = getComment(uri); + if (comment != null) { + Matcher matcher = getCommentPattern().matcher(comment); + if (matcher.find()) { + String contributionName = matcher.group(1); + int repositoryIndex = Integer.parseInt(matcher.group(2)); + Contribution contribution = findContribution(contributions, contributionName); + if (contribution == null) { + throw new RuntimeException("'updateFrom' failed: cannot find contribution with label \"" + contributionName + "\""); //$NON-NLS-1$ //$NON-NLS-2$ + } + updates.add(new UpdateItem<>(uri, contribution, repositoryIndex)); + } else if (comment.contains("updateFrom")) { //$NON-NLS-1$ + throw new Exception("Wrong syntax for 'updateFrom' : should be " + getCommentSyntax()); //$NON-NLS-1$ + } + } + } + + if (confirmUpdates(parentShell, updates, context)) { + for (UpdateItem<T> next : updates) { + updateWithContribution(parentShell, next.uriNode, next.contribution, next.repositoryIndex, context); + } + + save(mapFile); + + mapFile.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor()); + } + } catch (OperationCanceledException e) { + throw e; + } catch (Exception e) { + throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Error updating map: " + e.getLocalizedMessage(), e)); //$NON-NLS-1$ + } + } + + abstract protected void save(IFile file) throws Exception; + + protected void updateWithContribution(final Shell parentShell, final T uri, final Contribution contribution, final int repositoryIndex, final Map<Object, Object> context) { + EList<MappedRepository> repositories = contribution.getRepositories(); + if (repositoryIndex >= repositories.size()) { + throw new RuntimeException("wrong index in updateFrom(\"" + contribution.getLabel() + "\"" + repositoryIndex //$NON-NLS-1$ //$NON-NLS-2$ + + ") : there are " + repositories.size() + " contributions"); //$NON-NLS-1$ //$NON-NLS-2$ + } + String location = repositories.get(repositoryIndex).getLocation(); + + updateUri(uri, location); + } + + protected abstract String getCurrentLocation(T uri); + + protected abstract void updateUri(T uri, String location); + + protected Contribution findContribution(Iterable<? extends Contribution> contributions, final String contributionName) { + Contribution matchingContribution = null; + for (Contribution contribution : contributions) { + if (contributionName.equalsIgnoreCase(contribution.getLabel())) { + matchingContribution = contribution; + } + } + return matchingContribution; + } + + protected abstract String getComment(T node); + + protected Pattern getCommentPattern() { + return commentPattern; + } + + protected String getCommentSyntax() { + return "updateFrom(\"<contributionName>\",<index>)"; //$NON-NLS-1$ + } + + private boolean promptToReplaceSingle(Shell parentShell, LocationUpdate<T> locationUpdate, Map<Object, Object> context) { + String message = NLS.bind("{0}\n\nUpdate anyways?", locationUpdate.strategy.getUpdateConfirmationMessage(locationUpdate.update, locationUpdate.oldLocation, locationUpdate.newLocation)); //$NON-NLS-1$ + boolean result = MessageDialog.openQuestion(parentShell, "Confirm Location Update", message); + setReplace(locationUpdate.update, result, context); + + return result; + } + + private Boolean getReplace(UpdateItem<T> update, Map<Object, Object> context) { + return (Boolean) context.get("$replace$::" + update.contribution.getLabel()); //$NON-NLS-1$ + } + + private void setReplace(UpdateItem<T> update, Boolean replace, Map<Object, Object> context) { + context.put("$replace$::" + update.contribution.getLabel(), replace); //$NON-NLS-1$ + } + + /** + * Prompt to confirm multiple suspicious dependency replacements, returning those updates that the + * user confirms to perform. + */ + private Collection<LocationUpdate<T>> promptToReplaceMultiple(Shell parentShell, Collection<? extends LocationUpdate<T>> locationUpdates, Map<Object, Object> context) { + final List<LocationUpdate<T>> result = Lists.newArrayList(locationUpdates); + + ILabelProvider labels = new LabelProvider() { + @Override + public String getText(Object element) { + return ((LocationUpdate<?>) element).update.contribution.getLabel(); + } + }; + + ListSelectionDialog dialog = new ListSelectionDialog(parentShell, locationUpdates, ArrayContentProvider.getInstance(), labels, "Select dependencies to confirm updating.") { + @Override + protected Control createDialogArea(Composite parent) { + Composite result = (Composite) super.createDialogArea(parent); + + createDetailsArea(result); + + return result; + } + + void createDetailsArea(Composite parent) { + Label label = new Label(parent, SWT.NONE); + label.setText("Details:"); + + final Text details = new Text(parent, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL | SWT.WRAP | SWT.READ_ONLY); + GridData data = new GridData(GridData.FILL_BOTH); + data.heightHint = details.getLineHeight() * 4; + details.setLayoutData(data); + + getViewer().addSelectionChangedListener(new ISelectionChangedListener() { + + @Override + public void selectionChanged(SelectionChangedEvent event) { + IStructuredSelection sel = (IStructuredSelection) event.getSelection(); + if (sel.isEmpty()) { + details.setText(""); //$NON-NLS-1$ + } else { + LocationUpdate<?> update = (LocationUpdate<?>) sel.getFirstElement(); + details.setText(update.strategy.getUpdateConfirmationMessage(update.update, update.oldLocation, update.newLocation)); + } + } + }); + } + }; + + dialog.setInitialElementSelections(result); + dialog.setTitle("Confirm Location Update"); + if (dialog.open() == Window.OK) { + Set<?> toUpdate = ImmutableSet.copyOf(dialog.getResult()); + for (Iterator<LocationUpdate<T>> iter = result.iterator(); iter.hasNext();) { + LocationUpdate<T> next = iter.next(); + + boolean update = toUpdate.contains(next); + setReplace(next.update, update, context); + if (!update) { + // User elects not to update, so remove from the result + iter.remove(); + } + } + } else { + throw new OperationCanceledException(); + } + + return result; + } + + private boolean confirmUpdates(final Shell parentShell, List<UpdateItem<T>> updates, Map<Object, Object> context) { + Map<UpdateItem<T>, LocationUpdate<T>> toPrompt = Maps.newHashMap(); + + for (Iterator<UpdateItem<T>> iter = updates.iterator(); iter.hasNext();) { + UpdateItem<T> next = iter.next(); + + // Check for previous prompt answer + Boolean previousAnswer = getReplace(next, context); + if (previousAnswer != null) { + if (previousAnswer) { + // Don't prompt this one + } else { + iter.remove(); // Don't update this one + } + } else { + EList<MappedRepository> repositories = next.contribution.getRepositories(); + if (next.repositoryIndex >= repositories.size()) { + throw new RuntimeException("wrong index in updateFrom(\"" + next.contribution.getLabel() + "\"" + next.repositoryIndex //$NON-NLS-1$ //$NON-NLS-2$ + + ") : there are " + repositories.size() + " contributions"); //$NON-NLS-1$ //$NON-NLS-2$ + } + String location = repositories.get(next.repositoryIndex).getLocation(); + String current = getCurrentLocation(next.uriNode); + + if ((current == null) || !current.equals(location)) { + if ((current != null) && !current.isEmpty()) { + LocationUpdateStrategy strategy = findLocationUpdateStrategy(next, current, location); + if (strategy != null) { + toPrompt.put(next, new LocationUpdate<>(next, strategy, current, location)); + } + } + } + } + } + + if (!toPrompt.isEmpty()) { + if (toPrompt.size() == 1) { + // Simple dialog + UpdateItem<T> update = Iterables.getOnlyElement(toPrompt.keySet()); + LocationUpdate<T> location = toPrompt.get(update); + if (!promptToReplaceSingle(parentShell, location, context)) { + updates.remove(update); + } + } else { + // List dialog + toPrompt.values().removeAll(promptToReplaceMultiple(parentShell, toPrompt.values(), context)); + updates.removeAll(toPrompt.keySet()); // What remains are the update exclusions + } + } + + return !updates.isEmpty(); + } + + private LocationUpdateStrategy findLocationUpdateStrategy(UpdateItem<T> update, String oldLocation, String newLocation) { + LocationUpdateStrategy result = null; + + for (LocationUpdateStrategy next : locationUpdateStrategies) { + if (!next.shouldAutoUpdate(update, oldLocation, newLocation)) { + result = next; + break; + } + } + + return result; + } + + // + // Nested types + // + + private static class UpdateItem<T> { + final T uriNode; + final Contribution contribution; + final int repositoryIndex; + + UpdateItem(T uriNode, Contribution contribution, int repositoryIndex) { + super(); + + this.uriNode = uriNode; + this.contribution = contribution; + this.repositoryIndex = repositoryIndex; + } + } + + private static class LocationUpdate<T> { + final UpdateItem<T> update; + final LocationUpdateStrategy strategy; + final String oldLocation; + final String newLocation; + + LocationUpdate(UpdateItem<T> update, LocationUpdateStrategy strategy, String oldLocation, String newLocation) { + super(); + + this.update = update; + this.strategy = strategy; + this.oldLocation = oldLocation; + this.newLocation = newLocation; + } + } + + private interface LocationUpdateStrategy { + Pattern URL_PREFIX_PATTERN = Pattern.compile("^(?:\\$\\{[^}]+\\}/|\\Qhttp://download.eclipse.org/\\E)"); //$NON-NLS-1$ + + boolean shouldAutoUpdate(UpdateItem<?> update, String oldLocation, String newLocation); + + String getUpdateConfirmationMessage(UpdateItem<?> update, String oldLocation, String newLocation); + + default boolean hasRecognizedURLPrefix(String location) { + return URL_PREFIX_PATTERN.matcher(location).find(); + } + + default String stripRecognizedURLPrefix(String location) { + Matcher m = URL_PREFIX_PATTERN.matcher(location); + return !m.find() ? location : location.substring(m.end()); + } + + default boolean matchURLPattern(Pattern urlPattern, String oldLocation, String newLocation) { + boolean result = true; // Optimistically assume sameness if we can't find matching URL path segment structures + + Matcher oldMatcher = urlPattern.matcher(oldLocation); + Matcher newMatcher = urlPattern.matcher(newLocation); + boolean foundOld = oldMatcher.find(); + boolean foundNew = newMatcher.find(); + + if (foundOld != foundNew) { + // definitely different + result = false; + } else if (foundNew) { + // Compare prefixes + String oldPrefix = oldLocation.substring(0, oldMatcher.start()); + String newPrefix = newLocation.substring(0, newMatcher.start()); + if (hasRecognizedURLPrefix(oldPrefix) && hasRecognizedURLPrefix(newPrefix)) { + // Both have equivalent URL prefixes, so remove those for comparison + oldPrefix = stripRecognizedURLPrefix(oldPrefix); + newPrefix = stripRecognizedURLPrefix(newPrefix); + } + + result = newPrefix.equals(oldPrefix); + } + + return result; + } + } + + static class MilestoneLocationStrategy implements LocationUpdateStrategy { + private final Pattern typicalBuildTimestampPattern = Pattern.compile("[NISMR](?:-\\d+\\.\\d+(?:\\.\\d+)?(?:M|RC)\\d[abcd]-)?20\\d\\d[-0-9]+"); //$NON-NLS-1$ + + @Override + public boolean shouldAutoUpdate(UpdateItem<?> update, String oldLocation, String newLocation) { + return matchURLPattern(typicalBuildTimestampPattern, oldLocation, newLocation); + } + + @Override + public String getUpdateConfirmationMessage(UpdateItem<?> update, String oldLocation, String newLocation) { + return NLS.bind("The new location \"{0}\" for project \"{1}\" is not like the current location \"{2}\". This could roll back to an older (obsolete) build.", new Object[] { newLocation, update.contribution.getLabel(), oldLocation }); + } + } + + static class EMFLocationStrategy implements LocationUpdateStrategy { + private final Pattern typicalMilestonesPattern = Pattern.compile("\\d+\\.\\d+(milestones|interim)$"); //$NON-NLS-1$ + + @Override + public boolean shouldAutoUpdate(UpdateItem<?> update, String oldLocation, String newLocation) { + boolean result = true; // Optimistically assume sameness if we can't find any milestones/interim segment + + Matcher oldMatcher = typicalMilestonesPattern.matcher(oldLocation); + Matcher newMatcher = typicalMilestonesPattern.matcher(newLocation); + boolean foundOld = oldMatcher.find(); + boolean foundNew = newMatcher.find(); + + if (foundOld != foundNew) { + // definitely different + result = false; + } else if (foundNew && !(oldMatcher.group(1).equals(newMatcher.group(1)))) { + result = (oldMatcher.group(1).equals("interim")); + } + + return result; + } + + @Override + public String getUpdateConfirmationMessage(UpdateItem<?> update, String oldLocation, String newLocation) { + return NLS.bind("The current location \"{2}\" for project \"{1}\" provides interim builds. Updating from \"{0}\" could roll back to a previous milestone build.", new Object[] { newLocation, update.contribution.getLabel(), oldLocation }); + } + } +} diff --git a/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/GenerateTargetsHandler.java b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/GenerateTargetsHandler.java new file mode 100644 index 00000000000..28e9e42356e --- /dev/null +++ b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/GenerateTargetsHandler.java @@ -0,0 +1,337 @@ +/***************************************************************************** + * Copyright (c) 2016 CEA LIST and others. + * + * 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: + * CEA LIST - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.releng.tools.internal.popup.actions; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.IJobChangeEvent; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.core.runtime.jobs.JobChangeAdapter; +import org.eclipse.core.runtime.jobs.JobGroup; +import org.eclipse.emf.common.util.BasicDiagnostic; +import org.eclipse.emf.common.util.Diagnostic; +import org.eclipse.emf.common.util.URI; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.papyrus.releng.tools.internal.Activator; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.handlers.HandlerUtil; + +import com.google.inject.Injector; + +import fr.obeo.releng.targetplatform.TargetPlatformBundleActivator; +import fr.obeo.releng.targetplatform.pde.Converter; + +/** + * A global handler, enabled on a set of IResources (Project/Folder/File), which recursively: + * + * - Finds all *.tpd files + * - Updates them from the selected Simrel/B3 model + * - Generates all *.target files + * - Generates an Eclipse-Server version for each *.target file (In an eclipse/*.target folder) + * + * The Eclipse-Server version of the target is similar to the default one, except it uses + * the file:/ protocol instead of http:// for all access to download.eclipse.org, + * for improved performances when building on Eclipse Servers + * + * @author Camille Letavernier + * + */ +public class GenerateTargetsHandler extends AbstractHandler { + + /** + * @see org.eclipse.core.commands.IHandler#execute(org.eclipse.core.commands.ExecutionEvent) + * + * @param event + * @return + * @throws ExecutionException + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + ISelection selection = HandlerUtil.getCurrentSelection(event); + if (selection instanceof IStructuredSelection) { + IStructuredSelection sSelection = (IStructuredSelection) selection; + Iterator<?> iterator = sSelection.iterator(); + + List<IFile> tpdFiles = new ArrayList<>(); + + try { + + while (iterator.hasNext()) { + Object next = iterator.next(); + + if (next instanceof IResource) { + collectTPDFiles((IResource) next, tpdFiles); + } + } + + final Shell activeShell = HandlerUtil.getActiveShell(event); + + if (!tpdFiles.isEmpty()) { + new UpdateDependencies().updateDependencies(tpdFiles, activeShell); // Update all TPD Files from Simrel + + String jobTitle = String.format("Generate %s target files", tpdFiles.size()); + Job topLevelJob = new Job(jobTitle) { + /** + * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor) + * + * @param monitor + * @return + */ + @Override + protected IStatus run(IProgressMonitor monitor) { + int maxThreads = 2; // Multi-threading is not really relevant, most time is spent in downloading artifacts + + JobGroup tpdConverters = new JobGroup("Generate Targets", maxThreads, tpdFiles.size()); + for (IFile tpdFile : tpdFiles) { + generate(tpdFile, tpdConverters); // Generate *.target files + } + + try { + tpdConverters.join(0, monitor); + } catch (InterruptedException e) { + return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Unexpected exception", e); + } + + return tpdConverters.getResult(); + } + }; + + topLevelJob.setUser(true); + topLevelJob.addJobChangeListener(new JobChangeAdapter() { + /** + * @see org.eclipse.core.runtime.jobs.JobChangeAdapter#done(org.eclipse.core.runtime.jobs.IJobChangeEvent) + * + * @param event + */ + @Override + public void done(IJobChangeEvent event) { + if (Display.getCurrent() != null) { + done(event.getResult()); + } else { + Display.getDefault().asyncExec(() -> { + done(event.getResult()); + }); + } + } + + void done(IStatus status) { + String title = "Generate targets"; + switch (status.getCode()) { + case IStatus.OK: + case IStatus.INFO: + MessageDialog.openInformation(activeShell, title, "Operation complete"); + break; + case IStatus.CANCEL: + MessageDialog.openInformation(activeShell, title, "Operation canceled"); + break; + case IStatus.ERROR: + MessageDialog.openError(activeShell, title, "The operation completed with errors. Check error log for details"); + break; + case IStatus.WARNING: + MessageDialog.openWarning(activeShell, title, "The operation completed with warnings. Check error log for details"); + break; + } + } + }); + topLevelJob.schedule(); + + } + } catch (CoreException e) { + Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Unexpected exception", e)); + } + + } + + return null; + + } + + /** + * Finds all *.tpd files, recursively, in the given IResource (IFile, IFolder, IProject...) + * The collected *.tpd IFiles are stored in the result List + * + * @param resource + * @param result + * @throws CoreException + */ + protected void collectTPDFiles(IResource resource, List<IFile> result) throws CoreException { + if (resource instanceof IFile) { + IFile file = (IFile) resource; + if ("tpd".equals(file.getFileExtension())) { //$NON-NLS-1$ + result.add(file); + } + } else if (resource instanceof IContainer) { + collectTPDFiles((IContainer) resource, result); + } + } + + /** + * Finds all *.tpd files, recursively, in the given IContainer (IFolder, IProject...) + * The collected *.tpd IFiles are stored in the result List + * + * @param resource + * @param result + * @throws CoreException + */ + protected void collectTPDFiles(IContainer parent, List<IFile> result) throws CoreException { + for (IResource child : parent.members()) { + collectTPDFiles(child, result); + } + } + + /** + * Inits a job for converting a single *.tpd file to a *.target file + * Also creates an Eclipse Server version of each *.target file (Using file:/ protocol instead of http://) + * + * @param file + * A *.tpd IFile + * @param jobGroup + * The job group used to managed all *.tpd to *.target conversion jobs + * @throws CoreException + */ + protected void generate(IFile file, JobGroup jobGroup) { + // The Converter currently only supports File URIs (No platform resource) + // URI tpdURI = URI.createPlatformResourceURI(file.getFullPath().toString(), true); + + String filePath = file.getLocation().toFile().getAbsolutePath(); + URI tpdURI = URI.createFileURI(filePath); + + Converter converter = new Converter(); + + Injector injector = TargetPlatformBundleActivator.getInstance().getInjector(TargetPlatformBundleActivator.TARGET_PLATFORM_LANGUAGE_NAME); + injector.injectMembers(converter); + + Job job = new Job("Generate Target Platform for " + file.getLocation().lastSegment()) { + + @Override + protected IStatus run(IProgressMonitor monitor) { + Diagnostic result = converter.generateTargetDefinitionFile(tpdURI, new NullProgressMonitor()); + if (result.getSeverity() >= Diagnostic.WARNING) { + Activator.getDefault().getLog().log(BasicDiagnostic.toIStatus(result)); + } + + try { + file.getParent().refreshLocal(IResource.DEPTH_ONE, null); + generateEclipseTarget(file); + } catch (CoreException ex) { + return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Unexpected exception", ex);//$NON-NLS-1$ + } + + return BasicDiagnostic.toIStatus(result); + } + }; + + job.setJobGroup(jobGroup); + job.schedule(); + } + + /** + * Generates an Eclipse-Server version of the *.target file for the given *.tpd file (Assuming the + * standard *.target file has already been generated) + * + * The Eclipse-Server version of the target is similar to the default one, except it uses + * the file:/ protocol instead of http:// for all access to download.eclipse.org, + * for improved performances when building on Eclipse Servers + * + * @param tpdFile + * @throws CoreException + */ + protected void generateEclipseTarget(IFile tpdFile) throws CoreException { + String targetSuffix = "eclipse"; //$NON-NLS-1$ + + IContainer parent = tpdFile.getParent(); + + String fileName = tpdFile.getFullPath().removeFileExtension().addFileExtension("target").lastSegment(); //$NON-NLS-1$ + + IFile portableTargetFile = parent.getFile(new Path(fileName)); + + IFolder eclipseFolder = parent.getParent().getFolder(new Path(targetSuffix)); + if (!eclipseFolder.exists()) { + eclipseFolder.create(true, true, new NullProgressMonitor()); + } + + IFile eclipseTargetFile = eclipseFolder.getFile(fileName.replaceAll("portable", targetSuffix)); + + InputStream convertedStream = convert(portableTargetFile.getContents(), "http://download.eclipse.org/", "file:/home/data/httpd/download.eclipse.org/"); + + if (eclipseTargetFile.exists()) { + + eclipseTargetFile.setContents(convertedStream, IResource.NONE, null); + } else { + eclipseTargetFile.create(convertedStream, true, null); + } + + eclipseFolder.refreshLocal(IResource.DEPTH_ONE, null); + } + + /** + * Returns an InputStream similar to the source stream, replacing all occurrences of the source pattern + * with the target pattern + * + * @param source + * @param sourcePattern + * @param targetPattern + * @return + * @throws CoreException + */ + protected InputStream convert(InputStream source, String sourcePattern, String targetPattern) throws CoreException { + BufferedReader reader = new BufferedReader(new InputStreamReader(source)); + + StringBuilder builder = new StringBuilder(); + String line; + + String patternSt = sourcePattern.replaceAll("\\.", "\\."); //$NON-NLS-1$ //$NON-NLS-2$ + Pattern pattern = Pattern.compile(patternSt); + + try { + while ((line = reader.readLine()) != null) { + Matcher matcher = pattern.matcher(line); + String newLine = matcher.replaceAll(targetPattern); + builder.append(newLine).append("\n"); //$NON-NLS-1$ + } + } catch (IOException ex) { + throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Unexpected error", ex)); + } + + ByteArrayInputStream result = new ByteArrayInputStream(builder.toString().getBytes()); + + return result; + } + +} diff --git a/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/MapUpdater.java b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/MapUpdater.java new file mode 100644 index 00000000000..e0e6fdd32d1 --- /dev/null +++ b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/MapUpdater.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2011, 2015 Mia-Software, CEA LIST, Christian W. Damus, and others. + * 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: + * Nicolas Bros (Mia-Software) - Bug 366567 - [Releng] Tool to update rmaps + * Camille Letavernier (CEA LIST) - Generalize to support POMs + * Christian W. Damus - Support updating of multiple selected files + *******************************************************************************/ +package org.eclipse.papyrus.releng.tools.internal.popup.actions; + +import org.eclipse.core.resources.IFile; +import org.w3c.dom.Node; + +/** + * Updates a Buckminster rmap (XML file) from a B3 build model. The rmap is updated using + * comments in the XML that reference the model elements from which the update sites must be copied. + * <p> + * These comments must appear before each "rm:uri" element which must be updated automatically, like this: + * + * <pre> + * <!-- updateFrom("Eclipse", 0) --> + * <rm:uri format="http://download.eclipse.org/eclipse/updates/4.2milestones/S-4.2M3-201110281100"/> + * </pre> + * + * The first parameter in updateFrom is the label of a contribution, which you can find in the b3aggrcon files: + * + * <pre> + * <aggregator:Contribution ... label="xxx"> + * </pre> + * + * The second parameter is the index of the "repositories" element that must be used (in case there are several update sites defined on one contribution). + */ +public class MapUpdater extends XMLDependencyUpdater { + + public MapUpdater() { + super(); + } + + @Override + public boolean canUpdate(IFile file) { + return "rmap".equals(file.getFileExtension()); //$NON-NLS-1$ + } + + @Override + protected String getXpath() { + return "/rmap/searchPath/provider[@readerType='p2']/uri"; //$NON-NLS-1$ + } + + @Override + protected String getCurrentLocation(Node uri) { + return uri.getAttributes().getNamedItem("format").getTextContent(); //$NON-NLS-1$ + } + + @Override + protected void updateUri(Node uri, String location) { + if (location.startsWith(PREFIX)) { + location = "{0}/" + location.substring(PREFIX.length()); //$NON-NLS-1$ + } + uri.getAttributes().getNamedItem("format").setTextContent(location); //$NON-NLS-1$ + } + +} diff --git a/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/OomphSetupUpdater.java b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/OomphSetupUpdater.java new file mode 100644 index 00000000000..8794a4295f9 --- /dev/null +++ b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/OomphSetupUpdater.java @@ -0,0 +1,259 @@ +/***************************************************************************** + * Copyright (c) 2014, 2015 CEA LIST, Christian W. Damus, and others. + * + * 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: + * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation + * Christian W. Damus (CEA) - Add support for updating Oomph setup models + * Christian W. Damus - Add support for updating multiple development streams in a setup model + * Christian W. Damus - Support updating of multiple selected files + * + *****************************************************************************/ +package org.eclipse.papyrus.releng.tools.internal.popup.actions; + +import java.io.File; +import java.io.InputStream; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.eclipse.cbi.p2repo.aggregator.Contribution; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; +import org.eclipse.emf.ecore.xmi.XMLResource; +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.osgi.util.NLS; +import org.eclipse.papyrus.releng.tools.internal.Activator; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.dialogs.ListDialog; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + + +public class OomphSetupUpdater extends XMLDependencyUpdater { + + public static final String ANNOTATION_SOURCE = "http://www.eclipse.org/Papyrus/2014/releng/dependencytools";//$NON-NLS-1$ + + public static final String UPDATE_KEY = "updateFrom";//$NON-NLS-1$ + + private final Pattern annotationPattern = Pattern.compile("updateFrom:([^:]+):(\\d+)"); //$NON-NLS-1$ + + private final Pattern indexPattern = Pattern.compile(":\\d+$"); //$NON-NLS-1$ + + private String streamName; + + public OomphSetupUpdater() { + super(); + } + + @Override + public boolean canUpdate(IFile file) { + return "setup".equals(file.getFileExtension()); //$NON-NLS-1$ + } + + @Override + public void updateDocument(final Shell parentShell, final IFile mapFile, final EList<Contribution> contributions, final Map<Object, Object> context) throws CoreException { + streamName = promptForStreamName(parentShell, mapFile, context); + if (streamName == null) { + // Cancel + return; + } + + super.updateDocument(parentShell, mapFile, contributions, context); + } + + @Override + protected Pattern getCommentPattern() { + return annotationPattern; + } + + @Override + protected String getCommentContent(Node comment) { + StringBuilder result = new StringBuilder("updateFrom:"); //$NON-NLS-1$ + + Element annotation = (Element) comment; + NodeList details = annotation.getElementsByTagName("detail"); //$NON-NLS-1$ + for (int i = 0; i < details.getLength(); i++) { + Element next = (Element) details.item(i); + if (UPDATE_KEY.equals(next.getAttribute("key"))) { //$NON-NLS-1$ + String repoSpec = null; + if (next.hasAttribute("value")) { //$NON-NLS-1$ + repoSpec = next.getAttribute("value"); //$NON-NLS-1$ + } else { + NodeList values = next.getElementsByTagName("value"); //$NON-NLS-1$ + if (values.getLength() > 0) { + repoSpec = values.item(0).getTextContent().trim(); + } + } + if (repoSpec != null) { + result.append(repoSpec); + if (!indexPattern.matcher(repoSpec).find()) { + // default index + result.append(":0"); //$NON-NLS-1$ + break; + } + } + } + } + + return result.toString(); + } + + @Override + protected String getCommentSyntax() { + return String.format("Annotation with source %s and detail 'updateFrom=<contributionName>[:<index>]?'", ANNOTATION_SOURCE); //$NON-NLS-1$ + } + + @Override + protected Node getPrecedingComment(Node node) { + Element result = null; + + for (Node next = node.getFirstChild(); next != null; next = next.getNextSibling()) { + if (next.getNodeType() == Node.ELEMENT_NODE) { + if ("annotation".equals(next.getNodeName())) { //$NON-NLS-1$ + Element annotation = (Element) next; + if (ANNOTATION_SOURCE.equals(annotation.getAttribute("source"))) { //$NON-NLS-1$ + result = annotation; + break; + } + } + } + } + + return result; + } + + @Override + protected String getXpath() { + return String.format("//setupTask[@type='setup.targlets:TargletTask']/targlet/repositoryList[@name='%s']/repository", streamName); + } + + @Override + protected String getCurrentLocation(Node uri) { + return uri.getAttributes().getNamedItem("url").getTextContent(); //$NON-NLS-1$ + } + + @Override + protected void updateUri(Node uri, String location) { + uri.getAttributes().getNamedItem("url").setTextContent(location); //$NON-NLS-1$ + } + + @Override + protected void save(Document document, File destination) throws Exception { + // Use EMF resource serialization to format the file in the EMF style + ResourceSet rset = new ResourceSetImpl(); + Resource resource = rset.createResource(URI.createFileURI(destination.getAbsolutePath())); + Map<Object, Object> options = new HashMap<>(); + options.put(XMLResource.OPTION_RECORD_UNKNOWN_FEATURE, true); + options.put(XMLResource.OPTION_DEFER_IDREF_RESOLUTION, true); + ((XMLResource) resource).load(document, options); + + options.clear(); + options.put(XMLResource.OPTION_FORMATTED, true); + options.put(XMLResource.OPTION_PROCESS_DANGLING_HREF, XMLResource.OPTION_PROCESS_DANGLING_HREF_RECORD); + resource.save(options); + } + + protected String promptForStreamName(Shell parentShell, IFile setupFile, Map<Object, Object> context) throws CoreException { + final String key = "$setup.stream$"; //$NON-NLS-1$ + + String result = (String) context.get(key); + if (result != null) { + return result; + } + + final Set<String> repositoryLists = new LinkedHashSet<>(); + + try (InputStream input = setupFile.getContents()) { + SAXParser parser = SAXParserFactory.newInstance().newSAXParser(); + parser.parse(input, new DefaultHandler() { + int inTarglet; + String repositoryListName; + boolean foundAnnotation; + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + if ("targlet".equals(qName)) { //$NON-NLS-1$ + if ((inTarglet > 0) || "${eclipse.target.platform}".equals(attributes.getValue("activeRepositoryList"))) { //$NON-NLS-1$ //$NON-NLS-2$ + // This is a stream-switching targlet. Get its repository names + inTarglet++; + } + } else if ((inTarglet > 0) && "repositoryList".equals(qName)) { + String listName = attributes.getValue("name"); //$NON-NLS-1$ + if (listName != null && !listName.isEmpty()) { + repositoryListName = listName; + foundAnnotation = false; + } + } else if ("annotation".equals(qName) && OomphSetupUpdater.ANNOTATION_SOURCE.equals(attributes.getValue("source"))) { //$NON-NLS-1$ //$NON-NLS-2$ + foundAnnotation = true; + } + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + if ("targlet".equals(qName)) { //$NON-NLS-1$ + inTarglet = Math.max(inTarglet - 1, 0); + } else if ("repositoryList".equals(qName) && (repositoryListName != null)) { //$NON-NLS-1$ + if (foundAnnotation) { + repositoryLists.add(repositoryListName); + } + repositoryListName = null; + foundAnnotation = false; + } + } + }); + } catch (Exception e) { + throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Failed to scan setup model for available streams.", e)); + } + + if (repositoryLists.isEmpty()) { + throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "No streams are defined in the selected setup model.")); + } + + String first = repositoryLists.iterator().next(); + if (repositoryLists.size() == 1) { + return first; + } + + ILabelProvider labels = new LabelProvider(); + ListDialog dlg = new ListDialog(parentShell); + dlg.setContentProvider(ArrayContentProvider.getInstance()); + dlg.setLabelProvider(labels); + dlg.setInput(repositoryLists); + dlg.setInitialSelections(new Object[] { repositoryLists.iterator().next() }); + dlg.setTitle("Select Stream"); + dlg.setMessage(NLS.bind("Select the development stream to update in \"{0}\".", setupFile.getFullPath())); + labels.dispose(); + + dlg.open(); + Object[] dlgResult = dlg.getResult(); + result = ((dlgResult == null) || (dlgResult.length == 0)) ? null : (String) dlgResult[0]; + if (result != null) { + context.put(key, result); + } + return result; + } +} diff --git a/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/PomPropertiesUpdater.java b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/PomPropertiesUpdater.java new file mode 100644 index 00000000000..7047619026f --- /dev/null +++ b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/PomPropertiesUpdater.java @@ -0,0 +1,48 @@ +/***************************************************************************** + * Copyright (c) 2014, 2015 CEA LIST, Christian W. Damus, and others. + * + * 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: + * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation + * Christian W. Damus - Support updating of multiple selected files + *****************************************************************************/ +package org.eclipse.papyrus.releng.tools.internal.popup.actions; + +import org.eclipse.core.resources.IFile; +import org.w3c.dom.Node; + + +public class PomPropertiesUpdater extends XMLDependencyUpdater { + + public PomPropertiesUpdater() { + super(); + } + + @Override + public boolean canUpdate(IFile file) { + return "xml".equals(file.getFileExtension()) && file.getName().contains("pom"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + @Override + protected String getXpath() { + return "/project/properties/*[substring(name(), string-length(name()) - 8) = '.repo.url']"; //$NON-NLS-1$ + } + + @Override + protected String getCurrentLocation(Node uri) { + return uri.getTextContent(); + } + + @Override + protected void updateUri(Node uri, String location) { + if (location.startsWith("http://download.eclipse.org")) { + location = location.replace("http://download.eclipse.org", "${eclipse.download}"); + } + uri.setTextContent(location); + } + +} diff --git a/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/PomUpdater.java b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/PomUpdater.java new file mode 100644 index 00000000000..4bb3a38dc34 --- /dev/null +++ b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/PomUpdater.java @@ -0,0 +1,48 @@ +/***************************************************************************** + * Copyright (c) 2014, 2015 CEA LIST, Christian W. Damus, and others. + * + * 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: + * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation + * Christian W. Damus - Support updating of multiple selected files + *****************************************************************************/ +package org.eclipse.papyrus.releng.tools.internal.popup.actions; + +import org.eclipse.core.resources.IFile; +import org.w3c.dom.Node; + + +public class PomUpdater extends XMLDependencyUpdater { + + public PomUpdater() { + super(); + } + + @Override + public boolean canUpdate(IFile file) { + return "xml".equals(file.getFileExtension()) && file.getName().contains("pom"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + @Override + protected String getXpath() { + return "/project/repositories/repository/url"; //$NON-NLS-1$ + } + + @Override + protected String getCurrentLocation(Node uri) { + return uri.getTextContent(); + } + + @Override + protected void updateUri(Node uri, String location) { + if (location.startsWith("http://download.eclipse.org")) { + location = location.replace("http://download.eclipse.org", "${eclipse.download}"); + } + uri.setTextContent(location); + } + +} diff --git a/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/TPDUpdater.java b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/TPDUpdater.java new file mode 100644 index 00000000000..496c62ebd5f --- /dev/null +++ b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/TPDUpdater.java @@ -0,0 +1,142 @@ +/***************************************************************************** + * Copyright (c) 2016 CEA LIST and others. + * + * 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: + * CEA LIST - Initial API and implementation + * + *****************************************************************************/ +package org.eclipse.papyrus.releng.tools.internal.popup.actions; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; +import org.eclipse.xtext.TerminalRule; +import org.eclipse.xtext.nodemodel.ICompositeNode; +import org.eclipse.xtext.nodemodel.ILeafNode; +import org.eclipse.xtext.nodemodel.INode; +import org.eclipse.xtext.nodemodel.util.NodeModelUtils; + +import fr.obeo.releng.targetplatform.Location; +import fr.obeo.releng.targetplatform.TargetPlatform; + +/** + * @author Camille Letavernier + * + */ +public class TPDUpdater extends DependencyUpdater<Location> { + + private Resource currentTarget; + + /** + * @see org.eclipse.papyrus.releng.tools.internal.popup.actions.DependencyUpdater#canUpdate(org.eclipse.core.resources.IFile) + * + * @param file + * @return + */ + @Override + public boolean canUpdate(IFile file) { + return "tpd".equals(file.getFileExtension()); + } + + @Override + protected List<Location> getNodesToUpdate(IFile file) throws CoreException { + ResourceSet resourceSet = new ResourceSetImpl(); + + URI workspaceURI = URI.createPlatformResourceURI(file.getFullPath().toString(), true); + + currentTarget = resourceSet.getResource(workspaceURI, true); + + for (EObject rootElement : currentTarget.getContents()) { + if (rootElement instanceof TargetPlatform) { + TargetPlatform tp = (TargetPlatform) rootElement; + return tp.getLocations(); + } + } + + return Collections.emptyList(); + } + + @Override + protected void save(IFile file) throws Exception { + currentTarget.save(null); + } + + @Override + protected String getCurrentLocation(Location uri) { + return uri.getUri(); + } + + @Override + protected void updateUri(Location uri, String location) { + uri.setUri(location); + } + + @Override + protected String getComment(Location location) { + List<String> comments = findCommentsAsString(location); + + for (String comment : comments) { + if (comment.contains("updateFrom")) { + return comment; + } + } + + return null; + } + + /** + * Expected structure: the Location contains a Multiline or Single line comment before the location keyword + * + * <pre> + * // A Comment + * /* Another Comment / + * location locID "http://locURL/repo" { + * installable.unit1.id + * installable.unit2.id + * } + * </pre> + * + * @param location + * @return + */ + protected List<String> findCommentsAsString(Location location) { + List<String> comments = new ArrayList<>(); + + INode grammarNode = NodeModelUtils.getNode(location); + if (grammarNode instanceof ICompositeNode) { + ICompositeNode compositeNode = (ICompositeNode) grammarNode; + for (INode child : compositeNode.getChildren()) { + if (child instanceof ILeafNode) { + ILeafNode leafNode = (ILeafNode) child; + if (leafNode.isHidden()) { + if (child.getGrammarElement() instanceof TerminalRule) { + TerminalRule rule = (TerminalRule) child.getGrammarElement(); + String name = rule.getName(); + if ("SL_COMMENT".equals(name) || "ML_COMMENT".equals(name)) { //$NON-NLS-1$ //$NON-NLS-2$ + String text = leafNode.getText(); + text = text.replaceAll("[\\*/]", "").trim(); // Remove all / and */, as the leafNode is the raw element + comments.add(text); + } + } + } + } + } + } + + return comments; + } + +} diff --git a/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/UpdateDependencies.java b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/UpdateDependencies.java new file mode 100644 index 00000000000..9523c6f75eb --- /dev/null +++ b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/UpdateDependencies.java @@ -0,0 +1,243 @@ +/******************************************************************************* + * Copyright (c) 2015-2016 Christian W. Damus and others. + * 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: + * Nicolas Bros (Mia-Software) - Bug 366567 - [Releng] Tool to update rmaps + * Camille Letavernier (CEA LIST) - camille.letavernier@cea.fr - Generalize to handle POMs + * Christian W. Damus (CEA) - Add support for updating Oomph setup models + * Christian W. Damus - Support updating of multiple selected files + * Camille Letavernier (CEA LIST) - Move the behavior from a Handler to a dedicated class + * + *******************************************************************************/ +package org.eclipse.papyrus.releng.tools.internal.popup.actions; + +import java.lang.reflect.Modifier; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.eclipse.cbi.p2repo.aggregator.Aggregation; +import org.eclipse.cbi.p2repo.aggregator.AggregatorPackage; +import org.eclipse.cbi.p2repo.aggregator.transformer.TransformationManager; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Status; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.osgi.util.NLS; +import org.eclipse.papyrus.releng.tools.internal.Activator; +import org.eclipse.papyrus.releng.tools.internal.Messages; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.dialogs.ElementListSelectionDialog; +import org.osgi.framework.Bundle; + +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +/** + * @author Camille Letavernier + * + */ +public class UpdateDependencies { + public void updateDependencies(List<IFile> filesToUpdate, Shell activeShell) { + IFile aggregationBuildFile = null; + boolean cancelled = false; + + List<IFile> updated = Lists.newArrayListWithExpectedSize(4); + + try { + if (!filesToUpdate.isEmpty()) { + List<IFile> aggregationBuildFiles = findAggregationBuildFiles(); + aggregationBuildFile = chooseAggregationBuildFile(aggregationBuildFiles, activeShell); + if (aggregationBuildFile == null) { + cancelled = true; + } else { + Aggregation aggregation = loadAggregationModel(aggregationBuildFile); + if (aggregation != null) { + Map<Object, Object> context = Maps.newHashMap(); + for (IFile file : filesToUpdate) { + if (updateFile(file, aggregation, activeShell, context)) { + updated.add(file); + } + } + } + } + } + + } catch (OperationCanceledException e) { + cancelled = true; + } catch (Exception e) { + Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Error", e)); //$NON-NLS-1$ + MessageDialog.openError(activeShell, Messages.UpdateRMapAction_error, e.getLocalizedMessage()); + } + + if (updated.isEmpty()) { + // Don't waste the user's attention on this if he cancelled + if (!cancelled) { + MessageDialog.openInformation(activeShell, "No Files Updated", "No files were updated for new dependencies."); + } + } else { + String fileList = Joiner.on(", ").join(Iterables.transform(updated, new Function<IFile, IPath>() { + @Override + public IPath apply(IFile input) { + return input.getFullPath(); + } + })); + MessageDialog.openInformation(activeShell, Messages.UpdateRMapAction_mapWasUpdatedTitle, NLS.bind(Messages.UpdateRMapAction_mapWasUpdated, fileList, aggregationBuildFile.getFullPath().toString())); + } + } + + public static List<IFile> findAggregationBuildFiles() throws CoreException { + List<IFile> aggregationBuildFiles = new ArrayList<>(); + IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects(); + for (IProject project : projects) { + if (!project.isOpen()) { + continue; + } + IResource[] members = project.members(); + for (IResource resource : members) { + if (resource.getType() == IResource.FILE && resource.getName().endsWith(".aggr")) { //$NON-NLS-1$ + aggregationBuildFiles.add((IFile) resource); + } + } + } + return aggregationBuildFiles; + } + + public static IFile chooseAggregationBuildFile(final List<IFile> aggregationBuildFiles, Shell activeShell) { + if (aggregationBuildFiles.size() == 0) { + MessageDialog.openWarning(activeShell, Messages.UpdateRMapAction_noBuildModelFound, Messages.UpdateRMapAction_noBuildModelFoundLong); + return null; + } + LabelProvider labelProvider = new LabelProvider() { + + @Override + public String getText(final Object element) { + if (element instanceof IFile) { + IFile file = (IFile) element; + return file.getProject().getName() + "/" + file.getName(); //$NON-NLS-1$ + } + return super.getText(element); + } + }; + + ElementListSelectionDialog dialog = new ElementListSelectionDialog(activeShell, labelProvider); + dialog.setTitle(Messages.UpdateRMapAction_chooseBuildModel); + dialog.setMessage(Messages.UpdateRMapAction_chooseBuildModelLong); + dialog.setElements(aggregationBuildFiles.toArray()); + dialog.open(); + return (IFile) dialog.getFirstResult(); + } + + protected static Aggregation loadAggregationModel(IFile aggregationBuildFile) throws CoreException { + Aggregation result = null; + + // make sure the EPackage is initialized + AggregatorPackage.eINSTANCE.getEFactoryInstance(); + URI uri = URI.createPlatformResourceURI(aggregationBuildFile.getFullPath().toString(), true); + + final ResourceSet resourceSet = new ResourceSetImpl(); + Resource resource = null; + try { + // with the latest version of the metamodel + resource = resourceSet.getResource(uri, true); + resource.load(null); + } catch (Exception e) { + // with an older version of the metamodel + try { + TransformationManager transformationManager = new TransformationManager(uri); + resource = transformationManager.transformResource(true); + } catch (Exception e1) { + throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Error loading b3aggr. Make sure you have the latest version of B3 installed. : " + e.getLocalizedMessage(), e1)); //$NON-NLS-1$ + } + } + + if (resource.getContents().size() == 0) { + throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "The b3aggr resource is empty")); //$NON-NLS-1$ + } + + EObject root = resource.getContents().get(0); + if (root instanceof Aggregation) { + result = (Aggregation) root; + } else { + throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "The b3aggr resource does not contain an aggregation model")); //$NON-NLS-1$ + } + + return result; + } + + protected boolean updateFile(IFile selectedFile, Aggregation aggregationModel, Shell activeShell, Map<Object, Object> context) throws CoreException { + boolean result = false; + + List<DependencyUpdater<?>> updaters = findDependencyUpdater(selectedFile); + for (DependencyUpdater<?> updater : updaters) { + updater.updateDocument(activeShell, selectedFile, aggregationModel.getAllContributions(true), context); + result = true; + } + + return result; + } + + protected List<DependencyUpdater<?>> findDependencyUpdater(IFile mapFile) throws CoreException { + final String path = "org/eclipse/papyrus/releng/tools/internal/popup/actions/"; //$NON-NLS-1$ + + Bundle bundle = Activator.getDefault().getBundle(); + + // Try dev mode, first + Enumeration<URL> urls = bundle.findEntries("bin/" + path, "*.class", false); + if (urls == null) { + // Deployed mode + urls = bundle.findEntries(path, "*.class", false); + } + + List<DependencyUpdater<?>> updaters = new LinkedList<>(); + + while (urls.hasMoreElements()) { + URL classURL = urls.nextElement(); + URI classURI = URI.createURI(classURL.toExternalForm(), true); + + try { + String className = classURI.trimFileExtension().lastSegment(); + + if (!"DependencyUpdater".equals(className) && !"XMLDependencyUpdater".equals(className) && className.endsWith("Updater")) { + Class<? extends DependencyUpdater> updaterClass = bundle.loadClass(path.replace('/', '.') + className).asSubclass(DependencyUpdater.class); + if (!Modifier.isAbstract(updaterClass.getModifiers())) { + DependencyUpdater<?> updater = updaterClass.newInstance(); + if (updater.canUpdate(mapFile)) { + updaters.add(updater); + } + } + } + } catch (ClassNotFoundException e) { + throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "No such class: " + classURI.lastSegment(), e)); + } catch (IllegalAccessException | InstantiationException e) { + throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Failed to instantiated " + classURI.lastSegment(), e)); + } catch (Throwable t) { // Classes with missing optional dependencies. Simple Warning + Activator.getDefault().getLog().log(new Status(IStatus.WARNING, Activator.PLUGIN_ID, "Failed to instantiate " + classURI.lastSegment(), t)); + } + } + + return updaters; + } +} diff --git a/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/XMLDependencyUpdater.java b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/XMLDependencyUpdater.java new file mode 100644 index 00000000000..873299151d2 --- /dev/null +++ b/plugins/toolsmiths/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/XMLDependencyUpdater.java @@ -0,0 +1,133 @@ +/***************************************************************************** + * Copyright (c) 2016 CEA LIST and others. + * + * 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: + * CEA LIST - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.releng.tools.internal.popup.actions; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathFactory; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Status; +import org.eclipse.papyrus.releng.tools.internal.Activator; +import org.w3c.dom.Comment; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * Specialization of the DependencyAdapter for XML Documents + * + * @author Camille Letavernier + * + */ +public abstract class XMLDependencyUpdater extends DependencyUpdater<Node> { + + private Document currentDocument; + + protected abstract String getXpath(); + + @Override + protected List<Node> getNodesToUpdate(IFile file) throws CoreException { + File rmapFile = file.getLocation().toFile(); + + try { + DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); + currentDocument = docBuilder.parse(rmapFile); + currentDocument.normalize(); + Element documentElement = currentDocument.getDocumentElement(); + + XPath xpath = XPathFactory.newInstance().newXPath(); + NodeList uris = (NodeList) xpath.evaluate(getXpath(), documentElement, XPathConstants.NODESET); + + List<Node> result = new ArrayList<>(uris.getLength()); + for (int i = 0; i < uris.getLength(); i++) { + result.add(uris.item(i)); + } + + return result; + } catch (OperationCanceledException ex) { + throw ex; + } catch (Exception ex) { + throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Error updating map: " + ex.getLocalizedMessage(), ex)); //$NON-NLS-1$ + } + + + } + + @Override + protected String getComment(final Node node) { + Node comment = getPrecedingComment(node); + + return comment == null ? null : getCommentContent(comment); + } + + protected Node getPrecedingComment(Node node) { + Comment comment = null; + + Node previous = node.getPreviousSibling(); + while (previous != null) { + if (previous.getNodeType() == Node.COMMENT_NODE) { + comment = (Comment) previous; + break; + } else if (previous.getNodeType() != Node.TEXT_NODE) { + break; + } + previous = previous.getPreviousSibling(); + } + + return comment; + } + + protected String getCommentContent(Node comment) { + return comment.getTextContent(); + } + + /** + * @see org.eclipse.papyrus.releng.tools.internal.popup.actions.DependencyUpdater#save(org.eclipse.core.resources.IFile) + * + * @param file + */ + @Override + protected void save(IFile file) throws Exception { + File destination = file.getLocation().toFile(); + + save(currentDocument, destination); + } + + protected void save(Document document, File destination) throws Exception { + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$ + + StreamResult result = new StreamResult(destination); + DOMSource source = new DOMSource(currentDocument); + transformer.transform(source, result); + } + +} |