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.