Object-Relational Programming

Rationale

While the relational data model and its commercial implementations have undergone considerable extensions to become object-relational, comparable advances from mainstream object-oriented programming languages seem to be rare. Complementing recent efforts to integrate SQL-like querying of memory-based collections into languages such as C# and Java, we specify a gentle extension of the object-oriented programming model with bidirectional relationships whose use in a program is independent of their multiplicities.

Breakdown

Because of the extent of the undertaking, it has been broken down into different parts, or phases.

Host Language

Because of the dependence on traits as containers of relationships and associated behaviour, our host language is Scala.

Phase 1

In phase 1, we extend Scala with expressions that can evaluate to any number of, rather than just one, objects. For this, we introduce three annotations, option, one, and any, that can be used to declare the multiplicity of typed declared entities, that is, of variabels (fields, formal parameters, and other local variables) and methods. Like types, the delcared multiplicities propagate through expressions so that the compiler can check well-formedness of a program with respect to multiplicities, and can compile any multiplicities to collections.

Example Person

The following is a simple sample program using multiplicities:

package orp.coc.test.codegeneration

import orp.coc._
import scala.collection.mutable.ListBuffer

class Person(val name: String) {

  override def toString = "Person(\"" + name + "\")"

  @any(classOf[ListBuffer[Person]]) var children: Person = _
  @one var father: Person = _
  @option var spouse: Person = _

  def childrenNames: List[String] = children.name
  // def childrenNamesWithoutType: List[Person] = children.name
  def childrenNamesNative = childrenContainer.asBare map (p => p.name)

}

A usage of that class could look like this:

object PersonApp extends App {
  val p = new Person("John")
  val lisa = new Person("Lisa")
  val dummy = new Person("Dummy")

  println("p.children.getClass() : " + p.children.getClass())
  println("classOf[List[Person]].isAssignableFrom(p.children.getClass()) : " +
  	classOf[List[Person]].isAssignableFrom(p.children.getClass()))

  p.children_+=(lisa)
  p.children_+=(dummy)
  println("p.children : " + p.children.asBare)

  println("remove (1) : " + p.childrenContainer(1))
  p.children_-=(p.children()(1))
  println("p.children : " + p.children.asBare)

  val ben = new Person("Ben")
  p.children_+=(ben)
  println("add (Ben) : " + p.children()(1))
  println("p.children = " + p.children.asBare)

  println("childrenNamesNative : " + p.childrenNamesNative)
  println("p.children.name : " + p.children.name)
  println("childrenNames : " + p.childrenNames)

  println("Select 1st Child")
  val child1 = p.children.apply(1)

  println("child 1 : " + child1)
  println("child 1's children : " + child1.children().asBare)

  println("p.children.children : " + p.children.children.asBare)
  println("p.children.children.names : " + p.children.children.name)

  println("adding to lisa")
  lisa.children_+=(new Person("LisaChild1"))
  lisa.children_+=(new Person("LisaChild2"))
  println("p.children.children : " + p.children.children.asBare)
  println("p.children.children.names : " + p.children.children.name)
  println("adding to dummy")
  ben.children_+=(new Person("BenChild1"))
  println("p.children.children : " + p.children.children.asBare)
  println("p.children.children.names : " + p.children.children.name)
  println("p.childrenContainer.map(x => x.childrenContainer) : " +
  	p.childrenContainer.map(x => x.childrenContainer))
  println("dummy.children : " + dummy.children().asBare)

  println("get first child of p - should be Ben")
  println("p.children()(1) " + p.children()(1))
  val pChild1 = p.children()(1)

}

Example Observer

Another example is the implementation of the Observer Pattern:

package orp.coc.examples.observer
import orp.coc._
import scala.collection.mutable.ListBuffer

trait Subject {
  @any(classOf[ListBuffer[Observer]]) var observers: Observer = _
  def notifyObservers {
    observers().update(this)
  }
}

trait Observer {
  def update(s: Subject): Unit
}

case class NamedObserver(val name: String) extends Observer {
  override def update(s: Subject) {
    println("update of " + name + " from " + s)
  }
}

case class NamedSubject(val name: String) extends Subject
object ObserverApp extends App {
  val subject1 = new NamedSubject("Subject1")
  val subject2 = new NamedSubject("Subject2")
  val observer1 = NamedObserver("observer1")
  val observer2 = NamedObserver("observer2")
  val observer3 = NamedObserver("observer3")
  subject1.observers_+=(observer1)
  subject1.observers_+=(observer2)
  subject2.observers_+=(observer2)
  subject2.observers_+=(observer3)
  subject1.notifyObservers
  subject2.notifyObservers
}

Compiler

Available Downloads

The current version of the Scala compiler can be downloaded here.

Requirements

You need to have a installed and setted up Java Runtime Enviroment in the version <= Java 6.

Usage

The Zip should be extracted anywhere on your computer. The archive includes some examples (contained in src/) and some scripts to compile and run the included or your own sources.

Alternativly you can call the compiler (and run the code) via

java -classpath lib\orp-coc-plugin-assembly-1.0.jar %MAIN% -classpath lib\orp-coc-plugin-assembly-1.0.jar -Xplugin-require:orp-coc -d target -sourcepath ../src -Xpluginsdir lib <args>

can be any *.scala file or {src} to compile the whole src directory.

you can run the code via

java -classpath lib\orp-coc-plugin-assembly-1.0.jar;target <qualified class name>