/** * Copyright (c) 2017 committers of YAKINDU 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: * committers of YAKINDU - initial API and implementation * */ package org.yakindu.sct.ui.editor.validation; import java.util.Arrays; import java.util.List; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.workspace.util.WorkspaceSynchronizer; import org.eclipse.xtext.validation.CheckType; import org.eclipse.xtext.validation.Issue; import org.yakindu.sct.model.sgraph.ui.validation.ISctIssueCreator; import org.yakindu.sct.model.sgraph.ui.validation.SCTIssue; import org.yakindu.sct.model.sgraph.ui.validation.SCTMarkerType; import org.yakindu.sct.ui.editor.validation.IValidationIssueStore; import org.yakindu.sct.ui.editor.validation.IResourceChangeToIssueProcessor.ResourceDeltaToIssueResult; import com.google.common.base.Predicate; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import com.google.common.collect.Sets.SetView; import com.google.inject.Inject; /** * Maintains the list of current visible issues based on persistent markers and * live validation results. * * @author Johannes Dicks - Initial contribution and API */ public class DefaultValidationIssueStore implements IValidationIssueStore, IResourceChangeListener { @Inject private ISctIssueCreator issueCreator; @Inject private IResourceChangeToIssueProcessor resourceChangeToIssues; protected final List<IValidationIssueStoreListener> listener; protected Multimap<String, SCTIssue> visibleIssues; protected boolean connected = false; protected Resource connectedResource; public DefaultValidationIssueStore() { listener = Lists.newArrayList(); visibleIssues = ArrayListMultimap.create(); } @Override public void addIssueStoreListener(IValidationIssueStoreListener newListener) { synchronized (this.listener) { this.listener.add(newListener); } } @Override public void removeIssueStoreListener(IValidationIssueStoreListener oldListener) { synchronized (listener) { listener.remove(oldListener); } } protected void notifyListeners() { synchronized (listener) { for (IValidationIssueStoreListener iResourceIssueStoreListener : listener) { iResourceIssueStoreListener.issuesChanged(); } } } protected void notifyListeners(String semanticURI) { synchronized (listener) { for (IValidationIssueStoreListener iResourceIssueStoreListener : listener) { if (semanticURI.equals(iResourceIssueStoreListener.getSemanticURI())) { iResourceIssueStoreListener.issuesChanged(); } } } } @Override public void connect(Resource resource) { if (connected) { throw new IllegalStateException("Issue store is already connected to a resource"); } connectedResource = resource; IFile file = WorkspaceSynchronizer.getFile(resource); if ((file != null) && file.isAccessible()) { ResourcesPlugin.getWorkspace().addResourceChangeListener(this); connected = true; } initFromPersistentMarkers(); } protected synchronized void initFromPersistentMarkers() { Multimap<String, SCTIssue> newVisibleIssues = ArrayListMultimap.create(); List<IMarker> markers = getMarkersOfConnectedResource(); for (IMarker iMarker : markers) { SCTIssue issue = issueCreator.createFromMarker(iMarker, iMarker.getAttribute(SCTMarkerType.SEMANTIC_ELEMENT_ID, "")); newVisibleIssues.put(issue.getSemanticURI(), issue); } synchronized (visibleIssues) { visibleIssues.clear(); visibleIssues.putAll(newVisibleIssues); } notifyListeners(); } protected List<IMarker> getMarkersOfConnectedResource() { List<IMarker> markers = Lists.newArrayList(); try { IFile file = WorkspaceSynchronizer.getFile(connectedResource); if ((file != null) && file.isAccessible()) { markers.addAll( Arrays.asList(file.findMarkers(SCTMarkerType.SUPERTYPE, true, IResource.DEPTH_INFINITE))); } } catch (CoreException e) { e.printStackTrace(); } return markers; } @Override public void disconnect(Resource resource) { IFile file = WorkspaceSynchronizer.getFile(resource); if ((file != null) && file.isAccessible()) { ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); connected = false; connectedResource = null; synchronized (listener) { listener.clear(); } } } @Override public synchronized void processIssues(List<Issue> issues, IProgressMonitor monitor) { final Multimap<String, SCTIssue> newVisibleIssues = ArrayListMultimap.create(); for (Issue issue : issues) { if (issue instanceof SCTIssue) { String semanticURI = ((SCTIssue) issue).getSemanticURI(); newVisibleIssues.put(semanticURI, (SCTIssue) issue); } } final Multimap<String, SCTIssue> oldVisibleIssues = ArrayListMultimap.create(); synchronized (visibleIssues) { oldVisibleIssues.putAll(visibleIssues); // normal and expensive checks will not be executed by the live // validation, so persistent markers have to be copied Iterable<SCTIssue> persistentIssues = Iterables.filter(visibleIssues.values(), new Predicate<SCTIssue>() { public boolean apply(SCTIssue input) { return input.getType() == CheckType.NORMAL || input.getType() == CheckType.EXPENSIVE; } }); for (SCTIssue sctIssue : persistentIssues) { newVisibleIssues.put(sctIssue.getSemanticURI(), sctIssue); } visibleIssues.clear(); visibleIssues.putAll(newVisibleIssues); } SetView<String> changes = Sets.symmetricDifference(oldVisibleIssues.keySet(), newVisibleIssues.keySet()); for (String semanticElementID : changes) { notifyListeners(semanticElementID); } } @Override public synchronized List<SCTIssue> getIssues(String uri) { List<SCTIssue> result = Lists.newArrayList(); synchronized (visibleIssues) { Iterables.addAll(result, visibleIssues.get(uri)); } return result; } @Override public void resourceChanged(IResourceChangeEvent event) { if ((IResourceChangeEvent.POST_CHANGE != event.getType())) { return; } ResourceDeltaToIssueResult markerChangeResult = null; synchronized (visibleIssues) { markerChangeResult = resourceChangeToIssues.process(event, connectedResource, visibleIssues); if (markerChangeResult != null) visibleIssues = markerChangeResult.getIssues(); } if (markerChangeResult != null) for (String elementID : markerChangeResult.getChangedElementIDs()) { notifyListeners(elementID); } } }