Well after a bit of umming and erring, I’ve decided that the safest way for me to share the code that I developed to extract the UML diagrams from SAP is to share it here. I could have potentially used GitHub, but an entire repository for one file that I doubt I’ll ever change again seemed like overkill.
It’s worth referencing this recent post by Nigel James and the responses to it:
http://scn.sap.com/community/abap/blog/2013/08/16/share-and-share-alike
This code is shared under an Apache Licence version 2.0 http://www.apache.org/licenses/LICENSE-2.0
class ZCL_XU_SEQUENCE_DIAGRAM definition public inheriting from CL_ATRA_UML create public .
public section.
* Copyright 2013 Chris Paine * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License.
methods IF_ATRA_UML_TOOL~CREATE_UML_DATA redefinition . methods IF_ATRA_UML_TOOL~GET_XMI redefinition . protected section. private section.
data _T_CALL_STACK type TY_CALL_STACK_TAB .
interface IF_ATRA_UML_TOOL load . methods ADD_CALL importing !IV_DIAGRAM type STRING !IS_DETAILS type IF_ATRA_UML_TOOL=>TY_SAT_RECORD !IV_CALLER type I !IV_CALLED type I returning value(RV_DIAGRAM) type STRING . methods ADD_PARTICIPANT importing !IV_DIAGRAM type STRING !IS_OBJECT type TY_OBJECT !IT_OBJECTS type TY_OBJECT_TAB !IV_FIRST_TIME type BOOLE_D returning value(RV_DIAGRAM) type STRING . methods ADD_RETURN importing !IV_DIAGRAM type STRING !IV_RETURN_TO type I returning value(RV_DIAGRAM) type STRING . methods FILL_GAPS importing !IT_WITH_GAPS type IF_ATRA_UML_TOOL~TY_SAT_TAB returning value(RV_FILLED_GAPS) type IF_ATRA_UML_TOOL~TY_SAT_TAB . methods GET_SEQUENCE_DIAGRAM returning value(RV_DIAGRAM) type STRING . ENDCLASS.
CLASS ZCL_XU_SEQUENCE_DIAGRAM IMPLEMENTATION.
* <SIGNATURE>---------------------------------------------------------------------------------------+ * | Instance Private Method ZCL_XU_SEQUENCE_DIAGRAM->ADD_CALL * +-------------------------------------------------------------------------------------------------+ * | [--->] IV_DIAGRAM TYPE STRING * | [--->] IS_DETAILS TYPE IF_ATRA_UML_TOOL=>TY_SAT_RECORD * | [--->] IV_CALLER TYPE I * | [--->] IV_CALLED TYPE I * | [<-()] RV_DIAGRAM TYPE STRING * +--------------------------------------------------------------------------------------</SIGNATURE> METHOD add_call.
DATA: lv_call TYPE string, ls_call_stack TYPE ty_call_stack, lv_text TYPE string, lv_prefix TYPE string.
IF iv_caller = iv_called AND is_details-caller <> is_details-called. lv_text = is_details-called && '->' && is_details-called_mod. ELSE. lv_text = is_details-called_mod. ENDIF.
CASE is_details-id. WHEN 'F'. lv_prefix = 'Perform'. WHEN 'U'. lv_prefix = 'Call FM'. WHEN 'm'. lv_prefix = 'Call method'. WHEN 'R'. lv_prefix = 'Create instance of class'. lv_text = is_details-called. WHEN 'X'. "skip over SAP code lv_prefix = '<b>Skipping over SAP code until calling'. lv_text = is_details-called_mod && '</b>'. ENDCASE.
lv_call = iv_caller && ` -> ` && iv_called && `: ` && lv_prefix && ` ` && lv_text && cl_abap_char_utilities=>cr_lf && `activate ` && iv_called && cl_abap_char_utilities=>cr_lf.
IF is_details-id = 'X'. lv_call = lv_call && `note over ` && iv_caller && ',' && iv_called && cl_abap_char_utilities=>cr_lf && 'Standard SAP code has called some custom code' && cl_abap_char_utilities=>cr_lf && 'end note' && cl_abap_char_utilities=>cr_lf. ENDIF.
ls_call_stack-code = iv_called. ls_call_stack-sap_code = is_details-aus_ebene. APPEND ls_call_stack TO _t_call_stack.
rv_diagram = iv_diagram && lv_call.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+ * | Instance Private Method ZCL_XU_SEQUENCE_DIAGRAM->ADD_PARTICIPANT * +-------------------------------------------------------------------------------------------------+ * | [--->] IV_DIAGRAM TYPE STRING * | [--->] IS_OBJECT TYPE TY_OBJECT * | [--->] IT_OBJECTS TYPE TY_OBJECT_TAB * | [--->] IV_FIRST_TIME TYPE BOOLE_D * | [<-()] RV_DIAGRAM TYPE STRING * +--------------------------------------------------------------------------------------</SIGNATURE> METHOD add_participant.
DATA: lv_participant TYPE string, lv_name TYPE string, ls_object TYPE ty_object, lv_counter TYPE i, lv_create_or_not TYPE string.
CASE is_object-object_type. WHEN 'CLAS'. IF is_object-instance = 0. lv_name = 'Static Methods of Class\n' && is_object-object. ELSE. * check how many other instances of same class exist. LOOP AT it_objects INTO ls_object WHERE object = is_object-object AND object_type = is_object-object_type AND instance <> 0. lv_counter = lv_counter + 1. IF ls_object-instance = is_object-instance. EXIT. "leave the loop. ENDIF. ENDLOOP. lv_name = `Instance ` && lv_counter && ` of Class\n` && is_object-object. ENDIF. WHEN 'FUGR'. lv_name = `Function Group\n` && is_object-object+4. WHEN OTHERS. lv_name = is_object-object_type && '\n' && is_object-object. ENDCASE.
IF iv_first_time = abap_true. lv_create_or_not = 'participant "'. ELSE. lv_create_or_not = 'create "'. ENDIF. lv_participant = lv_create_or_not && lv_name && `" as ` && is_object-code && cl_abap_char_utilities=>cr_lf.
rv_diagram = iv_diagram && lv_participant.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+ * | Instance Private Method ZCL_XU_SEQUENCE_DIAGRAM->ADD_RETURN * +-------------------------------------------------------------------------------------------------+ * | [--->] IV_DIAGRAM TYPE STRING * | [--->] IV_RETURN_TO TYPE I * | [<-()] RV_DIAGRAM TYPE STRING * +--------------------------------------------------------------------------------------</SIGNATURE> METHOD add_return.
DATA: ls_call_stack_from TYPE ty_call_stack, ls_call_stack_to TYPE ty_call_stack, lv_stack_pointer TYPE i, lv_return TYPE string.
lv_stack_pointer = lines( _t_call_stack ). rv_diagram = iv_diagram.
IF lv_stack_pointer > 0. READ TABLE _t_call_stack INTO ls_call_stack_from INDEX lv_stack_pointer. WHILE ls_call_stack_from-sap_code >= iv_return_to. READ TABLE _t_call_stack INTO ls_call_stack_to INDEX ( lv_stack_pointer - 1 ).
lv_return = ls_call_stack_from-code && ` --> ` && ls_call_stack_to-code && cl_abap_char_utilities=>cr_lf && `deactivate ` && ls_call_stack_from-code && cl_abap_char_utilities=>cr_lf. rv_diagram = rv_diagram && lv_return.
DELETE _t_call_stack INDEX lv_stack_pointer. lv_stack_pointer = lv_stack_pointer - 1. IF lv_stack_pointer = 0. EXIT. ENDIF. READ TABLE _t_call_stack INTO ls_call_stack_from INDEX lv_stack_pointer. ENDWHILE. ENDIF.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+ * | Instance Private Method ZCL_XU_SEQUENCE_DIAGRAM->FILL_GAPS * +-------------------------------------------------------------------------------------------------+ * | [--->] IT_WITH_GAPS TYPE IF_ATRA_UML_TOOL~TY_SAT_TAB * | [<-()] RV_FILLED_GAPS TYPE IF_ATRA_UML_TOOL~TY_SAT_TAB * +--------------------------------------------------------------------------------------</SIGNATURE> METHOD fill_gaps. DATA: lt_without_gaps TYPE if_atra_uml_tool=>ty_sat_tab, ls_previous_line TYPE if_atra_uml_tool=>ty_sat_record, ls_gap TYPE if_atra_uml_tool=>ty_sat_record, ls_line TYPE if_atra_uml_tool=>ty_sat_record.
LOOP AT it_with_gaps INTO ls_line. IF ls_previous_line IS NOT INITIAL. IF ( ls_line-caller <> ls_previous_line-called OR ls_line-caller_inst <> ls_previous_line-called_inst OR ls_line-caller_type <> ls_previous_line-called_type ) AND ls_line-aus_ebene > ls_previous_line-aus_ebene. * need to insert a new line into the table at this point to link the two together. ls_gap-caller = ls_previous_line-called. ls_gap-caller_inst = ls_previous_line-called_inst. ls_gap-caller_type = ls_previous_line-called_type. ls_gap-called = ls_line-caller. ls_gap-called_inst = ls_line-caller_inst. ls_gap-called_type = ls_line-caller_type. ls_gap-aus_ebene = ls_previous_line-aus_ebene. ls_gap-id = 'X'. "skip
APPEND ls_gap TO lt_without_gaps. ELSE. ls_previous_line = ls_line. ENDIF. ELSE. ls_previous_line = ls_line. ENDIF.
APPEND ls_line TO lt_without_gaps. ENDLOOP.
rv_filled_gaps = lt_without_gaps.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+ * | Instance Private Method ZCL_XU_SEQUENCE_DIAGRAM->GET_SEQUENCE_DIAGRAM * +-------------------------------------------------------------------------------------------------+ * | [<-()] RV_DIAGRAM TYPE STRING * +--------------------------------------------------------------------------------------</SIGNATURE> METHOD get_sequence_diagram.
DATA: ls_line TYPE if_atra_uml_tool=>ty_sat_record, ls_object TYPE ty_object, ls_last_call TYPE if_atra_uml_tool=>ty_sat_record, lv_next_object TYPE i, lv_first_time TYPE boole_d, ls_first_call TYPE ty_call_stack, lt_objects TYPE ty_object_tab.
FIELD-SYMBOLS: <ls_caller> TYPE ty_object, <ls_called> TYPE ty_object.
rv_diagram = '@startuml' && cl_abap_char_utilities=>cr_lf && 'hide footbox' && cl_abap_char_utilities=>cr_lf && 'autonumber' && cl_abap_char_utilities=>cr_lf.
* first of all, lets get all the objects
LOOP AT if_atra_uml_tool~sat_tab INTO ls_line.
IF ( ls_line-caller(1) = 'Z' OR ls_line-called(1) = 'Z' ) AND ls_line-caller IS NOT INITIAL. ls_object-object = ls_line-caller. ls_object-object_type = ls_line-caller_type. ls_object-instance = ls_line-caller_inst.
IF ls_object-instance <> 0. READ TABLE lt_objects TRANSPORTING NO FIELDS WITH KEY instance = ls_object-instance. ELSE. READ TABLE lt_objects TRANSPORTING NO FIELDS WITH KEY object = ls_object-object instance = ls_object-instance. ENDIF. IF sy-subrc <> 0. APPEND ls_object TO lt_objects. ENDIF.
ls_object-object = ls_line-called. ls_object-object_type = ls_line-called_type. ls_object-instance = ls_line-called_inst.
IF ls_object-instance <> 0. READ TABLE lt_objects TRANSPORTING NO FIELDS WITH KEY instance = ls_object-instance. ELSE. READ TABLE lt_objects TRANSPORTING NO FIELDS WITH KEY object = ls_object-object instance = ls_object-instance. ENDIF. IF sy-subrc <> 0. APPEND ls_object TO lt_objects. ENDIF.
*we do special handling for construction - but don't want to show twice in diagram IF ls_last_call-id = 'R'. "constructor IF ls_line-called_mod = 'CONSTRUCTOR' AND ls_line-called = ls_last_call-called AND ls_line-called_inst = ls_last_call-called_inst. * this line is a duplicate of the previous line DELETE if_atra_uml_tool~sat_tab. ENDIF. ENDIF.
ELSE. * this line does not concern code that we are concerned about documenting DELETE if_atra_uml_tool~sat_tab. ENDIF.
ls_last_call = ls_line. ENDLOOP.
* now there will possibly be some gaps in the sequence if custom code calls standard SAP code * that calls custom code.
if_atra_uml_tool~sat_tab = fill_gaps( if_atra_uml_tool~sat_tab ).
* ok now have all objects, let's go through and put together the diagram ls_last_call-aus_ebene = - 1. lv_first_time = abap_true. LOOP AT if_atra_uml_tool~sat_tab INTO ls_line.
* is this an implicit return? IF ls_line-aus_ebene <= ls_last_call-aus_ebene.
rv_diagram = add_return( iv_diagram = rv_diagram iv_return_to = ls_line-aus_ebene ). ENDIF.
* does caller object already exist? READ TABLE lt_objects ASSIGNING <ls_caller> WITH KEY object = ls_line-caller instance = ls_line-caller_inst.
IF <ls_caller>-code IS INITIAL. lv_next_object = lv_next_object + 1. <ls_caller>-code = lv_next_object. * add participant rv_diagram = add_participant( iv_diagram = rv_diagram is_object = <ls_caller> it_objects = lt_objects iv_first_time = lv_first_time ). lv_first_time = abap_false.
ENDIF. * handle the case of very first call IF ls_first_call IS INITIAL. ls_first_call-code = 1. ls_first_call-sap_code = ls_line-aus_ebene. APPEND ls_first_call TO _t_call_stack. rv_diagram = rv_diagram && 'activate 1' && cl_abap_char_utilities=>cr_lf. ENDIF.
* does called object already exist? READ TABLE lt_objects ASSIGNING <ls_called> WITH KEY object = ls_line-called instance = ls_line-called_inst.
IF <ls_called>-code IS INITIAL. lv_next_object = lv_next_object + 1. <ls_called>-code = lv_next_object. * add participant rv_diagram = add_participant( iv_diagram = rv_diagram is_object = <ls_called> it_objects = lt_objects iv_first_time = abap_false ).
ENDIF.
rv_diagram = add_call( iv_diagram = rv_diagram iv_caller = <ls_caller>-code iv_called = <ls_called>-code is_details = ls_line ).
ls_last_call = ls_line. ENDLOOP.
rv_diagram = add_return( iv_diagram = rv_diagram iv_return_to = ls_first_call-sap_code ).
rv_diagram = rv_diagram && '@enduml' && cl_abap_char_utilities=>newline.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+ * | Instance Public Method ZCL_XU_SEQUENCE_DIAGRAM->IF_ATRA_UML_TOOL~CREATE_UML_DATA * +-------------------------------------------------------------------------------------------------+ * | [--->] IT_SAT_TAB TYPE IF_ATRA_UML_TOOL=>TY_SAT_TAB * +--------------------------------------------------------------------------------------</SIGNATURE> METHOD if_atra_uml_tool~create_uml_data.
* rebuild to export data as UMLet compatible source
if_atra_uml_tool~sat_tab = it_sat_tab.
DATA: lv_filename TYPE string, lv_path TYPE string, lv_fullpath TYPE string.
cl_gui_frontend_services=>file_save_dialog( EXPORTING window_title = 'Save As PlantUML data' " Window Title default_extension = '.txt'" Default Extension * default_file_name = " Default File Name * with_encoding = * file_filter = " File Type Filter Table * initial_directory = " Initial Directory * prompt_on_overwrite = 'X' CHANGING filename = lv_filename " File Name to Save path = lv_path " Path to File fullpath = lv_fullpath " Path + File Name * user_action = " User Action (C Class Const ACTION_OK, ACTION_OVERWRITE etc) * file_encoding = EXCEPTIONS OTHERS = 0 ).
if_atra_uml_tool~fname = lv_fullpath.
if_atra_uml_tool~generate_xmi_file( ) .
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+ * | Instance Public Method ZCL_XU_SEQUENCE_DIAGRAM->IF_ATRA_UML_TOOL~GET_XMI * +-------------------------------------------------------------------------------------------------+ * | [<---] C_DATA TYPE REF TO DATA * +--------------------------------------------------------------------------------------</SIGNATURE> METHOD if_atra_uml_tool~get_xmi.
DATA lv_ref_xstr TYPE REF TO xstring.
FIELD-SYMBOLS <lv_xstr> TYPE xstring.
CREATE DATA lv_ref_xstr. ASSIGN lv_ref_xstr->* TO <lv_xstr>.
TRY. <lv_xstr> = cl_bcs_convert=>string_to_xstring( iv_string = get_sequence_diagram( ) ). CATCH cx_bcs. "#EC NO_HANDLER * if this happens we're buggered. not much to do really
ENDTRY. c_data = lv_ref_xstr.
ENDMETHOD. ENDCLASS.