Last active
August 29, 2015 14:17
-
-
Save spoenemann/1875cda54a43a45d8cfd to your computer and use it in GitHub Desktop.
DomainModelDiagramSynthesis
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/******************************************************************************* | |
* Copyright (c) 2015 itemis AG (http://www.itemis.eu) 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 | |
*******************************************************************************/ | |
package org.eclipse.xtext.example.domainmodel.view | |
import com.google.common.collect.ImmutableList | |
import com.google.inject.Inject | |
import de.cau.cs.kieler.core.kgraph.KEdge | |
import de.cau.cs.kieler.core.kgraph.KNode | |
import de.cau.cs.kieler.core.krendering.Colors | |
import de.cau.cs.kieler.core.krendering.KRenderingFactory | |
import de.cau.cs.kieler.core.krendering.LineCap | |
import de.cau.cs.kieler.core.krendering.extensions.KColorExtensions | |
import de.cau.cs.kieler.core.krendering.extensions.KEdgeExtensions | |
import de.cau.cs.kieler.core.krendering.extensions.KLabelExtensions | |
import de.cau.cs.kieler.core.krendering.extensions.KNodeExtensions | |
import de.cau.cs.kieler.core.krendering.extensions.KRenderingExtensions | |
import de.cau.cs.kieler.core.properties.IProperty | |
import de.cau.cs.kieler.core.properties.MapPropertyHolder | |
import de.cau.cs.kieler.kiml.LayoutMetaDataService | |
import de.cau.cs.kieler.kiml.options.Direction | |
import de.cau.cs.kieler.kiml.options.EdgeType | |
import de.cau.cs.kieler.kiml.options.LayoutOptions | |
import de.cau.cs.kieler.klighd.SynthesisOption | |
import de.cau.cs.kieler.klighd.syntheses.AbstractDiagramSynthesis | |
import org.eclipse.emf.ecore.EObject | |
import org.eclipse.jdt.core.Flags | |
import org.eclipse.xtext.common.types.JvmType | |
import org.eclipse.xtext.common.types.JvmTypeReference | |
import org.eclipse.xtext.example.domainmodel.DomainmodelStandaloneSetup | |
import org.eclipse.xtext.example.domainmodel.domainmodel.AbstractElement | |
import org.eclipse.xtext.example.domainmodel.domainmodel.DomainModel | |
import org.eclipse.xtext.example.domainmodel.domainmodel.Entity | |
import org.eclipse.xtext.example.domainmodel.domainmodel.PackageDeclaration | |
import org.eclipse.xtext.example.domainmodel.domainmodel.Property | |
import org.eclipse.xtext.xbase.jvmmodel.IJvmModelAssociations | |
import static extension org.eclipse.emf.ecore.util.EcoreUtil.* | |
/** | |
* A KLighD diagram synthesis for the Xtext Domain Model example language. | |
*/ | |
class DomainModelDiagramSynthesis extends AbstractDiagramSynthesis<DomainModel> { | |
/** Foreground color for entities. */ | |
public static val OPTION_ENTITY_FOREGROUND = createUserSynthesisOption( | |
"domainmodel.entityForeground", "darkred") | |
/** Background color for entities. */ | |
public static val OPTION_ENTITY_BACKGROUND = createUserSynthesisOption( | |
"domainmodel.entityBackground", "wheat") | |
private static def <T> createUserSynthesisOption(String id, T defaultValue) { | |
new de.cau.cs.kieler.core.properties.Property(id, defaultValue) | |
} | |
private static val OPTION_VISUALIZE_JVMTYPES = SynthesisOption.createCheckOption( | |
"JVM Types", true) | |
private static val OPTION_VISUALIZE_PACKAGES = SynthesisOption.createCheckOption( | |
"Package Hierarchy", false) | |
@Inject extension KNodeExtensions | |
@Inject extension KEdgeExtensions | |
@Inject extension KLabelExtensions | |
@Inject extension KRenderingExtensions | |
@Inject extension KColorExtensions | |
extension KRenderingFactory = KRenderingFactory.eINSTANCE | |
extension LayoutMetaDataService = LayoutMetaDataService.instance | |
extension IJvmModelAssociations modelAssociations | |
new() { | |
// The JVM model associations must be loaded with a different injector | |
val injector = (new DomainmodelStandaloneSetup).createInjector | |
modelAssociations = injector.getInstance(IJvmModelAssociations) | |
} | |
/** | |
* Specify which synthesis options should be offered in the user interface. | |
*/ | |
override public getDisplayedSynthesisOptions() { | |
return ImmutableList::of( | |
OPTION_VISUALIZE_JVMTYPES, | |
OPTION_VISUALIZE_PACKAGES | |
) | |
} | |
/** | |
* Specify which layout options should be offered in the user interface. | |
*/ | |
override getDisplayedLayoutOptions() { | |
return ImmutableList.of( | |
specifyLayoutOption(LayoutOptions.DIRECTION, ImmutableList.of(Direction.RIGHT, Direction.LEFT, Direction.DOWN, Direction.UP)), | |
specifyLayoutOption(LayoutOptions.SPACING, ImmutableList.of(5, 200)) | |
) | |
} | |
/** | |
* Entry point of the Domain Model to diagram transformation. | |
*/ | |
override KNode transform(DomainModel model) { | |
// Create the root node | |
val root = model.createNode.associateWith(model) | |
// Process configuration elements: synthesis options and layout options | |
val synthesisOptions = processConfigElements(model, root) | |
// Transform the model | |
model.elements.forEach[transform(root, synthesisOptions)] | |
return root | |
} | |
/** | |
* Process configuration elements: synthesis options and layout options. | |
*/ | |
private def processConfigElements(DomainModel model, KNode root) { | |
val synthesisOptions = new MapPropertyHolder | |
val synthesisProperties = class.fields.filter[ | |
Flags.isStatic(modifiers) && typeof(IProperty).isAssignableFrom(type) | |
].map[get(null) as IProperty<Object>].toList | |
model.configElements.filter[key != null && value != null].forEach[ element | | |
val property = synthesisProperties.findFirst[id == element.key] | |
if (property != null) { | |
// Parse a synthesis option (here all options are of type String) | |
val value = element.value | |
synthesisOptions.setProperty(property, value) | |
} else { | |
// Parse a layout option | |
val layoutOption = element.key.optionDataBySuffix | |
val value = | |
if (layoutOption == LayoutOptions.ALGORITHM) | |
element.value.algorithmDataBySuffix?.id | |
else if (layoutOption != null) | |
layoutOption.parseValue(element.value) | |
if (value != null) { | |
root.setLayoutOption(layoutOption, value) | |
} | |
} | |
] | |
// Set default values | |
if (!model.configElements.exists[!key.nullOrEmpty && LayoutOptions.ALGORITHM.id.endsWith(key)]) { | |
root.setLayoutOption(LayoutOptions.ALGORITHM, "de.cau.cs.kieler.kiml.ogdf.planarization") | |
} | |
root.setLayoutOption(LayoutOptions.LAYOUT_HIERARCHY, true) | |
return synthesisOptions | |
} | |
/** | |
* Transform a package declaration. Whether the packages are made visible in the | |
* diagram is specified through a synthesis option that can be controlled by the user. | |
*/ | |
private def dispatch void transform(PackageDeclaration declaration, KNode parentNode, | |
MapPropertyHolder synthesisOptions) { | |
if (OPTION_VISUALIZE_PACKAGES.booleanValue) { | |
val packageNode = declaration.createNode.associateWith(declaration) => [ | |
parent = parentNode | |
data += createKRectangle => [ | |
foreground = Colors.GRAY | |
setBackground(Colors.ANTIQUE_WHITE, 100) | |
] | |
] | |
processConfigElements(declaration.rootContainer as DomainModel, packageNode) | |
declaration.elements.forEach[transform(packageNode, synthesisOptions)] | |
} else { | |
declaration.elements.forEach[transform(parentNode, synthesisOptions)] | |
} | |
} | |
/** | |
* Transform a single entity and its outgoing edges. | |
*/ | |
private def dispatch void transform(Entity entity, KNode parentNode, | |
MapPropertyHolder synthesisOptions) { | |
val entityNode = entity.createNode(entity.name, parentNode, synthesisOptions) | |
// Create generalization edges | |
val superTypeNode = entity.superType.getTargetNode(parentNode, synthesisOptions) | |
if (superTypeNode != null) { | |
createEdge => [ | |
source = entityNode | |
target = superTypeNode | |
createGeneralizationArrow | |
setLayoutOption(LayoutOptions.EDGE_TYPE, EdgeType.GENERALIZATION) | |
] | |
} | |
// Create association edges | |
entity.features.filter(Property).forEach[ property | | |
val propertyTypeNode = property.type.getTargetNode(parentNode, synthesisOptions) | |
if (propertyTypeNode != null) { | |
createEdge.associateWith(property) => [ | |
source = entityNode | |
target = propertyTypeNode | |
createAssociationArrow | |
createLabel => [ | |
text = property.name | |
data += createKText => [ | |
fontSize = 8 | |
] | |
] | |
setLayoutOption(LayoutOptions.EDGE_TYPE, EdgeType.ASSOCIATION) | |
] | |
} | |
] | |
} | |
/** | |
* Transform a referenced JVM type without any outgoing edges. | |
*/ | |
private def dispatch void transform(JvmType type, KNode parentNode, | |
MapPropertyHolder synthesisOptions) { | |
type.createNode(type.simpleName, parentNode, synthesisOptions) | |
} | |
/** | |
* Create a node for the given element (either an Entity or a JVM type). | |
*/ | |
private def createNode(EObject element, String name, KNode parentNode, | |
MapPropertyHolder synthesisOptions) { | |
element.createNode.associateWith(element) => [ | |
parent = parentNode | |
data += createKRoundedRectangle => [ | |
if (element instanceof Entity) { | |
foreground = synthesisOptions.getProperty(OPTION_ENTITY_FOREGROUND).color | |
setBackgroundGradient('white'.color, | |
synthesisOptions.getProperty(OPTION_ENTITY_BACKGROUND).color, 90) | |
} else { | |
foreground = Colors.DARK_BLUE | |
setBackgroundGradient(Colors.WHITE, Colors.AQUAMARINE, 90) | |
} | |
lineWidth = 0.7f | |
children += createKText => [ | |
text = name | |
fontSize = 11 | |
setSurroundingSpace(3, 0) | |
] | |
] | |
] | |
} | |
/** | |
* Retrieve the target node for the given type reference, so an edge can be created. | |
*/ | |
private def getTargetNode(JvmTypeReference typeReference, KNode parentNode, | |
MapPropertyHolder synthesisOptions) { | |
val modelElement = typeReference?.type?.primarySourceElement | |
if (modelElement instanceof AbstractElement) { | |
// The referenced type is a Domain Model element | |
return modelElement.getNode | |
} else if (typeReference?.type != null && OPTION_VISUALIZE_JVMTYPES.booleanValue) { | |
val targetNode = typeReference.type.getNode | |
// Create a node for the JVM type only when it's demanded | |
if (targetNode.parent == null) { | |
typeReference.type.transform(parentNode, synthesisOptions) | |
} | |
return targetNode | |
} | |
} | |
private def createGeneralizationArrow(KEdge edge) { | |
edge.data += createKPolyline => [ | |
lineCap = LineCap.CAP_FLAT | |
children += createKPolygon => [ | |
points += createKPosition(LEFT, 0, 0, TOP, 0, 0) | |
points += createKPosition(RIGHT, 0, 0, TOP, 0, 0.5f) | |
points += createKPosition(LEFT, 0, 0, BOTTOM, 0, 0) | |
it.placementData = createKDecoratorPlacementData => [ | |
rotateWithLine = true | |
relative = 1.0f | |
width = 8 | |
height = 8 | |
XOffset = -8 | |
YOffset = -4 | |
] | |
background = Colors.WHITE | |
] | |
] | |
} | |
private def createAssociationArrow(KEdge edge) { | |
edge.data += createKPolyline => [ | |
lineCap = LineCap.CAP_FLAT | |
children += createKPolyline => [ | |
points += createKPosition(LEFT, 0, 0, TOP, 0, 0) | |
points += createKPosition(RIGHT, 0, 0, TOP, 0, 0.5f) | |
points += createKPosition(LEFT, 0, 0, BOTTOM, 0, 0) | |
it.placementData = createKDecoratorPlacementData => [ | |
rotateWithLine = true | |
relative = 1.0f | |
width = 6 | |
height = 6 | |
XOffset = -6 | |
YOffset = -3 | |
] | |
] | |
] | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment