/******************************************************************************* * Copyright (c) 2011 protos software gmbh (http://www.protos.de). * 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: * Thomas Jung, Thomas Schuetz (initial contribution) * *******************************************************************************/ package org.eclipse.etrice.generator.doc.gen import com.google.inject.Inject import com.google.inject.Singleton import java.util.List import org.eclipse.emf.ecore.EObject import org.eclipse.etrice.core.common.base.Documentation import org.eclipse.etrice.core.fsm.fSM.State import org.eclipse.etrice.core.fsm.fSM.StateGraph import org.eclipse.etrice.core.genmodel.etricegen.Root import org.eclipse.etrice.core.room.ActorClass import org.eclipse.etrice.core.room.Attribute import org.eclipse.etrice.core.room.CompoundProtocolClass import org.eclipse.etrice.core.room.DataClass import org.eclipse.etrice.core.room.EnumerationType import org.eclipse.etrice.core.room.GeneralProtocolClass import org.eclipse.etrice.core.room.LogicalSystem import org.eclipse.etrice.core.room.Operation import org.eclipse.etrice.core.room.Port import org.eclipse.etrice.core.room.ProtocolClass import org.eclipse.etrice.core.room.RoomClass import org.eclipse.etrice.core.room.RoomModel import org.eclipse.etrice.core.room.SubSystemClass import org.eclipse.etrice.core.room.util.RoomHelpers import org.eclipse.etrice.generator.base.io.IGeneratorFileIO import org.eclipse.etrice.generator.fsm.base.CodegenHelpers import org.eclipse.xtext.documentation.IEObjectDocumentationProvider import static org.eclipse.etrice.core.common.documentation.DocumentationMarkup.* import org.eclipse.etrice.generator.doc.Main import org.eclipse.etrice.generator.base.AbstractGeneratorOptionsHelper @Singleton class AsciiDocGen { @Inject extension RoomHelpers @Inject extension CodegenHelpers @Inject protected extension AbstractGeneratorOptionsHelper @Inject IEObjectDocumentationProvider eObjDocuProvider def doGenerate(Root root, IGeneratorFileIO fileIO, boolean includeImages) { val packages = root.models.groupBy[name].entrySet.map[new RoomPackage(key, value)].sortBy[name] fileIO.generateFile("doc.adoc", generateSingleDoc(packages, includeImages)) } def generateSingleDoc(Iterable packages, boolean includeImages) ''' = Model Documentation generated by eTrice {docdatetime} :toc: left :toclevels: 2 :table-caption!: «IF !packages.empty» .Room Packages «FOR pkg: packages» * «crossReference(pkg.name)» «ENDFOR» «ENDIF» «FOR pkg: packages» «generatePackageDoc(pkg)» «FOR en: pkg.enumerationTypes» «en.generateEnumerationDoc» «ENDFOR» «FOR dc: pkg.dataClasses» «dc.generateDataDoc» «ENDFOR» «FOR pc: pkg.protocolClasses» «pc.generateProtocolDoc» «ENDFOR» «FOR sys: pkg.systems» «sys.generateLogicalSystemDoc(includeImages)» «ENDFOR» «FOR subSys: pkg.subSystemClasses» «subSys.generateSubSystemDoc(includeImages)» «ENDFOR» «FOR ac: pkg.actorClasses» «ac.generateActorDoc(includeImages)» «ENDFOR» «ENDFOR» ''' def private generatePackageDoc(RoomPackage pkg) { ''' «defineAnchor(pkg.name)» == «pkg.name» «IF !pkg.systems.empty» .Logical System Classes |=== | Name | Description «FOR s : pkg.systems» | «crossReference(s)» | «s.shortDocText» «ENDFOR» |=== «ENDIF» «IF !pkg.subSystemClasses.empty» .Subsystem Classes |=== | Name | Description «FOR s : pkg.subSystemClasses» | «crossReference(s)» | «s.shortDocText» «ENDFOR» |=== «ENDIF» «IF !pkg.protocolClasses.empty» .ProtocolClasses |=== | Name | Description «FOR c : pkg.protocolClasses» | «crossReference(c)» | «c.shortDocText» «ENDFOR» |=== «ENDIF» «IF !pkg.enumerationTypes.empty» .Enumeration Types |=== | Name | Description «FOR e : pkg.enumerationTypes» | «crossReference(e)» | «e.shortDocText» «ENDFOR» |=== «ENDIF» «IF !pkg.dataClasses.empty» .Data Classes |=== | Name | Description «FOR c : pkg.dataClasses» | «crossReference(c)» | «c.shortDocText» «ENDFOR» |=== «ENDIF» «IF !pkg.actorClasses.empty» .Actor Classes |=== | Name | Description «FOR c : pkg.actorClasses» | «crossReference(c)» | «c.shortDocText» «ENDFOR» |=== «ENDIF» ''' } def private generateLogicalSystemDoc(LogicalSystem system, boolean includeImages) { ''' «defineAnchor(system)» === «system.name» «tagStart(system)» «system.docText» «IF includeImages» «includeImage(system.name + "_instanceTree.jpg")» «ENDIF» «tagEnd(system)» ''' } def private generateSubSystemDoc(SubSystemClass ssc, boolean includeImages) ''' «defineAnchor(ssc)» === «ssc.name» «tagStart(ssc)» «ssc.docText» «IF includeImages» «includeImage(ssc.name + "_structure.jpg")» «ENDIF» «tagEnd(ssc)» ''' def private generateEnumerationDoc(EnumerationType en) ''' «defineAnchor(en)» === «en.name» «tagStart(en)» «en.docText» «IF en.primitiveType !== null» The literals of this enumeration are based on PrimitiveType «en.primitiveType.name». «ELSE» The literals of this enumeration are of type int. «ENDIF» .Literals |=== | Name | Value | Hex Value | Binary Value «FOR lit: en.literals» | «lit.name» | «lit.literalValue» | 0x«Long.toHexString(lit.literalValue)» | «Long.toBinaryString(lit.literalValue)» «ENDFOR» |=== «tagEnd(en)» ''' def private generateDataDoc(DataClass dc) ''' «defineAnchor(dc)» === «dc.name» «tagStart(dc)» «dc.docText» «dc.attributes.generateAttributesDoc» «IF !dc.operations.empty» «dc.operations.generateOperationsDoc» «ENDIF» «tagEnd(dc)» ''' def private dispatch generateProtocolDoc(ProtocolClass pc) ''' «defineAnchor(pc)» === «pc.name» «tagStart(pc)» «pc.docText» «IF !pc.allIncomingMessages.empty» .Incoming Messages |=== | Message | Type | Description «FOR ims : pc.allIncomingMessages» | «ims.name» | «IF ims.data !== null»«ims.data.refType.type.name»«ELSE»void«ENDIF» a| «ims.docText» «ENDFOR» |=== «ENDIF» «IF !pc.allOutgoingMessages.empty» .Outgoing Messages |=== | Message | Type | Description «FOR oms : pc.allOutgoingMessages» | «oms.name» | «IF oms.data !== null»«oms.data.refType.type.name»«ELSE»void«ENDIF» a| «oms.docText» «ENDFOR» |=== «ENDIF» «IF !pc.getAllOperations(true).empty» [discrete] ==== Regular PortClass «pc.getAllOperations(true).generateOperationsDoc» «ENDIF» «IF !pc.getAllOperations(false).empty» [discrete] ==== Conjugated PortClass «pc.getAllOperations(false).generateOperationsDoc» «ENDIF» «tagEnd(pc)» ''' def private dispatch generateProtocolDoc(CompoundProtocolClass pc) ''' «defineAnchor(pc)» === «pc.name» «tagStart(pc)» «pc.docText» .Sub Protocols |=== | Name | Protocol «FOR sub : pc.subProtocols» | «sub.name» | «sub.protocol.name» «ENDFOR» |=== «tagEnd(pc)» ''' def private generateActorDoc(ActorClass ac, boolean includeImages) ''' «defineAnchor(ac)» === «ac.name» «tagStart(ac)» «ac.docText» [discrete] «IF Main::settings.generateAsLibrary» ==== Interface «IF !ac.allInterfacePorts.empty» «generatePortInterfaceDoc(ac)» «ENDIF» «ELSE» ==== Structure «IF includeImages» «includeImage(ac.name + "_structure.jpg")» «ENDIF» «IF !ac.allPorts.empty» «generatePortDoc(ac)» «ENDIF» «IF !ac.attributes.empty» «ac.attributes.generateAttributesDoc» «ENDIF» «IF ac.hasNonEmptyStateMachine || !ac.operations.empty || ac.isBehaviorAnnotationPresent("BehaviorManual")» [discrete] ==== Behavior «IF !ac.operations.empty» «ac.operations.generateOperationsDoc» «ENDIF» «IF ac.isBehaviorAnnotationPresent("BehaviorManual")» The behavior for ActorClass «ac.name» is implemented manually. «ELSEIF ac.hasNonEmptyStateMachine» «generateFsmDoc(ac, includeImages)» «ENDIF» «ENDIF» «ENDIF» «tagEnd(ac)» ''' def private generateFsmDoc(ActorClass ac, boolean includeImages) ''' .State Machine Top Level State:: «generateStateGraphDoc(ac, ac.stateMachine, includeImages, 1)» ''' def private CharSequence generateStateGraphDoc(ActorClass ac, StateGraph stateGraph, boolean includeImages, int depth) { val statePath = if(stateGraph.eContainer instanceof State) "_" + (stateGraph.eContainer as State).genStatePathName else "" ''' «IF includeImages» «includeImage(ac.name + statePath + "_behavior.jpg")» «ENDIF» «FOR state: stateGraph.states» «state.name»::«fill(':', depth)» «state.docText» «IF !state.leaf» «generateStateGraphDoc(ac, state.subgraph, includeImages, depth + 1)» «ENDIF» «ENDFOR» ''' } def private String generatePortInterfaceDoc(ActorClass ac) ''' .Ports |=== | Name | Protocol | Type | Multiplicity | Description «FOR at : ac.allInterfacePorts» | «at.name» | «at.protocol.name» | «at.type» | «at.multAsText» a| «at.docText» «ENDFOR» |=== ''' def private String generatePortDoc(ActorClass ac) ''' .Ports |=== | Name | Protocol | Type | Kind | Multiplicity | Description «FOR at : ac.allPorts» | «at.name» | «at.protocol.name» | «at.type» | «at.kind» | «at.multAsText» a| «at.docText» «ENDFOR» |=== ''' def private generateAttributesDoc(List attributes) ''' .Attributes |=== | Name | Type | Description «FOR at : attributes» | «at.name» | «at.type.type.name» a| «at.docText» «ENDFOR» |=== ''' def private generateOperationsDoc(List operations) ''' .Operations |=== | Name | Return type | Arguments | Description «FOR op : operations SEPARATOR '\n'» |«op.name» | «IF op.returnType !== null»«op.returnType.type.name»«ELSE»void«ENDIF» | «FOR pa : op.arguments SEPARATOR ", "»«pa.name»: «pa.refType.type.name»«ENDFOR» a| «op.docText» «ENDFOR» |=== ''' def private getType(Port p) { if (p.conjugated) "conjugated" else "regular" } def private getKind(Port p) { if (p.internal) "internal" else if (p.external) "external" else if (p.relay) "relay" else "?" } def private String getMultAsText(Port p) { if(p.multiplicity == -1) "*" else p.multiplicity.toString } def private getDocText(EObject object) { val eClass = object.eClass; val feature = eClass.getEStructuralFeature("docu") if(feature !== null) { val docu = object.eGet(feature) as Documentation if(docu !== null) { return String.join('\n', docu.lines) } } val docu = object.documentation if(docu !== null) docu else "" } def private getShortDocText(EObject object) { val docText = object.docText val index = docText.indexOf('\n') if(index != -1) docText.subSequence(0, index) else docText } def private includeImage(String filename) { '''image:«filename»[]''' } def private static crossReference(RoomClass rc) { crossReference(rc.FQN) } def private static crossReference(CharSequence anchor) { '''<<«anchor»>>''' } def private static defineAnchor(RoomClass rc) { defineAnchor(rc.FQN) } def private static defineAnchor(CharSequence anchor) { '''[[«anchor»]]''' } def private static getFQN(RoomClass rc) { '''«(rc.eContainer as RoomModel).name».«rc.name»''' } def private static tagStart(RoomClass rc) '''tag::«rc.FQN»[]''' def private static tagEnd(RoomClass rc) '''end::«rc.FQN»[]''' def private static String fill(char c, int length) { val builder = new StringBuilder(length) for(var i = 0; i < length; i++) { builder.append(c) } builder.toString } def private String documentation(EObject obj) { val raw = eObjDocuProvider.getDocumentation(obj) if(raw === null) return null; switch type : getMarkupType(raw) { case type == MARKUP_HTML && raw.contains("<"): ''' ++++

«trimMarkupTag(raw)»

++++ ''' default: { trimMarkupTag(raw) } } } private static class RoomPackage { public final String name public final Iterable systems public final Iterable subSystemClasses public final Iterable protocolClasses public final Iterable enumerationTypes public final Iterable dataClasses public final Iterable actorClasses private new(String name, Iterable models) { this.name = name systems = models.map[it.systems].flatten.sortBy[name] subSystemClasses = models.map[it.subSystemClasses].flatten.sortBy[name] protocolClasses = models.map[it.protocolClasses].flatten.sortBy[name] enumerationTypes = models.map[it.enumerationTypes].flatten.sortBy[name] dataClasses = models.map[it.dataClasses].flatten.sortBy[name] actorClasses = models.map[it.actorClasses].flatten.sortBy[name] } } }