Wednesday, 24 April 2024

How to improve the ADT Class Runner with additional Features

What is a Class Runner?


A class runner is a great tool to test code snippets and run code directly from ADT. You can even write a simple log to the console in Eclipse.

What can be improved?


When using multiple systems and complex projects I got annoyed at some point in time, that I don't know from which system the last log was, when it was written by which runner and if the last execution has finished or failed maybe.

How to improve the ADT Class Runner with additional Features

Thats when I started my own class runner that have some improved capabilities.

How does it look like?


The output looks like this:

How to improve the ADT Class Runner with additional Features

◉ You see when, what, by whom and where it was started.
◉ For every log entry you get a timestamp.
◉ You see when the execution finished.
◉ All times and dates are formatted in your personal date and time format

What about the ABAP code? Your code gets defined in the logic() method.

CLASS zcl_runner_demo DEFINITION
  PUBLIC
  INHERITING FROM zcl_base_runner
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.
    METHODS: logic REDEFINITION.
  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.

CLASS zcl_runner_demo IMPLEMENTATION.
  METHOD logic.
    write( `Hello World` ).
    write( `this is a demo of the enhanced class runner` ).
    write( 123 ).
  ENDMETHOD.

ENDCLASS.
 

Methods write() and out->write()


The write() method has the same signature as the out->write() method, you can even access the original out->write() if needed.

  METHOD logic.
    write( `Hello World` ).
    write( `this is a demo of the enhanced class runner` ).
    write( 123 ).
    out->write( `bare write` ).
  ENDMETHOD.

How to improve the ADT Class Runner with additional Features

of course you can pass structured and table data to write(), like you can do to out->write()

  METHOD logic.
    DATA ls_airport type /dmo/airport.
    write( `Hello World` ).
    ls_airport = value #( airport_id = 'SAP' city = 'Dokkerland' ).
    write( ls_airport ).
    write( name = 'Info about airport' data = ls_airport ).
  ENDMETHOD.

How to improve the ADT Class Runner with additional Features

Uncaught exceptions


Method logic is secured against uncaught exceptions. 

  METHOD logic.
    write( `Hello World` ).
    raise EXCEPTION new cx_abap_invalid_name(  ).
  ENDMETHOD.
 
It logs the behaviour:

How to improve the ADT Class Runner with additional Features

How does it look under the hood?


It is basically an abstract class that wraps the original ADT class runner:

"! <p class="shorttext synchronized" lang="en">Base Runner</p>
"! Improved class runner with enhanced logging capabilities
CLASS zcl_base_runner DEFINITION
  PUBLIC
  ABSTRACT
  CREATE PUBLIC.

  PUBLIC SECTION.

    INTERFACES if_oo_adt_classrun .

    "! <p class="shorttext synchronized" lang="en">This method implements your logic</p>
    "! You can use {  .METH:write } with enhanced capabilities or the {  .DATA:out }->write( ) for plain logging.
    "! @raising cx_root | <p class="shorttext synchronized" lang="en">any exception not caught, will be handled in the runner.</p>
    METHODS logic ABSTRACT
      RAISING cx_root.

    CLASS-METHODS convertuuid
      IMPORTING
                str            TYPE string
      RETURNING VALUE(rv_uuid) TYPE sysuuid_c32.

  PROTECTED SECTION.

    DATA out TYPE REF TO if_oo_adt_classrun_out.

    "! <p class="shorttext synchronized" lang="en">wrapper for out->write( )</p>
    "! this enhances the default function by writing a timestamp.
    "! this method should be used in {@link .METH:logic }
    "! @parameter data | <p class="shorttext synchronized" lang="en"></p>
    "! @parameter name | <p class="shorttext synchronized" lang="en"></p>
    METHODS write
      IMPORTING
        data TYPE any
        name TYPE string OPTIONAL.

  PRIVATE SECTION.

    "! <p class="shorttext synchronized" lang="en">determine the current timestamp and returns as string</p>
    "! <ul><li>in users timezone</li><li>in users prefered format</li></ul>
    "! @parameter rv_dateandtime | <p class="shorttext synchronized" lang="en"></p>
    CLASS-METHODS getCurrentDateandTimeFormatted
      RETURNING VALUE(rv_dateandtime) TYPE string.

    "! <p class="shorttext synchronized" lang="en">draws a horizontal line on the console</p>
    "!
    "! @parameter out | <p class="shorttext synchronized" lang="en"></p>
    CLASS-METHODS horizontalLine
      IMPORTING out TYPE REF TO if_oo_adt_classrun_out.

ENDCLASS.

CLASS zcl_base_runner IMPLEMENTATION.

  METHOD if_oo_adt_classrun~main.

    me->out = out.

    horizontalline( out ).

    TRY.

        out->write( |Start on { xco_cp=>current->tenant( )->get_url( io_type = xco_cp_tenant=>url_type->ui )->get_host(  ) } runner { cl_abap_classdescr=>get_class_name( me ) } by { cl_abap_context_info=>get_user_formatted_name( ) }| &&
        | at { getcurrentdateandtimeformatted(  ) }| ).

      CATCH cx_abap_context_info_error INTO DATA(lc_context_error).
        "handle exception
        out->write( 'Error occured in determine current user:' ).
        out->write( lc_context_error->get_text(  ) ).
    ENDTRY.

    TRY.
        me->logic( ).

      CATCH cx_root INTO DATA(lc_error).
        "handle exception
        write( 'Error occured in executing the logic:' ).
        write( lc_error->get_text(  ) ).
        DATA(previous) = lc_error->previous.
        IF lc_error->previous IS BOUND.
          write( name = 'Previous' data = lc_error->previous->get_text(  ) ).
        ENDIF.


    ENDTRY.

    out->write( |Done at { getcurrentdateandtimeformatted(  ) }| ).

    horizontalline( out ).

  ENDMETHOD.

  METHOD write.

    DATA(descr_ref) = cl_abap_typedescr=>describe_by_data( data ).

    IF name IS INITIAL.

      IF descr_ref IS INSTANCE OF cl_abap_elemdescr.
        out->write( data = |{ getcurrentdateandtimeformatted(  ) }: { data }| ).
      ELSE.
        out->write( data = |{ getcurrentdateandtimeformatted(  ) }:| ).
        out->write( data = data ).
      ENDIF.

    ELSE.
      IF descr_ref IS INSTANCE OF cl_abap_elemdescr.
        out->write( data = |{ getcurrentdateandtimeformatted(  ) }: { Name }:{ data }| ).
      ELSE.
        out->write( data = |{ getcurrentdateandtimeformatted(  ) }: { Name }:| ).
        out->write( data = data ).
      ENDIF.
    ENDIF.
  ENDMETHOD.

  METHOD getcurrentdateandtimeformatted.
    DATA tsp TYPE tzntstmps.
    DATA(lo_unix_timestamp) = xco_cp=>sy->unix_timestamp( ).
    DATA(lo_moment) = lo_unix_timestamp->get_moment( xco_cp_time=>time_zone->user ).
    tsp = lo_moment->as( xco_cp_time=>format->abap )->value.
    rv_dateandtime = |{ tsp TIMESTAMP = USER }|.
  ENDMETHOD.

  METHOD horizontalline.
    out->write( repeat( val = '-' occ = 120 ) ).
  ENDMETHOD.

  METHOD convertuuid.
    DATA(lo_uuid) = xco_cp_uuid=>format->c36->to_uuid( str ).
    rv_uuid = xco_cp_uuid=>format->c32->from_uuid( lo_uuid ).
  ENDMETHOD.

ENDCLASS.

No comments:

Post a Comment