1 2 Previous Next

ABAP Objects

20 Posts

I' ve been in SAP development for a while now - and from the very start followed the often fierce struggle between senior ABAP developers and developers coming either from another (object-oriented) programming language or completely starting out new in ABAP development when it comes to the choice of the right implementation technology.

 

The first have the developing and project experience, module and technological know how - they know, what customers expect from development in SAP environments: It has to be quick and - more important - cheap, due to the fact that SAP comes with the promise to be standardized software has been chosen by the management for exactly that reason: standardization and cost optimization. The company pays a fortune on the initial implementation project, on consultant fees and licenses - and is not willing to extra pay expensive individual programming.

 

So, a good, experienced senior ABAPer exactly knows how much a custom development may cost in order for the underlying user requirement not to be rejected for its price - and tries to satisfy his customers with rapid development; here a customer exit in a SAP program, there a report, here a file interface for importing or exporting data, there some new fields in an existing print form - always choosing the quickest, least dirty, most efficient way of implementation. In the end, the customer is satisfied, and the consultant will be booked again.

 

Coming from an object-oriented programming language like Java or directly from school, the latter often haven' t got the deep knowledge of SAPs technical and business environment, but they all share the same development foundations - and that is object orientation in analysis, design and implementation. A report in Java? No problem, we build one or two classes, a web frontend and on we go. Enhancing standard APIs? No problem either, let' s create a new class inheriting the features from the standard entity and implementing the functional delta.

 

Now, the problem with the object oriented approach in SAPs custom development are - from my point of view - the following:

  1. SAP introduced ABAP Objects as a an equal programming model and pushed it with its guidelines and in its development projects through the years, but not in as a truly object oriented language, but a hybrid with 4GL inside. So, the developer with object oriented background cannot transfer his know how to the ABAP objects environment - simply because there are no static enhancement points, no pre-/post-/overwrite exits for methods and inheritance is in most cases not possible because SAPs classes have to be final!
  2. Procedural programming techniques still exist - and will exist - being necessary for basic tasks like Dynpro programming, RFC interfaces and ON COMMIT work to be done - so there' s simply no need for an old-fashioned ABAPer to switch from procedural to object oriented programming except where absolutely necessary, like in BAdI implementations and ALV programming. But even there the need for OO coding is reduced to the absolute minimum. Even more, many of the daily business can be performed even better with the old-fashioned ABAP programming than with what is called the "Next Generation" ABAP development - just think of a simple report with a selection screen with user variants and editable ALV; no new ALV object model necessary (or even possible) here, not to speak of any class-based transactions with dynpros encapsulated in function modules.
  3. Object oriented programming comes as a bundle of activities - analysis and design with additional methods like UML, implementation and unit testing. It is most powerful when used in large-scale software development projects - guess what: like the ones SAP is dealing with when introducing or completely rewriting now business or basic modules like RE/FX or Web Dynpro ABAP - both being designed, implemented and used in a truly object oriented way. In these cases the overhead of object-oriented design and development pays off - with an understandable, well designed, modern software architecture. Consultants and developers on customer get the chance to build a new business functionality of such dimension in ABAP from the scratch once in a lifetime; they mostly deal with small implementations in their daily business where the overhead of OO design and development is simply to expensive - compared to the unique benefit of it (as most tasks can be performed by procedurals programming entities, too).

 

So, what' s his point you may ask? I still consider ABAP Objects as a very elegant implementation of an object oriented language - just think of the built-in event handling compared to Java-based event APIs. But for most ABAP custom development it is simply not necessary - or even suitable - to create a class hierarchy based on the model-view-controller principle as suggested by SAPs latest ABAP programming guidelines. Of course, ABAP objects can be chosen for daily programming in the most simple approach - local classes for reporting, static methods, no interfaces or eventing - but then where' s the benefit compared to using procedural programming entities like forms and function modules?

Few days ago I was facing a problem, where I needed to use some common algorithm which differs only in some small aspect, that is delegating some task to different methods. Please consider below academic version of a case.

 

Quick jump in

 

Model class

model.png


This simple class has merely three methods, get_all, get_active, get_inactive. All they do is to return some set of components. Now, let's create simple Controller class which utilizes model methods to show the results.

 

 

Controller class

controller-def.jpg

controller1-impl.jpg

 

The important part is that for some reason in each method we need to repeat model creation. Let's assume this is very complex process which relies on different factors, which we should not bother here. Each method however does the same thing, but it only differantiates model's method invocation. One time it calls get_all, another time get_active or get_inactive. This is the only part which differs all three controller methods, but for some reason must are tied to the code embracing it.

 

 

Test program

run.jpg

 

Result

All:

01 COMP1

02 COMP2

03 COMP3

Active:

01 COMP1

Inactive

02 COMP2

03 COMP3

 

 

 

Redundancy


As we noticed above we have redundant code which does exactly the same with small difference of model methods' call. What we wish to have instead is one common method which only differs method call based on some parameter. Using IFs or CASE is ugly practice. We will therefore utilize Object Orientation.

 

wish to have

controller-wish-to-have.jpg

 

 

Hiding method invocation

 

First we need to introduce new class, whose subclasses represent different component's sets. It has only one method get_comp which returns different sets of components in different subclasses.

 

Abstract class

model-comp-abs.jpg

 

Subclasses

model-comp-impl.jpg
All three classes simply receive model instance and use them appropriately to their meaning.

 

 

 

As simple as that


Now all we need is our common method in the controller which hides model methods invocation by utilizing interface (abstract class).

 

new Controller definition

controller-2-def.jpg

 

uniform get_comp method

controller-2-get_comp.jpg

 

Notice that get_comp represent redundant code and is no longer aware of which method we want to call. It even doesn't know which object is it working with. The only information it posses is that this object is of type lcl_model_comp (abstract type). So in fact we are programming to an interface as at the time of program creation we don't know which class we are working with.

 

We leave the decision of method selection to the specific instance of lcl_model_comp, be it either lcl_model_comp_all, lcl_model_comp_active or lcl_model_comp_inactive. The last thing to do in order get_comp works correctly is to call it with different lcl_model_comp instances in the appropriate methods.

 

controller-2-impl.jpg

Please don't bother the fact that showing the result is implemented in all the methods. We could of coruse move that to other method which only shows what was returned. What I wanted to show here is that the result lt_comp originates from get_comp depending on model_comp object we choose.

 

 

Conclusion


Sometimes it may be required to invoke different methods of same object inside some complex algorithm. By utilizing simple characteristic of Object Orientation - that is object diversity, we are able to create more uniform code which eliminates redundancy. By introducing another level of abstraction (lcl_model_comp) we don't need to worry anymore of the method selection at its invocation place. Instead we move that responsibility to that new class instance.

 

 



 

 



I could give number of reasons why I think we should commonly use Object Oriented Programming style and probably equal number of reasons why I think is it so magical. Anyhow, this blog is not intended to write a thesis, but to share with you, the Reader, with couple nice tricks I have learnt recently when I decided to take OO approach for some trivial issue. I leave it to your decision, whether you smuggle any of these to your applications or just share your thoughts about that.

 

Case study


In SAP HR module, different objects can be stored in infotype 1000 (table HRP1000, where PLVAR (2char) OTYPE (2char) and OBJID (8 numc) are key info of the object). Among others we can have i.e. priority object. The object can have long description stored in infotype 1002 (co-joined tables HRP1002 and HRT1002). SAP delivers by standard a function RH_OBJECT_DESCRIPTION_WRITE which allows to store the text w/o bothering of how it is splited b/w these tables. All we should do is to give object coordinates (key info) and its description with PT1002 table type to function.

 

In my case I had the data originated from excel file. Long description was provided in form of a STRING. So, I needed a conversion from STRING to PT1002 table type. Seems that function HRHAP_CONVERT_STRG_TO_PT1002 was ideal for this task. It turned out, however, it could not parse the STRING correctly. I wanted to handle extra chars (i.e. new line) but it didn’t. Instead it parsed the text at 79th char. So the split could happen i.e. in the middle of a word. This caused that below snippet...

 

 

data  lv_data   type string.
data: lt_pt1002 type hap_t_pt1002,
        lw_pt1002 type pt1002.
 concatenate
 'Do I/we...'
 '... collaborate?'
 '... communicate openly ?'
 '... share ideas?'
 '... leverage knowledge?'
 '... have a focus outside and inside (about customers, partners)?'
 into lv_data separated by cl_abap_char_utilities=>newline.
 call function 'HRHAP_CONVERT_STRG_TO_PT1002'
   exporting
     string_description = lv_data
   importing
     t_desc_tab         = lt_pt1002.
 loop at lt_pt1002 into lw_pt1002.
   write: / lw_pt1002-tline.
endloop.

 

... resulted in

 

Do I/we...#... collaborate?#... communicate openly ?#... share ideas?#... lever

age knowledge?#... have a focus outside and inside (about customers, partners)?

 

This was wrong. I should have data splitted both at new line and prevent split at 79th char. So I came with custom solution.

 

 

Common approach


The requirement was to:

  • split the string into lines
  • if a line doesn't fit 79th chars restriction, it should break into words and split should happen after last fitting word
  • I decided to build more general solution where I could split at char level if a word does not fit restriction other than my module defined

 

I ended up with procedular solution encapsulated within function module and some local routines. Please don't study the code, just take a brief look how the flow is organizated.

 

function z_glpm_fum_sstr_to_pt1002.
*"----------------------------------------------------------------------
*"*"Local Interface:
*"  IMPORTING
*"     VALUE(STRING_DESCRIPTION) TYPE  STRING
*"  EXPORTING
*"     REFERENCE(T_DESC_TAB) TYPE  PTM_T_PT1002
*"----------------------------------------------------------------------
* this is a custom version of function HRHAP_CONVERT_STRG_TO_PT1002
* which splits string either at 79th char or cut it if it doesn't fiy
  data: lw_row    type pt1002.
  data: lt_lines  type string_table,
        lw_line   type string.
  data: lt_words  type string_table,
        lw_word   type string.
  data: lt_chars  type string_table,
        lw_char   type string.
  data: lv_fits   type flag,
        lv_windex type i,
        lv_cindex type i,
        lv_exc    type c.
**-------------------------------------------------------------------- LINES start
* get lines                                                        
  perform get_lines using string_description changing lt_lines.        
* for each line
  loop at lt_lines into lw_line.
*  line fits row?
    perform is_line_fitting using lw_line lw_row changing lv_fits.
    if lv_fits = 'X'.                                                 
*    add line
      perform conv_line_2_row using lw_line changing lw_row.           
    else.
**-------------------------------------------------------------------- WORDS start
*    get words                                                   
      perform get_words using lw_line changing lt_words.            
*    for each word
      do.
        add 1 to lv_windex.
        read table lt_words into lw_word index lv_windex.
        if sy-subrc <> 0 .
          exit.
        else.
*        word fits row?
          perform is_word_fitting using lw_word lw_row changing lv_fits.
          if lv_fits = 'X'.                                               
*          add word
            perform conv_word_2_row using lw_word changing lw_row.       
          else.
*          word does not fit empty row?
            if lw_row is initial.                                        
**-------------------------------------------------------------------- CHARS start
*            get chars                                            
              perform get_chars using lw_word changing lt_chars.      
*            for each char
              do.
                add 1 to lv_cindex.
                read table lt_chars into lw_char index lv_cindex.
                if sy-subrc <> 0.
                  exit.
                else.
*                char fits row?
                  perform is_char_fitting using lw_char lw_row changing lv_fits.
                  if lv_fits = 'X'.                                      
*                  add char
                    perform conv_char_2_row using lw_char changing lw_row. 
                  else.
*                  char does not fit empty row?
                    if lw_row is initial.                                 
*                     the smallest possible entity does not fit, this situation can't be handled, leave the application
                      lv_exc = 'X'.                                      
                      exit.
                    else.
*                    EOR - end of row
                      append lw_row to t_desc_tab.                       
                      clear lw_row.
*                    char does not fit, redo process for the char once again
                      subtract 1 from lv_cindex.                          "Format2
                    endif.
                  endif.
                endif.
              enddo.
              if lv_exc = 'X'.
                exit.
              endif.
              clear lv_cindex.
**-------------------------------------------------------------------- CHARS end
            else.
*            EOR - end of row
              append lw_row to t_desc_tab.                               
              clear lw_row.
*            word does not fit, redo process for the word once again
              subtract 1 from lv_windex.                                 
            endif.
          endif.
        endif.
      enddo.
**-------------------------------------------------------------------- WORDS end
    endif.
    if lv_exc = 'X'.                                                  "Converter
      exit.
    endif.
*  EOR - end of row
    append lw_row to t_desc_tab.                                      "Converter
    clear: lw_row, lv_windex.
  endloop.
**-------------------------------------------------------------------- LINES end
 endfunction.
 form get_lines using ip_data  type string
            changing ct_lines type string_table.
 ...
endform.                    "get_lines
 form get_words using ip_data  type string
            changing ct_words type string_table.
...
endform.                    "get_words
form get_chars using ip_data  type string
            changing ct_chars type string_table.
...
endform.                    "get_chars
form is_line_fitting using is_line type string
                           is_row  type pt1002
                  changing cp_fits type flag.
 ...
endform.                    "is_line_fitting
form is_word_fitting using is_word type string
                           is_row  type pt1002
                  changing cp_fits type flag.
 ...
endform.                    "is_word_fitting
form is_char_fitting using is_char type string
                           is_row  type pt1002
                  changing cp_fits type flag.
 ...
endform.                    "is_char_fitting
 form conv_line_2_row using is_line type string
                  changing cs_row  type pt1002.
...
endform.                    "conv_line_2_row
form conv_word_2_row using is_word type string
                  changing cs_row  type pt1002.
 ...
endform.                    "conv_word_2_row
form conv_char_2_row  using is_char type string
                   changing cs_row  type pt1002.
...
endform.                    "conv_char_2_row

 

The flow of a converter FM is controlled with three loops and bunch of conditions. Although some separation was achieved by introducing routines, the main part isn't state-of-the-art programming example.

 

 

Pros and cons


What if we want to:

  • reuse the same module in other application with different length restriction and different definition what is a line, a word or a char. Sure, we could distinguish them with number of parameters but how will that look like?
  • split just by words and chars not by lines. We would need to introduce ugly conditions which would check that. Assuming we would also like to have combination of lines and words, the condition might get complicated more.

 

Basically I would like to have a "dynamically configurable" tool. I want the configuration be done at code level, not in the DB where, I still would need to maitain the entries. As I feel frustrated seeing to much of a complicated logic at one spot I would probably want to simplify it. Hey, isn't that a perfect situation for OO solution? Sure it is! Let's see what objects can propose us.

 

 

Object Oriented Analysis & Design

 

Candidates

 

The first thing to do would we be looking for the candidates for the objects. Let's create CRC cards first. Basically CRC card denotes Class Responsibility Collaboration card and serve the purpose for simplier design. It let us visualize what the candidates are responsible for and who they will interact with. CRCs are cool as they don't require technical tools. For instance I use office yellow stickers for them. Seeing the candidates will ease seeing the proces they support by moving them on the table. Besides such candidate can be easily replaced or removed at all, if we decide it doesn't meet our needs.

 

CRC Line.png

 

CRC Word.png

 

CRC Char.png

 


CRC Formatted Part.png

 

CRC Converter.png

 

Product.png

 

 

Patterns


With these candidates on board, let’s see if there is any common design pattern that we could use as a base solution. After googling a bit, I have found a Builder Pattern that seems suitable for our case. Let’s see how we can adapt it to our situation.

We have a Product, we have a Builder (Converter), we have also some extra candidate Formatted Part (I will call it Part for short), but this seems to be used only internally. We are missing a Director, who controls the flow and delegates job to build certain parts to concrete Builder. Here I decided that the Builder itself can serve the purpose of a Director too. Moreover, who would pay this guy for doing nothing that the rest of the team couldn’t handle itself

Ok, so let's change our Converter candidate to be called Builder. It now looks like (changes in red):

 

Builder alone.png

 

 

Note!

Intentionally I am not working with classes yet, just with CRC cards, so that we can easily change them if required.

 

 

We still miss one thing here. Although the Builder defines uniform approach for the Parts to be put together to a Product, it must introduce different ways of producing these Parts (its initial responsibility). I.e. building Parts will be different for Lines, Words, Chars. That means we will need different kinds of Builders to be able to decide which Part it want to use as Product parts. Quick modification to CRC card with give us below:

 

Builder.png

 

Builder now also serves the purpose of an Factory Method, where in general, concreate Builders are responsible for building concrete Parts.

 

 

Final look at candidates

 

A lot has happened to our candidates, so let's take final look at what we have on the table.

 


Diagram v1.png

 

 

 

More patterns

 

There is one major feature emphasized here. We have a Builder who is able to build a Product based on Part type we choose. This would suffice if we wanted to split the text either to Lines or Words or Chars. But how about utilizing them all at a time. We need Lines and Words and Chars to correctly address the split process. In other words, if concrete Builder cannot process the request (cannot add the parts), we should delegate this to sub Builder which we try to process it at more detailed level. If that one fails too, we will again delegate to Sub Builder of the current sub Builder. Such chain of one task delegation can be easily implemented with variant of a Decorator Pattern .

 

 

Again we will need to adapt it, just to ease its structure. In fact all we need to add to the Builder, is a reference to its sub Builder, so that we know who we can delegate certain job to. Our candidate then gains new responsibility.

 

 

Builder v3.png

 

Note that this new Responsibility is a private one. From the Client perspective it should be transparent how the build is done. It is only the Builder who knows how to do the job.

 

 

Centralized control style

 

The candidates can defend they candidacy, so we are probably fine with turning them into classes. Anyhow, before that happens, let’s take closer look how the build process will look like (in the Builder).

 

Create Parts based on the Builder we choose

For each part

                Does Part fits product buffer (i.e. currently filled work area)?

                               Append to buffer

                Else

                               Does part fit empty buffer at all (i.e. empty work area)?

                                               Create Parts based on the sub Builder we have

                                                               For each Part

…we repeat the steps for the conditions and delegate the job to another sub Builder in case it cannot be processed at this level

 

                               Else

                                               Finalize current context (i.e. append buffer work area to output table)

                                               Flush buffer and add the part to it

 

Finalize the Part add (i.e. put line break at the end of part join to the output table)

 

 

The above logic reflects the procedural code I shared at the beginning. There are lots of conditions to be handled at one spot. This doesn’t look good. What we can remark here:

  • The Builder controls entire consolidation process, he is the master of a ceremony
  • It decides when to delegate the process to sub Builder if it cannot be processed at given granularity
  • Part and Product are very simple, unintelligent candidates

 

Tendency of one object to gather all the responsibility of an application for process control, is an example of centralized control style.  Such object usually lies at application services layer. In fact here we have some variation of this kind of control, namely a clustered control style. This means that the control is spread across the objects of same type (Builders), but they still reside at same layer of an abstraction (application).

 

 

Classes

 

Before we go further we would like to see some code. The abstraction we have is enough to build classes from the candidates.

 

Note!

I used abstract classes not interfaces because the classes share common behaviour with their implementation (sub classes). With a bit of a struggle, we could also use interfaces and delegate common behaviour to i.e. external utility classes. In this case it would however introduce extra complexity. The aim here is to show how to design classes responsibility and how these classes can collaborate to fulfill application responsibility.

 

 

 

Builder

 

class lcl_builder definition abstract.
   public section.
     methods: constructor   importing value(ir_subbuilder) type ref to lcl_builder OPTIONAL,
              build_product importing ip_text              type string
                            changing  value(cr_product)    type ref to lcl_product.
   protected section.
     methods: get_formatted_type abstract
                                 importing ip_text type string
                                 returning value(rr_formatted_type) type ref to lcl_formatted_part.
   private section.
     data: product    type ref to lcl_product,
           subbuilder type ref to lcl_builder.
     methods: add_part           importing ir_part    type ref to lcl_formatted_part,
              parse_to_formatted importing ip_text    type string
                                 returning value(rt_formatted_part) type lcl_formatted_part=>tt_formatted_parts.
 endclass.             


Let’s leave the implementation details now. We only need to know that:

  • get_formatted_type method returns instance of a Part. It is concrete type of Builder who decides what type of Part instance to return. The method is protected and abstract as it needs to be redefined in the Builder subclass. Here is where Factory Method comes into play.
  • parse_to_formatted method gets Parts instances based on given text. This one need to return the same type that above method will return in order we work only with one type of Part objects. Details will come later. 
  • build_product method is the heart of application control. This one makes all the decisions as for when and how to add the Parts to the Product.

 

 

Part


class lcl_formatted_part definition abstract.
   public section.
     types tt_formatted_parts type table of ref to lcl_formatted_part with default key.
     methods: constructor     importing ip_text type string,
              get_formatted   returning value(rp_formatted) type string,
              get_length      returning value(rp_len) type i,
              get_separator   returning value(rp_sep) type string.  
   private section.
     data formatted type string.
 endclass.              

 

Part class gives us following information:

  • All of its methods are public and are used purely to expose some information of its internal state
  • As with the previous class it will receive their own implementation in the subclass, i.e. the separator is different for Line and different for Word

 

Product

 

class lcl_product definition abstract.
   public section.
     methods: is_fitting_buffer     importing ir_part type ref to lcl_formatted_part
                                    returning value(rp_fits) type flag,
              is_fitting_new_buffer importing ir_part type ref to lcl_formatted_part
                                    returning value(rp_fits) type flag,
              add_to_buffer         importing ir_part type ref to lcl_formatted_part,
              add_to_new_buffer     importing ir_part type ref to lcl_formatted_part,
              finalize_part         importing ir_part type ref to lcl_formatted_part.
   protected section.
     methods  get_max_buffer_length abstract returning value(rp_len) type i.
   private section.
     data     product type table of string.
     data     buffer  type string.
 endclass.   

 

Here what we notice that Product:

  • Has lots of public methods which Builder will call based on decisions it makes. Basically these will be used to control process of adding the parts to the Product
  • get_max_buffer_length method decides about the output restriction (product length). Again, as in case of a Builder, the subclass which redefines this method will decide on this factor

 

 

Delegated control style

 

The delegated control programming style is preferred over above one in order to avoid application which consists of one big intelligent object and several "dumb" ones. Instead we will have application intelligence spread across all of them, resulting in small, decent objects that can make some decision upon themselves and the application they belong to. The idea is to split the responsibility b/w objects and get involved those from the domain application layer,

 

Ok. Nice words, but how to inject some intelligence to our Part and Product, taking over some of a Builder’s burden. Let’s update our CRCs.

 

CRC Builder v3.pngCRCR Formatted Part v2.png

 

CRC Product v2.png

 

 

The Builder now has transformed from the Controller to a simple Coordinator. It does not do any decision anymore, it simply gathers generated Parts (from within Part class itself) and redirects them to the Product which knows how to add them. Finally it delegates the job of finalizing part addition to the Part itself. This one knows how to finalize itself by calling appropriate method of a Product.

 

Let’s have a look how classes interface has changed.

 

class lcl_builder definition abstract friends lcl_formatted_part.
   public section.
     methods: constructor   importing value(ir_subbuilder) type ref to lcl_builder OPTIONAL,
              build_product importing ip_text              type string
                            changing  value(cr_product)    type ref to lcl_product.
   protected section.
     methods: get_formatted_type abstract
                                 importing ip_text type string
                                 returning value(rr_formatted_type) type ref to lcl_formatted_part.
   private section.
     data: product    type ref to lcl_product,
           subbuilder type ref to lcl_builder.
     methods: add_part           importing ir_part    type ref to lcl_formatted_part,
              parse_to_formatted importing ip_text    type string
                                 returning value(rt_formatted_part) type lcl_formatted_part=>tt_formatted_parts.
endclass.     

             

 

Builder now:

  • in parse_to_formatted method use static method of Part to generate objects. To assure type consistency it passes its reference in order that static method could determine type of Part Builder is attached to. It simply utilizies method get_formatted_type of a Builder and gets an instance of the object Builder is responsible to build. As the method is protected (we don't want someone from the outside generate formatted parts) we must ensure Part is on friends list of the Builder. Then it is able to use its protected and private merhods.

 

class lcl_formatted_part definition abstract.
   public section.
     types tt_formatted_parts type table of ref to lcl_formatted_part with default key.
     methods: constructor     importing ip_text type string,
              get_formatted   returning value(rp_formatted) type string,
              get_length      returning value(rp_len) type i,
              finalize        importing ir_product type ref to lcl_product,
              get_separator   returning value(rp_sep) type string.
     class-methods: get_parts importing ir_builder type ref to lcl_builder
                                        ip_text    type string
                              returning value(rt_formatted_parts) type tt_formatted_parts.
   protected section.
     methods  parse  abstract importing ir_builder type ref to lcl_builder
                                        ip_text    type string
                              returning value(rt_formatted_parts) type tt_formatted_parts.
   private section.
     data formatted type string.
endclass.               

   

 

Part now:

  • introduces static get_parts method which receives a Builder instance who requested parsing
  • each its subclass provides different way of parsing input text i.e. Line needs to be split at \n, while Char needs to be analyzed char by char
  • when static method receives concrete Builder, it gets the Part with entire initial text out of it. It then delegates parsing the text to the concrete Part. This one knows how to parse it in order to generate set of objects of same type (Lines, Words or Chars)
  • was enhanced with new method finalize. Now concrete Part type knows how to finalize its addition to the Product by calling its appropriate method. This decision is no longer made in the Product. It was simply spread across subclasses of Part and the polimorphism did the job. 

 

 

class lcl_product definition abstract.
   public section.
     methods: add_part           importing ir_part type ref to lcl_formatted_part
                                 returning value(rp_added) type flag,
              finalize_part.
   protected section.
     methods  get_max_buffer_length abstract returning value(rp_len) type i.
   private section.
     data     product type table of string.
     data     buffer  type string.
     methods: is_fitting_buffer     importing ir_part type ref to lcl_formatted_part
                                    returning value(rp_fits) type flag,
              is_fitting_new_buffer importing ir_part type ref to lcl_formatted_part
                                    returning value(rp_fits) type flag,
              add_to_buffer         importing ir_part type ref to lcl_formatted_part,
              add_to_new_buffer     importing ir_part type ref to lcl_formatted_part.
endclass.              

 

 

Product now:

  • introduces method add_part which makes the decision whether and how the part should be added to current Product buffer. Only this one is called from within the Builder
  • all the methods which handle complexity of the process are now private and only above method knows how to correctly use them (defines the process)
  • gets new finalize_part method which Part could call directly from its finalize method (if finalizing this part is required)

 

Complete code

 

It's high time to look into details. Please download the code in SAP Link format and study implementation details if you like. If not, just go to below diagram and study how messages are exchanged.

 

 

Sequence diagram

 

Below a pseudo UML diagram showing the flow of the application.

 

Diagram v2.png

 

As we can see now:

  • Client indicates the Product he wishes to get
  • Client create the Builders and nest them within one another as sub Builders
  • Client request to build_product to the Builder
  • Builder needs to get Parts out of non converted input text, for this it sends its reference to Part via static method get_parts
  • This Builder reference is used to get Part object which the Builder is responsible to build (method get_formatted_part)
  • Using above Part object, creation process (method parse) takes place and objects of same type are generated, representing text as set of Part objects
  • The Parts are returned to the Builder
  • The Builder iterates over returned Parts (of one type) and sends them to Product requesting add_part
  • Product does necessary internal check and makes decision if this part can be added at all (i.e. line does not fit Product restriction of 20 chars)
  • Builder receives information if add process succedded
    • If so, Part is finalized by calling it's finalize method. Concrete Part itself decides then how to finalize its build (i.e. we want to call Products finalize_part method at the end of a Line or Word, but not necessary at the end of a Char). This way we avoid Builder to check what concrete Part type is the Part object of
    • If not, build_product is triggered against sub Builder as long as there is one available; the process repeats for new sub Builder
  • At the end of process, the Product gets returned to the Client

 


Test run

 

This is what application generates for 79 char Product - only Line Builder comes into play

 

Do I/we...

... collaborate?

... communicate openly ?

... share ideas?

... leverage knowledge?

... have a focus outside and inside (about customers, partners)?

 

 

For 20 char Product - both Line and Word Builders used

Do I/we...

... collaborate?

... communicate

openly ?

... share ideas?

... leverage

knowledge?

... have a focus

outside and inside

(about customers,

partners)?

 

For 6 char Product - utilizing Line and Word and Char Builders

 

Do

I/we..

.

...

collab

orate?

...

commun

icate

openly

?

...

share

ideas?

...

levera

ge

knowle

dge?

...

have a

focus

outsid

e and

inside

(about

custom

ers,

partne

rs)?

 

 

Drawbacks


There are no perfect solutions, neither is this one. Here are some reasons why:

 

  • Product methods add_part / finalize_part are made public in order Builder or Part could call them at right time. For the price of exposing Product very intrinsic details, we encapsule the decision of how Parts are added to it. As the Client has direct access to the Product, he can easily change Product's intrenal state
  • In order Part can call protected method get_formatted_type of a Builder (to generate objects of same type Builder is responsible for), we must put Part on Product's friends list. This makes these classes coupled. Such situation is acceptable for the objects at same hierarchy level (i.e. siblings) but here Bulider and Part are of different type, so the design "sucks" a bit
  • There is probably too much of an abstraction especially for the Product. We could delegate some of Product sub classes responsibility to the Builder i.e. getting required Product lenght. Anyhow this would contradict separation of concerns where we would mix application service layer (Builder) with a domain layer (Product)

 

 

Design Power


Modularity / Encapsulation

 

As we introduced clear object distinction, each object received unique responsibility. The objects by fulfilling their goals, collaborate with each other and allow to achieve more general aim.

 

We have clear separation b/w objects from the domain layer (Product, Part) and application services layer (Builder). Though, the Builder need to know someting about the Part, it is fine as this object it is application specific. Bad design would do the opposite - couple domain object with the application one.

 

By introducing delegated style, objects match their stereotype and focus on elementary tasks, rather than on huge range of tasks. We end up with simplier to read and easier to maintain application. The objects also fit more their existence purpose and are not overloaded with additional heavy responsibilities.

 

Reusability

 

Object oriented programming style gives us ability to reuse same objects in different context. We can easily use this functionality in some other application which has different Product length and Part requirements. All we do, is to use different kind of object that share common interface and polimorphism will do the job. The beauty sits in the dynamically configurable application which depends on what Client chooses. The Builder is even not aware which objects the Client has selected.

 

Maintainability via separation

 

Changing one object doesn't affect the other one. You may i.e. change the way Product conducts "add part" process and the rest of the application will stay untouched. It will still magically work.

 

 

Flexibility

 

Adding new Parts is not a problem anymore. We create a class which inherits from the Formatted Part and define entity which introduces new semantic text presentation. I.e. we could ask to split by Hex digits, double char mark etc. The same applies to Product, where adding new length restriction is not a problem now. With some efford we could even request some more complex form of internal product representation.

 

 

Conclusion

 

Although there are some gaps in this design and we could further study it to enhance a bit, it show us how we should do an Object Oriented Analysis and Design in order to break the responsibility of the entire applicaiton into smaller, more unique units. Using delegated style let us inject some inteligence to the objects used by the application (residing at domain level). Patterns are helpful here, but don't need to be the heart of the design. If we pick one, we should always try to adpat it to our needs and context. Using only the most valuable part of them in conjunction with adaptation to our needs, creates more reliable and custom-made applications.

Next time you have a chance, pick the Object Oriented approach and create your own solution.

 

 

Questions? Ideas? Share your thoughts please!




In this blog, I will not be discussing about Object Oriented Concepts and theory as there are many documents and information available regarding the same. I am gonna discuss about how to start thinking in object oriented rather than procedural approach, how to implement or use the object oriented concepts while writing your code by benefiting your code performance, making your code compact and more reusable etc.

 

THINK OO

 

It is easy for an ABAPer who has been coding in ABAP(procedural) to code in ABAP OO by defining a local class and using methods instead of performs but that is not object oriented! To make use of object oriented concepts, you need to think OO. Think of what you need to develop in your code as objects.

You need to first design the requirement in Object oriented then it will be easy when you start coding.

For e.g. if you need to develop a report which displays a header and item alv then consider a parent class alv which has all the common attributes and methods which are inherited by 2 sub classes header and item alv. you can define the methods that are common for both in the parent class and methods that are different in each of the sub classes.

 

CODING IN OO

 

After designing when you are coding in OO, make use of the following points:

  • Standard classes/ methods: Try to use standard classes and methods available as much as you can. There are methods available for almost all function modules and more.
  • Re-usability: keep re-usability in mind and create methods which can be re-used more than once in your program.
  • Encapsulation: Encapsulate code into methods wherever you can.
  • Class-constructor and Constructor: When you want to initialize(assign values to variables/attributes) or perform a particular task once initially when a class object/method is called then use class-constructor. If you want to do the same when an object is created of the class then use constructor(instance constructor).
  • Interfaces: Make use of interfaces when two or more classes need common methods but with different logic/implementation.
  • Events: events are used when you want to trigger a particular method when an event happens. For e.g. Events for hot spot click and top of page.
  • Static and Instance attributes/methods: Static methods or attributes are used when you don't want it to change the whole time for that particular time but Instance attributes value changes for each object.
  • Abstract classes: When there are certain methods which we do not want to use in the parent class but want to implement in the sub classes then we can make the class and those methods as abstract. If we do not want to use them in the subclass then make the subclass as abstract too but then if there is a sub class for this subclass which is not abstract these methods will need to be implemented there.
  • Alias: Alias is used when you want to shorten the name of the interface methods for a class.

 

SAMPLE PROGRAM CODE

 

To develop a Report for Purchase order using ABAP OOPS.

  • The selection screen contains Purchase order No and Date in Ranges.

Three radio button

  1. PO released list,
  2. PO rejected List
  3. PO Not released list
  • On selecting the corresponding radio button it should show the details for given selection screen values using container ALV.
  • The PO Description should be editable. On changing the description and saving, it should be updated to corresponding PO Number.
  • On clicking on PO number it should show Item details.
  • No Global data to be used in the report
  • The header and the item fields to be displayed in the ALV is the programmers choice

 

As per the feedback and comments I have received from all of you, I have corrected my code. I hope that I have been able to remove the flaws in my previous design. I am not posting screen shots this time as one complained that it disables them to copy paste my code if they want to try it.

 

In the main program, you can see that I have separated various parts of the code inside includes. It is not recommended to add an include unless there is at least 200 lines of code but top of page/ data definitions is usually written inside the include as the variables/objects can be reused by other programs in the package(I have very few definitions and hence I have removed the data definitions include).

 

REPORT  ZPO_OOPS.

 

TABLES: ekko.

 

INCLUDE ZPO_OOPS_SS.  "Selection Screen

 

INCLUDE ZPO_OOPS_CDI. "Class Definitions and Implementations

 

DATA: o_purchaseorder TYPE REF TO cl_purchaseorder,

       o_cont1         TYPE REF TO cl_gui_custom_container,

       o_cont2         TYPE REF TO cl_gui_custom_container,

       o_grid1         TYPE REF TO cl_gui_alv_grid,

       o_grid2         TYPE REF TO cl_gui_alv_grid.

 

INCLUDE ZPO_OOPS_PBO. "PBO Modules

 

INCLUDE ZPO_OOPS_PAI. "PAI Modules

 

START-OF-SELECTION.

CREATE OBJECT: o_purchaseorder.

 

CALL METHOD o_purchaseorder->select_po.

 

 

Class definition deferred statement is used when we create the objects of the class before the actual definition and implementation. This way the debugger will come to know that the definition is written later in the code. If we do not define it later on then it will show an error.( I do not need to use the deferred statement here as I have declared classes after the definition).

 

Selection screen

 

SELECTION-SCREEN: BEGIN OF BLOCK po WITH FRAME TITLE text-009.

SELECT-OPTIONS: s_ebeln FOR ekko-ebeln,

                 s_aedat FOR ekko-aedat.

SELECTION-SCREEN: END OF BLOCK po.

 

SELECTION-SCREEN: BEGIN OF BLOCK rb WITH FRAME TITLE text-010.

SELECTION-SCREEN: BEGIN OF LINE.

PARAMETERS: r_releas  RADIOBUTTON GROUP r1.

SELECTION-SCREEN COMMENT 2(15) text-000 FOR FIELD r_releas.

PARAMETERS: r_reject  RADIOBUTTON GROUP r1.

SELECTION-SCREEN COMMENT 20(15) text-001 FOR FIELD r_reject.

PARAMETERS: r_no_rel  RADIOBUTTON GROUP r1.

SELECTION-SCREEN COMMENT 40(17) text-002 FOR FIELD r_no_rel.

SELECTION-SCREEN: END OF LINE.

SELECTION-SCREEN: END OF BLOCK rb.

 

 

I have used a controller interface just to separate the methods used for alv display.

 

INTERFACE i_controller.

   METHODS: alv_display EXPORTING cont TYPE REF TO cl_gui_custom_container

                                  grid TYPE REF TO cl_gui_alv_grid

                                  name TYPE scrfname,

            fc_header,

            on_click FOR EVENT hotspot_click

            OF cl_gui_alv_grid

            IMPORTING e_row_id.

 

ENDINTERFACE.                    "Interface for output


 

I have used one Purchase order class where the attributes and methods that are accessed by the program directly are declared in the Public section and the attributes that are used inside methods of the class are declared under Private section(Encapsulation). Class-constructor is used when you want to initiate the attributes when the class is accessed the first time. In this case I am assigning the layout structure initially.

 

*----------------------------------------------------------------------*

*       CLASS cl_purchaseorder DEFINITION

*----------------------------------------------------------------------*

*

*----------------------------------------------------------------------*

CLASS cl_purchaseorder DEFINITION.

   PUBLIC SECTION.

     CONSTANTS:  lc_cont1     TYPE scrfname VALUE 'HEAD_CON',

                 lc_cont2     TYPE scrfname VALUE 'ITEM_CON'.

     DATA:       lv_cont      TYPE scrfname.

     DATA:       lt_ekko TYPE STANDARD TABLE OF zpo_st_ekko,

                 lt_ekpo TYPE STANDARD TABLE OF zpo_st_ekpo.

     DATA: lt_ekko_tmp TYPE STANDARD TABLE OF zpo_st_ekko.

     CLASS-DATA: ok_code      TYPE syucomm.

     CLASS-DATA: lx_layout    TYPE lvc_s_layo.

     DATA:       lt_fcat      TYPE lvc_t_fcat.

     CLASS-METHODS: class_constructor.

     INTERFACES: i_controller.

     ALIASES: alv_display FOR i_controller~alv_display,

              fc_header   FOR i_controller~fc_header,

              on_click    FOR i_controller~on_click.

     METHODS:   select_po,

                save,

                back,

                exit.

 

   PRIVATE SECTION.

     CONSTANTS: lc_procstat1 TYPE meprocstate VALUE '01',

                lc_procstat4 TYPE meprocstate VALUE '04',

                lc_procstat5 TYPE meprocstate VALUE '05',

                lc_procstat6 TYPE meprocstate VALUE '06'.

 

     DATA: lv_ans   TYPE c.

     DATA: lv_procstat1 TYPE meprocstate,

           lv_procstat2 TYPE meprocstate.

     DATA: lx_fcat TYPE lvc_s_fcat.

     DATA: lx_ekko     TYPE zpo_st_ekko,

           lx_ekko_tmp TYPE zpo_st_ekko.

     DATA: lt_fieldcat  TYPE slis_t_fieldcat_alv,

           lx_fieldcat  TYPE slis_fieldcat_alv.

 

ENDCLASS.                    "cl_purchaseorder DEFINITION


 

 

I am created structures in the database to avoid building the field catalog, used the FM 'REUSE_ALV_FIELDCATALOG_MERGE' to get field catalog of a structure in order to modify it for hotspot and to make a field editable. Event hotspot_click is used for the method on_click. When we call set handler for this event, the method gets called each time the event is triggered. Constructor is called initially when the object of a class is created.

 

*----------------------------------------------------------------------*

*       CLASS cl_purchaseorder IMPLEMENTATION

*----------------------------------------------------------------------*

*

*----------------------------------------------------------------------*

CLASS cl_purchaseorder IMPLEMENTATION.


   METHOD class_constructor.

     lx_layout-zebra = 'X'.

     lx_layout-cwidth_opt = 'X'.

   ENDMETHOD.                    "class_constructor


   METHOD alv_display.

     CREATE OBJECT cont

       EXPORTING

         container_name = name.

 

     CREATE OBJECT grid

       EXPORTING

         i_parent = cont.

   ENDMETHOD.                    "alv_display

 

   METHOD select_po.

     IF r_releas EQ 'X'.

       lv_procstat1 = lc_procstat5.

       lv_procstat2 = lc_procstat5.

     ELSEIF r_reject EQ 'X'.

       lv_procstat1 = lc_procstat5.

       lv_procstat2 = lc_procstat6.

     ELSEIF r_no_rel EQ 'X'.

       lv_procstat1 = lc_procstat1.

       lv_procstat2 = lc_procstat4.

     ENDIF.

     SELECT ebeln

            bukrs

            description

            bstyp

            bsart

            aedat

            ernam

     FROM ekko

     INTO TABLE lt_ekko

     WHERE ebeln IN s_ebeln

     AND   aedat IN s_aedat

     AND   procstat BETWEEN lv_procstat1 AND lv_procstat2.

     IF lt_ekko IS NOT INITIAL.

       lt_ekko_tmp[] = lt_ekko[].

       CALL SCREEN 9000.

     ELSE.

       MESSAGE s000(8i) WITH text-011 DISPLAY LIKE 'E'.

     ENDIF.

   ENDMETHOD.                    "select_po

 

   METHOD back.

     CALL FUNCTION 'POPUP_TO_CONFIRM'

       EXPORTING

         text_question  = text-006

         text_button_1  = text-007                           "'Ja'(001)

         text_button_2  = text-008"'Nein'(002)

       IMPORTING

         answer         = lv_ans

       EXCEPTIONS

         text_not_found = 1

         OTHERS         = 2.

     IF sy-subrc EQ 0.

       CASE lv_ans.

         WHEN '1'.

           CALL METHOD me->save.

           LEAVE TO SCREEN 0.

         WHEN '2'.

           LEAVE TO SCREEN 0.

         WHEN 'X'.

       ENDCASE.

     ENDIF.

   ENDMETHOD.                    "back


   METHOD exit.

     CALL FUNCTION 'POPUP_TO_CONFIRM'

       EXPORTING

         text_question  = text-006

         text_button_1  = text-007                           "'Ja'(001)

         text_button_2  = text-008"'Nein'(002)

       IMPORTING

         answer         = lv_ans

       EXCEPTIONS

         text_not_found = 1

         OTHERS         = 2.

     IF sy-subrc EQ 0.

       CASE lv_ans.

         WHEN '1'.

           CALL METHOD me->save.

           LEAVE PROGRAM.

         WHEN '2'.

           LEAVE PROGRAM.

         WHEN 'X'.

       ENDCASE.

     ENDIF.

   ENDMETHOD.                  "constructor

 

   METHOD save.

     LOOP AT lt_ekko_tmp INTO lx_ekko_tmp.

       READ TABLE lt_ekko INTO lx_ekko WITH KEY ebeln = lx_ekko_tmp-ebeln.

       IF sy-subrc EQ 0.

         IF lx_ekko-description NE lx_ekko_tmp-description.

           UPDATE ekko

       SET description = lx_ekko-description

       WHERE ebeln = lx_ekko-ebeln.

           IF sy-subrc EQ 0.

             MESSAGE s000(8i) WITH text-005.

           ENDIF.

         ENDIF.

       ENDIF.

     ENDLOOP.

     lt_ekko_tmp[] = lt_ekko[].

   ENDMETHOD.                    "save

 

   METHOD fc_header.

     CALL FUNCTION 'REUSE_ALV_FIELDCATALOG_MERGE'

       EXPORTING

         i_structure_name       = 'ZPO_ST_EKKO'

       CHANGING

         ct_fieldcat            = lt_fieldcat

       EXCEPTIONS

         inconsistent_interface = 1

         program_error          = 2

         OTHERS                 = 3.

     LOOP AT lt_fieldcat INTO lx_fieldcat.

       MOVE-CORRESPONDING lx_fieldcat TO lx_fcat.

       lx_fcat-coltext = lx_fieldcat-seltext_l.

       IF lx_fcat-fieldname = 'EBELN'.

         lx_fcat-hotspot   = 'X'.

       ELSEIF lx_fcat-fieldname = 'DESCRIPTION'.

         lx_fcat-edit      = 'X'.

       ENDIF.

       APPEND lx_fcat TO lt_fcat.

     ENDLOOP.

   ENDMETHOD.                    "fc


   METHOD on_click.

     READ TABLE lt_ekko INTO lx_ekko INDEX e_row_id-index.

     IF sy-subrc EQ 0.

       SELECT ebelp

              werks

              lgort

              aedat

              txz01

              matnr

       FROM ekpo

       INTO TABLE lt_ekpo

       WHERE ebeln = lx_ekko-ebeln.

       IF sy-subrc EQ 0.

         CALL SCREEN 9001.

       ENDIF.

     ENDIF.

   ENDMETHOD.                    "on_click

 

ENDCLASS.                    "cl_purchaseorder IMPLEMENTATION

 

 

 

PBO

 

*&---------------------------------------------------------------------*

*&  Include           ZPO_OOPS_PBO

*&---------------------------------------------------------------------*

*&---------------------------------------------------------------------*

*&      Module  STATUS_9000  OUTPUT

*&---------------------------------------------------------------------*

*       Header screen

*----------------------------------------------------------------------*

MODULE status_9000 OUTPUT.

   SET PF-STATUS 'STATUS_9000'.

   SET TITLEBAR 'TITLE_9000'.

   IF o_grid1 IS INITIAL.

 

     o_purchaseorder->lv_cont = o_purchaseorder->lc_cont1.

 

     CALL METHOD o_purchaseorder->alv_display

       IMPORTING

         cont = o_cont1

         grid = o_grid1

         name = o_purchaseorder->lv_cont.

 

     CALL METHOD o_purchaseorder->fc_header.

 

     CALL METHOD o_grid1->set_table_for_first_display

       EXPORTING

         is_layout        = o_purchaseorder->lx_layout

*        i_structure_name = 'ZPO_ST_EKKO'

       CHANGING

         it_outtab        = o_purchaseorder->lt_ekko

         it_fieldcatalog  = o_purchaseorder->lt_fcat.

 

     SET HANDLER o_purchaseorder->on_click FOR o_grid1.

   ELSE.

     CALL METHOD o_grid1->refresh_table_display.

   ENDIF.

 

ENDMODULE.                 " STATUS_9000  OUTPUT

 

*&---------------------------------------------------------------------*

*&      Module  STATUS_9001  OUTPUT

*&---------------------------------------------------------------------*

*       Item Screen

*----------------------------------------------------------------------*

MODULE status_9001 OUTPUT.

   SET PF-STATUS 'STATUS_9000' EXCLUDING 'SAVE'.

   SET TITLEBAR 'TITLE_9001'.

   IF o_grid2 IS INITIAL.

     o_purchaseorder->lv_cont = o_purchaseorder->lc_cont2.

 

     CALL METHOD o_purchaseorder->alv_display

       IMPORTING

         cont = o_cont2

         grid = o_grid2

         name = o_purchaseorder->lv_cont.

 

     CALL METHOD o_grid2->set_table_for_first_display

       EXPORTING

         is_layout        = o_purchaseorder->lx_layout

         i_structure_name = 'ZPO_ST_EKPO'

       CHANGING

         it_outtab        = o_purchaseorder->lt_ekpo.

   ELSE.

     CALL METHOD o_grid2->refresh_table_display.

   ENDIF.

 

ENDMODULE.                 " STATUS_9001  OUTPUT

 

PAI

 

*&---------------------------------------------------------------------*

*&  Include           ZPO_OOPS_PAI

*&---------------------------------------------------------------------*

*&---------------------------------------------------------------------*

*&      Module  USER_COMMAND_9000  INPUT

*&---------------------------------------------------------------------*

*       text

*----------------------------------------------------------------------*

MODULE user_command_9000 INPUT.

 

   CASE cl_purchaseorder=>ok_code.

     WHEN 'BACK'.

       CALL METHOD o_grid1->check_changed_data.

       IF o_purchaseorder->lt_ekko_tmp NE o_purchaseorder->lt_ekko.

         CALL METHOD o_purchaseorder->back.

       ELSE.

         LEAVE TO SCREEN 0.

       ENDIF.

     WHEN 'EXIT' OR 'CANCEL'.

       CALL METHOD o_grid1->check_changed_data.

       IF o_purchaseorder->lt_ekko_tmp NE o_purchaseorder->lt_ekko.

         CALL METHOD o_purchaseorder->exit.

       ELSE.

         LEAVE PROGRAM.

       ENDIF.

 

     WHEN 'SAVE'.

       CALL METHOD o_grid1->check_changed_data.

       IF o_purchaseorder->lt_ekko_tmp NE o_purchaseorder->lt_ekko.

         CALL METHOD o_purchaseorder->save.

       ELSE.

         MESSAGE s000(8i) WITH text-012 DISPLAY LIKE 'E'.

       ENDIF.

 

   ENDCASE.

 

ENDMODULE.                 " USER_COMMAND_9000  INPUT

*&---------------------------------------------------------------------*

*&      Module  USER_COMMAND_9001  INPUT

*&---------------------------------------------------------------------*

*       text

*----------------------------------------------------------------------*

MODULE user_command_9001 INPUT.

   CASE cl_purchaseorder=>ok_code.

     WHEN 'BACK'.

       LEAVE TO SCREEN 0.

     WHEN 'EXIT' OR 'CANCEL'.

       CALL METHOD o_grid2->check_changed_data.

       IF o_purchaseorder->lt_ekko_tmp NE o_purchaseorder->lt_ekko.

         CALL METHOD o_purchaseorder->exit.

       ELSE.

         LEAVE PROGRAM.

       ENDIF.

   ENDCASE.

ENDMODULE.                 " USER_COMMAND_9001  INPUT

Introduction


In the first part of this series I introduced a file backend which supports multiple types of storage types and allows the combination with authorization check or file filter classes.

The design pattern in use is the decorator pattern. Every class which implements additional functionality declares the usage of a member instance which is described by the same interface which is already implemented by the class itself.

decorator.PNG

This allows us to have more flexibility than in another approach which used inheritance.

Having a decorator based file access backend and a factory which creates and combines these instances for you comes close to what the requirements said. But already the very first requirement – “Multiple storages will need to be supported by configuration, e.g. cluster database storage or SAP Generic Objects Services based storage” is not yet satisfied: There is no possibility to configure the object hierarchy yet.

In this blog post I’m going to show you how this can be achieved by utilizing an IoC Container.

 

Configuring the IoC Container by API

 

The decorator pattern allows us to have the flexibility we need. The instances may be combined without any restriction since they implement the same interface. But this approach is still a static one, because of the way the objects are created.

Creation without IoC.PNG

If this coding was part of a factory, you would need to have a new method every time you request a slightly new combination of object instances.

In software which is to be shipped to customers, having a factory which is not extensible would be too restrictive.

The coding which creates and combines all the instances could also be implemented by a BADI. The advantage would be that every customer could implement its own file backend by combining certain instances.

Unfortunately, in the first part of this series, one requirement stated clearly that there could be multiple scenarios in which the file access could be used: “within the same system, there could be multiple scenarios, which require different kinds of storage types”

So what would be the solution? Have a BADI implementation for each of these scenarios?

With an IoC Container you could define freely combined object hierarchies. This definition could be done in source code but could also be done in customizing.

The basis of the configuration of an IoC Container is the contract. The contract is, technically spoken, an ABAP OO Interface.

There could be one or multiple implementers for each contract. Implementers are, technically spoken, ABAP OO classes which implement this interface.

  The easiest example which shows the usage of an IoC Container is the creation of one class for one interface:

IoC 01.PNG

The IoC Container is created using the following call:

        DATA lo_ioc_container TYPE REF TO /leos/if_ioc_container.
*       create IoC container
        lo_ioc_container = /leos/cl_ioc_container=>create_empty_instance( ).

 

Next, a contract and its implementer need to be registered:

*       register instance for binary access
        lo_ioc_container->add_implementer(
          EXPORTING
            iv_contract = '/leos/if_ioc_demo_bin_access'
            iv_implementer =  '/leos/cl_ioc_demo_bin_access' ).

The instance can be resolved with the GET_IMPLEMENTER-method of the IoC Container.

This example does not have any benefit yet. The implementation is not yet defined in the customizing and we didn’t even combine two instances with each other. The combination comes next.

If there are multiple implementers for one contract, they can be distinguished by using a filter value.

IOC filter value.PNG

This filter value can be handed over when the GET_IMPLEMENTER-Method is called.

Since we need to combine two instances directly with each other, we use another basic concept of the IoC Container: Configuration of Constructor Injection. In the example the internal table LT_CI takes care of the constructor injection.

CI.PNG

Requesting the implementer still works the same way we’ve already seen before.

GET_IMPLEMENTER.PNG

The whole code looks like this:

IoC02.png

The debugger shows how this object is nicely nested:

debugger.PNG

The IoC Container will resolve all dependencies, which are declared by constructor injection, automatically for you.

If the implementer of the dependency declares its own dependencies, they are of course also resolved before the object is getting created – which means that you can nest dependencies as deep as you want.

Features like lifetime control, Setter-Injection, passing parameters by the application or passing default values to a constructor are also included.


Customizing

 

Up to this point, there was still a drawback: We defined the implementers directly in the coding at design time. Manually nesting these implementations would still be easier. So let’s shift all the setup routines of the previously shown coding to the customizing.

The whole definition starts with an application. It’s up to you which name you chose.

customizing01.PNG


Now let’s declare the contracts. There is only one in this application. Since we want the IoC Container to create a new instance every time we ask for it, we set the lifetime to “transient”

customizing02.PNG

There are two implementers yet, one is the default implementer, while the other one is only valid if a filter value ‘BA’ has been passed by the application or has been declared in the constructor injection configuration.

customizing03.PNG

The constructor injection configuration of /LEOS/CL_IOC_DEMO_ACCESS_AUTH comes next:

customizing04.PNG

That’s it – we are done.

The whole coding to get an instance can be simplified to this piece of code:

IoC 03.PNG


Conclusion

 

The setup of complex object hierarchies can be dramatically simplified with the help of an IoC Container. The container takes care of the creation of the objects you need, satisfies all required dependencies and controls the lifetimes. Changes to the hierarchy are possible by only changing the customizing. The code is never again touched for this type of change.



I believe that the SCN has an enormous creative potential. We use to discuss our ideas online and scetch up ideas on Programming in weblogs.

SAP introduces for about several years the ABAP Unit-Framework to support unit-testing in ABAP.

To this time I thougth it would be also an announcement of an supporting Mocking-Framework.

... But noting happens

 

Every Time I use ABAP Unit I decide If I should write Handwritten-Mocks or not. And sometimes the result is to do it not, because of lack of time.

 

A lot of Programmers think in the same direction and some Blogs are also published on SCN.

They are a lot of interesting Topics around Mocking in ABAP Objects:

 

A lot of Blogs help to understand how an Framework could or should work to develop an Mocking-Framework:

 

Of course, we could also start an open-source- (code-exchange-) project for an mocking-framework (Yes they exist the Project aMock https://cw.sdn.sap.com/cw/groups/amock , but no progress is visible...) but maybe SAP could support with an Standard-Mocking-Framework?

 

I´ve heard about an new created Standard-Framework for SAP HANA, maybe it is possible to down-port it to lower SAP-NetWeaver Releases?

What do you think?

Introduction

This is the first part of my new series “IoC Container in ABAP”.

“IoC” is an abbreviation of “Inversion of Control” which is a programming paradigm. Instead of describing it in endless boring chapters I’m going to give you an example today.

Today I’m going to give you an introduction in the scenario which we are going to deal with through the upcoming blog posts.

Bad news is: There will be no IoC Container shown today.

Good News is: I’m going to prepare an application architecture which will match pretty well to what an IoC Container should usually automate. There will be bad architecture and there will be a not so bad one.

Bad news II is, that this example is not really an example which shows the full power of an IoC Container. In fact, for an IoC Container it is a very tiny example which could also be realized without such a container. However this small example illustrates how dependencies work and should be used. And that is what an IoC is about in the first step (beside many other aspects).

 

Imagine, a new file access component would have to be designed in pure ABAP, having the following requirements

  • Multiple storages will need to be supported by configuration, e.g. cluster database storage or SAP Generic Objects Services based storage
  • The client of the component should not be aware of the implementation details, hence, should not need to know which implementation is chosen
  • Having an extensible backend, which supports the following features:
    • authorization checks when saving the files
    • authorization checks when accessing the files
    • file filters
    • multiple storage types (as mentioned above)
    • the above mentioned features need to be configurable based on the usage scenario
  • within the same system, there could be multiple scenarios, which require different kinds of storage types

This blog post is not going to deal with the implementation details of these features – it is going to deal with the architecture which allows us to support the kind of flexibility we require.


Interfaces

So what to do? Go and create a new file backend class which performs hard coded authorization checks on its own authorization object? Or which filters files based on some customizing? Check customizing and switch to sub-methods using IF- or CASE -Statements?

No – let’s start with some interface descriptions in order to get an idea of what a file backend should provide. Let’s do not take care of any implementation details.

  In this demo it is going to be a pretty simple interface description. The only operations which will be supported are saving and retrieving file contents. There will be no file lists. And there will be no directories, not in this demo.

Interface.PNG

In fact, this is the only interface which will be shown today.

 

Drawbacks of having one class

If the required functionality was only implemented by one class, there would be certain drawbacks:

  • Authorization checks would have to be implemented in the same class which also deals with storing files to the backend. Having more than one responsibility for one class is a clear violation of the Single Responsibility Principle
  • Same for the file filter – it would be again a violation of the Single Responsibility Principle
  • Additional functionalities would not be testable in an isolated environment
  • Customer specific changes, e.g. in the way files are stored in the backend would require to change the existing class. This is a violation of the Open Closed Principle.

  If the file access service would be used in various scenarios, a certain kind of file access would be chosen every time. This violates the principle of having loosely coupled components

 

Realization Variant 1 - Inheritance


The file access is described by the interface /LEOS/IF_IOC_DEMO_BIN_ACCESS.

The classic approach is to have classes which inherit from each other. The authorization check would inherit from the file access class. Every time a method is called, the authorization check class performs its checks before the super class method is called. Same for the file filter. Any client uses in fact not the file access, but rather one of its sub classes.

vererbung.PNG

Unfortunately this approach has some severe drawbacks. If the file access needs to be replaced by another implementation which access a different kind of file repository, the whole inheritance hierarchy would have to be created again, causing the creation of two classes for each file filter and authorization check, but having the same responsibility:

vererbung02.PNG

The written classes are not reusable since they are caught in an inheritance hierarchy. Further requirements would result in further child classes.

And any super class functionality that exists in between cannot just be switched off if the use case or the customer requirements ask for it.

So in terms of maintenance and extensibility, features that have been explicitly written down at the top of this blog, this approach is a bad one.


Realization Variant 2 - Decorator

So instead of having big inheritance hierarchies, we use the decorator pattern instead. Every new class implements  the interface /LEOS/IF_IOC_DEMO_BIN_ACCESS on its own.

Every class which implements additional functionality, like authorization checks or file filters, has a member instance MO_BIN_ATT_ACCESS type with the same Interface /LEOS/IF_IOC_DEMO_BIN_ACCESS.


decorator.PNG

Validations or checks can be done in the specific method of the specific class which is responsible for the implementation of the feature, and if certain checks fail, they are not going to be delegated to the member instance.

save_attachment authorization.PNG

The classes declare the dependencies by their constructors. The classes /LEOS/CL_IOC_DEMO_ACCESS_AUTH and /LEOS/CL_IOC_DEMO_FILTER do so by expecting another instance of type /LEOS/IF_IOC_DEMO_BIN_ACCESS in their constructor.

This means, any additional functionality and any file access class are freely combinable, since all of them implement the same interface and request a nested instance in their constructor.

A client may create the file backend by calling a factory which creates and combines the instances.

This piece of code shows the creation of a file access instance and combines it with an authorization check instance. In any upcoming example the file filter is not shown since its existence makes no difference from the architectural point of view. But the examples are getting smaller, hence better to understand.


Creation without IoC.PNG

Having a decorator based file access backend and a factory which creates and combines these instances for you comes close to what the requirements said. But already the very first requirement – “Multiple storages will need to be supported by configuration, e.g. cluster database storage or SAP Generic Objects Services based storage” is not yet satisfied: There is no possibility to configure the object hierarchy yet.

 

In my next blog post I’m going to show you how this can be achieved by utilizing an IoC Container.



Introduction

Unit Tests are an integral part of the software development lifecycle. The purpose of tests is to ensure the proper functionality of a system. Also, if changes are made to specific parts of the system, Unit Tests can immediately show if any functionality has been broken during the last update.

A common problem in unit testing is to separate the system under test, which is subject to the unit test, from other parts which are not to be tested. Common parts, which need to be separated and replaced specifically for unit tests are usually business object repositories or database access components as well as components which rely on third party systems.

This blog is not about unit testing in ABAP. This blog is about developing them faster.

 

Scenario

For demo purposes, I build up a scenario. It includes a simple flight observer, which has access to a flight information system.

Furthermore, it has access to an alert component which raises mail, SMS etc. alerts.

Both components are dependencies on which the observer relies on. Both components will need to be replaced by mockups for testing purpose since they are not subject to the unit test. Rather, the flight observer is to be tested, in the following sections referred to as the system under test (SUT).

The SUT is implemented by /LEOS/CL_FLIGHT_OBSERVER.

The flight information component is described by the interface /LEOS/IF_IS_IN_TIME_INFO.

The alert processor is described by the interface /LEOS/IF_FLIGHT_ALERT_PROCESS.

UML.PNG

 

The validation logic is very simple: It decides, weather an alert for a specific flight is raised or not. Every delay with more than 60 minutes is getting raised. However, in any other real scenario, there wouldn’t be such a simple hard coded routine but for demo purposes it is fine.
All dependencies to the two other components are set via its constructor using dependency injection.

 

Unit Test realization Part I

The method which is going to be tested is described below and implements the 60-minutes rule: every delay of more than 60 minutes is getting delegated to the alert system.

 

* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method /LEOS/CL_FLIGHT_OBSERVER->OBSERVE_FLIGHT
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_CARRID                      TYPE        S_CARR_ID
* | [--->] IV_CONNID                      TYPE        S_CONN_ID
* | [--->] IV_FLDATE                      TYPE        S_DATE
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD observe_flight.
  DATA lv_delay_minutes TYPE i.
lv_delay_minutes = mo_is_in_time_access->get_delay( iv_carrid = iv_carrid iv_connid = iv_connid iv_fldate = iv_fldate ).
  IF lv_delay_minutes > 60.
mo_alert_processor->alert_delay( iv_carrid = iv_carrid iv_connid = iv_connid iv_fldate = iv_fldate ).
  ENDIF.
ENDMETHOD.


This implementation is not a challenge at all. More tedious is the implementation of local classes which are implementing the functionality of the two components on which the SUT depends. These implementations are called mocks.

The flight information access, implementing /LEOS/IF_IS_IN_TIME_INFO, would have to return flights which are sometimes in time and sometimes not. This flight information would have to be hard coded in the unit test. It might look like this:

 

CLASS lcl_flight_info DEFINITION.
  PUBLIC SECTION.
    INTERFACES /leos/if_is_in_time_info.
ENDCLASS.                    "lcl_flight_info DEFINITION
CLASS lcl_flight_info IMPLEMENTATION.
  METHOD /leos/if_is_in_time_info~get_delay.
    IF iv_carrid = 'LH' AND iv_connid = 402 AND iv_fldate = '20121109'.
      rv_delay = 100.
    ENDIF.
    IF iv_carrid = 'LH' AND iv_connid = 402 AND iv_fldate = '2012110'.
      rv_delay = 5.
    ENDIF.
ENDMETHOD. "/LEOYM/if_is_in_time_info~get_delay
ENDCLASS.                    "lcl_flight_info IMPLEMENTATION


There would be another mockup for the alert component, tracking the number of calls made to the method ALERT_DELAY( ). We do not show it here.

The setup routine for the unit test to setup all the required objects would look like this:

 

  METHOD setup.
*   this call creates the flight information mockup
    CREATE OBJECT mo_is_in_time_access TYPE lcl_flight_info.
*   create an empty alert backend (we just need to track the number of method calls)
    CREATE OBJECT mo_alert_processor_mocker TYPE lcl_alert_process.
*   create the flight observer which is subject to this test
    CREATE OBJECT mo_system_under_test
      EXPORTING
io_alert_processor = mo_alert_processor
        io_in_time_access  = mo_is_in_time_access.
ENDMETHOD. "setup

 

The test now calls the SUT with various flight data. Some data will lead to a delay, like flight LH / 402 / in November 9th 2012, some will not (like LH / 402 in November 10th 2012). The test is going to evaluate if the alert processor has been called or not, depending on the requested flight data.

 

Unit Test realization Part II

The described approach requires  a lot of coding to implement the mockups. This coding is never ever reused and basically a waste of time.

This is where mocking frameworks come into place.

Mocking Frameworks are not meant for productive usage, however, their only usage is to save coding in unit tests, hence, development efforts.

The same mockup, which returns flight information data could be mocked using a mocking framework:

 

    DATA lo_is_in_time_access TYPE REF TO /leos/if_is_in_time_info .
    DATA lo_is_in_time_mocker TYPE REF TO /leos/if_mocker.
    DATA lo_mocker_method TYPE REF TO /leos/if_mocker_method.
*   create the flight information backend
lo_is_in_time_mocker = /leos/cl_mocker=>/leos/if_mocker~mock( iv_interface = '/LEOS/IF_IS_IN_TIME_INFO' ).
lo_mocker_method = lo_is_in_time_mocker->method( 'GET_DELAY' )."mock method GET_DELAY
lo_mocker_method->with( i_p1 = 'LH' i_p2 = 402 i_p3 = '20121109' )->returns( 100 )."flight LH / 402 in November 9th is not in time
lo_mocker_method->with( i_p1 = 'LH' i_p2 = 402 i_p3 = '20121110' )->returns( 5 )."flight LH / 402 in November 10th is in time
*   this call creates the flight information mockup
lo_is_in_time_access ?= mo_is_in_time_mocker->generate_mockup( ).


The whole mockup implementation is described by only two lines of ABAP coding. Three more lines include data declarations and one line actually generates the mock object for you. This sums up to 8 lines of code, in comparison to 14 lines of code which are needed for the manual implementation.

The mockup for the alert processor is even more simple, since its only purpose is to track the number of calls made against method ALERT_DELAY. This feature is already included in the mocking framework as described in the test coding utilizing these mocks. You would just need to call method HAS_METHOD_BEEN_CALLED( … ) on the corresponding mocker object.

 

    DATA lo_alert_processor_mocker TYPE REF TO /leos/if_mocker.
    DATA lo_alert_processor TYPE REF TO /leos/if_mocker_method.
*   create an empty alert backend (we just need to track the number of method calls)
lo_alert_processor_mocker = /leos/cl_mocker=>/leos/if_mocker~mock( iv_interface = '/LEOS/IF_FLIGHT_ALERT_PROCESS' ).
*   this call creates the alert processor mockup
lo_alert_processor ?= lo_alert_processor_mocker->generate_mockup( ).


Unit Test coding

 

REPORT /leos/_test_flight_observer.
*----------------------------------------------------------------------*
*       CLASS lcl_test_observer DEFINITION
*----------------------------------------------------------------------*
*
*----------------------------------------------------------------------*
CLASS lcl_test_observer DEFINITION FOR TESTING.
  "#AU Risk_Level Harmless
  "#AU Duration Short
  PROTECTED SECTION.
    DATA mo_is_in_time_access TYPE REF TO /leos/if_is_in_time_info .
    DATA mo_is_in_time_mocker TYPE REF TO /leos/if_mocker.
    DATA mo_alert_processor TYPE REF TO /leos/if_flight_alert_process .
    DATA mo_alert_processor_mocker TYPE REF TO /leos/if_mocker.
    DATA mo_system_under_test TYPE REF TO /leos/cl_flight_observer.
  PRIVATE SECTION.
    METHODS setup.
    METHODS teardown.
    METHODS test_no_alert FOR TESTING.
    METHODS test_with_alert FOR TESTING.
ENDCLASS.                    "lcl_test_observer DEFINITION
*----------------------------------------------------------------------*
*       CLASS lcl_test_observer IMPLEMENTATION
*----------------------------------------------------------------------*
*
*----------------------------------------------------------------------*
CLASS lcl_test_observer IMPLEMENTATION.
  METHOD setup.
    DATA lo_mocker_method TYPE REF TO /leos/if_mocker_method.
*   create the flight information backend
mo_is_in_time_mocker = /leos/cl_mocker=>/leos/if_mocker~mock( iv_interface = '/LEOS/IF_IS_IN_TIME_INFO' ).
lo_mocker_method = mo_is_in_time_mocker->method( 'GET_DELAY' ).
lo_mocker_method->with( i_p1 = 'LH' i_p2 = 402 i_p3 = '20121109' )->returns( 100 ).
    lo_mocker_method->with( i_p1 = 'LH' i_p2 = 402 i_p3 = '20121110' )->returns( 5 ).
*   this call creates the flight information mockup
mo_is_in_time_access ?= mo_is_in_time_mocker->generate_mockup( ).
*   create an empty alert backend (we just need to track the number of method calls)
mo_alert_processor_mocker = /leos/cl_mocker=>/leos/if_mocker~mock( iv_interface = '/LEOS/IF_FLIGHT_ALERT_PROCESS' ).
*   this call creates the alert processor mockup
mo_alert_processor ?= mo_alert_processor_mocker->generate_mockup( ).
*   create the flight observer which is subject to this test
    CREATE OBJECT mo_system_under_test
      EXPORTING
io_alert_processor = mo_alert_processor
io_in_time_access  = mo_is_in_time_access.
ENDMETHOD. "setup
  METHOD teardown.
ENDMETHOD. "teardown
  METHOD test_no_alert.
mo_system_under_test->observe_flight( iv_carrid = 'LH' iv_connid = 402 iv_fldate = '20121110' ).
cl_aunit_assert=>assert_initial( mo_alert_processor_mocker->has_method_been_called( 'ALERT_DELAY' ) ).
ENDMETHOD. "test_no_alert
  METHOD test_with_alert.
mo_system_under_test->observe_flight( iv_carrid = 'LH' iv_connid = 402 iv_fldate = '20121109' ).
    cl_aunit_assert=>assert_not_initial( mo_alert_processor_mocker->has_method_been_called( 'ALERT_DELAY' ) ).
ENDMETHOD. "test_with_alert
ENDCLASS.                    "lcl_test_observer IMPLEMENTATION

 

Conclusion

This was only a simple example. But even this example showed how easy about 40% of code required to mock objects could be saved. What about real world scenarios with even more complex dependencies and behaviour? In fact, 40% of coding can be saved also in these projects, often even more. Most objects can be easily mocked using a mocking framework if they are really treated as a black box.

What a good mocking framework should also provide, is mocking of specific classes using inheritance. Also, exceptions might need to be registered for specific inputs.

Currently, the described mocking framework is still under development. It supports mocks based Interfaces, method call observation and raising of exceptions but not yet the mocking based on already existing classes. However, it is in productive usage in our projects and has already been extremely valuable for making unit tests simpler and easier to implement.

Further blogs may focus on how to manage dependencies in big development projects and how dependency injection might work in ABAP using an IoC Container. Stay tuned.

Introduction: -

Display dynamic information (data) in three different formats.

  1. Simple ALV
  2. MS-Excel Sheet
  3. MS-Word Document

Topic Cover: -

  1. Dynamic Internal Table Creation
  2. Field-symbol Dynamic Programming
  3. OLE Automation Process on MS-Excel and MS-Document.

Step 1.

Enter a valid table name if Table input field and click “Show Columns”, button to get all columns of respective table.

All columns will display in below Table Control with their respective short description in column Name & Text.

Step 2.

Information would be display in three different formats based on the user selection.

By the “No of Rows” option user can restrict the information for display.

As well as user can display only those columns information which he/she wants to display with the help of the selection of column from table control.

 

Step 3.

After select some of the columns from Table control or not, by the clicking on “ALV Display” button, we will get the simple ALV Display type output in new screen.


 

In output user will get only those column records what they selected or it will display all columns records.

On top of the page the selected table description will display.

And by the selection of any one of the columns value, it will generate a pop-up message which will display the selected column with selected value.

 

Step 4: -

As the same way, by the clicking of “Excel Display” button a Browser will open to Select a folder, where the generated excel file would be save.

After selecting a folder, again a pop-up message will display, where user need to select a option to display the generated excel file or it will process in back ground.


By the selection of above pop-up message, excel will display in front end or process will be in background.

 

The Excel file will create with the respective information’s.

The OLE Process is little bit time taking process, so if we run the particular program for the large number of information it will take time to generate the final file.

Step 5: -

In the same way, by the clicking of “Word Display” button a Browser will open to select a folder, where the generated Word File would be saved.

After selecting a folder, again a pop-up message will display, where user need to select an option to display the generated word file or it will process in back ground.

By the selection of above pop-up message, word will display in front end or process will be in background.

 

Basically the current MS-Word file only support for 63 columns. If the selected column number is more then 63, then it will not process for word document.

Summary: -

Displaying and downloading the information in three different ways. Based on the Present scenario, it’s covering the customer main requirement in there won declared format, what they used to seek in output.

 

Source Code: -

It’s a Module Pool programming.

Two include are defined here.

  1. ZDATA_DISPLAY_TOP
  2. ZDATA_DISPLAY_PAI

In the ZDATA_DISPLAY_TOP Include, all global variables are declared.

*&---------------------------------------------------------------------*
*& Include ZDATA_DISPLAY_TOP                                 Module Pool      ZDATA_DISPLAY
*&
*&---------------------------------------------------------------------*

PROGRAM  ZDATA_DISPLAY.

TYPE-POOLS:   OLE2, ABAP, SLIS.
TABLES:       DD03L, DD02VV.

*&-----------------------------TYPE FOR DISPLAY THE SELECTED TABLE COLUMNS AND RESPECTIVE COLUMN TEXT
TYPES:       
BEGIN OF  S_INFO,
MARK,
FLDNAM 
TYPE DD03L-FIELDNAME,
FLDTXT 
TYPE AS4TEXT,
LEN    
TYPE I,
END OF    S_INFO.
*&-----------------------------TYPE FOR DISPLAY THE SELECTED TABLE COLUMNS AND RESPECTIVE COLUMN TEXT


*&-----------------------------TABLE CONTROL FOR DISPLAY THE SEELCTED TBALE COLUMNS AND TEXT
CONTROLS:     TB_MAIN
TYPE TABLEVIEW USING SCREEN '9000'.
*&-----------------------------TABLE CONTROL FOR DISPLAY THE SEELCTED TBALE COLUMNS AND TEXT


DATA:         G_TABNAME  
TYPE TABNAME.                   "TABLE NAME

DATA:         GIT_INFO   
TYPE TABLE OF S_INFO,           "INTERNAL TABLE TO STORE TABLE'S COLUMNS AND COLUMN TEXT
GFL_INFO   
LIKE LINE OF GIT_INFO.          "WORK AREA FOR READING THE GIT_INFO INTERNAL TABLE

DATA:         G_OK       
TYPE SY-UCOMM,                  "FOR READ THE USER INTERACTION
G_SAVE     
TYPE SY-UCOMM.

DATA:         GIT_TABINFO
TYPE ABAP_COMPDESCR_TAB,        "INTERNAL TABLE FOR STORE THE SELECTED TABLE COLUMNS INFORMATION
GFL_TABINFO
TYPE ABAP_COMPDESCR.            "WORK AREA FOR READ THE GIT_TABINFO INTERNAL TABLE
DATA:         CL_ABAP
TYPE REF TO CL_ABAP_STRUCTDESCR.    "CLASS OBJECT TO READ THE SELECTED TABLE COLUMNS INFORMATION

DATA:         G_RECS     
TYPE I.                         "GLOBAL INTEGER VARIABLE

DATA:         G_ROWS     
TYPE I.                         "ROW INFORMATION TO DISPLAY INFORMATION IN RESPECTIVE SELECTED METHOD

DATA:         GIT_FLDS   
TYPE LVC_T_FCAT,                "INTERNAL TABLE TO STORE THE COLUMNS & TEXT INFORMATION FOR CREATE THE DYNAMIC TABLE
GFL_FLDS   
TYPE LVC_S_FCAT.                "WORK AREA FOR READ THE GIT_FLDS INTERNAL TABLE


DATA:         GIT        
TYPE REF TO DATA,               "INTERNAL TABLE FOR DYNAMIC TABLE
GFL        
TYPE REF TO DATA.               "WORK AREA FOR THE DYNAMIC TABLE

FIELD-SYMBOLS:  <GIT>    
TYPE STANDARD TABLE,
<GFL>,
<VAL>.

DATA:         GIT_FCAT   
TYPE SLIS_T_FIELDCAT_ALV,     "INTERNAL TABLE FOR THE FIELD CATALOG INFORMATION FOR RESUE ALV GRID DISPLAY FUNCTION MODULE
GFL_FCAT   
TYPE SLIS_FIELDCAT_ALV.       "WORK AREA FOR THE GIT_FCAT INTERNAL TABLE.

DATA:         GIT_EVENT  
TYPE SLIS_T_EVENT,            "INTERNAL TABLE FOR ALV EVENT INFORMATION FOR TOP OF THE PAGE
GFL_EVENT  
TYPE SLIS_ALV_EVENT.          "WORK AREA FOR GIT_EVENT INTERNAL TABLE
DATA:         GIT_TOP    
TYPE SLIS_T_LISTHEADER,       "ALV TOP OF THE PAGE
GFL_TOP    
TYPE SLIS_LISTHEADER.         "WORK AREA FOR GIT_TOP

DATA:         G_SAVE_PATH
TYPE STRING.                  "PATH INFORMATION TO SAVE THE XL/DOC FILE
DATA:         G_VISB     
TYPE C.                       "VISIBLE OR INVISIBLE XL/DOC FILE

DATA:         OBJ_XCL    
TYPE OLE2_OBJECT,
OBJ_SHT    
TYPE OLE2_OBJECT,
OBJ_WBK    
TYPE OLE2_OBJECT,
OBJ_CEL    
TYPE OLE2_OBJECT,
OBJ_ROW    
TYPE OLE2_OBJECT,
OBJ_FNT    
TYPE OLE2_OBJECT,
OBJ_CLR    
TYPE OLE2_OBJECT,
OBJ_RNG    
TYPE OLE2_OBJECT,
OBJ_INT    
TYPE OLE2_OBJECT,
OBJ_BRD    
TYPE OLE2_OBJECT,
OBJ_COL    
TYPE OLE2_OBJECT.

TYPES:       
BEGIN OF S_CELL,
CELL 
TYPE STRING,
POS  
TYPE C,
END OF   S_CELL.
DATA:         GIT_CELL 
TYPE TABLE OF S_CELL,
GFL_CELL 
LIKE LINE OF GIT_CELL.

DATA:         OBJ_WRD    
TYPE OLE2_OBJECT,
OBJ_DOC    
TYPE OLE2_OBJECT,
OBJ_DCS    
TYPE OLE2_OBJECT,
OBJ_SLC    
TYPE OLE2_OBJECT,
OBJ_ADC    
TYPE OLE2_OBJECT,
OBJ_APP    
TYPE OLE2_OBJECT,
OBJ_ALG    
TYPE OLE2_OBJECT,
OBJ_TBS    
TYPE OLE2_OBJECT,
OBJ_TAB    
TYPE OLE2_OBJECT.

-------------------------------------------------------------------- ZDATA_DISPLAY_TOP-----------------------------------

In the ZDATA_DISPLAY_PAI Include all logic are declared with suitable understandable information’s.

*&---------------------------------------------------------------------*
*&  Include           ZDATA_DISPLAY_PAI
*&---------------------------------------------------------------------*

module STATUS_9000 output.
**  SET PF-STATUS 'xxxxxxxx'.
SET TITLEBAR 'INFO'.
DESCRIBE TABLE GIT_INFO LINES TB_MAIN-LINES.
endmodule.                
" STATUS_9000  OUTPUT


*----------------------------------------------------------------------*
***INCLUDE ZPRV_INFO_PAI .
*----------------------------------------------------------------------*
*&---------------------------------------------------------------------*
*&      Module  USER_COMMAND_9000  INPUT
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
module USER_COMMAND_9000 input.
G_SAVE = G_OK.
CLEAR: G_OK.
CASE G_SAVE.
WHEN 'CLICK'.
PERFORM SUB_GET_TABLE_INFO USING G_TABNAME.
WHEN 'ALV'.
PERFORM SUB_CREATE_DYNAMIC_TABLE.
PERFORM SUB_SELECT_TABLE.
PERFORM SUB_DISPLAY_ALV.
WHEN 'XL'.
PERFORM SUB_CREATE_DYNAMIC_TABLE.
PERFORM SUB_SELECT_TABLE.
PERFORM SUB_DISPLAY_INFO_XL.
WHEN 'DOC'.
PERFORM SUB_CREATE_DYNAMIC_TABLE.
IF <GIT> IS NOT ASSIGNED.
EXIT.
ENDIF.
PERFORM SUB_SELECT_TABLE.
PERFORM SUB_DISPLAY_INFO_WORD.

ENDCASE.
endmodule.                
" USER_COMMAND_9000  INPUT
*&---------------------------------------------------------------------*
*&      Form  SUB_GET_TABLE_INFO
*&---------------------------------------------------------------------*
*       Fetching the Table information based on the Table Name
*----------------------------------------------------------------------*
*      -->P_TABNAME  TYPE TABNAME
*----------------------------------------------------------------------*
form SUB_GET_TABLE_INFO  using    p_tabname TYPE TABNAME.
PERFORM SUB_IS_VALID_TABLE USING P_TABNAME.
IF P_TABNAME EQ ''.
EXIT.
ENDIF.
REFRESH: GIT_TABINFO.
CLEAR: CL_ABAP.
CL_ABAP ?= CL_ABAP_TYPEDESCR=>DESCRIBE_BY_NAME( p_tabname ).
GIT_TABINFO  = CL_ABAP->COMPONENTS[].
DESCRIBE TABLE GIT_TABINFO LINES G_RECS.
IF G_RECS EQ 0.
MESSAGE 'Not a Valid Table' TYPE 'I'.
CLEAR: P_TABNAME.
EXIT.
ENDIF.
REFRESH: GIT_INFO.
LOOP AT GIT_TABINFO INTO GFL_TABINFO.
CLEAR: GFL_INFO.
GFL_INFO-FLDNAM = GFL_TABINFO-NAME.
GFL_INFO-LEN    = GFL_TABINFO-LENGTH /
2.
PERFORM SUB_GET_FIELD_TEXT USING p_tabname GFL_TABINFO-NAME GFL_INFO-FLDTXT.
APPEND GFL_INFO TO GIT_INFO.
ENDLOOP.
endform.                   
" SUB_GET_TABLE_INFO
*&---------------------------------------------------------------------*
*&      Form  SUB_GET_FIELD_TEXT
*&---------------------------------------------------------------------*
*       Getting the Field Text based on the Field Name
*----------------------------------------------------------------------*
*      -->P_NAME
*      -->P_TXT
*----------------------------------------------------------------------*
form SUB_GET_FIELD_TEXT  using    P_TABLE TYPE ddobjname
p_name
TYPE FIELDNAME
p_txt
TYPE AS4TEXT.
DATA: LIT_DDT
TYPE DFIES OCCURS 0,
LFL_DDT
LIKE LINE OF LIT_DDT.
CALL FUNCTION 'DDIF_FIELDINFO_GET'
EXPORTING
tabname              = P_TABLE
FIELDNAME            = P_NAME
LANGU                = SY-LANGU
TABLES
DFIES_TAB            = LIT_DDT
EXCEPTIONS
NOT_FOUND            =
1
INTERNAL_ERROR       =
2
OTHERS               = 3
.
IF sy-subrc EQ 0.
LOOP AT LIT_DDT INTO LFL_DDT.
P_TXT = LFL_DDT-FIELDTEXT.
EXIT.
ENDLOOP.
ENDIF.
endform.                   
" SUB_GET_FIELD_TEXT
*&---------------------------------------------------------------------*
*&      Form  SUB_DISPLAY_ALV
*&---------------------------------------------------------------------*
*       Display the Selected table information in ALV Grid based on the
*       Selected Columns or Whole Table & Number of Rows Entered for Display
*----------------------------------------------------------------------*
form SUB_DISPLAY_ALV .
DESCRIBE TABLE <GIT> LINES G_RECS.
IF G_RECS EQ 0.
MESSAGE 'No Records Selected for Display' TYPE 'I'.
EXIT.
ENDIF.
PERFORM SUB_CREATE_FLD_CAT.
G_RECS =
1.
CALL FUNCTION 'REUSE_ALV_GRID_DISPLAY'
EXPORTING
I_CALLBACK_PROGRAM                = SY-REPID
I_CALLBACK_TOP_OF_PAGE            =
'TOP_OF_PAGE'
I_CALLBACK_USER_COMMAND           =
'USER_COMMAND'
IT_FIELDCAT                       = GIT_FCAT
I_SAVE                            =
'A'
TABLES
t_outtab                          = <GIT>
EXCEPTIONS
PROGRAM_ERROR                     =
1
OTHERS                            = 2
.
IF sy-subrc <> 0.
* MESSAGE ID SY-MSGID TYPE SY-MSGTY NUMBER SY-MSGNO
*         WITH SY-MSGV1 SY-MSGV2 SY-MSGV3 SY-MSGV4.
ENDIF.


endform.                   
" SUB_DISPLAY_ALV
*&---------------------------------------------------------------------*
*&      Form  SUB_CREATE_DYNAMIC_COLS
*&---------------------------------------------------------------------*
*       Creating the FieldCatalog based on the Field Name and Field Text
*----------------------------------------------------------------------*
*      -->P_FLDNAM  text
*      -->P_FLDTXT  text
*----------------------------------------------------------------------*
form SUB_CREATE_DYNAMIC_COLS  using    p_fldnam TYPE LVC_FNAME
p_fldtxt
P_LEN .
CLEAR: GFL_FLDS.
DATA: LFL_DD03L
TYPE DD03L.
GFL_FLDS-FIELDNAME  = p_fldnam.
GFL_FLDS-OUTPUTLEN  = P_LEN.
GFL_FLDS-COLTEXT    = p_fldtxt.
CLEAR: LFL_DD03L.
SELECT SINGLE * FROM DD03L INTO LFL_DD03L WHERE TABNAME EQ G_TABNAME AND FIELDNAME EQ p_fldnam.
IF SY-SUBRC EQ 0.
GFL_FLDS-DATATYPE      = LFL_DD03L-DATATYPE.
GFL_FLDS-ROLLNAME      = LFL_DD03L-ROLLNAME.
GFL_FLDS-INTTYPE       = LFL_DD03L-INTTYPE.
GFL_FLDS-DOMNAME       = LFL_DD03L-DOMNAME.
GFL_FLDS-CHECKTABLE    = LFL_DD03L-CHECKTABLE.
ENDIF.
APPEND GFL_FLDS TO GIT_FLDS.
endform.                   
" SUB_CREATE_DYNAMIC_COLS
*&---------------------------------------------------------------------*
*&      Form  SUB_CREATE_DYNAMIC_TABLE
*&---------------------------------------------------------------------*
*       Create Dynamic table based on the Selected column or All columns of the selected table
*----------------------------------------------------------------------*
form SUB_CREATE_DYNAMIC_TABLE .
REFRESH: GIT_FLDS.
LOOP AT GIT_INFO INTO GFL_INFO WHERE MARK EQ 'X'.
PERFORM SUB_CREATE_DYNAMIC_COLS USING GFL_INFO-FLDNAM GFL_INFO-FLDTXT GFL_INFO-LEN.
ENDLOOP.
DESCRIBE TABLE GIT_FLDS LINES G_RECS.
IF G_RECS EQ 0.
LOOP AT GIT_INFO INTO GFL_INFO.
PERFORM SUB_CREATE_DYNAMIC_COLS USING GFL_INFO-FLDNAM GFL_INFO-FLDTXT GFL_INFO-LEN.
ENDLOOP.
ENDIF.
DESCRIBE TABLE GIT_FLDS LINES G_RECS.
IF G_RECS GT 67.
MESSAGE 'MS-Word Only Support 67 Column in Table, Cannot Proceed.' TYPE 'I'.
EXIT.
ENDIF.
CALL METHOD CL_ALV_TABLE_CREATE=>CREATE_DYNAMIC_TABLE
EXPORTING
IT_FIELDCATALOG = GIT_FLDS
IMPORTING
EP_TABLE        = GIT.
IF <GIT> IS ASSIGNED.
UNASSIGN <GIT>.
ENDIF.
ASSIGN GIT->* TO <GIT>.
CREATE DATA GFL LIKE LINE OF <GIT>.
IF <GFL> IS ASSIGNED.
UNASSIGN <GFL>.
ENDIF.
ASSIGN GFL->* TO <GFL>.
endform.                   
" SUB_CREATE_DYNAMIC_TABLE
*&---------------------------------------------------------------------*
*&      Form  SUB_CREATE_FLD_CAT
*&---------------------------------------------------------------------*
*       Creating the Field Catalog for REUSE_ALV_GRID_DISPLAY Function Module
*----------------------------------------------------------------------*
form SUB_CREATE_FLD_CAT .
REFRESH: GIT_FCAT.
LOOP AT GIT_FLDS INTO GFL_FLDS.
PERFORM SUB_CREATE_CATALOG USING GFL_FLDS-FIELDNAME GFL_FLDS-COLTEXT SPACE  SPACE  'L' '<GIT>' GFL_FLDS-OUTPUTLEN.
ENDLOOP.
endform.                   
" SUB_CREATE_FLD_CAT


*&---------------------------------------------------------------------*
*&      Form  SUB_CREATE_CATALOG
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*      -->VALUE(P_FLDNAME)  text
*      -->P_COL_HEADING     text
*      -->VALUE(P_HOTSPOT)  text
*      -->VALUE(P_DOSUM)    text
*      -->VALUE(P_FORMAT)   text
*      -->VALUE(C_TABNAME)  text
*----------------------------------------------------------------------*
FORM SUB_CREATE_CATALOG USING VALUE(P_FLDNAME) TYPE SLIS_FIELDNAME
P_COL_HEADING   
TYPE SCRTEXT_L
VALUE(P_HOTSPOT)
TYPE C
VALUE(P_DOSUM)  
TYPE C
VALUE(P_FORMAT) 
TYPE C
VALUE(C_TABNAME)
TYPE SLIS_TABNAME
VALUE(LEN).

GFL_FCAT-TABNAME   = C_TABNAME.                
" Table name
GFL_FCAT-FIELDNAME = P_FLDNAME.                
" Field name
GFL_FCAT-SELTEXT_L = P_COL_HEADING.            
" Column heading
GFL_FCAT-HOTSPOT   = P_HOTSPOT.                
" Hot Spot
GFL_FCAT-DO_SUM    = P_DOSUM.                  
" Sum
GFL_FCAT-JUST      = P_FORMAT.                 
" Format
GFL_FCAT-OUTPUTLEN =  LEN.

APPEND GFL_FCAT TO GIT_FCAT.
CLEAR GFL_FCAT.

ENDFORM.                   
"sub_create_catalog
*&---------------------------------------------------------------------*
*&      Module  MOD_UPDATE_INFO  INPUT
*&---------------------------------------------------------------------*
*       Updating the selection information in Internal Table
*----------------------------------------------------------------------*
module MOD_UPDATE_INFO input.
MOVE-CORRESPONDING GFL_INFO TO GFL_INFO.
MODIFY  GIT_INFO FROM GFL_INFO INDEX TB_MAIN-CURRENT_LINE.
endmodule.                
" MOD_UPDATE_INFO  INPUT
*&---------------------------------------------------------------------*
*&      Form  TOP_OF_PAGE
*&---------------------------------------------------------------------*
*       FOR TOP OF THE PAGE INFORMATION OF THE ALV GRID
*----------------------------------------------------------------------*
form TOP_OF_PAGE .
IF G_RECS EQ 1.
GFL_TOP-TYP =
'H'.
PERFORM SUB_GET_TABLE_DESC USING G_TABNAME GFL_TOP-INFO.
CONCATENATE 'Table' GFL_TOP-INFO 'Report' INTO GFL_TOP-INFO SEPARATED BY SPACE.
APPEND GFL_TOP TO GIT_TOP.

CALL FUNCTION 'REUSE_ALV_COMMENTARY_WRITE'
EXPORTING
it_list_commentary       = GIT_TOP
.
G_RECS =
2.
ENDIF.
endform.                   
" SUB_TOP_OF_PAGE

FORM USER_COMMAND USING UCOMM   TYPE SY-UCOMM
FLDSEL 
TYPE SLIS_SELFIELD.
READ TABLE <GIT> INTO <GFL> INDEX FLDSEL-TABINDEX.
IF SY-SUBRC EQ 0.
DATA: L_MSG
TYPE STRING.
ASSIGN COMPONENT  FLDSEL-FIELDNAME OF STRUCTURE <GFL> TO <VAL>.
CLEAR: GFL_INFO.
READ TABLE GIT_INFO INTO GFL_INFO WITH KEY FLDNAM = FLDSEL-FIELDNAME.
CONCATENATE 'Column' GFL_INFO-FLDTXT  'Value is' <VAL> INTO L_MSG SEPARATED BY SPACE.
MESSAGE L_MSG TYPE 'I'.
ENDIF.
ENDFORM.
*&---------------------------------------------------------------------*
*&      Form  SUB_SELECT_TABLE
*&---------------------------------------------------------------------*
*       Select the table information
*----------------------------------------------------------------------*
form SUB_SELECT_TABLE .
IF NOT <GIT> IS ASSIGNED.
EXIT.
ENDIF.
IF G_ROWS EQ 0.
SELECT * FROM (G_TABNAME) INTO CORRESPONDING FIELDS OF TABLE <GIT>.
ELSE.
SELECT * FROM (G_TABNAME) UP TO G_ROWS ROWS INTO CORRESPONDING FIELDS OF TABLE <GIT>.
ENDIF.
endform.                   
" SUB_SELECT_TABLE
*&---------------------------------------------------------------------*
*&      Form  SUB_DISPLAY_INFO_XL
*&---------------------------------------------------------------------*
*       Display the Selected Table information in Excel File
*----------------------------------------------------------------------*
form SUB_DISPLAY_INFO_XL .
DESCRIBE TABLE <GIT> LINES G_RECS.
IF G_RECS EQ 0.
EXIT.
ENDIF.
PERFORM SUB_CREATE_XL_APP.
IF OBJ_XCL IS INITIAL.
EXIT.
ENDIF.
DATA: L_INFO
TYPE STRING.
CLEAR: G_SAVE_PATH.
PERFORM SUB_GET_PATH.
IF G_SAVE_PATH EQ ''.
FREE OBJECT OBJ_XCL.
EXIT.
ENDIF.
PERFORM SUB_GET_VISIBLE_INFO.
PERFORM SUB_DISPLAY_XL.
PERFORM SUB_XL_ADD_WBOOK.
PERFORM SUB_XL_WORK_SHEET.
PERFORM SUB_XL_ACTIVE.
PERFORM SUB_XL_MERGE_COLS USING 'D2' 'P4'.
PERFORM SUB_XL_SET_BORDER USING 'D2' 'P4'.
PERFORM SUB_XL_SET_FONT USING 'D2' 'ALGERIAN' '25' 1 0 1.
PERFORM SUB_XL_ALIGN USING 3 2.
PERFORM SUB_GET_TABLE_DESC USING G_TABNAME L_INFO.
CONCATENATE 'Table' L_INFO 'Report' INTO L_INFO SEPARATED BY SPACE.
PERFORM SUB_XL_WRITE USING L_INFO.
PERFORM SUB_XL_FILL_COLOR USING '20' '2' '2'.
PERFORM SUB_XL_SET_FONT_COLOR USING '5'.

DATA: L_CELL 
TYPE string VALUE 'B6',
L_STR  
TYPE STRING,
L_REC  
TYPE I.
LOOP AT GIT_FLDS INTO GFL_FLDS.
G_RECS = SY-TABIX.
FREE OBJECT OBJ_CEL.
PERFORM SUB_XL_SET_FONT USING L_CELL 'ARIAL BLACK' '11' 1 0 1.
PERFORM SUB_XL_ALIGN USING 3 2.
PERFORM SUB_XL_WRITE USING GFL_FLDS-COLTEXT.
PERFORM SUB_XL_FILL_COLOR USING '15' '2' '2'.
PERFORM SUB_XL_SET_FONT_COLOR USING '4'.
PERFORM SUB_XL_SET_BORDER USING L_CELL L_CELL.
IF GFL_FLDS-OUTPUTLEN LT 3.
GFL_FLDS-OUTPUTLEN = STRLEN( GFL_FLDS-COLTEXT ).
ENDIF.
PERFORM SUB_XL_COLUMN_WIDTH USING L_CELL GFL_FLDS-OUTPUTLEN.
FREE OBJECT OBJ_CEL.
PERFORM sub_get_next_cell USING L_CELL L_CELL 'H'.
ENDLOOP.
L_CELL =
'B7'.
LOOP AT <GIT> INTO <GFL>.
G_RECS = SY-TABIX.
L_STR = G_RECS +
6.
CONCATENATE 'B' L_STR INTO L_CELL.
LOOP AT GIT_FLDS INTO GFL_FLDS.
L_REC = SY-TABIX.
FREE OBJECT OBJ_CEL.
ASSIGN COMPONENT GFL_FLDS-FIELDNAME OF STRUCTURE <GFL> TO <VAL>.
PERFORM SUB_XL_SET_FONT USING L_CELL 'ARIAL' '10' 1 0 1.
PERFORM SUB_XL_WRITE USING <VAL>.
FREE OBJECT OBJ_CEL.
PERFORM sub_get_next_cell USING L_CELL L_CELL 'H'.
ENDLOOP.
ENDLOOP.
PERFORM SUB_XL_SAVEAS.
endform.                   
" SUB_DISPLAY_INFO_XL
*&---------------------------------------------------------------------*
*&      Form  SUB_CREATE_XL_APP
*&---------------------------------------------------------------------*
*       Creating Excel Application Object
*----------------------------------------------------------------------*
form SUB_CREATE_XL_APP.
CREATE OBJECT OBJ_XCL 'EXCEL.APPLICATION'.
IF SY-SUBRC NE 0.
MESSAGE 'Problem to Creating Excel File' TYPE 'I'.
ENDIF.
endform.                   
" SUB_CREATE_XL_APP
*&---------------------------------------------------------------------*
*&      Form  SUB_GET_PATH
*&---------------------------------------------------------------------*
*       Get the Path information for save the file
*----------------------------------------------------------------------*
form SUB_GET_PATH .
DATA: CL_SERV
TYPE REF TO CL_GUI_FRONTEND_SERVICES.
CALL METHOD cl_gui_frontend_services=>directory_browse
EXPORTING
window_title         =
'Select Folder to Save File'
*      initial_folder       =
CHANGING
selected_folder      = G_SAVE_PATH
EXCEPTIONS
cntl_error           =
1
error_no_gui         =
2
not_supported_by_gui =
3
others               = 4
.
IF sy-subrc <> 0.
*   MESSAGE ID SY-MSGID TYPE SY-MSGTY NUMBER SY-MSGNO
*              WITH SY-MSGV1 SY-MSGV2 SY-MSGV3 SY-MSGV4.
ENDIF.

endform.                   
" SUB_GET_PATH
*&---------------------------------------------------------------------*
*&      Form  SUB_GET_VISIBLE_INFO
*&---------------------------------------------------------------------*
*       GET THE VISIBLE INFORMATION OF THE CREATED FILE
*----------------------------------------------------------------------*
form SUB_GET_VISIBLE_INFO .
DATA: ANSWER 
TYPE C.
CALL FUNCTION 'POPUP_TO_CONFIRM'
EXPORTING
TITLEBAR                    = 'Display Mode'
*   DIAGNOSE_OBJECT             = ' '
text_question               =
'Will File will Display? Display mode may take time to complete'
TEXT_BUTTON_1               =
'YES'
*   ICON_BUTTON_1               = ' '
TEXT_BUTTON_2               =
'NO'
*   ICON_BUTTON_2               = ' '
*   DEFAULT_BUTTON              = '1'
*   DISPLAY_CANCEL_BUTTON       = 'X'
*   USERDEFINED_F1_HELP         = ' '
*   START_COLUMN                = 25
*   START_ROW                   = 6
*   POPUP_TYPE                  =
*   IV_QUICKINFO_BUTTON_1       = ' '
*   IV_QUICKINFO_BUTTON_2       = ' '
IMPORTING
ANSWER                      = ANSWER
* TABLES
*   PARAMETER                   =
EXCEPTIONS
TEXT_NOT_FOUND              =
1
OTHERS                      = 2
.
IF sy-subrc EQ 0.
IF ANSWER EQ '1'.
G_VISB =
'1'.
ELSE.
G_VISB =
'0'.
ENDIF.
ENDIF.

endform.                   
" SUB_GET_VISIBLE_INFO
*&---------------------------------------------------------------------*
*&      Form  SUB_DISPLAY_XL
*&---------------------------------------------------------------------*
*       Display the XL File based on the Visible Mode
*----------------------------------------------------------------------*
form SUB_DISPLAY_XL .
SET PROPERTY OF OBJ_XCL 'VISIBLE' = G_VISB.
endform.                   
" SUB_DISPLAY_XL
*&---------------------------------------------------------------------*
*&      Form  SUB_XL_WORK_SHEET
*&---------------------------------------------------------------------*
*      Creating a Work Sheet on Created Excel Obejct
*----------------------------------------------------------------------*
form SUB_XL_WORK_SHEET .
CALL METHOD OF OBJ_XCL 'WORKSHEETS' = OBJ_SHT
EXPORTING #1  = 1.
endform.                   
" SUB_XL_WORK_SHEET

*&---------------------------------------------------------------------*
*&      Form  SUB_XL_ADD_WBOOK
*&---------------------------------------------------------------------*
*       Adding a Work Book on Created Excel Object
*----------------------------------------------------------------------*
form SUB_XL_ADD_WBOOK .
CALL METHOD OF OBJ_XCL 'WORKBOOKS' = OBJ_WBK.
CALL METHOD OF OBJ_WBK 'ADD'.
endform.                   
" SUB_XL_ADD_WBOOK
*&---------------------------------------------------------------------*
*&      Form  SUB_XL_ACTIVE
*&---------------------------------------------------------------------*
*       Active the Current Excel Sheet
*----------------------------------------------------------------------*
form SUB_XL_ACTIVE .
CALL METHOD OF OBJ_SHT 'ACTIVATE'.
endform.                   
" SUB_XL_ACTIVE
*&---------------------------------------------------------------------*
*&      Form  SUB_XL_MERGE_COLS
*&---------------------------------------------------------------------*
*       Merge the Columns Based on the Giving Range
*----------------------------------------------------------------------*
*      -->P_FROM   text
*      -->P_TO   text
*----------------------------------------------------------------------*
form SUB_XL_MERGE_COLS  using    value(p_FROM)
value(p_TO).
CALL METHOD OF OBJ_SHT 'RANGE' = OBJ_RNG NO FLUSH
EXPORTING #1    = P_FROM
#2    = P_TO.
CALL METHOD OF OBJ_RNG 'SELECT' NO FLUSH.
SET PROPERTY OF OBJ_RNG 'MERGE' = 1 NO FLUSH.
FREE OBJECT OBJ_RNG.
endform.                   
" SUB_XL_MERGE_COLS
*&---------------------------------------------------------------------*
*&      Form  SUB_XL_SET_BORDER
*&---------------------------------------------------------------------*
*       Set the Border
*----------------------------------------------------------------------*
*      -->P_FROM   text
*      -->P_TO   text
*----------------------------------------------------------------------*
form SUB_XL_SET_BORDER  using    value(p_FROM)
value(p_TO).
DATA: l_int
TYPE i.
PERFORM sub_get_border_cells USING p_from p_to.
DESCRIBE TABLE git_cell LINES l_int.
IF l_int EQ 0.
EXIT.
ENDIF.
FREE OBJECT   OBJ_CEL.
LOOP AT git_cell INTO gfl_cell.
CALL METHOD OF OBJ_SHT 'RANGE' = OBJ_CEL
EXPORTING #1 = gfl_cell-cell.
IF gfl_cell-pos EQ 'U'.
PERFORM sub_set_border USING OBJ_CEL OBJ_BRD '3' '1' '4'.
ELSEIF gfl_cell-pos EQ 'B'.
PERFORM sub_set_border USING OBJ_CEL OBJ_BRD '4' '1' '4'.
ELSEIF gfl_cell-pos EQ 'L'.
PERFORM sub_set_border USING OBJ_CEL OBJ_BRD '1' '1' '4'.
ELSEIF gfl_cell-pos EQ 'R'.
PERFORM sub_set_border USING OBJ_CEL OBJ_BRD '2' '1' '4'.
ENDIF.
ENDLOOP.

endform.                   
" SUB_XL_SET_BORDER

*&---------------------------------------------------------------------*
*&      Form  SUB_GET_BORDER_CELLS
*&---------------------------------------------------------------------*
*       Get the all Border information
*----------------------------------------------------------------------*
*      -->P_P_FROM  text
*      -->P_P_TO  text
*----------------------------------------------------------------------*
FORM sub_get_border_cells  USING    value(p_from)
value(p_to).
"'D1' 'M3'
DATA: l_ncols
TYPE i,
l_rows 
TYPE i.
DATA: l_tot
TYPE i,
l_str
TYPE string,
l_int
TYPE i.
DATA: l_ncell1
TYPE string,
l_ncell2
TYPE string.
l_str = p_from.
l_int = STRLEN( l_str ).
PERFORM sub_get_no_cols USING p_from p_to l_ncols.
PERFORM sub_get_no_rows USING p_from p_to l_rows.
CLEAR:    git_cell.
REFRESH:  git_cell.
PERFORM sub_get_upper_cells USING p_from l_ncols l_ncell1 'U'.
PERFORM sub_get_side_cells USING p_from l_rows l_ncell2 'L'.
PERFORM sub_get_upper_cells USING l_ncell2 l_ncols l_ncell2 'B'.
PERFORM sub_get_side_cells USING l_ncell1 l_rows l_ncell2 'R'.
ENDFORM.                   
" SUB_GET_BORDER_CELLS

*&---------------------------------------------------------------------*
*&      Form  SUB_GET_NO_COLS
*&---------------------------------------------------------------------*
*       Get the total No fo Columns are exist in Giving Merge Differnece
*----------------------------------------------------------------------*
*      -->P_FONT  text
*      -->P_TO  text
*      -->P_NCOLS  text
*----------------------------------------------------------------------*
FORM sub_get_no_cols  USING    p_from
p_to
p_ncols
TYPE i.
DATA: l_str  
TYPE i,
l_end  
TYPE i.
PERFORM sub_get_column_no USING p_from
l_str.
PERFORM sub_get_column_no USING p_to
l_end.
p_ncols = l_end - l_str.
ENDFORM.                   
" SUB_GET_NO_COLS

*&---------------------------------------------------------------------*
*&      Form  SUB_GET_NO_ROWS
*&---------------------------------------------------------------------*
*       Getting the Rows information based on the given Cell
*----------------------------------------------------------------------*
*      -->P_FROM  text
*      -->P_TO  text
*      -->P_ROWS  text
*----------------------------------------------------------------------*
FORM sub_get_no_rows  USING    p_from
p_to
p_rows
TYPE i.
DATA: l_f
TYPE i,
l_t
TYPE i.
PERFORM sub_get_number USING p_from l_f.
PERFORM sub_get_number USING p_to l_t.
p_rows = l_t - l_f.
ENDFORM.                   
" SUB_GET_NO_ROWS

*&---------------------------------------------------------------------*
*&      Form  SUB_GET_UPPER_CELLS
*&---------------------------------------------------------------------*
*       Getting rhe Upper level of Excel's Cells information
*----------------------------------------------------------------------*
*      -->P_FROM  text
*      -->P_TO  text
*      -->P_NC  text
*----------------------------------------------------------------------*
FORM sub_get_upper_cells  USING    value(p_from)
value(p_nc)
TYPE i
p_nfrm
p_up
TYPE c.
DATA: p_ncel 
TYPE string.
CLEAR: gfl_cell, p_nfrm.
gfl_cell-cell = p_from.
gfl_cell-pos  = p_up.
APPEND gfl_cell TO git_cell.
DO p_nc TIMES.
PERFORM sub_get_next_cell USING p_from p_ncel 'H'.
CLEAR: gfl_cell.
gfl_cell-cell = p_ncel.
gfl_cell-pos  = p_up.
APPEND gfl_cell TO git_cell.
p_from = p_ncel.
ENDDO.
p_nfrm = p_from.
ENDFORM.                   
" SUB_GET_UPPER_CELLS

*&---------------------------------------------------------------------*
*&      Form  SUB_GET_LEFT_CELLS
*&---------------------------------------------------------------------*
*       Getting the all Left Side Cells information
*----------------------------------------------------------------------*
*      -->P_P_FROM  text
*      -->P_P_TO  text
*----------------------------------------------------------------------*
FORM sub_get_side_cells  USING    p_from
p_rows
TYPE i
p_nfrm
p_lr 
TYPE c.
DATA:  p_ncel 
TYPE string.
CLEAR: p_nfrm, gfl_cell.
gfl_cell-cell = p_from.
gfl_cell-pos  = p_lr.
APPEND gfl_cell TO git_cell.
DO p_rows TIMES.
PERFORM sub_get_next_cell USING p_from p_ncel 'V'.
CLEAR: gfl_cell.
gfl_cell-cell = p_ncel.
gfl_cell-pos  = p_lr.
APPEND gfl_cell TO git_cell.
p_from = p_ncel.
ENDDO.
p_nfrm = p_from.
ENDFORM.                   
" SUB_GET_LEFT_CELLS

*&---------------------------------------------------------------------*
*&      Form  SUB_GET_NUMBER
*&---------------------------------------------------------------------*
*       Get the Cell Number
*----------------------------------------------------------------------*
*      -->P_FROM  text
*      -->P_NO  text
*----------------------------------------------------------------------*
FORM sub_get_number  USING    p_cell
p_no
TYPE i.
DATA: int
TYPE i,
l_i
TYPE i,
str
TYPE string,
alp
TYPE c.
l_i = STRLEN( p_cell ).
IF l_i EQ 2.
str = p_cell+1(1).
ELSE.
int =
0.
DO l_i TIMES.
alp = p_cell+int(1).
IF alp CN sy-abcde.
EXIT.
ENDIF.
int = int +
1.
ENDDO.
l_i = l_i - int.
str = p_cell+int(l_i).
ENDIF.
p_no =  str.
ENDFORM.                   
" SUB_GET_NUMBER

*&---------------------------------------------------------------------*
*&      Form  SUB_GET_COLUMN_NO
*&---------------------------------------------------------------------*
*       Get the Cell Column Number
*----------------------------------------------------------------------*
*      -->P_FROM  text
*      -->P_COLN  text
*----------------------------------------------------------------------*
FORM sub_get_column_no  USING    p_from
p_coln
TYPE i.
DATA: l_str
TYPE string,
l_alp
TYPE c,
l_int
TYPE i.
l_int = STRLEN( p_from ).
IF l_int EQ 2.
l_alp = p_from+0(1).
PERFORM sub_get_alphabate_no USING  l_alp
p_coln.
ELSEIF l_int EQ 3.
l_alp = p_from+0(1).
PERFORM sub_get_alphabate_no USING  l_alp
l_int.
l_int = l_int *
26.
l_alp = p_from+1(2).
PERFORM sub_get_alphabate_no USING  l_alp
p_coln.
p_coln = p_coln + l_int.
ENDIF.

ENDFORM.                   
" SUB_GET_COLUMN_NO

*&---------------------------------------------------------------------*
*&      Form  SUB_GET_NEXT_CELL
*&---------------------------------------------------------------------*
*       Get the Next Cell information based on the current giving Cell and Postion (H/V)
*----------------------------------------------------------------------*
*      -->P_CELL  text
*      -->P_NCEL  text
*      -->P_POS  text
*----------------------------------------------------------------------*
FORM sub_get_next_cell  USING    value(p_cell)
p_ncel
p_pos
TYPE c.
DATA: int
TYPE i,
l_i
TYPE i,
str
TYPE string,
alp
TYPE c,
l_s
TYPE string.

CLEAR: p_ncel.
CONDENSE p_cell.
IF p_pos EQ 'H'.
l_i = STRLEN( p_cell ).
IF l_i EQ 2.
IF p_cell+0(1) EQ 'Z'.
CONCATENATE 'A' 'A' p_cell+1(1) INTO p_ncel.
ELSE.
alp = p_cell+0(1).
PERFORM sub_get_next_alpha USING alp  alp.
CONCATENATE alp p_cell+1(1) INTO p_ncel.
ENDIF.
ELSE.
int =
0.
DO l_i TIMES.
alp = p_cell+int(1).
IF alp NA sy-abcde.
EXIT.
ENDIF.
int = int +
1.
ENDDO.
IF int EQ 1.
alp = p_cell+0(1).
l_i = l_i - int.
IF alp EQ 'Z'.
CONCATENATE 'AA' p_cell+int(l_i) INTO p_ncel.
ELSE.
PERFORM sub_get_next_alpha USING alp alp.
CONCATENATE alp p_cell+int(l_i) INTO p_ncel.
ENDIF.
ELSE.
l_i = l_i - int.
l_s = p_cell+0(int).
IF int EQ 2.
IF l_s+1(1) EQ 'Z'.
alp = l_s+0(1).
PERFORM sub_get_next_alpha USING alp alp.
CONCATENATE alp 'A' p_cell+int(l_i) INTO p_ncel.
ELSE.
alp = l_s+1(1).
PERFORM sub_get_next_alpha USING alp alp.
CONCATENATE l_s+0(1) alp p_cell+int(l_i) INTO p_ncel.
ENDIF.
ENDIF.
ENDIF.
ENDIF.
ELSEIF p_pos EQ 'V'.
l_i = STRLEN( p_cell ).
IF l_i EQ '2'.
str = p_cell+1(1).
ELSE.
int =
0.
DO l_i TIMES.
alp = p_cell+int(1).
IF alp NS sy-abcde.
EXIT.
ENDIF.
int = int +
1.
ENDDO.
l_i = STRLEN( p_cell ) - int.
str = p_cell+int(l_i).
ENDIF.
int = str.
int = int +
1.
str = int.
CONCATENATE p_cell+0(1) str INTO p_ncel.
ENDIF.
ENDFORM.                   
" SUB_GET_NEXT_CELL

*&---------------------------------------------------------------------*
*&      Form  SUB_SET_BORDER
*&---------------------------------------------------------------------*
*       Set the Border of Cell line
*----------------------------------------------------------------------*
*      -->P_G_CELL  text
*      -->P_G_BRDR  text
*      -->P_LINE  text
*      -->P_WIDT  text
*----------------------------------------------------------------------*
FORM sub_set_border  USING    p_cell  TYPE ole2_object
p_brdr 
TYPE ole2_object
p_line
p_styl
p_widt.
*&--------------------------------
*  IF P_LINE EQ 3 THEN UPPER LEVEL OF CELL WILL BORDER
*  IF P_LINE EQ 4 THEN BOTTOM LEVEL OF CELL WILL BORDER
*  IF P_LINE EQ 1 THEN LEFT SIDE OF CELL
*  AND IF P_LINE EQ 2 THEN RIGHT SIDE OF CELL
*&--------------------------------
CALL METHOD OF p_cell 'BORDERS' = p_brdr
EXPORTING
#1 = p_line.
SET PROPERTY OF p_brdr 'LINESTYLE' = p_styl.
SET PROPERTY OF p_brdr 'WEIGHT'    = p_widt.
FREE OBJECT p_brdr.
ENDFORM.                   
" SUB_SET_BORDER

*&---------------------------------------------------------------------*
*&      Form  SUB_GET_ALPHABATE_NO
*&---------------------------------------------------------------------*
*       Get the Alphabate No
*----------------------------------------------------------------------*
*      -->P_P_ALP  text
*      -->P_P_COLN  text
*----------------------------------------------------------------------*
FORM sub_get_alphabate_no  USING    p_alp
p_coln
TYPE i.
CASE p_alp.
WHEN 'A'.
p_coln =
1.
WHEN 'B'.
p_coln =
2.
WHEN 'C'.
p_coln =
3.
WHEN 'D'.
p_coln =
4.
WHEN 'E'.
p_coln =
5.
WHEN 'F'.
p_coln =
6.
WHEN 'G'.
p_coln =
7.
WHEN 'H'.
p_coln =
8.
WHEN 'I'.
p_coln =
9.
WHEN 'J'.
p_coln =
10.
WHEN 'K'.
p_coln =
11.
WHEN 'L'.
p_coln =
12.
WHEN 'M'.
p_coln =
13.
WHEN 'N'.
p_coln =
14.
WHEN 'O'.
p_coln =
15.
WHEN 'P'.
p_coln =
16.
WHEN 'Q'.
p_coln =
17.
WHEN 'R'.
p_coln =
18.
WHEN 'S'.
p_coln =
19.
WHEN 'T'.
p_coln =
20.
WHEN 'U'.
p_coln =
21.
WHEN 'V'.
p_coln =
22.
WHEN 'W'.
p_coln =
23.
WHEN 'X'.
p_coln =
24.
WHEN 'Y'.
p_coln =
25.
WHEN 'Z'.
p_coln =
26.
ENDCASE.
ENDFORM.                   
" SUB_GET_ALPHABATE_NO

*&---------------------------------------------------------------------*
*&      Form  SUB_GET_NEXT_ALPHA
*&---------------------------------------------------------------------*
*       Get the Next Alphabate based on the Giving Alphabate
*----------------------------------------------------------------------*
*      -->P_1253   text
*      -->P_ALP  text
*----------------------------------------------------------------------*
FORM sub_get_next_alpha  USING    p_gal
p_alp
TYPE c.
CONDENSE p_gal.
CASE p_gal.
WHEN 'A'.
p_alp =
'B'.
WHEN 'B'.
p_alp =
'C'.
WHEN 'C'.
p_alp =
'D'.
WHEN 'D'.
p_alp =
'E'.
WHEN 'E'.
p_alp =
'F'.
WHEN 'F'.
p_alp =
'G'.
WHEN 'G'.
p_alp =
'H'.
WHEN 'H'.
p_alp =
'I'.
WHEN 'I'.
p_alp =
'J'.
WHEN 'J'.
p_alp =
'K'.
WHEN 'K'.
p_alp =
'L'.
WHEN 'L'.
p_alp =
'M'.
WHEN 'M'.
p_alp =
'N'.
WHEN 'N'.
p_alp =
'O'.
WHEN 'O'.
p_alp =
'P'.
WHEN 'P'.
p_alp =
'Q'.
WHEN 'Q'.
p_alp =
'R'.
WHEN 'R'.
p_alp =
'S'.
WHEN 'S'.
p_alp =
'T'.
WHEN 'T'.
p_alp =
'U'.
WHEN 'U'.
p_alp =
'V'.
WHEN 'V'.
p_alp =
'W'.
WHEN 'W'.
p_alp =
'X'.
WHEN 'X'.
p_alp =
'Y'.
WHEN 'Y'.
p_alp =
'Z'.
WHEN 'Z'.
p_alp =
'A'.
ENDCASE.
ENDFORM.                   
" SUB_GET_NEXT_ALPHA
*&---------------------------------------------------------------------*
*&      Form  SUB_XL_SET_FONT
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*      -->P_CELL   text
*      -->P_NAME   text
*      -->P_SIZE   text
*      -->P_BOLD   text
*      -->P_ITAL   text
*      -->P_UNDL   text
*----------------------------------------------------------------------*
form SUB_XL_SET_FONT  using    value(p_CELL)
value(p_NAME)
value(p_SIZE)
value(p_BOLD)
value(p_ITAL)
value(p_UNDL).
CALL METHOD OF OBJ_SHT 'RANGE'      =   OBJ_CEL
EXPORTING  #1        =   P_CELL.
CALL METHOD OF OBJ_CEL  'FONT'      =   OBJ_FNT.
SET PROPERTY OF OBJ_FNT 'NAME'      =   P_NAME.
SET PROPERTY OF OBJ_FNT 'BOLD'      =   P_BOLD.
SET PROPERTY OF OBJ_FNT 'SIZE'      =   P_SIZE.
SET PROPERTY OF OBJ_FNT 'ITALIC'    =   P_ITAL.
SET PROPERTY OF OBJ_FNT 'UNDERLINE' =   P_UNDL.
FREE OBJECT OBJ_FNT.

endform.                   
" SUB_XL_SET_FONT
*&---------------------------------------------------------------------*
*&      Form  SUB_XL_ALIGN
*&---------------------------------------------------------------------*
*       Make the Alignment
*----------------------------------------------------------------------*
*      -->P_3      text
*      -->P_2      text
*----------------------------------------------------------------------*
form SUB_XL_ALIGN  using    value(p_HVAL)
value(p_VERT).
SET PROPERTY OF OBJ_CEL  'HORIZONTALALIGNMENT'  = P_HVAL.
SET PROPERTY OF OBJ_CEL  'VERTICALALIGNMENT'    = P_VERT.
endform.                   
" SUB_XL_ALIGN
*&---------------------------------------------------------------------*
*&      Form  SUB_XL_WRITE
*&---------------------------------------------------------------------*
*       Writing the information in Cell
*----------------------------------------------------------------------*
*      -->P_0656   text
*----------------------------------------------------------------------*
form SUB_XL_WRITE  using    value(p_VAL).
SET PROPERTY OF OBJ_CEL 'VALUE' = P_VAL.
endform.                   
" SUB_XL_WRITE
*&---------------------------------------------------------------------*
*&      Form  SUB_XL_FILL_COLOR
*&---------------------------------------------------------------------*
*       Fill Color on Respective Cell
*----------------------------------------------------------------------*
*      -->P_INDX   text
*      -->P_PTRN   text
*      -->P_ALIGN   text
*----------------------------------------------------------------------*
form SUB_XL_FILL_COLOR  using    value(p_INDX)
value(p_PTRN)
value(p_ALIGN).
FREE OBJECT OBJ_INT.
CALL METHOD OF OBJ_CEL 'INTERIOR'     = OBJ_INT.
SET PROPERTY OF OBJ_INT 'COLORINDEX'  = P_INDX.
SET PROPERTY OF OBJ_INT 'PATTERN'     = P_PTRN.
SET PROPERTY OF OBJ_INT 'ALIGN'       = P_ALIGN.
FREE OBJECT OBJ_INT.
endform.                   
" SUB_XL_FILL_COLOR
*&---------------------------------------------------------------------*
*&      Form  SUB_XL_SET_FONT_COLOR
*&---------------------------------------------------------------------*
*       Setting the Font Color
*----------------------------------------------------------------------*
*      -->P_0666   text
*----------------------------------------------------------------------*
form SUB_XL_SET_FONT_COLOR  using    value(p_VAL).
FREE OBJECT OBJ_FNT.
GET PROPERTY OF OBJ_CEL 'FONT'        =  OBJ_FNT.
SET PROPERTY OF OBJ_FNT 'COLORINDEX'  =  P_VAL.
FREE OBJECT OBJ_FNT.
endform.                   
" SUB_XL_SET_FONT_COLOR
*&---------------------------------------------------------------------*
*&      Form  SUB_XL_SAVEAS
*&---------------------------------------------------------------------*
*       Saving the Generate XL file in Selected Path
*----------------------------------------------------------------------*
form SUB_XL_SAVEAS .
CONCATENATE G_SAVE_PATH '\' G_TABNAME '.XLS' INTO G_SAVE_PATH.
CALL METHOD OF OBJ_SHT 'SAVEAS'
EXPORTING #1 = G_SAVE_PATH.
CALL METHOD OF OBJ_SHT 'QUIT'.
SET PROPERTY OF OBJ_XCL 'VISIBLE' = 0.
FREE OBJECT OBJ_SHT.
**  FREE OBJECT OBJ_WBK.
**  FREE OBJECT OBJ_XCL.
endform.                   
" SUB_XL_SAVEAS
*&---------------------------------------------------------------------*
*&      Form  SUB_XL_COLUMN_WIDTH
*&---------------------------------------------------------------------*
*       Setting Columns Width
*----------------------------------------------------------------------*
*      -->P_CELL  text
*      -->P_LEN  text
*----------------------------------------------------------------------*
form SUB_XL_COLUMN_WIDTH  using    p_cell
p_len.
CALL METHOD OF OBJ_XCL 'RANGE' = OBJ_COL
EXPORTING  #1   = P_CELL.
SET PROPERTY OF OBJ_COL 'COLUMNWIDTH' = P_LEN.
FREE OBJECT OBJ_COL.
endform.                   
" SUB_XL_COLUMN_WIDTH
*&---------------------------------------------------------------------*
*&      Form  SUB_DISPLAY_INFO_WORD
*&---------------------------------------------------------------------*
*       Display and Save the information in Word File
*----------------------------------------------------------------------*
form SUB_DISPLAY_INFO_WORD .
DESCRIBE TABLE <GIT> LINES G_RECS.
IF G_RECS EQ 0.
EXIT.
ENDIF.
PERFORM SUB_CREATE_WORD_APP.
IF OBJ_WRD IS INITIAL.
EXIT.
ENDIF.
DATA: L_INFO
TYPE STRING.
CLEAR: G_SAVE_PATH.
PERFORM SUB_GET_PATH.
IF G_SAVE_PATH EQ ''.
FREE OBJECT OBJ_WRD.
EXIT.
ENDIF.
DATA: L_ROW 
TYPE I,
L_COL
TYPE I.
PERFORM SUB_GET_VISIBLE_INFO.
PERFORM SUB_WRD_DISPLAY.
PERFORM SUB_WRD_ADD_NEW.
PERFORM SUB_WRD_ACTIVE.
PERFORM SUB_GET_TABLE_DESC USING G_TABNAME L_INFO.
CONCATENATE 'Table' L_INFO 'Report' INTO L_INFO SEPARATED BY SPACE.
PERFORM SUB_WRD_SET_FONT USING 'ALGERIAN' '25' 1 1 1 1 5.
PERFORM SUB_WRD_WRITE USING L_INFO.

PERFORM SUB_WRD_NEW_LINE.
PERFORM SUB_WRD_SET_FONT USING 'ARIAL' '12' 0 0 0 1 15.
DESCRIBE TABLE GIT_FLDS LINES L_COL.
DESCRIBE TABLE <GIT> LINES L_ROW.
L_ROW = L_ROW +
1.
PERFORM SUB_WRD_CREATE_TABLE USING L_COL L_ROW.
PERFORM SUB_WRD_SET_BORDER USING '1'.
DATA: L_REC 
TYPE I.
LOOP AT GIT_FLDS INTO GFL_FLDS.
L_REC = SY-TABIX.
FREE OBJECT OBJ_CEL.
PERFORM SUB_WRD_CELL USING 1 L_REC.
PERFORM SUB_WRD_CELL_WRITE USING GFL_FLDS-FIELDNAME.
ENDLOOP.

LOOP AT <GIT> INTO <GFL>.
G_RECS = SY-TABIX.
G_RECS = G_RECS +
1.
LOOP AT GIT_FLDS INTO GFL_FLDS.
L_REC = SY-TABIX.
ASSIGN COMPONENT  GFL_FLDS-FIELDNAME OF STRUCTURE <GFL> TO <VAL>.
FREE OBJECT OBJ_CEL.
PERFORM SUB_WRD_CELL USING G_RECS L_REC.
PERFORM SUB_WRD_CELL_WRITE USING <VAL>.
ENDLOOP.
ENDLOOP.
PERFORM SUB_WRD_SAVEAS.
endform.                   
" SUB_DISPLAY_INFO_WORD
*&---------------------------------------------------------------------*
*&      Form  SUB_CREATE_WORD_APP
*&---------------------------------------------------------------------*
*       Creating the Word Document Object
*----------------------------------------------------------------------*
form SUB_CREATE_WORD_APP .
CREATE OBJECT OBJ_WRD 'WORD.APPLICATION'.
IF SY-SUBRC NE 0.
MESSAGE 'Problem to Create Word Document Object' TYPE 'I'.
FREE OBJECT OBJ_WRD.
ENDIF.
endform.                   
" SUB_CREATE_WORD_APP
*&---------------------------------------------------------------------*
*&      Form  SUB_WRD_DISPLAY
*&---------------------------------------------------------------------*
*       Display the Created Word file based on the Visible mode
*----------------------------------------------------------------------*
form SUB_WRD_DISPLAY .
IF G_VISB EQ '1'.
SET PROPERTY OF OBJ_WRD 'VISIBLE' = '1'.
ELSE.
SET PROPERTY OF OBJ_WRD 'VISIBLE' = '0'.
ENDIF.
endform.                   
" SUB_WRD_DISPLAY
*&---------------------------------------------------------------------*
*&      Form  SUB_WRD_ADD_NEW
*&---------------------------------------------------------------------*
*       Adding new word sheet for process.
*----------------------------------------------------------------------*
form SUB_WRD_ADD_NEW .
CALL METHOD OF OBJ_WRD 'DOCUMENTS' = OBJ_DOC.
CALL METHOD OF OBJ_DOC 'ADD'       = OBJ_DCS.
endform.                   
" SUB_WRD_ADD_NEW
*&---------------------------------------------------------------------*
*&      Form  SUB_WRD_ACTIVE
*&---------------------------------------------------------------------*
*      Activating the Current Word Document
*----------------------------------------------------------------------*
form SUB_WRD_ACTIVE .
CALL METHOD OF OBJ_DCS 'ACTIVATE'.
GET PROPERTY OF OBJ_WRD 'ACTIVEDOCUMENT'  = OBJ_ADC.
GET PROPERTY OF OBJ_ADC 'APPLICATION'     = OBJ_APP.
endform.                   
" SUB_WRD_ACTIVE
*&---------------------------------------------------------------------*
*&      Form  SUB_WRD_WRITE
*&---------------------------------------------------------------------*
*       Writing the information
*----------------------------------------------------------------------*
form SUB_WRD_WRITE  using    value(p_VAL).
CALL METHOD OF OBJ_WRD 'SELECTION' = OBJ_SLC.
CALL METHOD OF OBJ_SLC 'TYPETEXT'
EXPORTING #1 = P_VAL.
endform.                   
" SUB_WRD_WRITE
*&---------------------------------------------------------------------*
*&      Form  SUB_WRD_SET_FONT
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*      -->P_NAME   text
*      -->P_SIZE   text
*      -->P_BOLD   text
*      -->P_ITAL   text
*      -->P_UDLN   text
*----------------------------------------------------------------------*
form SUB_WRD_SET_FONT  using    value(p_NAME)
value(p_SIZE)
value(p_BOLD)
value(p_ITAL)
value(p_UNDL)
VALUE(P_ALIGN)
VALUE(P_COLR).
GET PROPERTY OF OBJ_APP 'SELECTION'       = OBJ_SLC.
GET PROPERTY OF OBJ_SLC 'PARAGRAPHFORMAT' = OBJ_ALG.
GET PROPERTY OF OBJ_SLC 'FONT'            = OBJ_FNT.
SET PROPERTY OF OBJ_FNT 'NAME'            = P_NAME.
SET PROPERTY OF OBJ_FNT 'SIZE'            = p_SIZE.
SET PROPERTY OF OBJ_FNT 'BOLD'            = P_BOLD.
SET PROPERTY OF OBJ_FNT 'ITALIC'          = p_ITAL.
SET PROPERTY OF OBJ_FNT 'UNDERLINE'       = p_UNDL.
SET PROPERTY OF OBJ_ALG 'ALIGNMENT'       = P_ALIGN.
SET PROPERTY OF OBJ_FNT 'COLORINDEX'      = P_COLR.
FREE OBJECT OBJ_FNT.

endform.                   
" SUB_WRD_SET_FONT
*&---------------------------------------------------------------------*
*&      Form  SUB_WRD_CREATE_TABLE
*&---------------------------------------------------------------------*
*       Creating A table in Word Document.
*----------------------------------------------------------------------*
*      -->P_COL  text
*      -->P_ROW  text
*----------------------------------------------------------------------*
form SUB_WRD_CREATE_TABLE  using    p_col
p_row.
GET PROPERTY OF OBJ_SLC 'TABLES'  = OBJ_TBS.
GET PROPERTY OF OBJ_SLC 'RANGE'   = OBJ_RNG.
CALL METHOD OF OBJ_TBS 'ADD'      = OBJ_TAB
EXPORTING #1       = OBJ_RNG
#2       = P_ROW
#3       = P_COL.
endform.                   
" SUB_WRD_CREATE_TABLE
*&---------------------------------------------------------------------*
*&      Form  SUB_WRD_NEW_LINE
*&---------------------------------------------------------------------*
*       Go to next Line
*----------------------------------------------------------------------*
form SUB_WRD_NEW_LINE.
CALL METHOD OF OBJ_SLC 'TYPEPARAGRAPH'.
endform.                   
" SUB_WRD_NEW_LINE
*&---------------------------------------------------------------------*
*&      Form  SUB_WRD_SET_BORDER
*&---------------------------------------------------------------------*
*       Set Boder of Created Table
*----------------------------------------------------------------------*
*      -->P_VAL   text
*----------------------------------------------------------------------*
form SUB_WRD_SET_BORDER  using    value(p_VAL).
GET PROPERTY OF OBJ_TAB 'BORDERS' = OBJ_BRD.
SET PROPERTY OF OBJ_BRD 'ENABLE'  = P_VAL.
endform.                   
" SUB_WRD_SET_BORDER
*&---------------------------------------------------------------------*
*&      Form  SUB_WRD_CELL
*&---------------------------------------------------------------------*
*       Select Table Cell for Process
*----------------------------------------------------------------------*
*      -->P_ROW  text
*      -->P_COL  text
*----------------------------------------------------------------------*
form SUB_WRD_CELL  using    p_row
p_col.
CALL METHOD OF OBJ_TAB  'CELL'  = OBJ_CEL
EXPORTING  #1    = P_ROW
#2    = P_COL.
endform.                   
" SUB_WRD_CELL
*&---------------------------------------------------------------------*
*&      Form  SUB_WRD_CELL_WRITE
*&---------------------------------------------------------------------*
*       Write information in Table's Cell
*----------------------------------------------------------------------*
*      -->P_TEXT  text
*----------------------------------------------------------------------*
form SUB_WRD_CELL_WRITE  using    p_text.
FREE OBJECT OBJ_RNG.
GET PROPERTY OF OBJ_CEL 'RANGE' = OBJ_RNG.
SET PROPERTY OF OBJ_RNG 'TEXT'  = P_TEXT.
endform.                   
" SUB_WRD_CELL_WRITE
*&---------------------------------------------------------------------*
*&      Form  SUB_WRD_SAVEAS
*&---------------------------------------------------------------------*
*       Save Word File
*----------------------------------------------------------------------*
form SUB_WRD_SAVEAS .
CONCATENATE G_SAVE_PATH '\' G_TABNAME '.doc' INTO G_SAVE_PATH.
CALL METHOD OF OBJ_DOC 'SAVEAS'
EXPORTING #1 = G_SAVE_PATH.
endform.                   
" SUB_WRD_SAVEAS
*&---------------------------------------------------------------------*
*&      Form  SUB_IS_VALID_TABLE
*&---------------------------------------------------------------------*
*       Checking, Given table is valid table or not.
*----------------------------------------------------------------------*
*      -->P_TABNAME  text
*----------------------------------------------------------------------*
FORM SUB_IS_VALID_TABLE  USING    P_TABNAME.
DATA: LFL_DD 
TYPE DD03L.
SELECT SINGLE * FROM DD03L INTO LFL_DD WHERE TABNAME EQ P_TABNAME.
IF SY-SUBRC NE 0.
MESSAGE 'Kindly enter a valid table' TYPE 'I'.
CLEAR: P_TABNAME.
EXIT.
ENDIF.
ENDFORM.                   
" SUB_IS_VALID_TABLE
*&---------------------------------------------------------------------*
*&      Form  SUB_GET_TABLE_DESC
*&---------------------------------------------------------------------*
*       Fecthing the table information
*----------------------------------------------------------------------*
*      -->P_G_TABNAME  text
*----------------------------------------------------------------------*
FORM SUB_GET_TABLE_DESC  USING    P_TABNAME
P_INFO.
DATA: LFL_DD 
TYPE DD02VV.
SELECT SINGLE * FROM DD02VV
INTO LFL_DD
WHERE DDLANGUAGE EQ SY-LANGU
AND   TABNAME EQ P_TABNAME.
IF SY-SUBRC EQ 0.
P_INFO = LFL_DD-DDTEXT.
ENDIF.
ENDFORM.                   
" SUB_GET_TABLE_DESC

------------------------------------------ZDATA_DISPLAY_PAI--------------

 

Screen 9000 Codes: -

PROCESS BEFORE OUTPUT.
MODULE STATUS_9000.
LOOP AT GIT_INFO INTO GFL_INFO WITH CONTROL TB_MAIN.
ENDLOOP.
*
PROCESS AFTER INPUT.
LOOP AT GIT_INFO.
MODULE MOD_UPDATE_INFO.
ENDLOOP.
MODULE USER_COMMAND_9000.

 

 

Thank You

Praveer Kumar Sen.

When you work with ABAP Objects you always need to create instances of classes. Even if you use "CREATE OBJECT" to create those instances, you probably faced with the problem, that it is not so comfortable as if you have a function for creating objects in the same way. Or, you have a complex logic to create each instance.

Background

For those cases, there is a design-pattern for creating instances. This pattern is called "Factory"-pattern. There is also a pattern called "factory-method", which is different, because it combines the creation and relationship of objects. Thanks to Naimesh Patel, who advise me of this point.

The factory-pattern is one of the most used design pattern. It creates object instances without exposing the concrete instantiation logic. The instantiated objects will be accessable through an interface, which encapsulate the concrete implementation to the users.

In the classical GoF-Pattern, the factory-pattern looks like this:

GOF Factory

A client needs a product (a.k.a. "class") and asks a factory for a concrete instance by passing the neccessary parameters to it. The factory creates a class of a specific interface ( "Product" ) and gives the instance back to the client. The specific interface should also be an abstract class, but by using an interface you will have a much better support for the separations-of-concerns principle.

The ABAP-way of the factory pattern

Sure, it should be very simple, to transfer the underlying UML to a ABAP class structure. But, you have much more than just a OO-language: The SAP Netweaver AS gives you some nice featues - customizing with a maintenance dialog is one of them.

So, here is my approach for the ABAP OO-Factory:

ABAP OO Factory

As you can see, the factory use informations out of a customizing table and holds all created instances in a internal table. The rest ( Interface, Concrete Classes) are simple ABAP OO Elements. By the way: If you can be sure, that the instances should not be buffered so far, you do not need an internal table with all created instances. 

At least, the "create" Method is not so complex: 

ABAP Factory Method

The customizing table should look like this:

Customizing Table

For your internal table in the factory, you should define a DDIC-Structure and Tabletype which includes the customizing information and a reference to the created instance.

One hint: Create a view on SEOCLASSTX and SEOMETAREL with the used interface as constant for "REFCLSNAME", use it as foreign-key and you get a DDIC-based check, if the used class implements the correct interface.

And last, but not least: you need a maintenance view, created by the maintenance view generator.

 

Why should you use a factory-pattern?

With the factory pattern you centralize the create of objects within your application. By using this approach, you can manage the instantiation of objects ( "Products" ) and separate the usage of the concrete implementation logic. 

Through interfaces you introduce the design principle "separation of concerns" and you give a contract for all users of your classes. By accepting this contract the users of your interface count on the correct implementation of the contract.

Additional, you get an overview about who implements your interface and where is it used - just look in the ABAP Workbench and expand the implementation tree of your interface:

Implementation Tree in SE80

Adding new aspects to your software

Another important thing, which I have not mentioned so far: You can build your factory in a way, that you can very easy introduce proxy-classes (another design pattern) which covers new aspects - for example: logging or authentication - without changing the underlying classes.

New aspects with factory

By using this, your user does not affect, that there is another implementation - the interface, and its underlying contract is stil fulfilled.

Conclusion

The factory-pattern is an important pattern for creating object instances within your own code. With this pattern, you can get a more manageable, extensible and customizable software. 

ABAP Objects is often accused for being complicated and having slow performance. This blog contains a design and performance benchmark of select, form, static method, and instance method.

 

In a previous blog - ABAP Objects: Rumble in the Jungle - Internal Table against Collection - I compared collections with internal tables. Some of the comments to the blog indicated that collections complicate things compared with simply using internal tables. The outcome of the blog was that internal tables should be used when programming ABAP. Does this mean that we should not use object-oriented ABAP for anything? Of course not. It is merely one area where you are better off sticking with traditional ABAP using kernel level functionality.

 

In this blog, I will focus on the encapsulation of select statements in methods. To many this is a no-brainer. Of course you should wrap select statements in methods. However, I have often experienced colleagues presenting arguments about KISS and performance in order to avoid encapsulation of select statements. These developers prefer copying select statements from one place to another, rather than creating a method containing the select statement which can be reused. In order to not make it a question of believing in ABAP Objects or not, I will present some facts regarding design and performance of these coding options.

 

Design

In the following table TADIR is used for retrieving data. It could have been any table and is only an example. The WHERE condition reads all objects starting with a D. The reason for this is simply that the result in my system is approximately 1 million rows.

 

Select

Consumer and Producer

 

pb1.gif

 

Form

Consumer

 

pb2.gif

 

Producer

 

pb3.gif

 

Static method

Consumer

 

pb4.gif

 

Producer

 

pb5.gif

 

Instance method

Consumer

 

pb6.gif

 

Producer

 

pb7.gif

 

From a code design perspective there is basically no overhead in creating classes. Although the pure select statement contains slightly less code, keep in mind that this code is replicated every time it is used. For the classes the select statement is written once and for each caller there are only one or two lines of code. If the select statement is reused the number of lines using methods is less than with the pure select statement. Adding separation of concern to the equation it is clear that encapsulating select statements should be default for any ABAP developer. Choosing between static and instance methods depends on the context the select statement is used in and should be decided on a case-by-case basis.

 

Performance

Below you find the results of the performance benchmark. The results were recorded using transaction SAT. Ten test runs were performed for each type of code. The best result of the test runs is displayed in the table below.

 

pb8.gif

 

Of course the select statement with no wrapping is the fastet with zero overhead. The form is swift with only 9 microseconds. In my findings the static method used 14 microseconds  and the instance method 1 (creation) + 13 (read_tadir) = 14 microseconds to run. The methods runtime is 55% longer compared with the (local) form. But does that matter in this context? The end user will not feel any difference between 9 and 14 microseconds. This is therefore only relevant when methods are frequently called. Otherwise it has insignificant impact on the overall performance.

 

Looking at the total running time, does this mean that instance methods are the fastest? No, that is a too hasty conclusion. Considering my nonacademic way of testing, the error tolerance is probably 5-20%. Deducting the error tolerance from the performance difference leads to equal results for all coding options. It is notable that using methods consumes <0.0.1% of the overall processing time. I repeated the test by only reading 300 entries from the table. The result is the same, using methods consumes <0.01% of the overall processing time. In other words, wrapping select statement is not a performance bottleneck. In fact, I do believe that encapsulating select statements will lead to better performance. Why? Because you can benefit from the separation of concerns. Your local SQL expert can spend time to optimize the select statements. Optimized select statement are in most cases much faster than select statements written by developers on the fly.

Let's get ready to ABAP Rumble! 

 

This blog covers a great fight between the ABAP champion Internal Table and challenger Collection. The purpose of the blog is a response to many of the excellent comments received in my previous blog ABAP Trilemma  (ABAP Trilemma), such as for example the blog Object Orientation and Performance - Mission Impossible?

 

Fighter Champion - Internal Table 

Internal Table is one of the most used concepts in ABAP programming and needs no further introduction here. More information can be found on help.sap.com

 

In this blog, I will use a standard internal table of type MAKT, material descriptions.

 

Fighter Challenger - Collection 

Collection is a very well-known concept in object-oriented languages. It is a dynamic array. More information on Collection can be found here

 

At first I wanted to use class CL_OBJECT_COLLECTION for the challenge, but that would be unfair as there is no easy way to retrieve a single object in a collection. Therefore, the challenger is a special type of collection called a map.  Unlike the CL_OBJECT_COLLECTION, it is possible to get a single entry by key in the map. This seems more fair compared with the read functionality of the internal table. The map is represented by standard class CL_OBJECT_MAP.


The objects of the collection is a custom class representation of material description. Please note that simply transferring a standard structure into a class is not something that I usually recommend. The purpose here is to be able to compare the Collection with the Internal Table. In real-life the class would span several structures and of course contain methods with logic rather than just get and set methods. I call the class ZCL_MAKT:


 

 

 

The class can be downloaded as a nugget here, please go to SAPlink for more information on nuggets.

 

Round One - Design 

From a design perspective I would prefer using a combination of collection/object instead of internal table/structure. It also follows the official SAP ABAP Guideline DEV-ABAP-2.

 

Please be aware that I have put no effort into making correct ABAP coding in this blog. The code is optimized so that it is easy to read and understand. Furthermore, I have tried to code in a way that makes the performance results comparable.

 

Lets have a look at how the coding differs:

 

Init Design

Prepare data in the Internal Table or Collection.

 

Internal Table:

rumble1.gif

 

Collection:

rumble2.gif

 

Loop Design

Loop or iterate all entries of the Collection or Internal Table.

 

Internal Table:

rumble3.gif

 

Collection:

rumble4.gif

 

Update Design

Update all records of the Collection or Internal Table.

 

Internal Table:

rumble5.gif

 

Collection:

rumble6.gif

 

Read Design

This functionality reads one entry of the Collection or Internal Table.

 

Internal Table:

rumble7.gif

 

Collection:

rumble8.gif

 

For the lazy developer, the code can be downloaded as a nugget here.

 

If you are an OO purist, the winner of Round One is: Collection. 

 

Round Two - Performance and Memory Consumption

I am by no means an expert on ABAP performance and memory analysis. I have simply followed Olga Dolinskaja 's blogs on performance  (Next Generation ABAP Runtime Analysis (SAT) – How to analyze performance) and memory analysis  (Next Generation ABAP Runtime Analysis (SAT) – How to analyze memory consumption) using transaction SAT. Do not focus on the absolute figures, it is the relation between the Map and Internal Table that counts. Here are the results:

 

Init Performance and Memory Consumption

 

The Init part of the performance analysis is the most scary reading of my findings. As you can see the processing time of the Map goes up by a factor 10 everytime the number of records increases by one. That is exponential growth!

 

Even the processing time of 1000 records is a staggering 13 times longer for the Map than for the Internal Table. For 100000 records it i 167 times longer ... uhh ... 167 times!

 

You probably think this has to do with my coding. I believe not. It turns out that it is the underlying PUT method of CL_OBJECT_MAP that is the performance bottleneck.

 

Although Internal Table has lower memory utilization it don't think that it makes a significant difference compared with the performance difference.

 

Loop Performance and Memory Consumption

 

The Loop performance of the Map is at least linear when it comes to iterating, but much worse than the Internal Table loop. Actually, the processing time is 27 times longer with 1000 records and 52 times longer with 10000.


 

The memory utilization is the same as for Init.


Update Performance and Memory Consumption

 

The Update performance of the Map is also linear, but much worse than the Internal Table loop. Actually, the processing time is 15 times longer for 1000 records and 30 times longer with 10000.


Same as for Init and Loop.

 

Read Performance and Memory Consumption 

 

The Read performance of the Map is better than the Internal Table However, as you can see the total processing time is low and therefore this advantage will have minor impact on the overall performance. Furthermore, the error tolerance of my unacademic way of testing exceeds the performance difference.


The memory utilization is that same as the previous examples.

 

The winnder of Round Two is: Internal Table. Depending on your priorities in the ABAP triangle you will either choose Internal Table or Collection. For large data volumes performance is key and therefore the answer is ... Internal Table.

In a previous ABAP Objects: Custom SAP ERP HCM Class Library - Example 3 - Exceptions, I described a general exception handling class ZCX_MESSAGES. The aim of the class is to help developers unify their exception handling.  It is based on the brilliant IF_RECA_MESSAGE_LIST interface, which I found by reading Uwe Schieferstein's Wiki Message Handling - Finding the Needle in the Haystack. Using the ZCX_MESSAGES exception class has turned out to be a success and well adopted by my peer developers at various SAP customers. It is robust, easy to understand, easy to use, backwards compatible, object-oriented and does solve the vast majority of ABAP exception handling needs. Having said that, I still think it is possible to write better exception handling, but that will imply more development efforts.

 

Whereas my previous blog presented a tool for exception handling, this blog focuses on how to use exceptions in general. There are numerous types of exceptions. In the following, I will shed some light on a few exception handling examples and how they affect the process flow; Retry, Add, Pass On, and Suppress.

 

Overview

Lets have a look at the scenario before going into more details with the types of exception.

 

The image depicts an ABAP process flow, i.e. the order in which the code is executed. In the examples below, a level is represented as a class. The boxes are either a method or a processing block within a method. As you will see, the exception handling does have a severe impact on the ABAP process flow.

 

Retry

The idea of the Retry exception type is basically to use the exception handling to catch an exception, process the error, and fix the issue. Fixing the issue can be done in a multitude of ways. Below I show a simple example where the method that threw the exception is recalled with another importing parameter.

 

eh1.gif

 

Lets assume that an exception is thrown in C11. In B1 the exception is caught and C11 is recalled. Besides recalling C11, there is no disruption in the overall ABAP process flow.

 

Resume

Since release 7.0 EhP2 there is a type of exception called resumable. The ABAP Keyword Documentation describes resumable as "When a resumable exception is handled in a catch block, you can resume processing with the statement RESUME directly after the triggering statement if the context of the exception still exists". I recommend debugging program DEMO_CATCH_EXCEPTION for better understanding of the resumable exception type.

 

Resumable is similar to retry, but there are some significant differences. An advantage is that the context is kept and therefore the processing before the exception does not have to be executed again. In my opinion a drawback is that RESUMABLE has a syntax of its own. For example, instead of CATCH you need to write CATCH BEFORE UNWIND. It is not possible to have a regular CATCH and CATCH BEFORE UNWIND for the same TRY block, which I find makes the exception handling less flexible. When raising an exception you need to add RESUMABLE, i.e. RAISE RESUMABLE EXCEPTION.

 

Remember to check the Resumable checkbox when declaring method exceptions, as shown in the screenshot below.

eh2.gif

 

eh3.gif

 

An exception is thrown in C11. In B1 the exception is caught and the processing is resumed in C11 where the exception was raised. Besides the exception handling, there is no disruption in the overall ABAP process flow.

 

Add

The Add exception type focuses on catching the exception and adding a message to message list before rethrowing the exception. One reason for doing this could be to keep track of the message path, i.e. a custom call stack.

 

eh4.gif

 

Lets assume that an exception is thrown in C12. The exception is caught in C11. In C11 a message is added to the message list and the exception is rethrown. The exception is caught in B1, another message is added and the exception is rethrown. Finally, the exception is caught in A. In A the developer needs to decide how to handle such exceptions. If nothing is done to repair the issue, then the ABAP process flow stops at C12.

 

Suppress

Unfortunately, too many ABAP Objects developers will recognize this type of exception handling. The Suppress exception type simply catches an exception and does nothing about it. As with all problems that are suppressed, they will eventually come back and haunt you when you least expect it. This is not good practice.

 

eh5.gif

 

Lets again assume that an exception is thrown in C11. The exception is caught in B1. The problem is suppressed and B1 calls B2 as if nothing has happend. The rest of the ABAP process flow continues, however, there is a big risk that something is missing due to the fact that C11, C12, C13, and C14 were never properly executed.

 

Pass On

Finally, there is the Pass On exception handling. This exception handling resembles the Add exception type. Unlike Add, Pass On simply passes the exception on to the next calling layer. As with Add, it is usable whenever the process flow should actually be stopped.

 

eh6.gif

 

An exception is thrown in C11. In B1 the exception is simply passed on to A. The ABAP process flow stops.

 

Please read the ABAP Keyword documentation for much more information on exception handling.

If you've read my previous blogs, you'll know that I really like working with ABAP Objects. But still there are a few things that are simply missing - for example a decent inheritance / type checking. Perhaps some of the people who are responsible for the language itself are listening in and might want to comment or even add this item to their agenda...

 

What I would really like to see is an extension to logical expressions that allow me to check whether a certain instance is a subclass of another class or implements a certain interface. For example:

  IF lr_foo IS INSTANCE OF cl_bar.

  IF lr_foo IS INSTANCE OF if_barable.

I'd also suggest that a dynamic variant might be helpful:

  l_classname = 'CL_BAR'. " ...however this might be determined
  IF lr_foo IS INSTANCE OF (l_classname).

I agree to the widespread notion that it's usually not a good idea to rely on a certain inheritance structure when designing applications and coding generic stuff. Walking an object tree that was passed using generic references and casting stuff around might lead to very fragile applications - what if I relied on some internal class of the framework and that class changes or disappears altogether? Therefore the first example (IS INSTANCE OF some_class) is probably not the cleanest approach to object-orientation - but sometimes, a programmer's got to do what a programmer's got to do.

 

On the other hand, being able to check whether an object implements a certain interface can be really helpful. Think about this:

  DATA: lr_displayable TYPE REF TO if_xyz_displayable.

  IF ir_object IS INSTANCE OF if_xyz_displayable.
    lr_displayable ?= ir_object.
    lr_displayable->display( ).
  ELSE.
*   perform some generic implementation
  ENDIF.

Unfortunately, this statement is not (yet?) implemented. So what are the alternatives?

The probably cleanest solution at the current time is to use the run-time type inspection (RTTI). This can be done roughly like this:

  DATA: lr_descriptor TYPE REF TO cl_abap_objectdescr,
        lr_displayable TYPE REF TO if_xyz_displayable.

  lr_descriptor ?= cl_abap_typedescr=>describe_by_name( 'IF_XYZ_DISPLAYABLE' ).
  IF lr_descriptor->applies_to( ir_object ) = abap_true.
    lr_displayable ?= ir_object.
    lr_displayable->display( ).
  ELSE.
*   perform some generic implementation
  ENDIF.

This is definitely harder to read and to write than an INSTANCE OF operator would be, and the additional variable required doesn't make things much better.

Another way of dealing with this kind of issue seems to originate from the fans of Demolition Derby. Simply put: Try to   cast the instance, and if that fails, it wasn't one of ours:

  DATA: lr_displayable TYPE REF TO if_xyz_displayable.

  TRY.
      lr_displayable ?= ir_object.
      lr_displayable->display( ).
    CATCH cx_sy_move_cast_error.
*    perform some generic implementation
  ENDTRY.

While this implementation does get rid of the additional variable, it also neatly disposes of code legibility. In my opinion, it's like catching a CX_SY_ZERODIVIDE to check whether a number is zero. But there's one idea that is so overwhelmingly mind-boggling that I just have to mention it:

IF_ISH_IDENTIFY_OBJECT.png

 

Yep, that's right. Force every programmer in your vincinity to implement that interface and to implement the methods correctly so that your inheritance checking actually works. Waste an unknown amount of time ensuring that GET_TYPE - which actually returns an integer - returns unique values. Even add a customizing table so that customers can 'reserve' 'namespaces' for their own subclasses of framework classes. And it doesn't even solve the problem of interface implementation checking. I sincerely hope there was a good reason to choose this design back in 2003 and an even better reason not to remove it in the meantime...

Arguably one of the most misleading field values in the entire ABAP Workbench is this one:

image

It suggests that the constructor is just another instance method that is just called automatically by the kernel whenever an instance is created - but this is fundamentally wrong. Let me show you a small example for what can go wrong if you happen to trigger this trapdoor.

(Note that I'm using local classes here to cut down on the screenshots, but of course the same applies to global classes as well.)

Let's create a class that performs some fancy stuff during its initialization. Because we're good citizens and want to keep things reusable, we'll encapsulate the fancy stuff in its own method and call it from the constructor.

CLASS lcl_super DEFINITION.
  PUBLIC SECTION.
    METHODS constructor.
  PROTECTED SECTION.
    METHODS initialize_me.
ENDCLASS.                   

CLASS lcl_super IMPLEMENTATION.

  METHOD constructor.
    WRITE: / 'entering LCL_SUPER CONSTRUCTOR'.
    initialize_me( ).
    WRITE: / 'leaving LCL_SUPER CONSTRUCTOR'.
  ENDMETHOD.                    

  METHOD initialize_me.
    WRITE: / 'executing fancy stuff in LCL_SUPER INITIALIZE_ME'.
  ENDMETHOD.                

ENDCLASS.                   

Creating an instance of lcl_super yields a rather unspectacular output:

entering LCL_SUPER CONSTRUCTOR
executing fancy stuff in LCL_SUPER INITIALIZE_ME
leaving LCL_SUPER CONSTRUCTOR

Now let's add a subclass with its own constructor:

CLASS lcl_sub DEFINITION INHERITING FROM lcl_super.
  PUBLIC SECTION.
    METHODS constructor.
ENDCLASS.           

CLASS lcl_sub IMPLEMENTATION.

  METHOD constructor.
    WRITE: / 'entering LCL_SUB CONSTRUCTOR'.
    super->constructor( ).
    WRITE: / 'leaving LCL_SUB CONSTRUCTOR'.
  ENDMETHOD.             

ENDCLASS.        

The results aren't very surprising either:

entering LCL_SUB CONSTRUCTOR
entering LCL_SUPER CONSTRUCTOR
executing fancy stuff in LCL_SUPER INITIALIZE_ME
leaving LCL_SUPER CONSTRUCTOR
leaving LCL_SUB CONSTRUCTOR

Now someone might think that the fancy stuff in initialize_me isn't fancy enough or needs a different flavor of fancyness - whatever, since it's a protected method, we can just go ahead and redefine it. Still being the good citizens that we started off as, we ensure that the inherited implementation is called from the redefinition:

CLASS lcl_sub DEFINITION INHERITING FROM lcl_super.
  PUBLIC SECTION.
    METHODS constructor.
  PROTECTED SECTION.
    METHODS initialize_me REDEFINITION.
ENDCLASS.                    

CLASS lcl_sub IMPLEMENTATION.

  METHOD constructor.
    WRITE: / 'entering LCL_SUB CONSTRUCTOR'.
    super->constructor( ).
    WRITE: / 'leaving LCL_SUB CONSTRUCTOR'.
  ENDMETHOD.                   

  METHOD initialize_me.
    WRITE: / 'entering LCL_SUB INITIALIZE_ME'.
    WRITE: / 'performing more fancy stuff'.
    super->initialize_me( ).
    WRITE: / 'setting fancyness level to 11'.
    WRITE: / 'leaving LCL_SUB INITIALIZE_ME'.
  ENDMETHOD.                  

ENDCLASS.         

Looking good? Okay then, let's give it a try:

entering LCL_SUB CONSTRUCTOR
entering LCL_SUPER CONSTRUCTOR
executing fancy stuff in LCL_SUPER INITIALIZE_ME
leaving LCL_SUPER CONSTRUCTOR
leaving LCL_SUB CONSTRUCTOR

What the ...?

Okay, back to the drawing board. What went wrong here? A quick look into the documentation reveals this nugget:

In a constructor method, the methods of the subclasses of the class are not visible. If an instance constructor calls an instance method of the same class using the implicit self-reference me->, the method is called as it is implemented in the class of the instance constructor, and not in any redefined form that may occur in the subclass you want to instantiate. This is an exception to the rule that states that when you call instance methods, the system always calls the method as it is implemented in the class to whose instance the reference is pointing.

The rationale behind this is that the constructor is there to ensure that an instance of a class is initialized correctly and completely before any other operation is attempted. Calling a redefined method would circumvent this principle - the system would execute a method of the subclass although the construction of that class was not yet completed.

The irony of this problem is that the compiler will even tell you exactly this if you try to call an abstract protected method from within a constructor:

image

However, it won't be able to prevent the issue we've encountered above. It's a single-pass compiler that doesn't know about the subclasses that redefine the protected methods when compiling the superclass, and it doesn't know about the superclass calling redefined methods from within the constructor when compiling the subclasses. The compiler would also have to follow the entire call graph to ensure that no redefined method is called by a non-redefined method that is called by the constructor, and since the exact static call graph is undecidable, that just won't happen.

So what are our options? The most basic to prevent this from happening is to make the method private. If the subclasses still need to be able to call the method, make it final - this will at least prevent subclasses from overriding it. And think about whether you really need to call this method from the constructor - in most cases, there's a different way of structuring the code that will avoid this problem altogether.

Actions

Filter Blog

By author: By date:
By tag: