blob: c8cf7d12c0cf8ebd49b5f8f3471094a1cfda7954 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011-2018 EclipseSource Muenchen GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Lucas Koehler - initial API and implementation
******************************************************************************/
package org.eclipse.emfforms.internal.core.services.structuralchange;
import java.util.LinkedHashSet;
import java.util.Set;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature.Setting;
import org.eclipse.emf.ecp.view.spi.model.ModelChangeNotification;
import org.eclipse.emf.ecp.view.spi.model.VDomainModelReference;
import org.eclipse.emf.ecp.view.spi.model.VDomainModelReferenceSegment;
import org.eclipse.emfforms.common.RankingHelper;
import org.eclipse.emfforms.spi.common.report.AbstractReport;
import org.eclipse.emfforms.spi.common.report.ReportService;
import org.eclipse.emfforms.spi.core.services.databinding.DatabindingFailedException;
import org.eclipse.emfforms.spi.core.services.databinding.emf.EMFFormsSegmentResolver;
import org.eclipse.emfforms.spi.core.services.structuralchange.EMFFormsStructuralChangeTester;
import org.eclipse.emfforms.spi.core.services.structuralchange.StructuralChangeSegmentTester;
import org.eclipse.emfforms.spi.core.services.structuralchange.StructuralChangeTesterInternal;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
/**
* Implementation of {@link EMFFormsStructuralChangeTester}.
*
* @author Lucas Koehler
*
*/
@Component(name = "EMFFormsStructuralChangeTesterImpl")
public class EMFFormsStructuralChangeTesterImpl implements EMFFormsStructuralChangeTester {
private ReportService reportService;
private final Set<StructuralChangeTesterInternal> dmrChangeTesters = new LinkedHashSet<StructuralChangeTesterInternal>();
private final Set<StructuralChangeSegmentTester> segmentChangeTesters = new LinkedHashSet<>();
private EMFFormsSegmentResolver segmentResolver;
private static final RankingHelper<StructuralChangeTesterInternal> DMR_RANKING_HELPER = //
new RankingHelper<StructuralChangeTesterInternal>(
StructuralChangeTesterInternal.class,
StructuralChangeTesterInternal.NOT_APPLICABLE,
StructuralChangeTesterInternal.NOT_APPLICABLE);
private static final RankingHelper<StructuralChangeSegmentTester> SEGMENTS_RANKING_HELPER = //
new RankingHelper<>(
StructuralChangeSegmentTester.class, StructuralChangeSegmentTester.NOT_APPLICABLE,
StructuralChangeSegmentTester.NOT_APPLICABLE);
/**
* Sets the {@link ReportService}.
*
* @param reportService The {@link ReportService}
*/
@Reference(unbind = "-")
protected void setReportService(ReportService reportService) {
this.reportService = reportService;
}
/**
* Called by the framework to add a {@link StructuralChangeTesterInternal}.
*
* @param structuralChangeTester The {@link StructuralChangeTesterInternal} to add
*/
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
protected void addStructuralChangeTesterInternal(StructuralChangeTesterInternal structuralChangeTester) {
dmrChangeTesters.add(structuralChangeTester);
}
/**
* Called by the framework to remove a {@link StructuralChangeTesterInternal}.
*
* @param structuralChangeTester The {@link StructuralChangeTesterInternal} to remove
*/
protected void removeStructuralChangeTesterInternal(StructuralChangeTesterInternal structuralChangeTester) {
dmrChangeTesters.remove(structuralChangeTester);
}
/**
* Called by the framework to add a {@link StructuralChangeSegmentTester}.
*
* @param segmentChangeTester The {@link StructuralChangeSegmentTester} to add
*/
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
protected void addStructuralChangeSegmentTester(StructuralChangeSegmentTester segmentChangeTester) {
segmentChangeTesters.add(segmentChangeTester);
}
/**
* Called by the framework to remove a {@link StructuralChangeSegmentTester}.
*
* @param segmentChangeTester The {@link StructuralChangeSegmentTester} to remove
*/
protected void removeStructuralChangeSegmentTester(StructuralChangeSegmentTester segmentChangeTester) {
segmentChangeTesters.remove(segmentChangeTester);
}
/**
* Sets the {@link EMFFormsSegmentResolver}.
*
* @param segmentResolver The {@link EMFFormsSegmentResolver}
*/
@Reference(unbind = "-")
protected void setEMFFormsSegmentResolver(EMFFormsSegmentResolver segmentResolver) {
this.segmentResolver = segmentResolver;
}
@Override
public boolean isStructureChanged(final VDomainModelReference reference, final EObject domainRootObject,
ModelChangeNotification notification) {
// Changed EAttributes or only touched EReferences do not constitute structural changes
if (EAttribute.class.isInstance(notification.getStructuralFeature())) {
return false;
}
if (notification.getRawNotification().isTouch()) {
return false;
}
final EList<VDomainModelReferenceSegment> segments = reference.getSegments();
if (segments.isEmpty()) {
// fall back to DMR based structural change testing
return testOnDmr(reference, domainRootObject, notification);
}
return testOnSegments(reference, domainRootObject, notification);
}
/**
* Test for structural changes based on the {@link VDomainModelReference VDomainModelReference's} segments.
*
* @param reference The {@link VDomainModelReference} whose segments are tested
* @param domainRootObject The root domain object to resolve and test the segments against
* @param notification The {@link ModelChangeNotification} triggering the structural change test
* @return <code>true</code> if the structure has changed, <code>false</code> otherwise
*/
private boolean testOnSegments(final VDomainModelReference reference, final EObject domainRootObject,
ModelChangeNotification notification) {
final EList<VDomainModelReferenceSegment> segments = reference.getSegments();
boolean relevantChange = false;
EObject currentDomainObject = domainRootObject;
for (int i = 0; i < segments.size(); i++) {
final VDomainModelReferenceSegment segment = segments.get(i);
final StructuralChangeSegmentTester segmentTester = SEGMENTS_RANKING_HELPER
.getHighestRankingElement(segmentChangeTesters, tester -> tester.isApplicable(segment));
if (segmentTester == null) {
reportService.report(new AbstractReport(String.format(
"Structural changes of the DMR: %1$s could not be analyzed because no suitable StructuralChangeSegmentTester was available for segment %2$s.", //$NON-NLS-1$
reference, segment), IStatus.WARNING));
return false;
}
final Setting setting;
try {
setting = segmentResolver.resolveSegment(segment, currentDomainObject);
} catch (final DatabindingFailedException ex) {
reportService.report(new AbstractReport(ex,
"Could not finish structural change calculation.")); //$NON-NLS-1$
break;
}
relevantChange |= segmentTester.isStructureChanged(segment, currentDomainObject, notification);
if (relevantChange || !EReference.class.isInstance(setting.getEStructuralFeature())) {
return relevantChange;
}
// Do not resolve the last setting and convert its value because the last value is not needed and might be
// an EList because the last segment may represent a multi reference
if (i == segments.size() - 1) {
break;
}
// The value of the Setting is an EObject because its EStructuralFeature is an EReference.
currentDomainObject = (EObject) setting.get(true);
}
return relevantChange;
}
/**
* Test for structural changes based on the whole {@link VDomainModelReference}.
*
* @param reference The {@link VDomainModelReference} to test
* @param domainRootObject The root domain object to resolve and test the DMR against
* @param notification The {@link ModelChangeNotification} triggering the structural change test
* @return <code>true</code> if the structure has changed, <code>false</code> otherwise
*/
private boolean testOnDmr(final VDomainModelReference reference, final EObject domainRootObject,
ModelChangeNotification notification) {
final StructuralChangeTesterInternal bestTester = DMR_RANKING_HELPER.getHighestRankingElement(
dmrChangeTesters, tester -> tester.isApplicable(reference));
if (bestTester == null) {
reportService.report(new AbstractReport("Structural changes of the DMR: " + reference //$NON-NLS-1$
+ "could not be analyzed because no suitable StructuralChangeTesterInternal was available.", //$NON-NLS-1$
IStatus.WARNING));
return false;
}
return bestTester.isStructureChanged(reference, domainRootObject, notification);
}
}