Technical Articles
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?
If you are already on HANA, you can use the search in Eclipse: https://blogs.sap.com/2014/01/23/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:
- 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.
Update 19.02.2020: I recently discovered that someone called “sap-russia” copied my code and cloned the SAP standard code to provide a “ready-to-use” Z-program for code search (without asking me). It is available via ABAPgit or copy&paste here: https://github.com/sap-russia/ZRS_ABAP_SOURCE_SCAN
Wow, that is great. I am using RS_ABAP_SOURCE_SCAN almost daily. Thank you for sharing!
I will try to implement it when I get more time.
I'd definitely amend the original. Clones cause so many problems - although RS_ABAP_SOURCE_SCAN ever changing seems very unlikely!
I share your opinion. I will edit the text to make that clear.
Hi Edo,
with HANA you can also use the Source code search in Eclipse. See my blog post about it:
https://blogs.sap.com/2014/01/23/abap-sourcecode-search/
Regards,
Thomas.
thanks Thomas, I adapted the title and text
Best regards, Edo
Hi Thomas,
we are now in the upgrade project to HANA, and I finally tested the Eclipse source code search.
Wow!!! That is so fast!!! About 2 seconds to search through all our AND all SAP coding!!! (RS_ABAP_SOURCE_SCAN with my extensions takes about 5 Minutes for all our coding.)
One feature wish, that would save me a lot of typing:
A history box for "Search Filter", like there is for "Search Term".
Because 98% of the time, I only want to search our coding, so I enter: package:z_draeger. But I do not like to type this again and again.
I assume that most SAP customers (on HANA) have the same wish.
Best regards,
Edo
Nice one. Personally I do not use that often the code search and so I’m curious to know, what you guys are searching with it. Do you have some common Comments to get all on one screen or what do I need to imagine at that moment?
~florian
Hi Florian,
we have legacy implementations of (SAP-)customer-specific logic, that are distributed over multiple places (Z reports, User Exits, Modifications). Usually this involves a specific criterium, for example a specific order reason (VBAK-AUGRU). So the related coding will have IF or CASE statements with the order reason e.g. 'Z16' (or a corresponding constant).
In this context, it is really important to search the modifications and enhancements as well, not just the "normal" Z coding.
I think the code search is most often used in analysis, when you are maintaining or cleaning up legacy code (that is not perfectly documented).
Best regards, Edo
Hi Edo,
Good work on the enhancement to the search scan tool, I will give it a try.
One approach that I use is to link enhancements or modifications to a custom Checkpoint Group created in transaction SAAB (e.g. Z_MOD_FOR_Z16). This means adding one line to each enhancement / modification:
or
There are some benefits to this:
Regards,
Jonathan
Hi Jonathan,
yes, that is a useful Approach with LOG- and BREAK-POINT ID in enhancements and modifications.
I use ASSERT ID a lot with a checkpoint Group that dumps on the Quality/Test System, but only logs on the production System. In this way, I find out about "unexpected situations" (potential Bugs) quickly, but do not risk disruptions in the production System.
Best regards,
Edo
Hello Edo
Great effort! Maybe SAP can provide enhancement points in the standard program for your modification or even upgrade the code with your development. I think this is useful for all customers.
Cheers
Heiko
Thanks for putting this together, Edo!
I'll find out what my colleagues think about this modification and whether or not we'd actually be making regular use of the added feature. This in any case seems a lot less involved than enabling the switch framework Thomas mentions upthread and the high memory usage that entails - and in systems where such a search wouldn't really be executed anyway.
Cheers
Bärbel
Interesting!! Thanks for your lucid explanation.
Hi Edo. This enhancement to search is excellent! It really saved me from a lot of trouble. Thank you so much for your efforts.
There is one little bug: If an include is found both in the standard search and in add_modification_includes method, it gets duplicated in the output. At the end of lcl_source_scan=>get_includes method, you should do a cleanup on gt_object table.
Hi Mehmet,
Glad you find it useful!
I think you are right about the bug. My version of the code has evolved from the version published here (I added an ALV display option, and fields for usage information from SCMON), and in that newer version I remove duplicates at a later stage, in the findings table.
It is better to remove them at the source, of course. But I do not want to adapt the version published here, because I do not want to retest. It's a minor bug, anyway.
Hi Edo,
this is an excellent piece of work; I wish I'd found it when it was first posted!
And of course SAP have now done something similar (Notes 2927009 and 3137413), although I don't know (yet) if their version is as useful as yours.
More power to you.
Best regards,
John Moulding.