! { dg-do compile }
!
! PR fortran/51995
!
! Contributed by jilfa12@yahoo.com
!

MODULE factory_pattern

  TYPE CFactory
     PRIVATE
     CHARACTER(len=20) :: factory_type      !! Descriptive name for database
     CLASS(Connection), POINTER :: connection_type !! Which type of database ?
   CONTAINS                                        !! Note 'class' not 'type' !
     PROCEDURE :: init                             !! Constructor
     PROCEDURE :: create_connection                !! Connect to database
     PROCEDURE :: finalize                         !! Destructor
  END TYPE CFactory

  TYPE, ABSTRACT :: Connection
   CONTAINS
     PROCEDURE(generic_desc), DEFERRED, PASS(self) :: description
  END TYPE Connection

  ABSTRACT INTERFACE
     SUBROUTINE generic_desc(self)
       IMPORT :: Connection
       CLASS(Connection), INTENT(in) :: self
     END SUBROUTINE generic_desc
  END INTERFACE

  !! An Oracle connection
  TYPE, EXTENDS(Connection) :: OracleConnection
   CONTAINS
     PROCEDURE, PASS(self) :: description => oracle_desc
  END TYPE OracleConnection

  !! A MySQL connection
  TYPE, EXTENDS(Connection) :: MySQLConnection
   CONTAINS
     PROCEDURE, PASS(self) :: description => mysql_desc
  END TYPE MySQLConnection

CONTAINS

  SUBROUTINE init(self, string)
    CLASS(CFactory), INTENT(inout) :: self
    CHARACTER(len=*), INTENT(in) :: string
    self%factory_type = TRIM(string)
    self%connection_type => NULL()            !! pointer is nullified
  END SUBROUTINE init

  SUBROUTINE finalize(self)
    CLASS(CFactory), INTENT(inout) :: self
    DEALLOCATE(self%connection_type)          !! Free the memory
    NULLIFY(self%connection_type)
  END SUBROUTINE finalize

  FUNCTION create_connection(self)  RESULT(ptr)
    CLASS(CFactory) :: self
    CLASS(Connection), POINTER :: ptr

    IF(self%factory_type == "Oracle") THEN
       IF(ASSOCIATED(self%connection_type))   DEALLOCATE(self%connection_type)
       ALLOCATE(OracleConnection :: self%connection_type)
       ptr => self%connection_type
    ELSEIF(self%factory_type == "MySQL") THEN
       IF(ASSOCIATED(self%connection_type))   DEALLOCATE(self%connection_type)
       ALLOCATE(MySQLConnection :: self%connection_type)
       ptr => self%connection_type
    END IF

  END FUNCTION create_connection

  SUBROUTINE oracle_desc(self)
    CLASS(OracleConnection), INTENT(in) :: self
    WRITE(*,'(A)') "You are now connected with Oracle"
  END SUBROUTINE oracle_desc

  SUBROUTINE mysql_desc(self)
    CLASS(MySQLConnection), INTENT(in) :: self
    WRITE(*,'(A)')  "You are now connected with MySQL"
  END SUBROUTINE mysql_desc
end module


  PROGRAM main
   USE factory_pattern

   IMPLICIT NONE

   TYPE(CFactory) :: factory
   CLASS(Connection), POINTER :: db_connect => NULL()

   CALL factory%init("Oracle")
   db_connect => factory%create_connection()   !! Create Oracle DB
   CALL db_connect%description()

   !! The same factory can be used to create different connections
   CALL factory%init("MySQL")                  !! Create MySQL DB

   !! 'connect' is a 'class' pointer. So can be used for either Oracle or MySQL
   db_connect => factory%create_connection()
   CALL db_connect%description()

   CALL factory%finalize()        ! Destroy the object

  END PROGRAM main
