| /******************************************************************************* |
| * Copyright (c) 2016 Obeo. |
| * 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: |
| * Obeo - initial API and implementation and/or initial documentation |
| * ... |
| *******************************************************************************/ |
| package org.eclipse.intent.mapping.ide.ui.view; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| |
| import org.eclipse.intent.mapping.base.ILink; |
| import org.eclipse.intent.mapping.base.ILocation; |
| import org.eclipse.intent.mapping.base.ILocationListener.Stub; |
| |
| /** |
| * Provide {@link ILink linked} {@link ILocation} for a given {@link ILocation}. |
| * |
| * @author <a href="mailto:yvan.lussaud@obeo.fr">Yvan Lussaud</a> |
| */ |
| public class LinkedLocationContentProvider extends AbstractLocationContentProvider { |
| |
| /** |
| * Recursively listen to {@link ILocation#getContents() content} changes. |
| * |
| * @author <a href="mailto:yvan.lussaud@obeo.fr">Yvan Lussaud</a> |
| */ |
| private final class ContentLocationListener extends Stub { |
| |
| /** |
| * Link listener for content listener. It basically attach to an {@link ILink} when its |
| * {@link ILink#getSource() source} or {@link ILink#getTarget() target} is not setted yet. |
| * |
| * @author <a href="mailto:yvan.lussaud@obeo.fr">Yvan Lussaud</a> |
| */ |
| private final class LinkListener extends org.eclipse.intent.mapping.base.ILinkListener.Stub { |
| |
| /** |
| * The link we are attached to. |
| */ |
| private final ILink link; |
| |
| /** |
| * Constructor. |
| * |
| * @param link |
| * the {@link ILink} to attach to. |
| */ |
| LinkListener(ILink link) { |
| this.link = link; |
| } |
| |
| @Override |
| public void sourceChanged(ILocation oldSource, ILocation newSource) { |
| update(); |
| currentViewer.refresh(); |
| link.removeListener(this); |
| } |
| |
| @Override |
| public void targetChanged(ILocation oldTarget, ILocation newTarget) { |
| update(); |
| currentViewer.refresh(); |
| link.removeListener(this); |
| } |
| } |
| |
| @Override |
| public void contentsAdded(ILocation location) { |
| update(); |
| currentViewer.refresh(); |
| addListener(location); |
| } |
| |
| @Override |
| public void contentsRemoved(ILocation location) { |
| removeListener(location); |
| } |
| |
| @Override |
| public void sourceLinkAdded(ILink link) { |
| if (direction == SOURCE) { |
| if (link.getSource() == null) { |
| link.addListener(new LinkListener(link)); |
| } else { |
| update(); |
| currentViewer.refresh(); |
| } |
| } |
| } |
| |
| @Override |
| public void targetLinkAdded(ILink link) { |
| if (direction == TARGET) { |
| if (link.getTarget() == null) { |
| link.addListener(new LinkListener(link)); |
| } else { |
| update(); |
| currentViewer.refresh(); |
| } |
| } |
| } |
| |
| /** |
| * Recursively add itself to the content of the given {@link ILocation}. |
| * |
| * @param location |
| * the {@link ILocation} to listen |
| */ |
| private void addListener(ILocation location) { |
| if (!listeners.containsKey(location)) { |
| location.addListener(this); |
| listeners.put(location, this); |
| for (ILocation child : location.getContents()) { |
| addListener(child); |
| } |
| } |
| } |
| |
| /** |
| * Recursively remove itself from the content of the given {@link ILocation}. |
| * |
| * @param location |
| * the {@link ILocation} to listen |
| */ |
| private void removeListener(ILocation location) { |
| location.removeListener(this); |
| listeners.remove(location); |
| for (ILocation child : location.getContents()) { |
| removeListener(child); |
| } |
| } |
| } |
| |
| /** |
| * {@link ILocation#getSourceLinks() source} direction. |
| */ |
| public static final int SOURCE = 0; |
| |
| /** |
| * {@link ILocation#getTargetLinks() target} direction. |
| */ |
| public static final int TARGET = 1; |
| |
| /** |
| * The direction either {@link #SOURCE source} or {@link #TARGET target}. |
| */ |
| private final int direction; |
| |
| /** |
| * Tells if the input location should be walked recursively for {@link ILink}. |
| */ |
| private final boolean recursive; |
| |
| /** |
| * Tells if we should provide {@link ILink}. |
| */ |
| private final boolean provideLinks; |
| |
| /** |
| * Mapping from an {@link ILocation} and its {@link ContentLocationListener}. |
| */ |
| private final Map<ILocation, ContentLocationListener> listeners = new HashMap<ILocation, ContentLocationListener>(); |
| |
| /** |
| * Constructor. |
| * |
| * @param provideLinks |
| * tells if we should provide {@link ILink} |
| * @param direction |
| * the direction either {@link #SOURCE source} or {@link #TARGET target} |
| * @param recursive |
| * tells if the input location should be walked recursively for {@link ILink} |
| */ |
| public LinkedLocationContentProvider(boolean provideLinks, int direction, boolean recursive) { |
| this.provideLinks = provideLinks; |
| this.direction = direction; |
| this.recursive = recursive; |
| } |
| |
| @Override |
| protected void setLeavesAndLinks(Object input, List<ILocation> locationLeaves, List<ILink> links) { |
| if (input instanceof ILocation) { |
| final ILocation location = (ILocation)input; |
| if (direction == SOURCE) { |
| for (ILink link : location.getSourceLinks()) { |
| links.add(link); |
| locationLeaves.add(link.getSource()); |
| } |
| } else if (direction == TARGET) { |
| for (ILink link : location.getTargetLinks()) { |
| links.add(link); |
| locationLeaves.add(link.getTarget()); |
| } |
| } |
| if (recursive) { |
| final ContentLocationListener listener = new ContentLocationListener(); |
| location.addListener(listener); |
| listeners.put((ILocation)input, listener); |
| for (ILocation child : location.getContents()) { |
| setLeavesAndLinks(child, locationLeaves, links); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public boolean hasChildren(Object element) { |
| final boolean res; |
| |
| if (provideLinks) { |
| if (element instanceof ILocation) { |
| res = !getchildLinks((ILocation)element).isEmpty() || super.hasChildren(element); |
| } else { |
| res = false; |
| } |
| } else { |
| res = super.hasChildren(element); |
| } |
| |
| return res; |
| } |
| |
| @Override |
| public Object[] getChildren(Object parentElement) { |
| final Object[] res; |
| |
| final Object[] childLocations = super.getChildren(parentElement); |
| if (provideLinks && parentElement instanceof ILocation) { |
| final List<ILink> childlinks = getchildLinks((ILocation)parentElement); |
| res = concat(childLocations, childlinks.toArray()); |
| } else { |
| res = childLocations; |
| } |
| |
| return res; |
| } |
| |
| /** |
| * Gets child {@link ILink} for the given parent {@link ILocation}. |
| * |
| * @param parentlocation |
| * the parent {@link ILocation} |
| * @return child {@link ILink} for the given parent {@link ILocation} |
| */ |
| private List<ILink> getchildLinks(ILocation parentlocation) { |
| final List<ILink> links = new ArrayList<ILink>(); |
| final List<ILink> linksToCheck; |
| if (direction == SOURCE) { |
| linksToCheck = parentlocation.getTargetLinks(); |
| } else if (direction == TARGET) { |
| linksToCheck = parentlocation.getSourceLinks(); |
| } else { |
| throw new IllegalStateException("should not happend"); |
| } |
| for (ILink link : linksToCheck) { |
| if (listenedLinks.contains(link)) { |
| links.add(link); |
| } |
| } |
| return links; |
| } |
| |
| /** |
| * Concatenates two given arrays of {@link Object}. |
| * |
| * @param first |
| * the first array |
| * @param second |
| * the second array |
| * @return a new array composed of the two given arrays |
| */ |
| private Object[] concat(Object[] first, Object[] second) { |
| final Object[] result = Arrays.copyOf(first, first.length + second.length); |
| |
| System.arraycopy(second, 0, result, first.length, second.length); |
| |
| return result; |
| } |
| |
| @Override |
| public Object getParent(Object element) { |
| final Object res; |
| |
| if (provideLinks && element instanceof ILink) { |
| if (direction == SOURCE) { |
| res = ((ILink)element).getTarget(); |
| } else if (direction == TARGET) { |
| res = ((ILink)element).getSource(); |
| } else { |
| throw new IllegalStateException("should not happend"); |
| } |
| } else { |
| res = super.getParent(element); |
| } |
| |
| return res; |
| } |
| |
| @Override |
| protected void update() { |
| for (Entry<ILocation, ContentLocationListener> entry : listeners.entrySet()) { |
| entry.getKey().removeListener(entry.getValue()); |
| } |
| listeners.clear(); |
| super.update(); |
| } |
| |
| } |