import java.lang.reflect.*;

class Ttt implements MyComparable {
    int t1;
    int t2;

    public Ttt(int x, int y) {
	t1 = x;
        t2 = y;
    }

    public int myCompareTo(Object obj) {


        //BOOLscher Vergleichsoperator fuer 
        // die Abfrage, ob ein Objekt einer bestimmten Klasse
        // angehoert.
        if ( obj instanceof Ttt ) {

	    Ttt other = (Ttt)obj;
            int sqThis = t1*t1 + t2*t2;
            int sqOther = other.t1*other.t1 + other.t2*other.t2;
            
            return ( sqThis - sqOther );
            
	}
        else return -1;
       
    }

    // Uberladen der toString()-Methode
    public String toString() {
 
	return "("
               +(new Integer(t1)).toString() 
               + ","
               + (new Integer(t2)).toString()+ ")";

    }

    // Die urspruengliche toString()-Methode von class Object
    // durch eine neue Methode fuer Nutzer wieder verfuegbar machen:
    public String toStringObject() {

	return super.toString();

        // ACHTUNG: Casting kann nicht verwendet werden:
        // Der Ausdruck ((Object)this).toString();
        // fuehrt zum Aufruf der toString()-Methode von Klasse Ttt
    }

}

//////////////////////////////////////////////////////////////////////////

class MyIllegalTypeException extends MyFiniteStackException {

    String theIllegalClassName;
    String theIlegalObjName;

    public MyIllegalTypeException() { super(); }

    public MyIllegalTypeException(Object obj) {
	super();
        theIllegalClassName = new String(obj.getClass().getName());
        theIlegalObjName = new String(obj.toString());
    }

    public String getMessage() {

        return "+++ Illegal object type: "
   	           + theIllegalClassName
                   + " of object\n"
                   + "\"" + theIlegalObjName + "\""
                   + "\n";

    }  

}

//////////////////////////////////////////////////////////////////////////
class MyFiniteWellTypedStack extends MyFiniteStack {

    Class myStackEntryType; 

    // Mit der Instanziierung wir der gewuenschte Typ 
    // angegeben
    public MyFiniteWellTypedStack(Class theLegalType) {
        super();
        myStackEntryType = theLegalType;
    }

    public MyFiniteWellTypedStack(Class theLegalType, 
                                  int   specialStacksize) {
        super(specialStacksize);
        myStackEntryType = theLegalType;
    }

    // Wir redefinieren push(), um die Sortenreinheit zu erzwingen
    public void push(Object obj) 
           throws MyIllegalTypeException, MyFiniteStackException {

	if ( obj.getClass() == myStackEntryType ) {
             super.push(obj);
	    //try { super.push(obj); }
            //catch (MyFiniteStackException e) { throw e; }
	}
        else {
	    throw new MyIllegalTypeException(obj);
	}
    }    

}

/////////////////////////////////////////////////////////////////////////

public class MyMain {

    public static void main(String[] args) {

	// public-Methoden oder public-Attribute anderer Klassen
        // koennen durch <Klassenname>.<Methodenname> bzw.
        // <Klassenname>.<Attributname> referenziert werden

        MyFiniteStack stk1, stk2;
        // Jetzt haben wir eine Referenz stk1 auf ein Stack-Obj

        // hier erfolgt die Allokation:
        // Bei der Erzeugung wird automatisch der 
        // Default-Konstruktor aufgerufen.
        stk1 = new MyFiniteStack();
        System.out.println(stk1.toString() + "numberOfAllStacks =" +
			   stk1.numberOfAllStacks);


//          try { stk1.push("jljljljljlljlkj"); }
//          catch (MyFiniteStackException e ) { e.getMessage(); }
//          try {  stk1.push(new Ttt(8,0)); }
//          catch (MyFiniteStackException e ) { e.getMessage(); }

//          try {
//          System.out.println("Name of Top-of-Stack Class =  " 
//  			   + stk1.peek().getClass().getName()
//                             + " has value "
//                             + stk1.peek().toString()
//                             + " which is the same as "
//          + stk1.getClass().getMethods()[1].invoke(stk1,null).toString());
//  	}
//          catch (java.lang.IllegalAccessException e){
//  	    System.out.println(e);
//  	}
//          catch (java.lang.reflect.InvocationTargetException e){
//  	    System.out.println(e);
//  	}

        
        stk2 = new MyFiniteStack(2);
        System.out.println(stk2.toString()
                           + "numberOfAllStacks nach stk2-Allok ="
                           + stk2.numberOfAllStacks);

        
        // Da die Methoden von MyFiniteStack beliebige Parameter
        // vom Typ Object akzeptieren, koennen wir Instanzen
        // aus unterschiedlichen Klassen in den Stack eintragen:
        // Die SORTENREINHEIT, wie sie beispielsweise durch das
        // C++-Template Konzept unterstuetzt wird, ist damit
        // in Java nicht zwingend. Es gibt daher auch keine Templates
        // in Java. Will man Sortenreinheit erzwingen, muss die
        // Pruefung auf zulaessigen Objekttyp zur Laufzeit erfolgen.
        // (siehe unten). 

        // Beispiel: die beiden push()-Aufrufe tragen erst ein 
        // String- und dann ein Ttt-Objekt in den Stack ein.
        try { stk2.push("1. Eintrag"); }
        catch (MyFiniteStackException e) {
	    System.out.println(e.getMessage() 
                               + " at stk2.push(), 1st invocation");
	    e.printStackTrace();
	} 
        
        try { stk2.push(new Ttt(19,20)); }
        catch (MyFiniteStackException e) {
	    System.out.println(e.getMessage() 
                               + " at stk2.push(), 2nd invocation");
	     e.printStackTrace();
	} 

        try { stk2.push("3. Eintrag"); }
        catch (MyFiniteStackException e) {
	    System.out.println(e.getMessage() 
                               + " at stk2.push(), 3rd invocation");
            // Die printStackTrace()-Methode wird von class 
            // Throwable geerbt. Wichtig: die von printStackTrace()
            // verwendete getMessage()-Methode ist die in 
            // von uns in class MyFiniteStackException eingefuehrte.
	    e.printStackTrace();
       	} 

        // Anwendung von pop():
        // Bisschen fiese Anwendung des Exception-Handlings:
        // Schleife, bis wir in den catch-Zweig kommen; dieser
        // springt mit break aus der Schleife. Damit wissen wir,
        // dass der Stack wieder leer ist.
        while (true) {
	    try { 
		Object obj  = stk2.pop(); 
		System.out.println("Popped from stk2: " + obj.toString());
	    }
	    catch (MyFiniteStackException e) { break; }
	}

        // 
        // Aufbau eines sortenreinen Stacks: Nur Objekte
        // vom Typ Ttt sollen hinein.
        //
        // Jede Klasse hat implizit die Methode 
        //      Class getClass()
        // definiert.
        Ttt t1 = new Ttt(88,99);
        MyFiniteWellTypedStack cleanStk = 
                   new MyFiniteWellTypedStack(t1.getClass());
 
        try { cleanStk.push(t1); }
        catch (MyIllegalTypeException e) { e.printStackTrace();	}
        catch (MyFiniteStackException e) { e.printStackTrace();	}

        try { cleanStk.push(new Ttt(100,101)); }
        catch (MyIllegalTypeException e) { e.printStackTrace();	}
        catch (MyFiniteStackException e) { e.printStackTrace();	}

        try { cleanStk.push(new Ttt(102,103)); }
        catch (MyIllegalTypeException e) { e.printStackTrace();	}
        catch (MyFiniteStackException e) { e.printStackTrace();	}

        try { 
	    // hier knallt's:
         String s = 
	     new String("Oh random fluctuations of space time continuum!");
         cleanStk.push(s); }
        catch (MyIllegalTypeException e) { e.printStackTrace();	}
        catch (MyFiniteStackException e) { e.printStackTrace();	}


        // nun alles wieder auslesen
        while (true) {
	    try { 
		Object obj  = cleanStk.pop(); 
		System.out.println("Popped from cleanStk: " + obj.toString());
	    }
	    catch (MyFiniteStackException e) { break; }
	}        



        // Die Eigenschaft, dass eine Methode
        // in unterschiedlichen Klassen verschiedene
        // Ausprägungen haben kann, heisst POLYMORPHISMUS
        
        // Beispiel: Ein Object-Array enthält Referenzen auf
        // Objektinstanzen unterschiedlicher Klassen, die ggf.
        // die Methode toString() redefiniert haben. Die Laufzeitumgebung
        // wählt  die zum Klassentyp gehörige Methode aus.
        Object[] a = new Object[10];

        a[0] = new String("bla");
        a[1] = new Ttt(3,7);
        a[2] = new Object();


       for (int i = 0; i < 3; i++) {
	    System.out.println("a["+i+"] = " 
                                + a[i].toString()
                                + " has Class "
                                +  a[i].getClass());

	    Class  cl = a[i].getClass();
	    Method ma[] = cl.getMethods();
            for (int j = 0; j < Array.getLength(ma); j++) {
              System.out.println("a["+i+"]" 
               + " exportiert public method " + j + ": "
				 + ma[j].getName());
	    }

	}

        // 
        // Im allgemeinen kann nicht zur Uebersetzungszeit
        // festgestellt werden, welche Methodenausprägung
        // zu verwenden ist. Die muss von der Laufzeitumgebung
        // vorgenommen werden.
        // Diese Eigenschaft heisst LATE BINDING (spätes Binden)

       // Beispiel: Referenz vom Typ Object zeigt auf unterschiedliche
       // Klassen, die vom Benutzer angegeben werden. Die Auswahl der
       // zu verwendenden toString()-Methode kann daher nur zur Laufzeit
       // geschehen.
       Object obj;
       if ( args[0].equals("Ttt") ) 
	   obj = new Ttt(12,15);
       else if ( args[0].equals("String") )
           obj = new String("Der neue String");
       else 
           obj = new Object();

       System.out.println("obj.toString() = " + obj.toString());
 
       // Dieser Ausdruck wird durch den cast auf Klassentyp Ttt
       // uebersetzbar. Zur Laufzeit erhaelt man einen Fehler,
       // wenn obj nicht wirklich eine Ttt-Instanz ist, da
       // dann die Methode toStringObject() nicht existiert.
       System.out.println("obj.toStringObject() = " 
                            + ((Ttt)obj).toStringObject());



    }

}
