Friday, 21 September 2018

Code Search in Modifications and Enhancements (without HANA)

As a long time SAP customer, you usually have a lot of code modifications and enhancements.

Wouldn’t it be useful sometimes to be able to search that code?

In this video you will see how you can use the new ABAP Sourcecode Search:


But our main system ist not yet running HANA.

So, I modified SAPs standard search report RS_ABAP_SOURCE_SCAN to search for modifications and enhancements. I will tell you in this blog what to do, so you can use the same function in your system.

Modifications in 5 places in the code are needed.

First, we need to add a selection parameter for the new Option.

I inserted this after the block with the search string (a05), after line 28 (in the unmodified source).

                  BEGIN OF BLOCK zz1 WITH FRAME TITLE TEXT-zz1.
SELECTION-SCREEN: BEGIN OF LINE.
PARAMETERS:       p_zz_mod TYPE xfeld AS CHECKBOX.
SELECTION-SCREEN: COMMENT (79) TEXT-zz3.
SELECTION-SCREEN: END OF LINE.
SELECTION-SCREEN: END OF BLOCK zz1,
Of course, you also need to add the texts. I use the following:

SAP ABAP Development, SAP ABAP Tutorial and Materials, SAP ABAP Study Materials

◈ p_zz_mod: DUMMY – see text-zz3
◈ text-zz1: Special Options
◈ text-zz3: Also search all modif., and enhancements for selected packages (default Z* Y*)

Next is the definition of the local class that contains the logic.

I put this behind SAPs definition of local class lcl_source_scan (line 229 in the unmodified source):

CLASS lcl_zz DEFINITION FINAL.
  PUBLIC SECTION.
    TYPES: BEGIN OF tp_dynpro, " needs to be identical to ty_dynpro in lcl_source_scan
             repname LIKE d020s-prog,
             dynnr   LIKE d020s-dnum,
           END OF tp_dynpro.
    TYPES: ttp_dynpro TYPE STANDARD TABLE OF tp_dynpro WITH DEFAULT KEY.
    CLASS-METHODS:
      add_modification_includes
        CHANGING c_includes TYPE prognames
                 c_screens  TYPE ttp_dynpro
    , add_enhancement_includes
        CHANGING c_includes TYPE prognames
    , filter_modifications
        IMPORTING i_include     TYPE progname
                  i_screen      TYPE sy-dynnr
                  i_source_tab   TYPE abaptxt255_tab
        CHANGING  c_findings TYPE match_result_tab
    .
  PRIVATE SECTION.
    TYPES: BEGIN OF tp_mod_include
         , include TYPE reposrc-progname
         , without_assistant TYPE abap_bool
         , END OF tp_mod_include
         , ttp_mod_includes TYPE STANDARD TABLE OF tp_mod_include WITH KEY include

         , BEGIN OF tp_mod_screen
         ,   program TYPE d020s-prog
         ,   screen  TYPE d020s-dnum
         ,   without_assistant TYPE abap_bool
         , END OF tp_mod_screen
         , ttp_mod_screens TYPE STANDARD TABLE OF tp_mod_screen WITH KEY program
         .
    CLASS-METHODS:
      get_method_include
        IMPORTING i_class        TYPE seoclsname
                  i_method       TYPE seocpdname
        RETURNING VALUE(r_include) TYPE progname
    , get_function_include
        IMPORTING i_function     TYPE tfdir-funcname
        RETURNING VALUE(r_include) TYPE progname
    , get_all_includes_and_screens
        IMPORTING i_type TYPE smodilog-obj_type
                  i_name TYPE smodilog-obj_name
        EXPORTING e_includes TYPE prognames
                  e_screens TYPE ttp_mod_screens
    , get_screen_flow_logic
        IMPORTING i_screen TYPE tp_mod_screen
        RETURNING VALUE(r_source_tab) TYPE d022s_t
    .
    CLASS-DATA:
      s_mod_includes TYPE ttp_mod_includes
    , s_mod_screens  TYPE ttp_mod_screens
    .
ENDCLASS.

Because I wanted to modify the report in a “minimally invasive” way, the additional logic is inserted in two places:

◈ Where the includes to be searched are collected
◈ And then, after an include has been searched, to filter out the search results that occur in the unmodified parts of the code.

The next position to modify code is for this filtering. It is in method search_source, after the two FIND ALL statements, before the CHECK lt_results IS NOT INITIAL (in the unmodified report, line 946).

Code to be inserted:

        IF p_zz_mod = abap_true AND lt_results IS NOT INITIAL.
          lcl_zz=>filter_modifications( EXPORTING i_include     = gv_report
                                                  i_screen      = gv_dynpro
                                                  i_source_tab   = gt_source
                                        CHANGING  c_findings = lt_results ).
        ENDIF.

And then, at the end of method get_source_names (line 1189 in the unmodified code):

    IF p_zz_mod = abap_true.
      lcl_zz=>add_enhancement_includes( CHANGING c_includes = gt_object ).
      lcl_zz=>add_modification_includes( CHANGING c_includes = gt_object
                                                  c_screens  = gt_dynpro ).
    ENDIF.

Finally, at the end of the report, the class implementation:

CLASS lcl_zz IMPLEMENTATION.
  METHOD add_enhancement_includes.
    DATA devclass_range TYPE RANGE OF tadir-devclass.
    IF devclass IS INITIAL. " if no devclass restriction on selection screen, assume the user only wants the customer (Z and Y) enhancements
      devclass_range = VALUE #( sign = 'I' option = 'CP' ( low = 'Z*' ) ( low = 'Y*' ) ).
    ELSE.
      devclass_range = devclass[].
    ENDIF.
    SELECT obj_name
      FROM tadir
      WHERE pgmid  = 'R3TR'
      AND   object = 'ENHO'
      AND   devclass IN @devclass_range
      AND   delflag = ''
      INTO TABLE @data(enhancements).
    LOOP AT enhancements INTO DATA(enh).
      TRANSLATE enh(30) USING ' ='.
      enh+30 = 'E'.
      APPEND enh TO c_includes.
    ENDLOOP.
  ENDMETHOD.

  METHOD add_modification_includes.
    DATA include TYPE progname.
    CLEAR: s_mod_includes, s_mod_screens.
    IF sy-batch IS INITIAL.
      CALL FUNCTION 'SAPGUI_PROGRESS_INDICATOR'
        EXPORTING
          text = 'GET MODIFICATION INCLUDES...'.
    ENDIF.
    SELECT DISTINCT obj_type, obj_name, sub_type, sub_name, operation, prot_only AS without_assistant
      FROM smodilog
      WHERE NOT operation IN ('MIGR', 'IMPL', 'TRSL', 'NOTE')  "in sync with SE95 and Clone Finder
          AND inactive = ''
          AND int_type NOT IN ('DUMY') "clone finder also ignores 'XXXX'=without modification assistant, but we think this is wrong
          AND obj_type IN ('PROG', 'LDBA',          " LDBA = logical database, similar to PROG
                           'FUGR', 'FUGX', 'FUGS',  " FUGX and FUGS = parts of function groups for user exits
                           'CLAS')
          AND sub_type NOT IN ( 'REPT', 'FUGT', 'CUAD', 'DOCU', 'VARI' )
      ORDER BY obj_type, obj_name, sub_type, sub_name, prot_only
      INTO TABLE @DATA(modifications).

    LOOP AT modifications REFERENCE INTO DATA(mod).
      CASE mod->sub_type.
        WHEN 'REPS'. " report source
          s_mod_includes = VALUE #( BASE s_mod_includes
                                   ( include           = mod->sub_name
                                     without_assistant = mod->without_assistant ) ).
        WHEN 'METH'.
          s_mod_includes = VALUE #( BASE s_mod_includes
                                   ( include           = get_method_include( i_class  = EXACT #( mod->obj_name )
                                                                             i_method = EXACT #( mod->sub_name+30 ) )
                                     without_assistant = mod->without_assistant ) ).
        WHEN 'FUNC'.
          s_mod_includes = VALUE #( BASE s_mod_includes
                                   ( include           = get_function_include( i_function = EXACT #( mod->sub_name ) )
                                     without_assistant = mod->without_assistant ) ).
        WHEN 'LDBA'. " logical databases. Seem to have these two code objects
          s_mod_includes = VALUE #( BASE s_mod_includes
                                    ( include           = mod->sub_name && 'SEL'
                                      without_assistant = mod->without_assistant )
                                    ( include           = 'SAP' && mod->sub_name
                                      without_assistant = mod->without_assistant ) ).
        WHEN 'CLAS'. " complete class
          cl_oo_classname_service=>get_all_class_includes( EXPORTING class_name = EXACT #( mod->obj_name )
                                                           RECEIVING result     = DATA(class_includes)
                                                           EXCEPTIONS OTHERS    = 0 ).
          DELETE class_includes WHERE table_line+30(2) = 'CS' OR table_line+30(2) = 'CP'.  " CS = complete source, CP = frameprogram for class pool
          s_mod_includes = VALUE #( BASE s_mod_includes
                                    FOR class_incl IN class_includes
                                    ( include           = class_incl
                                      without_assistant = mod->without_assistant ) ).
        WHEN 'CINC'. " class include
          s_mod_includes = VALUE #( BASE s_mod_includes
                                   ( include           = EXACT #( mod->sub_name )
                                     without_assistant = mod->without_assistant ) ).
        WHEN 'CPUB' OR 'CPRO' OR 'CPRI'. " class public/protected/private definitions
          include = mod->sub_name.
          TRANSLATE include USING ' ='.
          include+30 =  SWITCH #( mod->sub_type WHEN 'CPUB' THEN 'CU'
                                                WHEN 'CPRO' THEN 'CO'
                                                WHEN 'CPRI' THEN 'CI' ).
          s_mod_includes = VALUE #( BASE s_mod_includes
                                   ( include           = include
                                     without_assistant = mod->without_assistant ) ).
        WHEN 'FUGR' OR 'PROG'. " complete report or function group
          get_all_includes_and_screens( EXPORTING i_type = mod->sub_type
                                                  i_name = EXACT #( mod->sub_name )
                                        IMPORTING e_includes = DATA(includes)
                                                  e_screens  = DATA(screens) ).
          s_mod_includes = VALUE #( BASE s_mod_includes
                                    FOR incl IN includes
                                    ( include           = incl
                                      without_assistant = mod->without_assistant ) ).
          s_mod_screens = VALUE #( BASE s_mod_screens
                                   FOR scr IN screens
                                   ( program           = scr-program
                                     screen            = scr-screen
                                     without_assistant = mod->without_assistant ) ).
        WHEN 'DYNP'. " screen (dynpro) - the program only searches through the flow logic (code)
          s_mod_screens = VALUE #( BASE s_mod_screens
                                   ( program           = mod->sub_name(40)
                                     screen            = mod->sub_name+40(4)
                                     without_assistant = mod->without_assistant ) ).
        WHEN OTHERS.
          LOG-POINT ID zlog FIELDS mod->obj_type mod->obj_name mod->sub_type mod->sub_name. " unknown subtype of modification
      ENDCASE.
    ENDLOOP.

    " pass result to caller, but also keep our own data, to be used later for filtering
    SORT s_mod_includes BY include.                 " for binary search later
    DELETE ADJACENT DUPLICATES FROM s_mod_includes.
    SORT s_mod_screens BY program screen.           " for binary search later
    DELETE ADJACENT DUPLICATES FROM s_mod_screens COMPARING program screen.  " there are duplicates here because of the way SMODILOG is used
    c_includes = VALUE #( BASE c_includes
                             FOR incl_ IN s_mod_includes
                             ( incl_-include ) ).
    c_screens = VALUE #( BASE c_screens
                             FOR scr_ IN s_mod_screens
                             ( repname = scr_-program
                               dynnr   = scr_-screen ) ).
  ENDMETHOD.

  METHOD get_method_include.
    DATA: generic_instance TYPE REF TO if_oo_clif_incl_naming
          , class_instance TYPE REF TO if_oo_class_incl_naming.
    cl_oo_include_naming=>get_instance_by_name( EXPORTING  name           = i_class
                                                RECEIVING  cifref         = generic_instance
                                                EXCEPTIONS no_objecttype  = 1
                                                           internal_error = 2
                                                           OTHERS         = 3 ).
    class_instance ?= generic_instance.
    class_instance->get_include_by_mtdname( EXPORTING  mtdname                      = i_method
                                                       with_enhancements            = abap_true
                                                       with_alias_resolution        = abap_true
                                            RECEIVING  progname                     = r_include
                                            EXCEPTIONS internal_method_not_existing = 1
                                                       OTHERS                       = 2 ).
    ASSERT sy-subrc = 0. " method not found
  ENDMETHOD.

  METHOD get_function_include.
    SELECT SINGLE pname
      FROM tfdir
      WHERE funcname = @i_function
      INTO @r_include.
    ASSERT sy-subrc = 0.
  ENDMETHOD.

  METHOD get_all_includes_and_screens.
    IF i_type = 'PROG'.
      DATA(frame_program) = i_name.
    ELSE.
      DATA(function_group) = EXACT rs38l_area( i_name ).
      CALL FUNCTION 'FUNCTION_INCLUDE_CONCATENATE'
        CHANGING
          program       = frame_program
          complete_area = function_group
        EXCEPTIONS
          OTHERS        = 1.
      ASSERT sy-subrc = 0.
    ENDIF.

    CALL FUNCTION 'RS_GET_ALL_INCLUDES'
      EXPORTING
        program    = frame_program
      TABLES
        includetab = e_includes
      EXCEPTIONS
        OTHERS     = 0.
    DELETE e_includes WHERE table_line CP 'LSVIM*'.  "remove reused includes from maintenance views
    SORT e_includes.
    DELETE ADJACENT DUPLICATES FROM e_includes.

    SELECT prog AS program, dnum AS screen
      FROM d020s
      WHERE prog = @frame_program
      INTO CORRESPONDING FIELDS OF TABLE @e_screens.
  ENDMETHOD.

  METHOD get_screen_flow_logic.
    DATA: header TYPE d020s
        , fields TYPE STANDARD TABLE OF d021s
        , parameters TYPE STANDARD TABLE OF d023s
        , BEGIN OF screen
        ,   prog TYPE d020s-prog
        ,   dnum TYPE d020s-dnum
        , END OF screen
        .
    screen = VALUE #( prog = i_screen-program
                      dnum = i_screen-screen ).
    IMPORT DYNPRO header fields r_source_tab parameters ID screen.
  ENDMETHOD.

  METHOD filter_modifications.
    DATA in_modified_section TYPE abap_bool.

    IF i_screen IS INITIAL.
      READ TABLE s_mod_includes WITH KEY include = i_include REFERENCE INTO DATA(mod_incl) BINARY SEARCH.
      CHECK sy-subrc = 0.                             " do not filter findings of unmodified code
      CHECK mod_incl->without_assistant = abap_false. " do not filter findings of code modified without assistant
    ELSE.
      READ TABLE s_mod_screens WITH KEY program = i_include screen = i_screen REFERENCE INTO DATA(mod_scr) BINARY SEARCH.
      CHECK sy-subrc = 0.                             " do not filter findings of unmodified code
      CHECK mod_scr->without_assistant = abap_false.  " do not filter findings of code modified without assistant
    ENDIF.

    DATA(next_finding_index) = 1.
    READ TABLE c_findings INDEX 1 REFERENCE INTO DATA(next_finding).
    LOOP AT i_source_tab REFERENCE INTO DATA(source).
      DATA(source_index) = sy-tabix.
      IF source->*(2) = '*{'.
        IF in_modified_section = abap_true.
          LOG-POINT ID zlog FIELDS i_include i_screen. " modified include with irregular *{ *} pattern - can happen when modified code is copied into a modification
          RETURN.                                      " the best way can do is to leave the remaining source code findings unfiltered
        ELSE.
          in_modified_section = abap_true.
        ENDIF.
      ELSEIF source->*(2) = '*}'.
        IF in_modified_section = abap_false.
          LOG-POINT ID zlog FIELDS i_include i_screen. " modified include with irregular *{ *} pattern - can happen when modified code is copied into a modification
          RETURN.                                      " the best way can do is to leave the remaining source code findings unfiltered
        ELSE.
          in_modified_section = abap_false.
        ENDIF.
      ENDIF.
      DO.  " loop necessary, because there can be multiple findings for same source line
        IF source_index <> next_finding->line.
          EXIT.
        ENDIF.
        IF in_modified_section = abap_true.
          ADD 1 TO next_finding_index.                   " leave current, continue with next
        ELSE.
          DELETE c_findings INDEX next_finding_index. " delete current, continue with next
        ENDIF.
        READ TABLE c_findings INDEX next_finding_index REFERENCE INTO next_finding.
        IF sy-subrc <> 0.
          RETURN.
        ENDIF.
      ENDDO.
    ENDLOOP.
  ENDMETHOD.
ENDCLASS.

There are 3 statements “LOG-POINT ID zlog …” in the local class implementation. My intention is to have a technical information channel for “strange situations”. For this to work, you need to create a checkpoint group zlog in transaction SAAB (or replace zlog with your own, existing checkpoint group).

Alternatively, you could replace these with an ASSERT, or with a CHECK, or a MESSAGE statement(depending on the context in which the report is used in your company, and your preferred error handling style).

Implementation Notes:

◈ Of course, instead of modifying the SAP report, you could also copy and adapt it (cloning). You could even do that as a local object ($TMP) in the development system, to avoid possible overhead. 
In general though, I would prefer modification over cloning, because cloning comes with potential future cost/effort/risk (if SAP improves or bugfixes the standard code in future releases, and the clone code stays behind, uninformed).

◈ We have NetWeaver release 7.50, and I also use the new language features. If you have an older release, you should be able to adapt the Code, so it works with older versions of RS_ABAP_SOURCE_SCAN and ABAP.

◈ I also modified the sequence of the radio buttons, putting rb_all first, so this (search ABAP and screen flow logic) is the default

Usage Notes:

◈ If no package selection is given, enhancements are searched in all packages Z* and Y.

◈ For modifications, the program tries to search only the modified sections. However, if the modification assistant is switched off, or if there are irregular *{ patterns (through copying of modified code), the complete text is searched.

◈ Disclaimer: As usual, I cannot guarantee that the code is 100% bug-free.

No comments:

Post a Comment