/** | |
* Copyright (c) 2009, 2019 Mia-Software and others. | |
* All rights reserved. This program and the accompanying materials | |
* are made available under the terms of the Eclipse Public License v2.0 | |
* which accompanies this distribution, and is available at | |
* http://www.eclipse.org/legal/epl-v20.html | |
* | |
* Contributors: | |
* Gabriel Barbier (Mia-Software) - initial API and implementation | |
* Fabien Giquel (Mia-Software) - Bug 339720 : MoDisco Discoverers (infra + techno) API clean | |
* Fabien Giquel (Mia-Software) - Bug 559115 : Maintain currency with UML 2.5 | |
*******************************************************************************/ | |
package org.eclipse.modisco.usecase.simpletransformationschain; | |
import java.net.URL; | |
import java.util.HashSet; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.Set; | |
import java.util.logging.Logger; | |
import java.util.stream.Collectors; | |
import org.eclipse.core.resources.IProject; | |
import org.eclipse.core.runtime.IProgressMonitor; | |
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.util.EcoreUtil; | |
import org.eclipse.jdt.core.IJavaProject; | |
import org.eclipse.m2m.atl.common.ATLLogger; | |
import org.eclipse.modisco.infra.common.core.internal.logging.MoDiscoLogHandler; | |
import org.eclipse.modisco.infra.discovery.core.AbstractModelDiscoverer; | |
import org.eclipse.modisco.infra.discovery.core.annotations.Parameter; | |
import org.eclipse.modisco.infra.discovery.core.exception.DiscoveryException; | |
import org.eclipse.uml2.uml.Association; | |
import org.eclipse.uml2.uml.Property; | |
/** | |
* @deprecated See Bug 559506- the KDMtoUML transformation has not been revised for UML2 5.0.0. | |
*/ | |
@Deprecated | |
public class DiscoverUmlModelWithBidirectionalAssociationsFromJavaProject extends | |
AbstractModelDiscoverer<IJavaProject> { | |
private static final String MODEL_FILE_SUFFIX = "_BidirectionalAssociations.uml"; //$NON-NLS-1$ | |
private URL customTransformation = null; | |
@Parameter(name = "CUSTOM_TRANSFORMATION", description = "A URL pointing to an ATL transformation that will be used instead of the default one.") | |
public void setCustomTransformation(final URL customTransformation) { | |
this.customTransformation = customTransformation; | |
} | |
protected URL getCustomTransformation() { | |
return this.customTransformation; | |
} | |
@Override | |
public boolean isApplicableTo(final IJavaProject source) { | |
return source.getProject().isAccessible(); | |
} | |
@Override | |
protected void basicDiscoverElement(final IJavaProject source, final IProgressMonitor monitor) | |
throws DiscoveryException { | |
IProject project = source.getProject(); | |
setDefaultTargetURI(URI.createPlatformResourceURI( | |
project.getFullPath().append(project.getName()) + DiscoverUmlModelWithBidirectionalAssociationsFromJavaProject.MODEL_FILE_SUFFIX, true)); | |
final Logger logger = Logger.getLogger(ATLLogger.LOGGER_ID); | |
final MoDiscoLogHandler logHandler = new MoDiscoLogHandler(project.getLocation() | |
.append(project.getName()).toString() | |
+ ".log"); //$NON-NLS-1$ | |
logger.addHandler(logHandler); | |
try { | |
Resource uml2Model = DiscoverUmlModelFromJavaProject.getUML2ModelFromJavaProject( | |
source, getCustomTransformation()); | |
createBidirectionalAssociations(uml2Model); | |
setTargetModel(uml2Model); | |
} catch (Exception e) { | |
throw new DiscoveryException(e); | |
} finally { | |
logger.removeHandler(logHandler); | |
logHandler.close(); | |
} | |
} | |
/* | |
* The purpose of this refinement is that two unidirectional "opposite" associations become one bidirectional association. | |
* Two associations are considered as opposite when (same as old BidirectionalAssociation.atl conditions) : | |
* - {source,target} types are the same (but inverse) | |
* - there is only one property on each side referencing the other side as type. | |
* | |
*/ | |
private void createBidirectionalAssociations(final Resource uml2Model) { | |
Set<Association> associationsToRemove = new HashSet<>(); | |
Set<Property> propertiesToRemove = new HashSet<>(); | |
for (Iterator<EObject> i = uml2Model.getAllContents(); i.hasNext();) { | |
final EObject childEObject = i.next(); | |
if (childEObject instanceof Association && !associationsToRemove.contains(childEObject)) { | |
Association currentAssociation = (Association) childEObject; | |
Association oppositeAssociation = searchAndMergeOppositeAssociation(currentAssociation); | |
if (oppositeAssociation != null) { | |
// we have found and merged an opposite unidirectional association, we remove it | |
associationsToRemove.add(oppositeAssociation); | |
propertiesToRemove.addAll(currentAssociation.getOwnedEnds()); | |
} | |
} | |
} | |
EcoreUtil.deleteAll(associationsToRemove, true); | |
EcoreUtil.deleteAll(propertiesToRemove, true); | |
} | |
private Association searchAndMergeOppositeAssociation(final Association association) { | |
Association oppositeAssociation = null; | |
List<org.eclipse.uml2.uml.Class> endTypes = association.getEndTypes().stream() | |
.filter(org.eclipse.uml2.uml.Class.class::isInstance). | |
map(org.eclipse.uml2.uml.Class.class::cast).collect(Collectors.toList()); | |
if (endTypes.size() == 1) { | |
// May be a self association | |
oppositeAssociation = searchAndMergeSelfOppositeAssociation(association, | |
endTypes); | |
} else if (endTypes.size() == 2) { | |
org.eclipse.uml2.uml.Class sourceType = endTypes.get(0); | |
org.eclipse.uml2.uml.Class targetType = endTypes.get(1); | |
// We are looking to have exactly one property | |
// which type is the opposite class in each class | |
List<Property> sourceMatchingAtts = sourceType.getAttributes().stream().filter(att -> targetType.equals(att.getType()) | |
&& att.getAssociation() != null).collect(Collectors.toList()); | |
List<Property> targetMatchingAtts = targetType.getAttributes().stream().filter(att -> sourceType.equals(att.getType()) | |
&& att.getAssociation() != null).collect(Collectors.toList()); | |
if (sourceMatchingAtts.size() == 1 && targetMatchingAtts.size() == 1) { | |
if (sourceMatchingAtts.get(0).getAssociation() == association) { | |
oppositeAssociation = targetMatchingAtts.get(0).getAssociation(); | |
targetMatchingAtts.get(0).setAssociation(association); | |
} else { | |
oppositeAssociation = sourceMatchingAtts.get(0).getAssociation(); | |
sourceMatchingAtts.get(0).setAssociation(association); | |
} | |
} | |
} | |
return oppositeAssociation; | |
} | |
private Association searchAndMergeSelfOppositeAssociation(final Association association, | |
final List<org.eclipse.uml2.uml.Class> endTypes) { | |
org.eclipse.uml2.uml.Class sourceType = endTypes.get(0); | |
Association oppositeAssociation = null; | |
// May be a self association, so to have a bidirectional association | |
// we have to have a collection of property which type is sourceClass | |
// and which is linked to an association | |
// and this collection shall have a size equals to 2 | |
List<Property> sourceMatchingAtts = sourceType.getAttributes().stream().filter(att -> sourceType.equals(att.getType()) | |
&& att.getAssociation() != null).collect(Collectors.toList()); | |
if (sourceMatchingAtts.size() == 2) { | |
if (sourceMatchingAtts.get(0).getAssociation() == association) { | |
oppositeAssociation = sourceMatchingAtts.get(1).getAssociation(); | |
sourceMatchingAtts.get(1).setAssociation(association); | |
} else { | |
oppositeAssociation = sourceMatchingAtts.get(0).getAssociation(); | |
sourceMatchingAtts.get(0).setAssociation(association); | |
} | |
} | |
return oppositeAssociation; | |
} | |
} |