Skip to content

Instantly share code, notes, and snippets.

@spoenemann
Last active August 29, 2015 14:17
Show Gist options
  • Save spoenemann/1875cda54a43a45d8cfd to your computer and use it in GitHub Desktop.
Save spoenemann/1875cda54a43a45d8cfd to your computer and use it in GitHub Desktop.
DomainModelDiagramSynthesis
/*******************************************************************************
* 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