How to convert an existing ABAP Report to OOPs ABAP?

In the previous article ‘Just 4 Versions of the same program to learn OOPs ABAP‘ we saw how an ABAPer can evolve from the procedural programming to Object Oriented. In this post, we have added on the same program. Instead of just one PARAMETER in the selection screen, we have two SELECT OPTIONS. We have two RADIO BUTTONS which has to be handled and there would be two data fetching SELECTs.

Let us see the existing program which is present in the system. It takes transaction code or role as input and displays the classical report as output. If you scan through the below code, you would find that this is pure procedural language along with some old programming style (internal table with header lines and classical reporting).

Existing Report in traditional ABAP Procedural way

report sy-repid line-size 151
line-count 65
no standard page heading.
*----------------------------------------------------------------------*
* Title: Security report - Roles assigned to
* Transaction Codes or vice versa.
*----------------------------------------------------------------------*
* Function: Generates a report of relationships between Transaction
* Codes and Security Roles. It has options of switching the
* dimensions of the report - i.e. TCODE to ROLE or ROLE to
* TCODE
*----------------------------------------------------------------------*
tables: agr_1251,
agr_texts,
tstct.
*---------------------------------------------------------------------
selection-screen begin of block repopt with frame title text-001.
parameters: ptcode radiobutton group ropt.
select-options: s_tcode for tstct-tcode.
selection-screen skip.
parameters: prole radiobutton group ropt.
select-options: s_role for agr_1251-agr_name.
selection-screen end of block repopt.
*---------------------------------------------------------------------
 
data: begin of t_tcode occurs 0,
tcode like agr_1251-low,
ttext like tstct-ttext,
role like agr_1251-agr_name,
rtext like agr_texts-text,
end of t_tcode.
 
data: begin of t_role occurs 0,
role like agr_1251-agr_name,
rtext like agr_texts-text,
tcode like agr_1251-low,
ttext like tstct-ttext,
end of t_role.
 
*---------------------------------------------------------------------
top-of-page.
*---------------------------------------------------------------------
format color col_heading.
if ptcode = 'X'.
write: / sy-repid, 60 'Transaction Code to Role Matrix',
136 'Date:', sy-datum.
write: /136 'Page:', (10) sy-pagno no-sign.
uline.
write: /(20) 'Txn Code', (36) 'Txn Desc',
60(30)'Role Name', (61) 'Role Desc'.
uline.
else.
write: / sy-repid, 60 'Role to Transaction Code Matrix',
136 'Date:', sy-datum.
write: /136 'Page:', (10) sy-pagno no-sign.
uline.
write: /(30) 'Role Name', (61) 'Role Desc',
(20) 'Txn Code', (37) 'Txn Desc'.
uline.
endif.
format color off.
 
*---------------------------------------------------------------------
start-of-selection.
if ptcode = 'X'.
perform report_by_tcode.
else.
perform report_by_role.
endif.
*---------------------------------------------------------------------
 
*&---------------------------------------------------------------------*
*& Form report_by_tcode
*&---------------------------------------------------------------------*
* Generate report of Roles assigned to TCODEs
*----------------------------------------------------------------------*
form report_by_tcode.
select a~low b~ttext a~agr_name c~text into table t_tcode
from agr_1251 as a
inner join tstct as b on a~low = b~tcode
inner join agr_texts as c on a~agr_name = c~agr_name
where a~low in s_tcode
and a~object = 'S_TCODE'
and b~sprsl = sy-langu
and c~spras = sy-langu
and c~line = '00000'
order by a~low a~agr_name.
loop at t_tcode.
* at new ttext.
write: /(20) t_tcode-tcode, t_tcode-ttext.
* endat.
write: 60 t_tcode-role, (61) t_tcode-rtext.
new-line.
at end of ttext.
skip.
endat.
endloop.
endform. " report_by_tcode
 
*&---------------------------------------------------------------------*
*& Form report_by_role
*&---------------------------------------------------------------------*
* Generate report of TCODEs assigned to Roles
*----------------------------------------------------------------------*
form report_by_role.
select a~agr_name c~text a~low b~ttext into table t_role
from agr_1251 as a
inner join tstct as b on a~low = b~tcode
inner join agr_texts as c on a~agr_name = c~agr_name
where a~agr_name in s_role
and a~object = 'S_TCODE'
and b~sprsl = sy-langu
and c~spras = sy-langu
and c~line = '00000'
order by a~agr_name a~low.
loop at t_role.
* at new rtext.
write: / t_role-role, (61) t_role-rtext.
* endat.
write: 94(20) t_role-tcode, t_role-ttext.
new-line.
at end of rtext.
skip.
endat.
endloop.
endform. " report_by_role

Let us execute the program and see the output.

The above program was developed in 2000 era. In 2016 you receive a Scope Change Request (SCR) to convert the existing report to OOPs ABAP Report. Lucky ABAPers, who are looking for opportunity to implement OOPs theoretical knowledge to actual practical use, would jump off the seat to accept this work.

I am sure, all of you know OOPs concepts and theories right from your school and college days where you learnt C, C++ or Java. But destiny made you work in procedural ABAP. Here, we would not speak about theory. Just look at the above code and start writing your own OOPs program which would do the same thing as the above report. Plan out how you would handle the INITIALIZATION. Do you need any global/public attributes (variables)? Can we reuse one object instance in another object? After all that is one core fundamentals of OOPs.

Try to write your own program whenever you have time. That is the best way and the only way to learn OOPs. After all, you all know theory. Hands On is what differs us from the crowd.

If you have read the previous post on OOPs ABAP, you already know that we have divided our report into three local classes (roughly to follow MVC – Model View Controller Architecture). Class 1 for Selection parameter, Class 2 for Data Fetching and Class 3 for Display.

Before we proceed, let me show you two code snippets.

What is the difference between method FACTORY and VALIDATE_INPUT in the below OOPs Report?

Why is FACTORY method called using ‘=>’ but VALIDATE_INPUT using ‘->’ ?

You guessed it right. One is a STATIC method and another is INSTANCE method. STATIC is called using ‘=>’. You will know this only if you start coding. So log into your system as soon as you get time and try to call one STATIC and one INSTANCE method in a test program.

CALL METHOD cl_salv_table=>factory
IMPORTING
r_salv_table = me->o_salv_display
CHANGING
t_table = me->o_rep_fet->t_tcode.
t_table = me->o_rep_fet->t_tcode.
 
o_selection->validate_input( EXPORTING
ip_tcode_flag = ptcode
ip_role_flag = prole ).

Let us look another section of the program. Why is there no EXPORTING while creating object O_SELECTION, while O_FETCH and O_DISPLAY has EXPORTING parameters? Child’s play for experienced OOPs ABAPers. Please do not laugh at me if you think it is too silly.

Answer for the newbies. We always read in theory, CONSTRUCTOR method is called the moment we create the OBJECT instance. Class LCL_REPORT_FETCH and LCL_REPORT_DISPLAY both have CONSTRUCTOR method which needs some IMPORTING parameters. If you do not pass those parameters while creating the Object, your code will not be syntactically correct.

You need to log into SAP system now. Try the code yourself to see it. These are simple stuff which we cannot learn by reading PPTs and PDFs. We need to make our hands dirty.

*---------------------------------------------------------------------*
* INITIALIZATION *
*---------------------------------------------------------------------*
INITIALIZATION.
CREATE OBJECT o_selection.
CREATE OBJECT o_fetch EXPORTING io_rep_sel = o_selection.
CREATE OBJECT o_display EXPORTING io_rep_fetch = o_fetch.

Finally, why did we NOT default the public attribute (variable) of class LCL_REPORT_SELECTION at INITIALIZATION? Simple, users enter the selection screen options after INITIALIZATION is done. So AT SELECTION-SCREEN is the right place to capture the screen values. Too basic, but not sure why I felt like tell this too.

*----------------------------------------------------------------------*
* AT SELECTION-SCREEN.
*----------------------------------------------------------------------*
AT SELECTION-SCREEN.
 
* Pass to public variables of the class
o_selection->gt_tcode[] = s_tcode[].
o_selection->gt_role[] = s_role[].

Rest all code is pretty straight forward. Give special attention to the CONSTRUCTOR methods of FETCH and DISPLAY class. We have passed the whole object data of SELECTION class to FETCH object. Similarly, the complete data of FETCH object is passed to DISPLAY.

* +---------------------------------------------------------------------------------------+
* | Instance Public Method LCL_REPORT_FETCH->CONSTRUCTOR
* +---------------------------------------------------------------------------------------+
* +---------------------------------------------------------------------------------------+
METHOD constructor.
me->o_rep_sel = io_rep_sel.
ENDMETHOD.
* +---------------------------------------------------------------------------------------+
* | Instance Public Method YCL_REPORT_DISPLAY->CONSTRUCTOR
* +---------------------------------------------------------------------------------------+
* +---------------------------------------------------------------------------------------+
METHOD constructor.
me->o_rep_fet = io_rep_fetch.
ENDMETHOD.

Put the breakpoint at both the CONSTRUCTOR methods. Execute the program and check that the SELECTION object and FETCH object during INITIALIZATION is blank in the two CONSTRUCTOR methods but the container is ready. When you use the other method for FETCH object, the SELECTION container which was blank in INITIALIZATION has data in the container. That is the beauty of OOPs. It is like Pointers in C. The address is populated with the actual data.

Check at initialization, the SELECTION object attributes are empty/blank. But the container is ready.

When the program goes to SELECTION SCREEN, we have logic in place to populate SELECTION object attributes. But we do not pass this attribute explicitly again to FETCH object. But, when we are in another method of FETCH, the SELECTION object attribute which was initialized in CONSTRUCTOR method of FETCH is already populated. This is one concept of OOPs which we have unknowingly used. You need to debug for more clarity.

i.e. the CONTAINER for FETCH which was created in the CONSTRUCTOR method of FETCH during CREATE OBJECT command is automatically populated as soon as the O_SELECTION OBJECT populates the data.

Enough of trailers. Let us reveal the full movie. Please go through the code below. Try to debug every step and understand the concept. I am sure, this is not the best OOPs programming, but it should be sound enough for beginners like me.

I would like to call upon all experienced OOPs ABAPers and enthusiast to point out the issues in the below OOPs Report. Please reveal, how we could have made the Report better in OOPs. Please leave your suggestions so that our freshers in OOPs ABAP could learn from your experience and knowledge.

Same Report in OOPs ABAP.

*----------------------------------------------------------------------*
* Author: www.sapspot.com
* Title: Security report - Roles assigned to
* Transaction Codes or vice versa.
*----------------------------------------------------------------------*
* Desc : Generates a report of relationships between Transaction
* Codes and Security Roles. It has options of switching the
* dimensions of the report - i.e. TCODE to ROLE or ROLE to
* TCODE
*----------------------------------------------------------------------*
* ABAPer who just wrote his Second OOPs development nearly corret
*----------------------------------------------------------------------*
REPORT zsr_tcode_role_matrix_in_oops LINE-SIZE 151
LINE-COUNT 65
NO STANDARD PAGE HEADING.
* +---------------------------------------------------------------------------------------+
* | LCL_REPORT_SELECTION Definition
* +---------------------------------------------------------------------------------------+
CLASS lcl_report_selection DEFINITION.
 
PUBLIC SECTION.
 
TYPES: rt_tcode TYPE RANGE OF tstct-tcode..
TYPES: rt_role TYPE RANGE OF agr_1251-agr_name.
 
DATA gt_tcode TYPE rt_tcode .
DATA gt_role TYPE rt_role .
DATA g_langu TYPE spras .
DATA g_object TYPE agobject .
DATA g_line TYPE menu_num_5 .
DATA g_tcode_or_role TYPE flag.
 
METHODS validate_input
IMPORTING ip_tcode_flag TYPE flag
ip_role_flag TYPE flag.
 
METHODS validate_tcode.
 
METHODS validate_role.
 
PROTECTED SECTION.
PRIVATE SECTION.
 
ENDCLASS.
 
* +---------------------------------------------------------------------------------------+
* | LCL_REPORT_SELECTION Implementation
* +---------------------------------------------------------------------------------------+
CLASS lcl_report_selection IMPLEMENTATION.
* +---------------------------------------------------------------------------------------+
* | Instance Public Method LCL_REPORT_SELECTION->VALIDATE_INPUT
* +---------------------------------------------------------------------------------------+
* +---------------------------------------------------------------------------------------
METHOD validate_input.
 
IF ip_tcode_flag = abap_true.
 
validate_tcode( ).
 
ELSEIF ip_role_flag = abap_true.
 
validate_role( ).
 
ENDIF.
 
ENDMETHOD.
 
METHOD validate_tcode.
 
SELECT COUNT(*) UP TO 1 ROWS
FROM tstc
WHERE tcode IN gt_tcode.
 
IF sy-subrc NE 0.
MESSAGE 'Please enter a Valid T-Code' TYPE 'E'.
 
ELSE.
* Populate the global variable
g_langu = sy-langu.
g_object = 'S_TCODE'.
g_line = '00000'.
 
ENDIF.
 
ENDMETHOD.
 
METHOD validate_role.
 
SELECT COUNT(*) UP TO 1 ROWS
FROM agr_define
WHERE agr_name IN gt_role.
 
IF sy-subrc NE 0.
MESSAGE 'Please enter a Valid Role' TYPE 'E'.
 
ELSE.
* Populate the global variable
g_langu = sy-langu.
g_object = 'S_TCODE'.
g_line = '00000'.
ENDIF.
 
ENDMETHOD.
 
ENDCLASS.
 
* +---------------------------------------------------------------------------------------+
* | LCL_REPORT_FETCH Definition
* +---------------------------------------------------------------------------------------+
CLASS lcl_report_fetch DEFINITION.
 
PUBLIC SECTION.
 
TYPES:
*----------------------------------------------------------------------*
* TYPE Declaration
*----------------------------------------------------------------------*
BEGIN OF ty_tcode,
tcode TYPE tcode, " Transaction
ttext TYPE ttext_stct, " Transaction Text
role TYPE agr_name, " Role Name
rtext TYPE agr_title, " Short Description
END OF ty_tcode .
 
TYPES:
tt_tcode TYPE STANDARD TABLE OF ty_tcode .
 
DATA o_rep_sel TYPE REF TO lcl_report_selection .
DATA t_tcode TYPE tt_tcode .
 
METHODS fetch_data
IMPORTING ip_tcode_flag TYPE flag
ip_role_flag TYPE flag.
 
METHODS fetch_by_tcode
IMPORTING ip_tcode_flag TYPE flag.
 
METHODS fetch_by_role
IMPORTING ip_role_flag TYPE flag.
 
METHODS constructor
IMPORTING
!io_rep_sel TYPE REF TO lcl_report_selection .
 
PROTECTED SECTION.
 
PRIVATE SECTION.
 
ENDCLASS.
 
* +---------------------------------------------------------------------------------------+
* | LCL_REPORT_FETCH Implementation
* +---------------------------------------------------------------------------------------+
CLASS lcl_report_fetch IMPLEMENTATION.
* +---------------------------------------------------------------------------------------+
* | Instance Public Method LCL_REPORT_FETCH->CONSTRUCTOR
* +---------------------------------------------------------------------------------------+
* +---------------------------------------------------------------------------------------
METHOD constructor.
me->o_rep_sel = io_rep_sel.
ENDMETHOD.
 
* +---------------------------------------------------------------------------------------+
* | Instance Public Method LCL_REPORT_FETCH->FETCH_DATA
* +---------------------------------------------------------------------------------------+
* +---------------------------------------------------------------------------------------
METHOD fetch_data.
 
fetch_by_tcode( ip_tcode_flag ).
 
fetch_by_role( ip_role_flag ).
 
ENDMETHOD.
 
METHOD fetch_by_tcode.
 
IF ip_tcode_flag = abap_true.
 
SELECT a~low b~ttext a~agr_name c~text INTO TABLE t_tcode
FROM agr_1251 AS a
INNER JOIN tstct AS b ON a~low = b~tcode
INNER JOIN agr_texts AS c ON a~agr_name = c~agr_name
WHERE a~low IN me->o_rep_sel->gt_tcode
AND a~object = me->o_rep_sel->g_object
AND b~sprsl = me->o_rep_sel->g_langu
AND c~spras = me->o_rep_sel->g_langu
AND c~line = me->o_rep_sel->g_line
ORDER BY a~low a~agr_name.
 
ENDIF.
 
ENDMETHOD.
 
METHOD fetch_by_role.
 
IF ip_role_flag = abap_true.
 
SELECT a~low b~ttext a~agr_name c~text INTO TABLE t_tcode
FROM agr_1251 AS a
INNER JOIN tstct AS b ON a~low = b~tcode
INNER JOIN agr_texts AS c ON a~agr_name = c~agr_name
WHERE a~agr_name IN me->o_rep_sel->gt_role
AND a~object = me->o_rep_sel->g_object
AND b~sprsl = me->o_rep_sel->g_langu
AND c~spras = me->o_rep_sel->g_langu
AND c~line = me->o_rep_sel->g_line
ORDER BY a~low a~agr_name.
 
ENDIF.
 
ENDMETHOD.
 
ENDCLASS.
 
* +---------------------------------------------------------------------------------------+
* | LCL_REPORT_DISPLAY Definition
* +---------------------------------------------------------------------------------------+
CLASS lcl_report_display DEFINITION.
 
PUBLIC SECTION.
 
DATA o_rep_fet TYPE REF TO lcl_report_fetch .
DATA o_salv_display TYPE REF TO cl_salv_table.
 
METHODS display_alv .
METHODS constructor
IMPORTING
!io_rep_fetch TYPE REF TO lcl_report_fetch .
 
PROTECTED SECTION.
 
PRIVATE SECTION.
 
ENDCLASS.
 
* +---------------------------------------------------------------------------------------+
* | LCL_REPORT_DISPLAY Implementation
* +---------------------------------------------------------------------------------------+
CLASS lcl_report_display IMPLEMENTATION.
 
* +---------------------------------------------------------------------------------------+
* | Instance Public Method YCL_REPORT_DISPLAY->CONSTRUCTOR
* +---------------------------------------------------------------------------------------+
* +---------------------------------------------------------------------------------------
METHOD constructor.
me->o_rep_fet = io_rep_fetch.
ENDMETHOD.
 
* +---------------------------------------------------------------------------------------+
* | Instance Public Method YCL_REPORT_DISPLAY->DISPLAY_ALV
* +---------------------------------------------------------------------------------------+
* +---------------------------------------------------------------------------------------
METHOD display_alv.
 
TRY.
CALL METHOD cl_salv_table=>factory
IMPORTING
r_salv_table = me->o_salv_display
CHANGING
t_table = me->o_rep_fet->t_tcode.
 
me->o_salv_display->display( ).
 
CATCH cx_salv_msg .
ENDTRY.
 
ENDMETHOD.
 
ENDCLASS.
 
*----------------------------------------------------------------------*
* TABLES declaration
*----------------------------------------------------------------------*
TABLES: agr_1251,
agr_texts,
tstct.
 
*----------------------------------------------------------------------*
* Define the Object Data
*----------------------------------------------------------------------*
DATA:
o_selection TYPE REF TO lcl_report_selection,
o_fetch TYPE REF TO lcl_report_fetch,
o_display TYPE REF TO lcl_report_display.
 
*----------------------------------------------------------------------*
* SELECTION SCREEN
*----------------------------------------------------------------------*
SELECTION-SCREEN BEGIN OF BLOCK repopt WITH FRAME TITLE text-001.
 
PARAMETERS: ptcode RADIOBUTTON GROUP ropt.
SELECT-OPTIONS: s_tcode FOR tstct-tcode.
 
SELECTION-SCREEN SKIP.
 
PARAMETERS: prole RADIOBUTTON GROUP ropt.
SELECT-OPTIONS: s_role FOR agr_1251-agr_name.
 
SELECTION-SCREEN END OF BLOCK repopt.
 
*---------------------------------------------------------------------*
* INITIALIZATION *
*---------------------------------------------------------------------*
INITIALIZATION.
CREATE OBJECT o_selection.
CREATE OBJECT o_fetch EXPORTING io_rep_sel = o_selection.
CREATE OBJECT o_display EXPORTING io_rep_fetch = o_fetch.
 
*----------------------------------------------------------------------*
* AT SELECTION-SCREEN.
*----------------------------------------------------------------------*
AT SELECTION-SCREEN.
 
* Pass to public variables of the class
o_selection->gt_tcode[] = s_tcode[].
o_selection->gt_role[] = s_role[].
 
* Validation at Selection Screen
o_selection->validate_input( EXPORTING
ip_tcode_flag = ptcode
ip_role_flag = prole ).
 
*----------------------------------------------------------------------*
* START-OF-SELECTION
*----------------------------------------------------------------------*
START-OF-SELECTION.
 
o_fetch->fetch_data( EXPORTING
ip_tcode_flag = ptcode
ip_role_flag = prole ).
 
**----------------------------------------------------------------------*
** END-OF-SELECTION
**----------------------------------------------------------------------*
START-OF-SELECTION.
o_display->display_alv( ).

Needless to say, the output would be same. We are displaying an ALV report instead of classical report.

We cannot change any habit overnight. Similarly, an ABAPer who is accustomed to procedural language may not be comfortable with OOPs ABAP in a day or two. It is a continuous process of learning from the mistake and improving oneself.

So do not wait further. Let your starting few OOPs programs be very basic. But let us make a habit to change from procedural to object oriented.