You are on page 1of 22

MODERN ABAP PROGRAMMING

CD266
Exercises / Solutions Horst Keller, Jens Lieberum / SAP AG

SYSTEM INFORMATION
Logon to system M51, client 100. Your user ID is CD266_Y_XX where Y is a character (A, B, C, D ...) that is announced during the workshop and XX is your group number. Your initial password is SAPINIT. Use EN as the logon language. You will work with temporary programs in package $TMP. Templates and solutions are available in package TEST_CD266_TECHED_2011.

Exercise 1: From Function Groups and Subroutines to Classes and Methods


Scenario
There is an existing function module ZCALCULATOR_UI that encapsulates the user interface of a (pocket) calculator. This function module imports the contents of the calculators display and exports a function code for each button selected by the user. A program calling the function module can implement the functionality of the calculator. Such a program is ZCALCULATOR_OBSOLETE that is available in the system. If you execute the program you can play around with the calculator. Please note that the program serves demonstration purposes only - we do not claim that the calculator works perfectly. The functions of the calculator are implemented in subroutines that are called in dependence from the function codes returned by the function module. Now imagine that the owner of the function module has declared its direct usage as deprecated and that he or she has wrapped it in a global class ZCL_CALCULATOR_UI. This class has a static method GET_INSTANCE that returns a reference to an object of the class, an instance method SHOW that displays the calculators UI, a private instance attribute for the contents of the display that can be set via an instance method SET, several events that are triggered when the user selects a function of the calculator. Each event exports the contents of the display and the selected function code.

Task
Re-implement the functionality of program ZCALCULATOR_OBSOLETE in ZFIRST_CALCULATOR_Y_XX where _Y_XX is the postfix of your user ID. To do so, you have to create a (local) class that retrieves a reference to an object of ZCL_CALCULATOR_UI, invokes its method SHOW, provides and implements event handler methods for all possible events. For a quick start the implementation of the event handler methods can be copied mostly from the subroutines in ZCALCULATOR_OBSOLETE. Instead of changing the display field directly, you must write it back to the instance of ZCL_CALCULATOR_UI. A first solution can be found in program ZCALCULATOR_QUAINT in the system.

Existing Program ZCALCULATOR_OBSOLETE


REPORT zcalculator_obsolete. DATA: display(18), function TYPE syst-ucomm, value1 TYPE f, value2 TYPE f, memory TYPE f, operation(10), clear_flag, lock_flag.

DO. CALL FUNCTION 'ZCALCULATOR_UI' EXPORTING display = display IMPORTING function = function. REPLACE 'F_' WITH `` INTO function.

IF display = 'Error' AND function(2) <> 'CA'. CONTINUE. ENDIF. CASE function. WHEN 'CANCEL'. LEAVE PROGRAM. WHEN '0' OR '1' OR '2' OR '3' OR '4' OR '5' OR '6' OR '7' OR '8' OR '9' OR '.'. PERFORM number_input USING function. WHEN '1/X' OR '+-'. PERFORM change_value USING function.

WHEN '+' OR '-' OR '*' OR '/' OR '='. PERFORM calculate. WHEN 'MC' OR 'MR' OR 'MS' OR 'MP' OR 'MM'. PERFORM handle_memory USING function. WHEN 'C' OR 'CA'. PERFORM clear USING function. ENDCASE. ENDDO. FORM number_input USING function. DATA digits LIKE display. CHECK lock_flag = ' '. IF clear_flag = 'X'. CLEAR display. CLEAR clear_flag. ENDIF. IF function = '.' AND display CS '.'. EXIT. ELSE. MOVE display TO digits. CONDENSE digits. REPLACE: '.' WITH `` INTO digits, '-' WITH `` INTO digits. IF strlen( digits ) > 15. EXIT. ENDIF. ENDIF. CONDENSE display. CONCATENATE display function INTO display. WRITE display TO display RIGHT-JUSTIFIED. ENDFORM. FORM change_value USING function. DATA result TYPE f. MOVE display TO result. CASE function. WHEN '1/X'. IF result <> 0. COMPUTE result = 1 / result. ELSE. MOVE 'Error' TO display. EXIT. ENDIF. WHEN '+-'. COMPUTE result = -1 * result. ENDCASE. PERFORM write_display USING result. MOVE 'X' TO lock_flag. IF operation = '='. CLEAR operation. ENDIF. ENDFORM. FORM calculate. DATA result TYPE f. MOVE display TO value2. CATCH SYSTEM-EXCEPTIONS arithmetic_errors = 8. CASE operation. WHEN '+'. COMPUTE result = value1 + value2. WHEN '-'. COMPUTE result = value1 - value2. WHEN '*'. COMPUTE result = value1 * value2. WHEN '/'. COMPUTE result = value1 / value2. WHEN '='. MOVE value1 TO result. WHEN OTHERS. MOVE display TO result. ENDCASE. ENDCATCH. IF sy-subrc = 8. MOVE 'Error' TO display.

"#EC NOTEXT

"#EC NOTEXT

EXIT. ENDIF. MOVE result TO value1. CLEAR value2. PERFORM write_display USING result. MOVE function TO operation. MOVE 'X' TO clear_flag. IF function = '='. MOVE 'X' TO lock_flag. ELSE. MOVE ' ' TO lock_flag. ENDIF. ENDFORM. FORM handle_memory USING function. DATA value TYPE f. value = display. CASE function. WHEN 'MC'. CLEAR memory. WHEN 'MR'. IF lock_flag = ' '. MOVE memory TO value. PERFORM write_display USING value. MOVE 'X' TO lock_flag. ENDIF. WHEN 'MS'. MOVE value TO memory. WHEN 'MP'. ADD value TO memory. WHEN 'MM'. SUBTRACT value FROM memory. ENDCASE. ENDFORM. FORM clear USING function. CASE function. WHEN 'C'. CHECK lock_flag = ' '. CLEAR display. WHEN 'CA'. CLEAR: value1, value2, operation, clear_flag, lock_flag, display. ENDCASE. ENDFORM. FORM write_display USING value. SET COUNTRY 'US'. WRITE value TO display RIGHT-JUSTIFIED EXPONENT 0. ENDFORM.

Old Fashioned Class Based Program ZCALCULATOR_QUAINT


REPORT zcalculator_quaint. CLASS calculator DEFINITION FINAL CREATE PRIVATE. PUBLIC SECTION. CLASS-DATA: ui TYPE REF TO zcl_calculator_ui, value1 TYPE f, value2 TYPE f, memory TYPE f, operation(10), lock_flag, clear_flag. CLASS-METHODS: class_constructor, main, number_input FOR EVENT number_input OF zcl_calculator_ui

IMPORTING function display, change_value FOR EVENT change_value OF zcl_calculator_ui IMPORTING function display, calculate FOR EVENT calculate OF zcl_calculator_ui IMPORTING function display, handle_memory FOR EVENT handle_memory OF zcl_calculator_ui IMPORTING function display, clear FOR EVENT clear OF zcl_calculator_ui IMPORTING function, write_display IMPORTING value TYPE f RETURNING value(display) TYPE zcl_calculator_ui=>t_display. ENDCLASS. CLASS calculator IMPLEMENTATION. METHOD class_constructor. CALL METHOD zcl_calculator_ui=>get_instance RECEIVING ui = ui. ENDMETHOD. METHOD main. SET HANDLER: number_input change_value calculate handle_memory clear CALL METHOD ui->show. ENDMETHOD. FOR FOR FOR FOR FOR ui, ui, ui, ui, ui.

METHOD number_input. DATA digits LIKE display. CHECK lock_flag = ' '. IF clear_flag = 'X'. CLEAR display. CLEAR clear_flag. ENDIF. IF function = '.' AND display CS '.'. EXIT. ELSE. MOVE display TO digits. CONDENSE digits. REPLACE: '.' WITH `` INTO digits, '-' WITH `` INTO digits. IF strlen( digits ) > 15. EXIT. ENDIF. ENDIF. CONDENSE display. CONCATENATE display function INTO display. WRITE display TO display RIGHT-JUSTIFIED. CALL METHOD ui->set EXPORTING value = display. ENDMETHOD. METHOD change_value. DATA result TYPE f. MOVE display TO result. CASE function. WHEN '1/X'. IF result <> 0. COMPUTE result = 1 / result. ELSE. MOVE 'Error' TO display. CALL METHOD ui->set EXPORTING value = display. EXIT. ENDIF. WHEN '+-'. COMPUTE result = -1 * result. ENDCASE. CALL METHOD write_display EXPORTING

"#EC NOTEXT

value = result RECEIVING display = display. MOVE 'X' TO lock_flag. IF operation = '='. CLEAR operation. ENDIF. CALL METHOD ui->set EXPORTING value = display. ENDMETHOD. METHOD calculate. DATA result TYPE f. MOVE display TO value2. CATCH SYSTEM-EXCEPTIONS arithmetic_errors = 8. CASE operation. WHEN '+'. COMPUTE result = value1 + value2. WHEN '-'. COMPUTE result = value1 - value2. WHEN '*'. COMPUTE result = value1 * value2. WHEN '/'. COMPUTE result = value1 / value2. WHEN '='. COMPUTE result = value1. WHEN OTHERS. MOVE display TO result. ENDCASE. ENDCATCH. IF sy-subrc = 8. MOVE 'Error' TO display. CALL METHOD ui->set EXPORTING value = display. EXIT. ENDIF. MOVE result TO value1. CLEAR value2. CALL METHOD write_display EXPORTING value = result RECEIVING display = display. MOVE function TO operation. MOVE 'X' TO clear_flag. IF function = '='. MOVE 'X' TO lock_flag. ELSE. MOVE ' ' TO lock_flag. ENDIF. CALL METHOD ui->set EXPORTING value = display. ENDMETHOD. METHOD handle_memory. DATA value TYPE f. value = display. CASE function. WHEN 'MC'. CLEAR memory. WHEN 'MR'. IF lock_flag = ' '. MOVE memory TO value. CALL METHOD write_display EXPORTING value = value RECEIVING display = display. MOVE 'X' TO lock_flag. ENDIF. WHEN 'MS'. MOVE value TO memory.

"#EC NOTEXT

WHEN 'MP'. ADD value TO memory. WHEN 'MM'. SUBTRACT value FROM memory. ENDCASE. CALL METHOD ui->set EXPORTING value = display. ENDMETHOD. METHOD clear. CASE function. WHEN 'C'. CHECK lock_flag = ' '. WHEN 'CA'. CLEAR: value1, value2, operation, clear_flag, lock_flag. ENDCASE. CALL METHOD ui->set EXPORTING value = ``. ENDMETHOD. METHOD write_display. SET COUNTRY 'US'. WRITE value TO display RIGHT-JUSTIFIED EXPONENT 0. ENDMETHOD. ENDCLASS. START-OF-SELECTION. CALL METHOD calculator=>main.

Exercise 2: From Statements and Additions to Functions and Expressions Part 1


Scenario
Imagine that the solution for the first exercise was done by an old fashioned guy. The program ZCALCULATOR_QUAINT shows such a solution. In fact, it looks much worse than ZCALCULATOR_OBSOLETE. Although correct from a functional point of view, using ABAP Objects in such a way is not very modern (and not much fun).

Task
Make it better. Either copy your working solution of exercise 1 or copy ZCALCULATOR_QUAINT to ZSECOND_CALCULATOR_Y_XX, where _Y_XX is the postfix of your user ID. Modernize the program as far as possible. You can do the following: Use type decfloat34 instead of type f (you must adjust method write_display accordingly). Remove CALL METHOD, use functional method calls as far as possible. Remove further unnecessary keywords (ADD, COMPUTE, MOVE ...). Exchange all obsolete statements with recommended constructs. Especially look for new features in character string processing (more about this in exercise 3). Introduce class based exception handling. Replace pseudo comments with pragmas. What you can do further: Extract the handling of the calculators memory into a helper class. Transfer static components into instance components and provide an object of the class (singleton). Bundle those variables that represent the calculators state in a structure that can be reset in one go. ... A solution is available in program ZCALCULATOR_MODERN1 in the system.

Modern Class Based Program ZCALCULATOR_MODERN1


REPORT zcalculator_modern1. CLASS memory DEFINITION FINAL. PUBLIC SECTION. METHODS: get RETURNING value(value) TYPE decfloat34, set IMPORTING value TYPE decfloat34, add IMPORTING value TYPE decfloat34, sub IMPORTING value TYPE decfloat34. PRIVATE SECTION. DATA value TYPE decfloat34. ENDCLASS. CLASS calculator DEFINITION FINAL CREATE PRIVATE. PUBLIC SECTION. CLASS-DATA handle TYPE REF TO calculator READ-ONLY. CLASS-METHODS class_constructor. METHODS: constructor, main. PRIVATE SECTION. DATA: ui TYPE REF TO zcl_calculator_ui, BEGIN OF state, value1 TYPE decfloat34, value2 TYPE decfloat34,

10

operation TYPE string, lock_flag TYPE abap_bool, clear_flag TYPE abap_bool, END OF state, mem TYPE REF TO memory. METHODS: number_input FOR EVENT number_input OF zcl_calculator_ui IMPORTING function display, change_value FOR EVENT change_value OF zcl_calculator_ui IMPORTING function display, calculate FOR EVENT calculate OF zcl_calculator_ui IMPORTING function display, handle_memory FOR EVENT handle_memory OF zcl_calculator_ui IMPORTING function display, clear FOR EVENT clear OF zcl_calculator_ui IMPORTING function, write_display IMPORTING value TYPE decfloat34 RETURNING value(display) TYPE zcl_calculator_ui=>t_display. ENDCLASS. CLASS calculator IMPLEMENTATION. METHOD class_constructor. CREATE OBJECT handle. ENDMETHOD. METHOD constructor. ui = zcl_calculator_ui=>get_instance( ). CREATE OBJECT mem. ENDMETHOD. METHOD main. SET HANDLER: number_input change_value calculate handle_memory clear ui->show( ). ENDMETHOD. FOR FOR FOR FOR FOR ui, ui, ui, ui, ui.

METHOD number_input. CHECK state-lock_flag = abap_false. IF state-clear_flag = abap_true. CLEAR display. CLEAR state-clear_flag. ENDIF. IF function = '.' AND display CS '.'. RETURN. ELSEIF count( val = display regex = '\d' ) > 15. RETURN. ENDIF. ui->set( |{ condense( display ) && function WIDTH = 18 ALIGN = RIGHT }| ). ENDMETHOD. METHOD change_value. DATA result TYPE decfloat34. result = display. TRY. CASE function. WHEN '1/X'. result = 1 / result. WHEN '+-'. result = -1 * result. ENDCASE. display = write_display( result ). state-lock_flag = abap_true. IF state-operation = '='. CLEAR state-operation. ENDIF. CATCH cx_sy_arithmetic_error. display = 'Error' ##no_text. ENDTRY. ui->set( display ). ENDMETHOD.

11

METHOD calculate. DATA result TYPE decfloat34. state-value2 = display. TRY. CASE state-operation. WHEN '+'. result = state-value1 + state-value2. WHEN '-'. result = state-value1 - state-value2. WHEN '*'. result = state-value1 * state-value2. WHEN '/'. result = state-value1 / state-value2. WHEN '='. result = state-value1. WHEN OTHERS. result = display. ENDCASE. state-value1 = result. CLEAR state-value2. display = write_display( result ). state-operation = function. state-clear_flag = abap_true. state-lock_flag = boolc( function = '=' ). CATCH cx_sy_arithmetic_error. display = 'Error' ##no_text. ENDTRY. ui->set( display ). ENDMETHOD. METHOD handle_memory. DATA value TYPE decfloat34. value = display. CASE function. WHEN 'MC'. CREATE OBJECT mem. WHEN 'MR'. IF state-lock_flag = abap_false. display = write_display( mem->get( ) ). state-lock_flag = abap_true. ENDIF. WHEN 'MS'. mem->set( value ). WHEN 'MP'. mem->add( value ). WHEN 'MM'. mem->sub( value ). ENDCASE. ui->set( display ). ENDMETHOD. METHOD clear. CASE function. WHEN 'C'. CHECK state-lock_flag = abap_false. WHEN 'CA'. CLEAR state. ENDCASE. ui->set( `` ). ENDMETHOD. METHOD write_display. DATA mask TYPE c LENGTH 18. mask = value. display = |{ mask WIDTH = 18 ALIGN = RIGHT }|. ENDMETHOD. ENDCLASS. CLASS memory IMPLEMENTATION. METHOD get. value = me->value. ENDMETHOD. METHOD set. me->value = value. ENDMETHOD.

12

METHOD add. me->value = me->value + value. ENDMETHOD. METHOD sub. me->value = me->value - value. ENDMETHOD. ENDCLASS. START-OF-SELECTION. calculator=>handle->main( ).

Part 2
Scenario
Now that we have a nice modern version of the calculator program, we want to make it reusable by making the class publicly available. If you have not seen it up to now, this exercise will make you familiar with the new source code based class builder.

Task
Copy the class calculator from ZSECOND_CALCULATOR_Y_XX or ZCALCULATOR_MODERN1 to a global class ZCL_CALCULATOR_Y_XX in the class library, where _Y_XX is the postfix of your user ID. Create the class from the object navigator or from SE24 as a normal final class and activate it. Open the source code based class builder using the button in the application toolbar. Copy the definition and implementation of calculator from ZCALCULATOR_MODERN1 to the definition and implementation of ZCL_CALCULATOR_Y_XX in the source code based class builder, save it and adjust it until there are no syntax errors any more: o You must change the occurrences of the old name into the new name. o You must also transfer the class memory from ZCALCULATOR_MODERN1 into the class pool of ZCL_CALCULATOR_Y_XX: Goto -> Local Definitions/Implementations -> Class relevant local Definitions, copy the definition part of memory there. Goto -> Local Definitions/Implementations -> Local Definitions/Implementations, copy the declaration part of memory there. During editing, you can toggle between source code based class builder and form based class builder. Activate the class. As an optional task you can add an ABAP Unit test for the memory class: (Goto -> Local Definitions/Implementations -> Local Test Classes). To run the test, use Class -> Run -> Unit Tests.

A solution is available in class ZCL_CALCULATOR in the system. Now you can reduce ZSECOND_CALCULATOR_Y_XX or ZCALCULATOR_MODERN1 to a one-liner as shown in ZCALCULATOR_MODERN2.

Modern Class Based Program ZCALCULATOR_MODERN2


REPORT zcalculator_modern2. START-OF-SELECTION. zcl_calculator=>handle->main( ).

13

Optional Test Class in ZCL_CALCULATOR


CLASS test_memory DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT FINAL. PRIVATE SECTION. METHODS test_memory FOR TESTING. ENDCLASS. CLASS test_memory IMPLEMENTATION. METHOD test_memory. DATA mem TYPE REF TO memory. CREATE OBJECT mem. mem->set( 10 ). cl_abap_unit_assert=>assert_equals( exp = 10 act = mem->get( ) ). mem->add( 10 ). cl_abap_unit_assert=>assert_equals( exp = 20 act = mem->get( ) ). mem->sub( 10 ). cl_abap_unit_assert=>assert_equals( exp = 10 act = mem->get( ) ). CREATE OBJECT mem. cl_abap_unit_assert=>assert_initial( act = mem->get( ) ). ENDMETHOD. ENDCLASS.

14

Exercise 3: String Processing Can be Fun Part 1


Scenario
In the system you find a program ZHTML_TABLE_OLD that generates an HTML file and displays it in a browser window. For doing so it uses rather outdated features.

Task
Copy the program ZHTML_TABLE_OLD to a program ZHTML_TABLE _Y_XX, where _Y_XX is the postfix of your user ID, and replace all outdated constructs with modern language elements that produce the same HTML file. Use string templates to replace all string processing statements of the program. The aim is, to have three string templates in the program, one before, one in, and one after the loop. There should be no helper variables any more. A solution is available in program ZHTML_TABLE in the system. You can compare the plain HTML files of the old and the new program in the debugger or by using the context menu of the browser window. Note This exercise is about string processing and not about HTML. Of course we know about CSS, but kept the example as simple as possible.

Old Fashioned HTML Generation Program ZHTML_TABLE_OLD


REPORT zhtml_table_old. CLASS html_table DEFINITION. PUBLIC SECTION. CLASS-METHODS: class_constructor, main. PRIVATE SECTION. CLASS-DATA: scarr_tab TYPE TABLE OF scarr, html TYPE string. CLASS-METHODS display. ENDCLASS. CLASS html_table IMPLEMENTATION. METHOD class_constructor. SELECT * FROM scarr INTO TABLE scarr_tab. ENDMETHOD. METHOD main. CONSTANTS spc TYPE string VALUE `&nbsp;&nbsp;`. FIELD-SYMBOLS <scarr> LIKE LINE OF scarr_tab. DATA: ilength TYPE i, clength TYPE c LENGTH 10. CONCATENATE '<html><body><table border=1>' '<tr bgcolor="#D3D3D3">' '<td><b>' spc 'ID</b></td>' '<td><b>' spc 'Name</b></td>' '<td><b>' spc 'URL</b></td>' '<tr>' INTO html. LOOP AT scarr_tab ASSIGNING <scarr>. CONCATENATE html '<tr bgcolor="#F8F8FF">' INTO html. ilength = 10 * strlen( <scarr>-carrid ). WRITE ilength TO clength LEFT-JUSTIFIED. CONDENSE clength. CONCATENATE html '<td width=' clength ' >' spc <scarr>-carrid '</td>' INTO html.

15

ilength = 10 * strlen( <scarr>-carrname ). WRITE ilength TO clength LEFT-JUSTIFIED. CONDENSE clength. CONCATENATE html '<td width=' clength ' >' spc <scarr>-carrname '</td>' INTO html. ilength = 10 * strlen( <scarr>-url ). WRITE ilength TO clength LEFT-JUSTIFIED. CONDENSE clength. CONCATENATE html '<td width=' clength ' >' spc '<a href="' <scarr>-url '">' <scarr>-url '</a></td>' INTO html. CONCATENATE html '</tr>' INTO html. ENDLOOP. CONCATENATE html '</table></body><html>' INTO html. display( ). ENDMETHOD. METHOD display. CALL METHOD cl_abap_browser=>show_html EXPORTING html_string = html buttons = cl_abap_browser=>navigate_html context_menu = abap_true. ENDMETHOD. ENDCLASS. START-OF-SELECTION. html_table=>main( ).

Modern HTML Generation Program ZHTML_TABLE


REPORT zhtml_table. CLASS html_table DEFINITION. PUBLIC SECTION. CLASS-METHODS: class_constructor, main. PRIVATE SECTION. CLASS-DATA: scarr_tab TYPE TABLE OF scarr, html TYPE string. CLASS-METHODS display. ENDCLASS. CLASS html_table IMPLEMENTATION. METHOD class_constructor. SELECT * FROM scarr INTO TABLE scarr_tab. ENDMETHOD. METHOD main . CONSTANTS spc TYPE string VALUE `&nbsp;&nbsp;`. FIELD-SYMBOLS <scarr> LIKE LINE OF scarr_tab. html = |<html><body><table border=1>| & |<tr bgcolor="#D3D3D3">| & |<td><b>{ spc }ID</b></td>| & |<td><b>{ spc }Name</b></td>| & |<td><b>{ spc }URL</b></td>| & |</tr>|. LOOP AT scarr_tab ASSIGNING <scarr>. html = html && |<tr bgcolor="#F8F8FF">| & |<td width={ 10 * strlen( <scarr>-carrid ) } >{ spc }{ <scarr>-carrid }</td>| & |<td width={ 10 * strlen( <scarr>-carrname ) } >{ spc }{ <scarr>-carrname }</td>| & |<td width={ 10 * strlen( <scarr>-url ) } >{ spc }<a href="{ <scarr>-url }">{ <scarr>-url }</a></td>| & |</tr>|. ENDLOOP. html = |{ html }</table></body><html>|.

16

display( ). ENDMETHOD. METHOD display. cl_abap_browser=>show_html( html_string = html buttons = cl_abap_browser=>navigate_html context_menu = abap_true ). ENDMETHOD. ENDCLASS. START-OF-SELECTION. html_table=>main( ).

Part 2
Scenario
In the system you find a program ZHTML_MODIFY_OLD that retrieves the HTML file of part 1 from a global class ZCL_HTML_TABLE. The program modifies the HTML file in order to display the links in different color. For doing so it uses rather outdated features again.

Task
Copy the program ZHTML_TABLE_MODIFY_OLD to a program ZHTML_TABLE_MODIFY _Y_XX, where _Y_XX is the postfix of your user ID, and replace all outdated constructs with modern language elements that produce the same HTML file. Use one regular expression to replace all the string processing of the program. Use && instead of CONCATENATE to insert the color constant. There should be no WHILE statement any more. There should be no helper variables any more. A solution is available in program ZHTML_TABLE_MODIFY in the system. You can compare the plain HTML files of the old and the new program in the debugger or by using the context menu of the browser window. Note Again, this exercise is about string processing and not about HTML. Of course we know that there are better ways to change the link color and that one should use CSS instead of <font ...>.

Old Fashioned HTML Modification Program ZHTML_TABLE_MODIFY_OLD


REPORT zhtml_table_modify_old. CLASS html_table DEFINITION. PUBLIC SECTION. CLASS-METHODS main. PRIVATE SECTION. CLASS-DATA html TYPE string. CLASS-METHODS display. ENDCLASS. CLASS html_table IMPLEMENTATION. METHOD main. CONSTANTS color TYPE c LENGTH 6 VALUE '000000'. DATA DATA DATA DATA html_supplied TYPE string. position TYPE i. part1 TYPE string. part2 TYPE string.

CALL METHOD zcl_html_table=>get RECEIVING html = html_supplied. WHILE html_supplied CS '<a'.

17

SEARCH html_supplied FOR '<a'. position = sy-fdpos. SEARCH html_supplied+position FOR '>'. position = position + sy-fdpos + 1. part1 = html_supplied(position). part2 = html_supplied+position. SEARCH part2 FOR '</a>'. html_supplied = part2+sy-fdpos. part2 = part2(sy-fdpos). CONCATENATE html part1 '<font color="#' color '">' part2 '</font>' INTO html. ENDWHILE. CONCATENATE html html_supplied INTO html. CALL METHOD display. ENDMETHOD. METHOD display. CALL METHOD cl_abap_browser=>show_html EXPORTING html_string = html buttons = cl_abap_browser=>navigate_html context_menu = abap_true. ENDMETHOD. ENDCLASS. START-OF-SELECTION. html_table=>main( ).

Modern HTML Modification Program ZHTML_TABLE_MODIFY


REPORT zhtml_table_modify. CLASS html_table DEFINITION. PUBLIC SECTION. CLASS-METHODS main. PRIVATE SECTION. CLASS-DATA html TYPE string. CLASS-METHODS display. ENDCLASS. CLASS html_table IMPLEMENTATION. METHOD main. CONSTANTS color TYPE c LENGTH 6 VALUE '000000'. html = zcl_html_table=>get( ). REPLACE ALL OCCURRENCES OF REGEX `(<a[^>]*>)([^<]+)(</a>)` IN html WITH `$1<font color="#` && color && `">$2</font>$3` IGNORING CASE. display( ). ENDMETHOD. METHOD display. cl_abap_browser=>show_html( html_string = html buttons = cl_abap_browser=>navigate_html context_menu = abap_true ). ENDMETHOD. ENDCLASS. START-OF-SELECTION. html_table=>main( ).

18

Exercise 4: Boosting Internal Table Access


Scenario
In the system you find a program ZITAB_KEYS. A method compute_best_price computes the best price for all orders of a customer in a given country. The time critical section of this well-liked method is
LOOP AT orders ASSIGNING <fs1> WHERE customer = customer. " find best price for given order <fs1> and given country_code READ TABLE best_prices ASSIGNING <fs2> WITH KEY internal_product_id = <fs1>-internal_product_id country_code = country_code. best_price = best_price + <fs1>-count * <fs2>-price. ENDLOOP.

Tasks
The goal of the exercise is to define and to use suitable secondary keys for the internal tables orders and best_prices and to observe how these changes improve performance. 1) 2) Copy program ZITAB_KEYS to ZITAB_KEYS_Y_XX, where _Y_XX is the postfix of your user ID, and activate it. Execute ZITAB_KEYS_Y_XX. Observe the processing time of the critical section for five calls of compute_best_price and the result of the call. During this exercise, you will reduce the processing time without changing the result of the call. Look up the type definition for internal table orders (double click on orders, then double click on it_orders):
TYPES: it_orders TYPE SORTED TABLE OF str_order WITH UNIQUE KEY order_id ...

3)

4)

Define a secondary key for this internal table type that is suitable to speed up the LOOP statement above. Perform a syntax check right after adding the secondary key. Regard the syntax warning as a helpful friend . Use the secondary key in the loop statement . Execute the program again. If no runtime error ITAB_DUPLICATE_KEY occurs, your secondary key definition did not violate data integrity. Why is the first call slower than the subsequent calls? Under which circumstances does usage of the secondary key improve performance? Repeat definition and usage of secondary keys for the READ TABLE statement, in particular, find out which type of secondary is suited best this time . Replace the table type it_orders of internal table orders by a new type from the ABAP Dictionary with name ZIT_ORDERS_Y_XX, where _Y_XX is the postfix of your user ID. Use the structure ZSTRU_ORDER from the ABAP Dictionary as line type and define the same keys primary and secondary as for the local type.

5) 6)

7)

8)

Solutions
Steps 4 and 5 (solution in program ZITAB_KEYS_NEW1):
TYPES: it_orders TYPE SORTED TABLE OF str_order WITH UNIQUE KEY order_id WITH NON-UNIQUE SORTED KEY customer_key COMPONENTS customer, ... LOOP AT orders ASSIGNING <fs1> USING KEY customer_key WHERE customer = customer. ...

19

Step 6: The first call is slower than the subsequent calls because the index is built up lazily for non-unique keys. Without a doubt, using the secondary key for the loop pays off when the loop is executed at least five times before the content of table orders changes (in fact, subsequent index updates are much faster when orders did not change much, so the secondary index is almost always useful here). Step 7 (solution in program ZITAB_KEYS_NEW2):
TYPES: ... it_best_prices TYPE SORTED TABLE OF str_price WITH UNIQUE KEY primary_key COMPONENTS supplier_id supplier_product_id country_code WITH UNIQUE HASHED KEY product_country_key COMPONENTS internal_product_id country_code. ... READ TABLE best_prices ASSIGNING <fs2> WITH KEY product_country_key COMPONENTS internal_product_id = <fs1>-internal_product_id country_code = country_code.

Step 8 (solution in program ZITAB_KEYS_NEW3): With a definition of a secondary key as shown below, this should lead to the same result and performance as usage of the local table type.

... DATA: orders ... TYPE zit_orders,

20

Exercise 5: Lob Locating and Streaming


Scenario
The database table ZLOC_STREAM_TAB has the fields CONTENT_ID and CONTENT:

Tasks
Group number xx will use and manipulate strings of this table with content_id = 2 * xx and content_id = 2 * xx + 1 and find out how this can be done fast with a low consumption of ABAP memory. 1) 2) Copy program ZLOCATORS_AND_STREAMS to ZLOCATORS_AND_STREAMS_Y_XX where _Y_XX is the postfix of your user ID. Replace
CONSTANTS: team_number TYPE i VALUE 270,

with
CONSTANTS: team_number TYPE i VALUE xx,

where xx is your group number and activate the program. 3) Execute the program. The contents of ZLOC_STREAM_TAB for your group number are displayed before and after calling method copy. Increase constant string_size to work with larger strings. Set a break-point in method copy and execute the program again. In the debugger, choose New Tool -> Memory Management -> Memory Analysis and click on Tab Memory Objects. Step through method copy. Surprisingly, no new object with the size of the copied string shows up. (Optional: How can you most easily get rid of the displayed anonymous tables, among them one with a size proportional to string_size?) In method copy, you see the syntax
DATA wa TYPE zloc_stream_tab LOCATOR FOR COLUMNS content.

4)

5)

Read the documentation and look at wa in the debugger to find out what happens here. Change method copy such that it uses a string instead of a locator to copy content. As in step 4, look at the memory consumption in the debugger. Change method copy again to the fast, memory friendly implementation of your choice (optional: measure the performance difference between both implementations). 6) Method display feeds content line by line into a fancy user interface technology method that is emulated here by method write_line. Replace its local string variable str by a reference to cl_abap_db_c_reader and use streaming to pass str chunk by chunk to method write_line. Observe in the debugger when the instance of str is created and how much memory it consumes during execution of write_line.

21

Solutions
Step 4 (solution in program ZLOCATORS_AND_STREAMS_NEW1): Optional: Replace the first call demo->display( ) by a comment. Step 5 (solution in program ZLOCATORS_AND_STREAMS_NEW2A and ..._NEW2B): Method copy may temporarily look like this (Optional: if you are familiar with transaction SAT, you know a less interfering way to measure performance):
METHOD copy. DATA: wa TYPE zloc_stream_tab, "LOCATOR FOR COLUMNS content, t1 TYPE i, t2 TYPE i. GET RUN TIME FIELD t1. SELECT SINGLE content FROM zloc_stream_tab INTO (wa-content) WHERE content_id = string_key. IF sy-subrc NE 0. "handle error RETURN. ENDIF. wa-content_id = copy_to_key. INSERT zloc_stream_tab FROM wa. " wa-content->close( ). GET RUN TIME FIELD t2. write_line( |{ ( t2 - t1 ) * `0.000001` DECIMALS = 5 } seconds.| ). ENDMETHOD.

Step 6 (solution in program ZLOCATORS_AND_STREAMS_NEW3): Method display may look like this:
METHOD display. DATA: str TYPE REF TO cl_abap_db_c_reader, display_key TYPE i. CONSTANTS length TYPE abap_msize VALUE 70. DO 2 TIMES. display_key = string_key + sy-index - 1. write_line( |Table content with id { display_key }:| ). SELECT SINGLE content FROM zloc_stream_tab INTO (str) WHERE content_id = display_key. IF sy-subrc NE 0. write_line( `Empty` ). write_line( `-` ). CONTINUE. ENDIF. WHILE str->data_available( ) = abap_true. write_line( str->read( length ) ). ENDWHILE. str->close( ). write_line( `-` ). ENDDO. ENDMETHOD.

2011 by SAP AG. All rights reserved. SAP and the SAP logo are registered trademarks of SAP AG in Germany and other countries. Business Objects and the Business Objects logo are trademarks or registered trademarks of Business Objects Software Ltd. Business Objects is an SAP company.Sybase and the Sybase logo are registered trademarks of Sybase Inc. Sybase is an SAP company such products and services, if any. Nothing herein should be construed as constituting an additional warranty.

You might also like