/* Simple Employee Class Hierarchy (Scala Version)
   H. Conrad Cunningham
   Version #1b:  1 October 2008
   Version #1c: 16 February 2010

  This file defines a simple, and rather silly, class hierarchy
  representing employees within a university.  Its purpose is to
  illustrate a few aspects of Scala object-oriented programming.  It
  is not intended as a realistic example.

  This example was adapted from a Ruby version that was developed in
  September 2006, which was, in turn, based on an older Java version.
  The Scala version was originally developed for the Special Topics
  in Multiparadigm Programming (CSci 581/582) class in Fall 2008.  A
  few modifications (mostly to the comments) were done for the
  Software Design and Scala Programming class (CSci 490-1/Engr 596-6)
  in Spring 2010.

123456789012345678901234567890123456789012345678901234567890123456789012345678

  Notes:

  (1) Uses type inference.  This example leaves off the type
  declarations for private val and var features when the type is clear
  (e.g., from a parameter's type). The types are computed by the
  compiler using Scala's type inferencing.
 
  (2) Makes class interface explicit. This code seeks to define the
  public interface of a class explicitly by giving the return types
  for public features (e.g., methods) introduced in a class or object.
  It may, however, not give the return types of overridden methods.
  (Sometimes type inference could allow the return types of public
  methods to be omitted, but it could infer types to be different than
  intended by the designer.)

  (3) Uses the Null Object pattern. This hierarchy introduces the
  NullEmployee singleton object.  This object implements the Employee
  methods so that they behave in relatively benign ways.) 

  (4) Does not use abstraction extensively on parameters and
  attributes.  The current implementation does not attempt to abstract
  entities such as employee identifiers, names, department names, or
  titles.  In a more flexible design, these would be abstract rather
  than concrete types such as Strings or Ints.

  (5) Has unsafe feature.  This code does not attempt to detect cycles
  in the supervisory relationship.  A cycle will result in an infinite
  recursion.

*/

/*
   Object Employee holds attributes shared by all objects of class
   Employee.  In particular, it has the mechanism for generating new
   Employee identifiers.

   The Scala "object" construct introduces what might be called a
   singleton object.  A Scala object introduces a new type that has
   exactly one object associated with it.

   A Scala "object" and a Scala class can have the same name.  In such
   a case, the object is called the companion object of the class.
   The companion object's attributes and methods are similar to the
   "static" attributes and methods of a Java class.  A Scala class
   cannot have static attributes and methods.  The attributes and
   methods of a class's companion object serve these roles.  A class
   and its companion object can access each others private features.

*/

object Employee {

  private var nextid: Int = 1 // next Employeeidentifier to return

  // Return a unique new identifier for employees.
  def newId: Int = { 
    val ni = nextid
    nextid = nextid + 1
    ni
  }
}


/* 
   Class Employee is an abstract base class for employees of a
   university.

   In Scala the primary constructor is syntactically combined with the
   header of "class" declaration.  (Secondary constructors can be
   defined as well.)  Construction of an object that is from a
   subclass of Employee must supply at least four arguments.  Three of
   these are of type String to give the Employees first and last names
   and department name.  The fourth is a reference to another Employee
   who is this Employee's supervisor.

   Note: Here String is a builtin reference type that is the same as
   the Java String type.

*/

abstract class Employee(first: String, last: String, dept: String, 
                        boss: Employee) {

  /* 
     The values of some attributes are immutable, i.e., their values
     cannot change after the object is initialized.  Other attributes
     are mutable, i.e., their values can change.  Both kinds of
     attributes may have accessor (i.e., getter, reader, or query)
     operations if their values need to be made available to the
     clients of the class.  The mutable attributes may have mutator
     (i.e., setter, writer, or command) operations that alter the
     value of the attribute.  Any mutator operations associated with
     immutable fields need to create new objects and return those.

     In Scala, we can use "val" definitions to define immutable
     attributes and "var" definitions to define mutable ones. "val"
     attributes can be initialized at time of creation but not changed
     thereafter.  "var" attributes can be both initialized and
     subsequently changed by assignment statements.

     Subtlety: If a "val" refers to an object, the reference cannot be
     changed.  However, the object may have mutable attributes which
     can be changed.

     Good object-oriented programming practice suggests that in most
     cases the attributes of a class should be hidden.  Hence, this
     example uses the "private" modifier.  This keeps the actual
     representation of the attribute as a "secret" of the class and,
     hence, it could potentially be changed without affecting the
     clients of the class.

     Scala allows attributes to be exposed publicly (i.e., by
     leaving off the "private" modifier).  This enables the attribute
     variable to be read or written directly.  The effect is that all
     clients of the class need to know the specific concrete
     representation of the attribute.

     Scala supports Hindley-Milner type inference.  If the type of a
     "val" or "var" entity can be determined from the type of the
     right-hand-side of its initialing expression, then the type
     declaration can be left off the "val" or "var" declaration.  This
     also applies to the return types of non-recursive functions.

     Again, good object-oriented programming practice suggests that
     the types of all public features of a class class be declared
     explicitly.  
     
  */

  // Immutable attributes -- note use of "val" keyword
  private val empid = Employee.newId  // note type inference in these
  private val fname = first 
  private val lname = last 

   // Mutable attributes -- note use of "var" keyword
  private var deptid     = dept 
  private var supervisor = if (boss != null) boss else NullEmployee
                           // note use of "if" expression
                           // force to NullEmployee if undefined

  // Design question:  Should we allow name changes?

  /* Mutator methods -- change the value of attributes

     Syntax notes:
     * "def" introduces method declarations.
     * Type declaration follows parameter name
     * Below we have procedure methods -- no return value, body is
       enclosed in braces, executed for side-effects on mutable
       attributes.
  */

  def setDeptId(dept: String) { deptid = dept }

  def setSupervisor(newboss: Employee) { 
    supervisor = if (newboss != null) newboss else NullEmployee
                 // force to NullEmployee if undefined
  }

  /* Accessor methods -- return information about object

     Syntax notes: 
     * See declaration of return type on these public methods.
     * Note that the defining expression does not have to be in braces
       if simple.  More complex expressions may need to be enclosed in
       braces.
      
  */

  def getName: String         = lname + ", " + fname 
  def getEmpId: Int           = empid
  def getDeptId: String       = deptid
  def getSupervisor: Employee = supervisor

  /* Override toString method from Any

     Note that, unlike Java, Scala requires use of an explicit
     "override" keyword.

  */

  override def toString = 
    empid + " : " + getName + " : " + deptid + " (" + supervisor + ")"
    // Note local call to "get_name" above.  Target is this object.
}


/* 
   Subclass Staff represents full-time, regular employees of the
   university. 

   Note the use of the "extends" keyword to indicate subclassing of
   class Employee.

   Note the call to the superclass Employee's constructor in the
   header to do initialization.
*/

class Staff(first: String, last: String, dept: String, boss: Employee, 
            title: String)
     extends Employee(first,last,dept,boss) {
    
  // Mutable attribute
  private var theTitle = title

  // Mutators -- the mutators of Employee are inherited

  def setTitle(newTitle: String) { theTitle = newTitle }

  // Accessors -- the accessors of Employee are inherited

  def getTitle: String = theTitle

  // Override Employee toString but call up to Employee's toString method 
  // in doing so using "super.toString".
  override def toString = super.toString + ", " + theTitle
}


/* 
   Subclass Professor represents tenured or tenure-track faculty
   members of the university. 

   Note that Professor is s subclass of Staff, which is a subclass of
   Employee.

*/

class Professor(first: String, last: String, dept: String, boss: Employee,
                title: String, tenure: String) 
  extends Staff(first,last,dept,boss,title) {

  // Mutable attributes
  private var theTenureStatus = tenure

  // Mutators -- the mutators of Staff and Employee are inherited

  def setTenure(newTenure: String) { theTenureStatus = newTenure }

  // Accessors -- the accessors of Staff and Employee are inherited

  def getTenure: String = theTenureStatus

  // Override parent's toString but include call up to parent's method.
  override def toString = super.toString + ", " + theTenureStatus
}


/*
   Subclass NullEmployee is an error object for fields that take
   Employees.  It implements the Null Object design pattern by
   returning a special Employee singleton object that responds to
   method calls in a benign way.

   The Null Object design pattern is often a good alternative to
   throwing an exception or printing an error message deep inside
   the code.

   Note: The current implementation assigns a nonzero Employee ID to
   the NullEmployee via the constructor for Employee.  An alternative
   implemenation is probably needed.
*/

object NullEmployee extends Employee("","","",null) {

  private val empid: Int = 0

  // Mutator methods (override to do nothing)

  override def setDeptId(dept: String)       { }
  override def setSupervisor(boss: Employee) { }

  // Accessor methods (override to return benign values)

  override def getName       = ""
  override def getEmpId      = 0
  override def getDeptId     = ""
  override def getSupervisor = this

  // Override toString method from Employee
  override def toString = "NullEmployee"
}


/* 
   Class Manager associates an Employee with a department as its manager.
*/

class Manager(emp: Employee, dept: String, sec: Staff) {

  private val theEmpId     = if (emp != null) emp else NullEmployee
  private val theDeptId    = dept
  private var theSecretary = if (sec != null) sec else NullEmployee

  // Mutators 

  def setSecretary(sec: Staff) = { theSecretary = sec }

  // Accessors

  def getEmpId: Employee     = theEmpId
  def getDeptId: String      = theDeptId
  def getSecretary: Employee = theSecretary

  // Override toString to give appropriate display
  override def toString: String = 
    "Manager(" + theEmpId + ") of " + theDeptId + " with admin (" +
                 theSecretary + ")" }


/*
   Object EmployeeTest contains testing code for the Employee hierarchy.

   Note the "main" method, which is an entry point for calling this
   application.

   Note that "main" takes an array of strings for its command-line
   parameters.  In Scala, Array is a generic class.  The type name in
   square brackets [ and ] is the type parameter.  In the case of
   Array, the type parameter gives the type of the elements of the
   array.

   Note that if an object reference occurs in an expression where a
   String is needed, then the class's toString method is called to
   provide the value.  This works in Java as well, but it is an
   instance of a more general mechanism in Scala.

*/

object EmployeeTest {

  // Main method for testing
  def main(args: Array[String]) {

    println("NullEmployee                  ==> " + NullEmployee)
    println("NullEmployee.getClass.getName ==> " + 
      NullEmployee.getClass.getName)
    println("NullEmployee.getEmpId         ==> " + NullEmployee.getEmpId)
    println("NullEmployee.getName          ==> " + NullEmployee.getName)
    println("NullEmployee.getDeptId        ==> " + NullEmployee.getDeptId)
    println("NullEmployee.getSupervisor    ==> " + NullEmployee.getSupervisor)
    println(" ")

    val c1 = new Professor("Robert","Khayat","Law",null,"Chancellor",
                           "tenured")
    val d1 = new Professor("Alexander","Cheng","CE",null,"Professor","tenured")
    val p1 = new Professor("Conrad","Cunningham","CS",null,
                           "Professor","tenured")
    val s1 = new Staff("Bernice","Herod","Engineering",d1,"Secretary")
    val s2 = new Staff("Karen","Lovett","CS",p1,"Secretary")
    val m1 = new Manager(p1,p1.getDeptId,s2)
    val m2 = new Manager(d1,d1.getDeptId,s2)

    println("d1                   ==> " + d1)
    println("d1.getClass.getName  ==> " + d1.getClass.getName)
    println("d1.getEmpId          ==> " + d1.getEmpId)
    println("d1.getName           ==> " + d1.getName)
    println("d1.getDeptId         ==> " + d1.getDeptId)
    println("d1.getTitle          ==> " + d1.getTitle)
    println("d1.getTenure         ==> " + d1.getTenure)
    println("d1.getSupervisor     ==> " + d1.getSupervisor)
    println(" ")

    println("p1                   ==> " + p1)
    println("p1.getClass.getName  ==> " + p1.getClass.getName)
    println("p1.getEmpId          ==> " + p1.getEmpId)
    println("p1.getName           ==> " + p1.getName)
    println("p1.getDeptId         ==> " + p1.getDeptId)
    println("p1.getTitle          ==> " + p1.getTitle)
    println("p1.getTenure         ==> " + p1.getTenure)
    println("p1.getSupervisor     ==> " + p1.getSupervisor)
    println(" ")

    println("s1                   ==> " + s1)
    println("s1.getClass.getName  ==> " + s1.getClass.getName)
    println("s1.getEmpId          ==> " + s1.getEmpId)
    println("s1.getName           ==> " + s1.getName)
    println("s1.getDeptId         ==> " + s1.getDeptId)
    println("s1.getTitle          ==> " + s1.getTitle)
    println("s1.getSupervisor     ==> " + s1.getSupervisor)
    println(" ")

    println("s2                   ==> " + s2)
    println("s2.getClass.getName  ==> " + s2.getClass.getName)
    println("s2.getEmpId          ==> " + s2.getEmpId)
    println("s2.getName           ==> " + s2.getName)
    println("s2.getDeptId         ==> " + s2.getDeptId)
    println("s2.getTitle          ==> " + s2.getTitle)
    println("s2.getSupervisor     ==> " + s2.getSupervisor)
    println(" ")

    println("m1                   ==> " + m1)
    println("m1.getClass.getName  ==> " + m1.getClass.getName)
    println("m1.getEmpId          ==> " + m1.getEmpId)
    println("m1.getDeptId         ==> " + m1.getDeptId)
    println("m1.getSecretary      ==> " + m1.getSecretary)
    println(" ")

    println("m2                   ==> " + m2)
    println("m2.getClass.getName  ==> " + m2.getClass.getName)
    println("m2.getEmpId          ==> " + m2.getEmpId)
    println("m2.getDeptId         ==> " + m2.getDeptId)
    println("m2.getSecretary      ==> " + m2.getSecretary)
    println(" ")

    var p = p1

    println("p, after p = p1      ==> " + p)
    println("p.getTenure          ==> " + p.getTenure)
    println("p.setTenure(\"retired\") ==> " + p.setTenure("retired"))
    println("p.getTenure          ==> " + p.getTenure)

    println("p.getTitle           ==> " + p.getTitle)
    println("p.setTitle(\"Emeritus Profesor\") ==> " 
      + p.setTitle("Emeritus Professor"))
    println("p.getTitle           ==> " + p.getTitle)

    println("p.getDeptId          ==> " + p.getDeptId)
    println("p.setDeptId(\"EE\")    ==> " + p.setDeptId("EECS")) 
    println("p.getDeptId          ==> " + p.getDeptId)

    println("p.getSupervisor      ==> " + p.getSupervisor)
    println("p.setSupervisor(d1)  ==> " 
      + p.setSupervisor(d1))
    println("p.getSupervisor      ==> " + p.getSupervisor)
    println(" ")

    var m = m1

    println("m, after m = m1      ==> " + m)
    println("m.getSecretary       ==> " + m.getSecretary)
    println("m.setSecretary(s1)   ==> " + m.setSecretary(s1))
    println("m.getSecretary       ==> " + m.getSecretary)
    println(" ")

    var me =  m.getEmpId

    println("me, after me = m.getEmpId ==> " + me)
    println("me.getSupervisor          ==> " + me.getSupervisor)
    println("me.setSupervisor(d1)      ==> " + me.setSupervisor(d1))
    println("me.getSupervisor          ==> " + me.getSupervisor)
    println(" ")
  }
}
