Montag, 6. Juni 2011

 

Listings - Source Code drucken

Es sollte m.E. doch möglich sein, Listings in der folgenden - lesbaren - Form zu erzeugen:
Die Schrift ist leserlich, die Zeilen richtig eingerückt, Dateiname und Autor etc. sind ersichtlich (Ich gebe zu, dieses Bild ist schlecht...). Verwendet wurde recode und a2ps unter Linux. 
Wichtig ist auch, dass man allgemeine Programmierrichtlinien befolgt.
Aber mit Office kann man auch sehr schöne Listings erzeugen:
Man muss nur Kopf- und Fußzeilen einrichten, die Schrift auf Courier New (9 oder 10pt) oder eine andere mit fixer Breite und dann zwei Seiten auf eine drucken.
Ähnliches gibt es schon unter Listings (Source-Code drucken) zu lesen.

Stattdessen bekommt man so etwas.

Der Sourcecode ist unmöglich zu lesen, weil die Einrückung bald die dargestellte Zeilenlänge erreicht. Die Schriftgröße ist aber zumindest OK.

Schlimm ist aber das folgende Listing, da würden 4 Code-Seiten auf einem Blatt Platz haben. Trotzdem sind es aber nur 2 Seiten pro Blatt. Die Schrift ist unleserlich klein.

Das ist eine Zumutung!

Labels: , , , , ,


Mittwoch, 20. April 2011

 

Locking- und Updateprobleme mit RMI (PR: 5A, 5B)

Da bei normalen "Remote-Objekten" immer ganze Objekte übertragen werden, hat man echtes Call-By-Value. Man arbeitet also mit Kopien von Objekten. Das gilt sowohl für Objekte, die an eine Methode eines Remote-Objekts übergeben werden als auch für Objekte als Returnwerte.
Normalerweise werden innerhalb einer JVM Objekte immer als Referenz übergeben bzw. zurückgegeben. Über RMI werden die Objekte immer serialisiert und komplett übertragen.
Das wirkt sich bei unserem "Data-Beispiel" so aus, dass Updates erst "sichtbar" werden, wenn die Daten auch wieder von der Datei geladen werden. Sollte das Locking direkt in der Record-Implementierung erfolgen, so muss man bedenken, dass auch immer ein Record-Objekt übertragen wird und der Client mit einer Kopie arbeitet. Damit bekommt er immer den Lock!
Abhilfe?
Man darf dem Client nur Referenzen zur Verfügung stellen. Nur der Server arbeitet mit den "echten" Objekten. - Klar, und wie geht das?
Das herauszufinden war nicht einfach, die Java-Dokumentation von Sun (Oracle) schweigt sch diesbezüglich aus, zumindest was direkte Lösungsvorschläge und Beispielcode angeht.

Grundsätzliche Erklärung und Beispiele zur Funktionsweise von RMI hilft etwas: jGuru: Remote Method Invocation (RMI) aber die letzte zündende Idee bekommt man durch How to return object and return object reference in Java RMI

Der Unterschied liegt in der Klassendefinition. Wenn die Klasse eine Unterklasse von UnicastRemoteObject ist, so ist sie damit RMI-fähig (kann RMI-Service anbieten). In diesem Fall wird also eine Referenz zurückgegeben werden. Wenn die Klasse nur Serializable implementiert, so ist die Klasse nicht für RMI-Aufrufe geeignet (kein RMI-Service) und es wird das Objekt zurückgegeben werden.

Ein Beispiel wird in Kürze geliefert.

Labels: ,


Freitag, 15. April 2011

 

Serialisierungsprobleme mit RMI (PR: 5A, 5B)

Remote Method Invocation funktioniert nur für Objekte, welche serialisiert werden können (die also das Interface Serializable implementieren). Wird innerhalb eines Objekts, welches über RMI gebunden werden soll, eine Referenz auf ein nicht serialisierbares Objekt benötigt, so bekommt man immer eine Exception der Art (z.B. für RandomAccessFile):
sun.rmi.registry.RegistryImpl_Stub.rebind(Unknown Source)
java.rmi.Naming.rebind(Naming.java:177)
rmi.Server.run(Server.java:43)
rmi.Server.main(Server.java:68)
error marshalling arguments; nested exception is: 
 java.io.NotSerializableException: java.io.RandomAccessFile
RandomAccessFile ist nicht serialisierbar, weil der innere Zustand so eines Objekts von der Laufzeitumgebung (Betriebssystem, Dateisystem, etc.) abhängt. Eine Referenz auf ein RandomAccessFile-Objekt muss als transient deklariert werden. Wenn nötig, dann muss der Zustand selbst gespeichert bzw. wiederhergestellt werden, z.B. in Form des Dateinamens und der Position innerhalb der Datei.
Dazu kann man die Methoden des Interfaces Serializable implementieren:
private void writeObject(java.io.ObjectOutputStream out)
    throws IOException
private void readObject(java.io.ObjectInputStream in)
    throws IOException, ClassNotFoundException;
private void readObjectNoData() 
    throws ObjectStreamException;
Für die Zwecke der Data-Klasse genügen die ersten beiden Methoden. Folgender Ausschnitt aus der Data-Klasse soll dies verdeutlichen:
public class Data implements DB, Header, Serializable {

    private static final long serialVersionUID = -8958941842720498616L;
    private transient RandomAccessFile database;
    private String filename;
    private transient Logger log = Logger.getLogger(LOGURL);
    private DBHeader header;
    private TreeMap data;
    private int lastDeleted = -1; // zuletzt gelöschter Datensatz

    private void writeObject(ObjectOutputStream stream) throws IOException {
        stream.defaultWriteObject();
        stream.writeLong(database.getFilePointer());
    }

    private void readObject(ObjectInputStream stream) throws IOException {
        try {
            stream.defaultReadObject();
        } catch (ClassNotFoundException e) {
            String msg = "Unable to find class";
            if (e.getMessage() != null)
                msg += ": " + e.getMessage();
            throw new IOException(msg);
        }
        database = new RandomAccessFile(filename, "rw");
        database.seek(stream.readLong());
        log = Logger.getLogger(LOGURL);
    }

//... weiterer Code entfernt
}
Hier ist auch der Logger transient, da auch der Logger vom Dateisystem abhängt und nicht serialisiert werden kann.

Labels: ,


Mittwoch, 13. April 2011

 

Package für Data, Header, Record (PR: 5A, 5B)

Verwenden Sie unbedingt das Package data für die Klassen und Interfaces DB, Data, Header und Record. Nur so kann der Datenteil automatisch getestet werden.

Labels: , ,


Dienstag, 12. April 2011

 

Refactoring Data - Interface Record (PR: 5A, 5B)

Um einfacher automatisch testen zu können, soll das Interface Record (anstatt einer Klasse Record) bei der Datenklasse Data verwendet werden.
package data;

/**
 * RuD-Übung 2011
 *
 * data.Record.java
 * @author (c) 2011, Harald R. Haberstroh
 * 1.3.2011
 */

import java.rmi.Remote;
import java.rmi.RemoteException;

/**
 * @author Harald R. Haberstroh (hh)
 * 
 */
public interface Record extends Remote {
    /**
     * @return Anzahl der Felder dieses Datensatzes z.B. 8
     */
    public int getCntFields() throws RemoteException;

    /**
     * @return liefert alle Felder verkettet in einen String mit ';' getrennt
     *         z.B.
     *         "12;49.82012,17.260019;47.81497,16.260409;523;201102131510;67;2345121269;O"
     *         Strings sind unter Hochkomma zu setzen. z.B.
     *         "\"wlr1\";\"Werk Laderoboter 1\";\"   50\";\"   50\""
     */
    public String getCsvString() throws RemoteException;

    /**
     * Setzt die Felder auf die gegebenen Strings. Gegebenenfalls sind die
     * Strings abzuschneiden. Die Anzahl der Strings stimmt beim Aufruf von
     * setFields() auf alle Fälle mit getCntFields() überein (Prüfung braucht
     * nicht zu erfolgen).
     * 
     * @param fields
     *            Array mit den Feldwerten.
     * @throws RemoteException
     */
    public void setFields(String[] fields) throws RemoteException;
}
Damit kann per JUnit-Test ganz einfach der Inhalt eines Datensatzes und die Anzahl der Felder geprüft werden.
Überdies wird hier Refactoring geübt, da das Programm (die Klassen) entsprechend angepasst werden müssen (Ihre Record-Klasse muss umbenannt werden und eben das gegebene Interface implementieren).

Labels: , ,


Mittwoch, 6. April 2011

 

GUI für RMI-Client (PR: 5A, 5B)

Erstellen Sie für den Client unserer RMI-Client/Server-Anwendung eine grafische Oberfläche, welche den Inhalt der Datenbank (evtl als Ausschnitt) in einer JTable darstellt. Es soll möglich sein, einzelne Werte zu ändern, Zeilen zu löschen, neue Zeilen einzufügen und zu Suchen (eigener Dialog). Bei der Suche sind nur die gefundenen Datensätze anzuzeigen.
Das GUI könnte (minimal, die Funktionen zum Löschen, Einfügen und Suchen sind in "Edit" versteckt) etwa so aussehen:

Labels: , ,


Freitag, 18. März 2011

 

Experimente mit Queues (POS1-2: 2A, 2C, PR: 5A, 5B)

Implementieren Sie im Package ringbuffer eine Klasse Queue für Integer-Werte. Implementieren Sie die Queue in Form eines Ringpuffers. Es soll die Exception QueueFullException geworfen werden, wenn der Ringpuffer voll ist und die Exception QueueEmptyException, wenn der Ringpuffer leer ist.
Als Testklasse verwenden Sie bitte die folgende Klasse QueueTest, welche zwei Threads erzeugt. Einen Sender, der zufällige "Nachrichten" (Integerwerte) in die Warteschlange steckt und einen Empfänger, der Werte aus der Warteschlange entnimmt. Beide geben jeweils die gesendete bzw. gelesene Nachricht aus. Beide Threads haben unterschiedliche zufällige Wartezeiten.
Experimentieren Sie mit unterschiedlichen Wartezeiten, sodass die Queue (Ringpuffer) voll bzw. leer werden kann. Was passiert?
Wenn die Queue nicht synchronisiert wird, was kann passieren? Können Sie das Verhalten in Experimenten nachweisen?
Bauen Sie entsprechendes synchronized in Ihre Queue ein (5. Klasse). Was muss synchronisiert werden?
Source Code
/**
 * java-queue: ringbuffer.QueueTest.java
 * 
 * 17.03.2011, Harald R. Haberstroh
 */

package ringbuffer;

import java.util.Random;

/**
 * Testen der Queue mit 2 Threads.
 * 
 * @author Harald R. Haberstroh (hp)
 * 
 */
public class QueueTest extends Thread {
  private final static int MAXRUNS = 20;
  private Queue messages;
  private Random rnd = new Random();

  public QueueTest(String name, Queue messages) {
    super(name);
    this.messages = messages;
  }

  public void run() {
    if (getName().equalsIgnoreCase("Sender")) {
      for (int i = 0; i < MAXRUNS; i++) {
        int msg = rnd.nextInt(100);
        long sleepTime = (long) rnd.nextInt(500) + 500; // 500-1000ms
        try {
          System.out.println(getName() + " sending " + msg);
          messages.put(msg);
        } catch (QueueFullException e) {
          System.err.println(getName() + ":Oops - Queue full");
        }
        try {
          sleep(sleepTime);
        } catch (InterruptedException e) {
          System.err.println(getName() + ":Oops - interrupted");
        }
      }
    } else { // Receiver
      setName("                    " + getName()); // Einrückung
      for (int i = 0; i < MAXRUNS || !messages.isEmpty(); i++) {
        long sleepTime = (long) rnd.nextInt(900) + 600; // 900-1500ms
        try {
          if (!messages.isEmpty()) {
            System.out.println(getName() + " read " + messages.get());
          }
        } catch (QueueEmptyException e) {
          System.err.println(getName() + ": Oops - Queue empty");
        }
        try {
          sleep(sleepTime);
        } catch (InterruptedException e) {
          System.err.println(getName() + ":Oops - interrupted");
        }
      }
    }
    System.out.println(getName() + " finished");
  }

  public String toString() {
    return getName();
  }

  /**
   * @param args
   */
  public static void main(String[] args) {
    Queue messages = new Queue();
    QueueTest sender = new QueueTest("Sender", messages);
    QueueTest receiver = new QueueTest("Receiver", messages);
    sender.start();
    receiver.start();
    try {
      sender.join();
      receiver.join();
    } catch (InterruptedException e) {
      System.err.println(e.getLocalizedMessage());
    }
    System.out.println(".... all finished ....");
  }

}
Beispielausgabe bei Standardeinstellung
Sender sending 73
Sender sending 46
                    Receiver read 73
Sender sending 63
                    Receiver read 46
Sender sending 11
                    Receiver read 63
Sender sending 89
Sender sending 21
                    Receiver read 11
Sender sending 99
                    Receiver read 89
Sender sending 79
Sender sending 41
                    Receiver read 21
Sender sending 17
                    Receiver read 99
Sender sending 92
                    Receiver read 79
Sender sending 89
                    Receiver read 41
Sender sending 52
                    Receiver read 17
Sender sending 60
                    Receiver read 92
Sender sending 25
                    Receiver read 89
Sender sending 82
Sender sending 60
                    Receiver read 52
Sender sending 13
Sender sending 34
                    Receiver read 60
Sender sending 10
                    Receiver read 25
Sender finished
                    Receiver read 82
                    Receiver read 60
                    Receiver read 13
                    Receiver read 34
                    Receiver read 10
                    Receiver finished
.... all finished ....

Labels: , , ,


Mittwoch, 16. März 2011

 

RMI-Client/Server mit Java (PR: 5A, 5B)

Schreiben Sie eine Klasse Server, welche über RMI folgende Objekte bereitstellt:
localhost kann natürlich (theoretisch) jede beliebige IP-Adresse bzw. jeder beliebige Hostname sein, wenn unter dieser Adresse ein RMI-Server erreichbar ist.

Die zu öffnende Datei soll als Parameter der Kommandozeile angegeben werden. Aufruf:
java Server dateiname
Also z.B.:
java Server machines.db

Schreiben Sie weiters eine Klasse Client, welche über RMI die vom Server bereitgestellten Objekte verwendet, um einfache "Datenbank"-Zugriffe zu machen. Beim Aufruf soll über einen optionalen Kommandozeilenparameter die Adresse des Servers angegeben werden. Wird kein Parameter angegeben, so ist localhost zu verwenden. Aufruf:
java Client [hostaddress]
Also z.B.:
java Client
oder
java Client 127.0.0.1

Client soll einfach den Inhalt der Datenbank ausgeben (nur nicht gelöschte Datensätze). Die Spaltenüberschriften sind entsprechend der Informationen aus der Datei (über Header) ausgeben. Zeilen müssen gemäß der Feldlängen formatiert sein.

In weiterer Folge soll der Client eine Minute lang zufällige Datensätze auswählen und ausgeben (falls diese existieren). Zwischen zwei Zugriffen soll eine zufällige Zeit zwischen 1 und 5 Sekunden liegen (d.h. es werden mindestens 12 und höchstens 60 weitere Zeilen ausgegeben).

Labels: , ,


 

Binärdateien bearbeiten mit Java - Zusatz für Client (PR: 5A, 5B)

Wie unter "Binärdateien bearbeiten mit Java (PR: 5A, 5B)" beschrieben, ist in der Binärdatei auch die Feldbeschreibung enthalten. An Server bzw. in der Klasse Data wird aber nur die Typ bzw. Längeninformation benötigt. Implementieren Sie das Interface Header (entweder eigene Klasse oder Klasse Data), um die nötigen Informationen für den Client, der ja die Daten darstellen soll, bereitzustellen.
package data;
/**
 * RuD-Übung 2011
 *
 * data.Header.java
 * @author (c) 2011, Harald R. Haberstroh
 * 16.3.2011
 */

import java.rmi.Remote;
import java.rmi.RemoteException;

/**
 * @author Harald R. Haberstroh (hh)
 * 
 */
public interface Header extends Remote {
  /**
   * Liefert die Anzahl der Felder.
   *
   * @return Anzahl der Felder
   * @throws RemoteException
   */
  public int getNumFields() throws RemoteException;

  /**
   * Liefert den Feldnamen zur gegebenen Feldnummer
   * 
   * @param fieldNo
   *          Nummer Feldes.
   * @return Feldname
   * @throws RemoteException
   */
  public String getFieldName(int fieldNo) throws RemoteException;

  /**
   * Liefert die Beschreibung des Feldes zur gegebenen Feldnummer
   * 
   * @param fieldNo
   *          Nummer Feldes.
   * @return Feldbeschreibung
   * @throws RemoteException
   */
  public String getFieldDesc(int fieldNo) throws RemoteException;

  /**
   * Liefert den Typ des Feldes zur gegebenen Feldnummer.
   * 'F'...fix, 'V'...Variabel, 'C'...Zeichen
   * 
   * @param fieldNo
   *          Nummer Feldes.
   * @return Typ des Feldes ('F', 'V' oder 'C')
   * @throws RemoteException
   */
  public char getFieldType(int fieldNo) throws RemoteException;

  /**
   * Liefert die (maximale) Länge des Feldes zur gegebenen Feldnummer.
   * 
   * @param fieldNo
   *          Nummer Feldes.
   * @return (maximale) Feldlänge
   * @throws RemoteException
   */
  public int getFieldLen(int fieldNo) throws RemoteException;

}

Labels: , ,


Dienstag, 1. März 2011

 

Binärdateien bearbeiten mit Java (PR: 5A, 5B)

Entwickeln Sie sie eine Klasse Data, die das Interface DB implementiert. Die Klasse Data soll die Methoden zum Bearbeiten (Lesen, Schreiben) binärer Dateien nach dem unten beschriebenen Datenformat bereitstellen.

package data;
/**
 * RuD-Übung 2011
 *
 * data.DB.java
 * @author (c) 2011, Harald R. Haberstroh
 * 1.3.2011
 */


import java.rmi.Remote;
import java.rmi.RemoteException;

/**
 * @author Harald R. Haberstroh (hh)
 * 
 */
public interface DB extends Remote {
  /**
   * Sperre den gegebenen Datensatz. Ist der Datensatz bereits gesperrt, dann
   * wait().
   * 
   * @param recNo
   *          Nummer des zu sperrenden Datensatzes.
   * @return Sperr-Identifikation.
   * @throws RemoteException
   * @throws RecordNotFoundException
   *           ungültige Datensatznummer
   */
  public long lock(int recNo) throws RemoteException, RecordNotFoundException;

  /**
   * Hebe Sperre wieder auf. notify().
   * 
   * @param recNo
   *          Nummer des gesperrten Datensatzes
   * @param lockCookie
   *          Sperr-Identfikation.
   * @throws RemoteException
   * @throws RecordNotFoundException
   *           ungültige Datensatznummer
   * @throws SecurityException
   *           ungültiges lockCookie oder nicht gelockt
   */
  public void unlock(int recNo, long lockCookie) throws RemoteException,
      RecordNotFoundException, SecurityException;

  /**
   * lege neuen Datensatz an.
   * 
   * @param record
   *          Neuer Datensatz
   * @return Nummer des neuen Datensatzes
   * @throws RemoteException
   */
  public int create(Record record) throws RemoteException;

  /**
   * lösche Datensatz. Sperre wird aufgehoben, notify();
   * 
   * @param recNo
   *          Nummer des Datensatzes.
   * @param lockCookie
   *          Sperr-Identfikation.
   * @throws RemoteException
   * @throws RecordNotFoundException
   *           ungültige Datensatznummer
   * @throws SecurityException
   *           ungültiges lockCookie oder nicht gelockt
   */
  public void delete(int recNo, long lockCookie) throws RemoteException,
      RecordNotFoundException, SecurityException;

  /**
   * ändere Datensatz (überschreibe mit dem gegebenen Datensatz).
   * 
   * @param record
   *          neuer Datensatz
   * @param recNo
   *          Nummer des gesperrten Datensatzes
   * @param lockCookie
   *          Sperr-Identfikation.
   * @throws RemoteException
   * @throws RecordNotFoundException
   *           ungültige Datensatznummer
   * @throws SecurityException
   *           ungültiges lockCookie oder nicht gelockt
   */
  public void update(Record record, int recNo, long lockCookie)
      throws RemoteException, RecordNotFoundException, SecurityException;

  /**
   * suche Datensätze.
   * 
   * @param criteria
   *          String-Array mit Kriterien, null matcht alles, sonst
   *          Stringrepräsentation des Suchkriteriums (entspricht den Daten in
   *          Record).
   * @return Array von Recordnummern
   * @throws RemoteException
   */
  public int[] find(String[] criteria) throws RemoteException;

  /**
   * lese Datensatz.
   * 
   * @param recNo
   *          Nummer des zu lesenden Datensatzes.
   * @return Record
   * @throws RemoteException
   * @throws RecordNotFoundException
   *           Datensatz existiert nicht.
   */
  public Record read(int recNo) throws RemoteException, RecordNotFoundException;
}
Die Klasse Data muss zusätzlich einen Konstruktor Data(String dateiname) bereitstellen, der die Datei dateiname (z.B. "machines.db") öffnet und zur Bearbeitung mit den im Interface DB definierten Methoden bereitstellt. Der Konstruktor muss die nötigen Exceptions werfen, wenn die Datei nicht geöffnet werden kann.
Der Parameter String[] criteria der Methode find() enthält das Suchkriterium, wobei jedem Feld ein String zugeordnet ist. Soll also nur nach einem Namen (siehe unten) gesucht werden, so muss criteria[1] den gesuchten Namen (bzw. einen Teil davon) enthalten. Alle anderen Elemente des Arrays müssen dann null sein. Soll nach mehreren Kriterien selektiert werden, so sind die entsprechenden Felder mit Werten zu belegen.
find() liefert dann ein Array mit den Indizes der passenden Datensätze.
Die Datensatznummer recNo ist immer der Index innerhalb der Datei!
Gelöschte Datensätze werden einfach als gelöscht markiert (s.u.) und müssen beim Anlegen neuer Datensätze zuerst verwendet werden (erst wenn kein gelöschter Datensatz existiert, wird die Datei vergrößert).
RecordNotFoundException wird immer dann geworfen, wenn recNo auf einen gelöschten Datensatz weist oder, wenn der Datensatz gar nicht existiert. Lösen Sie das Problem, dass nur mit der einfachen RecordNotFoundException das Dateiende nicht ermittelt werden kann, in geeigneter Weise.

Record muss in geeigneter Weise implementiert werden.

Datensätze sollen gesperrt werden können (die Klasse Data kann von mehreren parallelen Threads verwendet werden). Dazu soll die Methode lock() eine (beliebige) dem Datensatz zuordenbare Zahl (Lockcookie bzw. Sperr-Identifikation) liefern und den Satz sperren. Die Methoden, die eine Sperre benötigen müssen dann anhand dieses Lockcookies überprüfen, ob der Datensatz gesperrt ist und die entsprechende Operation ausführen. Ist der Datensatz nicht gesperrt oder das Lockcookie ungültig, so ist eine SecurityException zu werfen. Die Methode unlock() hebt die Sperre wieder auf. Nach einem delete() braucht man (kann man) kein unlock() machen.

Datenformat


Die Strings sind kodiert mit dem Encoding ISO-8859-1, d.h. jedes Zeichen ist einem Byte kodiert und es sind nur ASCII-Zeichen sowie die westeuropäischen Erweiterungen erlaubt. Es gibt Strings fixer Länge (werden mit Leerzeichen aufgefüllt) und Strings variabler Länge (werden wie C-Strings mit '\0' abgeschlossen) mit einer Obergrenze.
Die Datei mit den (Test-)Daten hat folgendes Format:
Die Datei beginnt mit einem Header:
4 Bytes Magic Cookie, Wert 4242
4 Offset in Bytes zu den eigentlichen Daten
2 Bytes Anzahl Felder pro Datensatz
Pro Feld gibt es folgenden Eintrag:
2 Bytes Länge Feldname
n Bytes Feldname
2 Bytes Länge Feldbeschreibung
n Bytes Feldbeschreibung für die Tabellenansicht
1 Byte  Datentyp: 'F' ... fix, 'V' ... variabel, 'C' ... ein Zeichen
2 Bytes Feldlänge
Nach den Feldbeschreibungen, d.h. ab dem Offset, finden sich die eigentlichen Datensätze gemäß der Beschreibungen im Header:
2 Bytes Flag Gelöscht (== 0 ... gelöscht, != 0 ... aktiv)
n Bytes Daten gemäß der Datensatzbeschreibung

Testdatei machines.db


Diese Datei enthält nur die Geräte (und deren grafische Koordinaten):
Feldname Feldbeschreibung (maximale) Länge Typ Bemerkung
id Geräte-Id 10 String '\0' terminiert z.B. "z23"
name Gerätename 100 String '\0' terminiert Gerätebeschreibung, z.B. "Laderoboter Halle B"
x x 5 String fixe Länge z.B. " 50" (vorne mit Leerzeichen aufgefüllt)
y y 5 String fixe Länge z.B. " 200"

Testen der Klasse Data


Schreiben Sie passende Tests um die Klasse Data zu testen. Der erste Test soll einfach die Datei machines.db in lesbarer Form auf der Konsole ausgeben.
Die weiteren Tests sollen natürlich die gesamte Funktionalität und das Locking testen.

Downloads



Abgabe


Nennen Sie das Projekt klasse-name-data also z.B. 5ad-meier-data und checken Sie es am CVS ein. Termin wird noch bekanntgegeben.

Wie geht's weiter


Die Datenklasse soll dann später in einem Client-/Server-System zum Einsatz kommen, wo mehrere grafische Clients eine Binärdatei bearbeiten sollen. Die Angabe dazu folgt...

Labels: , ,


Mittwoch, 23. Februar 2011

 

Schwerpunkt Programmieren (PR: 5A, 5B)

Der letztmögliche Abgabetermin für die Ausarbeitung ist der 8. April 2011, damit noch genügend Zeit bleibt, die Ausarbeitung zu besprechen.
  1. JavaFX
  2. eclipse plugins schreiben
  3. Funktionale Programmierung
  4. Logikbasierte Programmierung
  5. Objective C
  6. Algorithmen für Textsuche
  7. Closures - Definition, Verwendung, Beispiele (Programmiersprachen)
  8. Garbage Collector - Algorithmen und Implementierung
Zu allen Themen gibt es Informationen im Internet. Die Links zur Wikipedia sind nur allererste Einstiege, da dort selbst oft die Qualität in Frage gestellt wird.


Ein wichtiger Aspekt beim Schwerpunktthema ist natürlich die eigene Recherche und das Erarbeiten in ein völlig neues Gebiet.
Ausarbeitung in etwa 10 bis 20 Seiten plus Sourcecode mit Beispielen.

Weitere Literatur:
Links:

Weitere Informationen, sobald ich welche habe, gibt es dann hier.

Labels:


Mittwoch, 15. Dezember 2010

 

Dynamische Speicherverwaltung in C (PR: 5A, 5B)

Implementieren Sie eine einfache dynamische Speicherverwaltung, die nach dem in der folgenden Skizze gezeigten Prinzip funktioniert.

Diese Speicherverwaltung soll es ermöglichen, Speicher in beliebiger Reihenfolge zu allozieren und wieder freizugeben. Siehe auch Dynamische Speicherverwaltung
Schreiben Sie ein Modul heap für die Speicherverwaltung selbst, welches folgende Funktionen enthält (exportiert):

Erstellen Sie eine weitere Version des Moduls heap, welches bei jedem Aufruf von halloc() bzw. hfree() den gesamten Speicher in lesbarer Form auf stderr ausgibt, damit man das Allozieren der Speicherblöcke sehen kann (die Größe des Heaps sollte dabei eher klein gewählt werden!).
Schreiben Sie weiters ein Modul, welches Ihre Speicherverwaltung testet durch Anfordern von Speicher, belegen und lesen des allozierten Speichers und wieder Freigeben des Speichers in verschiedenen Varianten.
Erstellen Sie dazu eine Liste von Testfällen und beschreiben Sie das Ergebnis der Tests (die Speicherauszüge bekommen Sie mit obiger Variante des Moduls).
Beispiele von Testfällen:
n. Test: allozieren eines Strings der Länge HEAPSIZE - sizeof(heap_t), füllen des Strings mit lauter 'A' und ausgeben des Strings. OK
n+1. Test: freigeben des Speichers aus Test n. OK

Zu jedem Test ist anzugeben, ob der Test erfolgreich war. Falls ein Test nicht erfolgreich war, so ist festzustellen, warum und ggf. der Fehler zu beheben. Wird am Programm etwas verändert, so müssen alle Tests noch einmal gemacht werden!
Schreiben Sie ein passendes Makefile, mit dem man alternativ die normale Variante von heap und die Testvariante von heap erzeugen kann!
Organisieren Sie sich in Kleingruppen und versuchen Sie die einzelnen Funktionen gemeinsam zu entwerfen und zu programmieren. Welche Hilfsfunktionen braucht z.B. das halloc()?
Erweitern Sie die Speicherverwaltung um eine "Garbage Collection", die freie Speicherbereiche, die hintereinanderliegen zusammenfasst.
Erstellen Sie eine geeignete Verzeichnisstruktur und verwenden Sie CVS.
Tauschen Sie die Module mit anderen Gruppen aus (das müßte ganz einfach sein, da die Schnittstellen immer gleich sind (s.o.).
Abgabe:
Nennen Sie das Projekt 5ad-name-heap bzw. 5bd-name-heap und checken Sie es ein.
Beantworten Sie weiters folgende Fragen (die Antworten müssen in der Datei heap.txt gespeichert werden):
  1. Was passiert, wenn man einen String der Länge n alloziert und einen String der Länge n speichert?
  2. Was passiert, wenn man einen String der Länge n alloziert und einen String der Länge n + 1 speichert?
  3. Sie allozieren einen String der Länge 11 und speichern "0123456789" und geben den String wieder frei. Danach allozieren Sie einen String der Länge 20 und geben diesen String aus. Was passiert? (die Speicheranforderungen bzw. die Freigabe muss unmittelbar hintereinander erfolgen).
  4. Welche Speicheranforderungen benötigen mehr Speicher - 10 Strings der Länge 100 oder 100 Strings der Länge 10? Begründen Sie die Antwort.
  5. Wie könnten Sie im Modul heap sicherstellen, dass bei einem Aufruf von hfree(p); der Parameter p tatsächlich auf einen mit halloc() allozierten Speicherbereich zeigt?
heap.txt muss auch die Liste der Testfälle mit ihren Ergebnissen enthalten.
Geben Sie einen Ausdruck der ausgearbeiteten Fragen ab!

Labels: , ,


Donnerstag, 2. Dezember 2010

 

Datum in C selbst gebastelt

Datumsberechnungen sind immer wieder eine Quelle großer langwieriger, fehleranfälliger Programmteile. Vor allem in Sprachen, die solche Datumsoperationen nicht unterstützen. Sie sind auch immer wieder geeignet, Programmieren zu üben. Hier die Musterlösung zu einem einfachen Kalenderprogramm in C, bei dem ein Großteil der Datumsfunktionen selbstgestrickt sind. Viel Spaß damit!
Verwendung:
Ohne Parameter gibt es einfach das aktuelle Monat mit Markierung < 2> für den heutigen Tag und : 8: als Markierung für österreichische Feiertage. Danach wird die Liste der Feiertage ausgegeben.
$ ./cal 
Dezember 2010

    Montag       6  13  20  27     
  Dienstag       7  14  21  28     
  Mittwoch   1 : 8: 15  22  29     
Donnerstag < 2>  9  16  23  30     
   Freitag   3  10  17 :24::31:    
   Samstag   4  11  18 :25:        
   Sonntag : 5::12::19::26:    

 5.12. 2. Advent
 8.12. Maria Empfängnis
12.12. 3. Advent
19.12. 4. Advent
24.12. Heiligabend
25.12. Christtag
26.12. Stefanitag
31.12. Silvester :-)

Here comes the Source...

/*
 * File:    cal.c
 *
 * Zweck:   Kalenderprogramm
 *          ...für ein gegebenes Monat.
 *          mit/ohne argc/argv
 *
 * Autor:   Harald Haberstroh
 *
 * Algorithmus:
 *
 * History: 2002-09-29
 *          2003-10-15, Feiertage + Argumente von main()
 *          2004-11-05, Liste der Feiertage
 */

/*--------------------------- includes ------------------------------*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <time.h>

/*--------------------------- defines -------------------------------*/
#define  WOCHENTAG   3                 /* Wochentag des 1.1.1582 */
#define  MONTAG 0
#define  DIENSTAG 1
#define  MITTWOCH 2
#define  DONNERSTAG 3
#define  FREITAG 4
#define  SAMSTAG 5
#define  SONNTAG 6

/*--------------------------- typedefs ------------------------------*/

/*--------------------------- globals -------------------------------*/
enum {
   KEIN = 0,
   NEUJAHR,
   HL3KOENIGE,
   ASCHERMITTWOCH,
   PALMSONNTAG,
   KARFREITAG,
   OSTERSONNTAG,
   OSTERMONTAG,
   STAATSFEIERTAG,
   MUTTERTAG,
   PFINGSTSONNTAG,
   PFINGSTMONTAG,
   DREIFALTIGKEITSSONNTAG,
   FRONLEICHNAM,
   MARIAHIMMELFAHRT,
   NATIONALFEIERTAG,
   REFORMATIONSTAG,
   ALLERHEILIGEN,
   ALLERSEELEN,
   MARIAEMPFAENGNIS,
   ADVENT1,
   ADVENT2,
   ADVENT3,
   ADVENT4,
   HEILIGABEND,
   CHRISTIANI,
   STEFANI,
   SILVESTER
};

/*-------------------------- prototypes -----------------------------*/
void printFeiertage(int jahr, int monat);
int tagemonat( int jahr, int monat );
int sonntag( int jahr, int monat, int tag );
void tag2datum( int tagzahl, int *jahr, int *monat, int *tag );
void ostern( int jahr, int *monat, int *tag );
int feiertag(int jahr, int monat, int tag);
int anzProMonat(int jahr, int monat);
void printcal(int start, int anz, int heute, int monat, int jahr);
int schaltjahr( int Jahr );
int Anzahl_Tage( int Jahr, int Monat, int Tag );

/*----------------------------- main --------------------------------*/
int main( int argc, char *argv[] )
{
   int tage, tag, monat, jahr, start;
   struct tm *zeit;
   time_t   sec;

   if (argc == 2 && strcmp(argv[1], "-i") == 0)
   {                                   /* interaktiv */
      printf("Jahr> ");scanf("%d", &jahr);
      printf("Monat> ");scanf("%d", &monat);
      printf("Tag (0 für kein bestimmter)> ");scanf("%d", &tag);
      if (tag == 0)
      {
         tag = 1;
         if (1 <= monat && monat <= 12 &&
             1582 < jahr && jahr < 3000)
         {
            tage = Anzahl_Tage( jahr, monat, 1);
            start = (tage + WOCHENTAG) % 7;
            printcal(start, anzProMonat(jahr, monat), 0, monat, jahr);
            return 0;
         }
         else
         {
            fprintf(stderr, "Monat zwischen 1 und 12 und Jahr zwischen "
                            "1583 und 2999\n");
            return 2;
         }
      }
      if (1 <= monat && monat <= 12 &&
          1582 < jahr && jahr < 3000 &&
          1 <= tag && tag <= anzProMonat(jahr,monat))
      {
         tage = Anzahl_Tage( jahr, monat, 1);
         start = (tage + WOCHENTAG) % 7;
         printcal(start, anzProMonat(jahr, monat), tag, monat, jahr);
         return 0;
      }
      else
      {
         fprintf(stderr, "Monat zwischen 1 und 12 und Jahr zwischen "
                         "1583 und 2999\n"
                         "[Tag zwischen 1 und Anzahl für dieses Monat]\n");
         return 2;
      }
   }
   else if (argc == 3)                      /* Monat, Jahr */
   {
      monat = atoi(argv[1]);
      jahr = atoi(argv[2]);
      tag = 1;
      if (1 <= monat && monat <= 12 &&
          1582 < jahr && jahr < 3000)
      {
         tage = Anzahl_Tage( jahr, monat, 1);
         start = (tage + WOCHENTAG) % 7;
         printcal(start, anzProMonat(jahr, monat), 0, monat, jahr);
         return 0;
      }
      else
      {
         fprintf(stderr, "Monat zwischen 1 und 12 und Jahr zwischen "
                         "1583 und 2999\n");
         return 2;
      }
   }
   else if (argc == 4)                 /* ganzes Datum */
   {
      monat = atoi(argv[2]);
      jahr = atoi(argv[3]);
      tag = atoi(argv[1]);
      if (1 <= monat && monat <= 12 &&
          1582 < jahr && jahr < 3000 &&
          1 <= tag && tag <= anzProMonat(jahr,monat))
      {
         tage = Anzahl_Tage( jahr, monat, 1);
         start = (tage + WOCHENTAG) % 7;
         printcal(start, anzProMonat(jahr, monat), tag, monat, jahr);
         return 0;
      }
      else
      {
         fprintf(stderr, "Monat zwischen 1 und 12 und Jahr zwischen "
                         "1583 und 2999\n"
                         "[Tag zwischen 1 und Anzahl für dieses Monat]\n");
         return 2;
      }
   }
   else if (argc == 1)                 /* aktuelles Monat */
   {
      time(&sec);
      zeit = localtime(&sec);
      tag = zeit->tm_mday;
      monat = zeit->tm_mon + 1;
      jahr = zeit->tm_year + 1900;
      tage = Anzahl_Tage( jahr, monat, 1);
      start = (tage + WOCHENTAG) % 7;
      printcal(start, anzProMonat(jahr, monat), tag, monat, jahr);
      return 0;
   }
   else
   {
      fprintf(stderr, "Aufruf:\n%s [[tag] monat jahr]\n", argv[0]);
      return 1;
   }
}

/*-------------------------- functions ------------------------------*/
/** Kalender ausgeben
 *  start ... Wochentag des 1. dieses Monats
 *  anz   ... Anzahl der Tage dieses Monats
 *  heute ... Datum des heutigen Tages, 0, falls in einem anderen Monat
 *  monat ... Monat (1..12)
 *  jahr
 */
void printcal(int start, int anz, int heute, int monat, int jahr)
{
   char *tage[] = { "Montag", "Dienstag", "Mittwoch", "Donnerstag",
                    "Freitag", "Samstag", "Sonntag" };
   char *monate[] = { "Jänner", "Februar", "März", "April",
                      "Mai", "Juni", "Juli", "August",
                      "September", "Oktober", "November", "Dezember" };
   int i;
   int tag;

   printf("%s %d\n\n", monate[monat-1], jahr);
   for (i = 0; i < 7; i++)
   {
      printf("%10s ", tage[i]);
      for (tag = i + 1 - start; tag < anz + 7 + start; tag += 7)
         if (tag == heute && feiertag(jahr, monat, tag)
               && tag >= 1 && tag <= anz)
            printf("[%2d]", tag);
         else if (tag == heute && tag >= 1 && tag <= anz)
            printf("<%2d>", tag);
         else if (feiertag(jahr, monat, tag) && tag >= 1 && tag <= anz)
            printf(":%2d:", tag);
         else if (tag >= 1 && tag <= anz)
            printf("%3d ", tag);
         else
            printf("    ");
      printf("\n");
   }
   printf("\n");
   printFeiertage(jahr, monat);
}

/*
 * Function schaltjahr
 *
 * Zweck:   Liefert 1, wenn angegebenes Jahr ein Schaltjahr ist
 *
 * Algorithmus:
 *          ab 1582 gilt folgende Regel für Schaltjahre:
 *             Ist die Jahreszahl durch 400 teilbar oder
 *             ist die Jahreszahl durch 4 teilbar aber nicht durch 100
 *             dann ist das Jahr ein Schaltjahr
 *
 * Parameter:
 *       IN: Jahr
 *      OUT:
 *
 * Return-Wert: 1 ... Schaltjahr
 *              0 ... sonst
 */
int schaltjahr( int Jahr )
{
   if ( Jahr % 400 == 0 )
      return 1;
   else if ( Jahr % 100 == 0 )
      return 0;
   else if ( Jahr % 4 == 0 )
      return 1;
   else 
      return 0;
}

int anzProMonat(int jahr, int monat)
{
   switch(monat)
   {
      case 2:
         return 28 + schaltjahr(jahr);
         break;
      case 4:
      case 6:
      case 9:
      case 11:
         return 30;
         break;
      default:
         return 31;
   }
}

/*
 * Function Anzahl_Tage
 *
 * Zweck:    Bestimmt die Anzahl der Tage ab 1.1.1582
 *
 * Algorithmus:
 *           Tag + Anzahl Tage für Monate + Anzahl Tage für Jahre
 *
 * Parameter:
 *       IN: Jahr, Monat, Tag
 *      OUT:
 *
 * Return-Wert: Anzahl der Tage ab 1.1.1582
 */
int Anzahl_Tage( int Jahr, int Monat, int Tag )
{
   int   j;
   int   m;
   int   tage = Tag; /* Monatstag */

   /*
    * Tage für Jahre berechnen
    */
   for ( j = 1582; j < Jahr; j++ )
      tage += 365 + schaltjahr( j );

   /*
    * Tage für Monate berechnen
    */
   for ( m = 1; m < Monat; m++ )
      switch( m )
      {
         case 2:
            tage += 28 + schaltjahr( Jahr );
            break;
         case 4:
         case 6:
         case 9:
         case 11:
            tage += 30;
            break;
         default:
            tage += 31;
      }
   return( tage );
}

/** Bestimmt Feiertag.
 *  @param jahr
 *  @param monat
 *  @param tag
 *  @return Nummer des Feiertags, 0 sonst
 */
int feiertag(int jahr, int monat, int tag)
{
   int tagzahl, j, m, t;
   ostern(jahr, &m, &t);
   tagzahl = Anzahl_Tage(jahr, m, t);
   
   if (monat == m && tag == t)         /* Ostern */
      return OSTERSONNTAG;
   tag2datum(tagzahl + 1, &j, &m, &t);
   if (monat == m && tag == t)         /* Ostern */
      return OSTERMONTAG;
   tag2datum(tagzahl - 46, &j, &m, &t);
   if (monat == m && tag == t)         /* Aschermittwoch */
      return ASCHERMITTWOCH;
   tag2datum(tagzahl - 7, &j, &m, &t);
   if (monat == m && tag == t)         /* Palmsonntag */
      return PALMSONNTAG;
   tag2datum(tagzahl - 2, &j, &m, &t);
   if (monat == m && tag == t)         /* Karfreitag */
      return KARFREITAG;
   tag2datum(tagzahl + 49, &j, &m, &t);
   if (monat == m && tag == t)         /* Pfingstsonntag */
      return PFINGSTSONNTAG;
   tag2datum(tagzahl + 50, &j, &m, &t);
   if (monat == m && tag == t)         /* Pfingstmontag */
      return PFINGSTMONTAG;
   tag2datum(tagzahl + 56, &j, &m, &t);
   if (monat == m && tag == t)         /* Dreifaltigkeitssonntag */
      return DREIFALTIGKEITSSONNTAG;
   tag2datum(tagzahl + 60, &j, &m, &t);
   if (monat == m && tag == t)         /* Fronleichnam */
      return FRONLEICHNAM;

   if (monat == 1 && tag == 1)         /* Neujahr */
      return NEUJAHR;
   if (monat == 1 && tag == 6)         /* Heilige 3 Könige */
      return HL3KOENIGE;
   if (monat == 5 && tag == 1)         /* Staatsfeiertag */
      return STAATSFEIERTAG;
   tag2datum(sonntag(jahr, 5, 13), &j, &m, &t);
   if (monat == 5 && tag == t)         /* Muttertag FIXME */
      return MUTTERTAG;
   if (monat == 8 && tag == 15)        /* Mariahimmelfahrt */
      return MARIAHIMMELFAHRT;
   if (monat == 10 && tag == 26)       /* Nationalfeiertag */
      return NATIONALFEIERTAG;
   if (monat == 10 && tag == 31)       /* Reformationstag */
      return REFORMATIONSTAG;
   if (monat == 11 && tag == 1)        /* Allerheiligen */
      return ALLERHEILIGEN;
   if (monat == 11 && tag == 2)        /* Allersselen */
      return ALLERSEELEN;
   if (monat == 11)
   {
      if (sonntag(jahr, 12, 24) == Anzahl_Tage(jahr, 12, 24))
         tagzahl = sonntag(jahr, 12, 24) - 21;
      else
         tagzahl = sonntag(jahr, 12, 24) - 28;
      tag2datum(tagzahl, &j, &m, &t);
      if (tag == t && m == 11)         /* 1. Advent */
         return ADVENT1;
   }
   if (monat == 12)
   {
      if (tag == 8)                    /* Maria Emfängnis */
         return MARIAEMPFAENGNIS;

      if (sonntag(jahr, 12, 24) == Anzahl_Tage(jahr, 12, 24))
         tagzahl = sonntag(jahr, 12, 24) - 21;
      else
         tagzahl = sonntag(jahr, 12, 24) - 28;
      tag2datum(tagzahl, &j, &m, &t);
      if (tag == t && m == 12)         /* 1. Advent */
         return ADVENT1;
      tag2datum(tagzahl+7, &j, &m, &t);
      if (tag == t)                    /* 2. Advent */
         return ADVENT2;
      tag2datum(tagzahl+14, &j, &m, &t);
      if (tag == t)                    /* 3. Advent */
         return ADVENT3;
      tag2datum(tagzahl+21, &j, &m, &t);
      if (tag == t)                    /* 4. Advent */
         return ADVENT4;
      if (tag == 24)                   /* Hl. Abend */
         return HEILIGABEND;
      if (tag == 25)                   /* Christiani */
         return CHRISTIANI;
      if (tag == 26)                   /* Stefanitag */
         return STEFANI;
      if (tag == 31)                   /* Silvester :-) */
         return SILVESTER;
   }

   return KEIN;                        /* Kein Feiertag */
}

void ostern( int jahr, int *monat, int *tag )
{
   int a = jahr % 19;
   int b = jahr / 100;
   int c = jahr % 100;
   int d = b / 4;
   int e = b % 4;
   int f = ( b + 8 ) / 25;
   int g = ( b - f + 1 ) / 3;
   int h = ( 19 * a + b - d - g + 15 ) % 30;
   int i = c / 4;
   int k = c % 4;
   int l = ( 32 + 2 * e + 2 * i -h -k ) % 7;
   int m = ( a + 11 * h + 22 * l ) / 451;
   int p = ( h + l - 7 * m + 114 ) % 31;

   *monat = ( h + l - 7 * m + 114 ) / 31;
   *tag = p + 1;
}
void tag2datum( int tagzahl, int *jahr, int *monat, int *tag )
{
   /*
    * Jahr berechnen
    */
   *jahr = 1582;
   while ( tagzahl > 365 + schaltjahr( *jahr ) )
   {
      tagzahl -= 365 + schaltjahr( *jahr );
      (*jahr)++;
   }
   /*
    * Monat
    */
   for ( *monat = 1; tagzahl > tagemonat( *jahr, *monat ); (*monat)++ )
      tagzahl -= tagemonat( *jahr, *monat );
   /*
    * Tag
    */
   *tag = tagzahl;
}

int sonntag( int jahr, int monat, int tag )
{
   int tagzahl = Anzahl_Tage( jahr, monat, tag );
   int wt = ( DONNERSTAG + tagzahl ) % 7;
   if ( wt == SONNTAG ) /* bereits Sonntag */
      return tagzahl;
   else
      return tagzahl + ( SONNTAG - wt );
}
int tagemonat( int jahr, int monat )
{
   switch( monat )
   {
      case 2:
         return 28 + schaltjahr( jahr );
         break;
      case 4:
      case 6:
      case 9:
      case 11:
         return 30;
         break;
      default:
         return 31;
   }
}

/** gib eine Liste der Feiertage aus.
 *  @param jahr IN
 *  @param monat OUT
 */
void printFeiertage(int jahr, int monat)
{
   int i;
   char *bez = "";
   for (i = 1; i <= tagemonat(jahr, monat); i++)
   {
      bez = "";
      switch (feiertag(jahr, monat, i))
      {
         case NEUJAHR:
            bez = "Neujahr";
            break;
         case HL3KOENIGE:
            bez = "Heilige Dreikönige";
            break;
         case ASCHERMITTWOCH:
            bez = "Aschermittwoch";
            break;
         case PALMSONNTAG:
            bez = "Palmsonntag";
            break;
         case KARFREITAG:
            bez = "Karfreitag";
            break;
         case OSTERSONNTAG:
            bez = "Ostersonntag";
            break;
         case OSTERMONTAG:
            bez = "Ostermontag";
            break;
         case STAATSFEIERTAG:
            bez = "Staatsfeiertag";
            break;
         case MUTTERTAG:
            bez = "Muttertag";
            break;
         case PFINGSTSONNTAG:
            bez = "Pfingstsonntag";
            break;
         case PFINGSTMONTAG:
            bez = "Pfingstmontag";
            break;
         case DREIFALTIGKEITSSONNTAG:
            bez = "Dreifaltigkeitssonntag";
            break;
         case FRONLEICHNAM:
            bez = "Fronleichnam";
            break;
         case MARIAHIMMELFAHRT:
            bez = "Maria Himmelfahrt";
            break;
         case NATIONALFEIERTAG:
            bez = "Nationalfeiertag";
            break;
         case REFORMATIONSTAG:
            bez = "Reformationstag";
            break;
         case ALLERHEILIGEN:
            bez = "Allerheiligen";
            break;
         case ALLERSEELEN:
            bez = "Allerseelen";
            break;
         case MARIAEMPFAENGNIS:
            bez = "Maria Empfängnis";
            break;
         case ADVENT1:
            bez = "1. Advent";
            break;
         case ADVENT2:
            bez = "2. Advent";
            break;
         case ADVENT3:
            bez = "3. Advent";
            break;
         case ADVENT4:
            bez = "4. Advent";
            break;
         case HEILIGABEND:
            bez = "Heiligabend";
            break;
         case CHRISTIANI:
            bez = "Christtag";
            break;
         case STEFANI:
            bez = "Stefanitag";
            break;
         case SILVESTER:
            bez = "Silvester :-)";
            break;
      }
      if (strlen(bez) > 0)
         printf("%2d.%d. %s\n", i, monat, bez);
   }
}

Labels: , ,


Donnerstag, 11. November 2010

 

Code-Kata

Beim Karate (z.B. Karatedo Doshinkan) gibt es Kata (dt. "Form", siehe auch Kata auf Wikipedia), eine spezielle Übungsform, bei der man als Training gegen einen oder mehrere (vorgestellte) Gegner kämpft. Dabei ist der Bewegungsablauf aber stilisiert und fix vorgegeben. Das oftmalige Wiederholen der Bewegungsabläufe in Form von Kata ist unerlässlich für den persönlichen Fortschritt. Denn nur Übung macht den Meister.

Auch Programmieren beherrscht man nur durch ständiges Training. Nur durch Üben wird man auch hier zum Meister. Aber nicht nur durch den Kampf (Programmieren für das Geschäft) sondern auch durch Katas. Diese Idee für's Programmieren hat Dave Thomas von den Pragmatischen Programmieren (meines Wissens) erstmalig aufgegriffen und publiziert: Code Kata

Sehr empfehlenswert für alle, die Programmieren meistern wollen.

Die Idee der Code Kata fasziniert und berührt mich besonders, da ich selber jahrelang trainiert habe.

Labels: , , , , , , ,


Donnerstag, 28. Oktober 2010

 

C lernen (PR5: 5AD, 5BD)

Lernen Sie C anhand der Vorgangsweise 15 Exercises for Learning a new Programming Language.

Machen Sie ein Projekt 5ad-name-learn-c bzw. 5bd-name-learn-c, in dem Sie die Lösungen der 15 Aufgaben dokumentieren (in erster Linie getesteter und kommentierter Source-Code).

Achtung: die erste Aufgabe ist ziemlich schwierig, da das Prüfen, ob eine Taste gedrückt wurde, systemabhängig ist. Lösen Sie diese Aufgabe sowohl für Windows als auch für Linux.

Informationen:
Die Links zu jobsnake funktionieren nicht mehr. Am besten sucht man nach
"15 exercises for learning a new programming language"

Labels: , ,


Dienstag, 26. Oktober 2010

 

ruby-loganalyse (PR: 5AD, 5BD)

Diverse Server (z.B. Webserver) tragen verschiedene Informationen in ein Logfile ein. Webserver loggen normalerweise jeden Zugriff auf die Website: IP-Adresse, Seite, Browser, Betriebssystem.
Bei Webservern ist es oft interessant, welche Browser oder Betriebssysteme auf der Clientseite verwendet werden.
Schreiben Sie ein ruby-Programm loganalyse.rb, welches ein (Apache-)Logfile einliest und folgende Information ermittelt:
  1. Liste der Browser und deren Häufigkeiten, absteigend sortiert nach Häufigkeit, prüfen Sie dabei folgende Browser(strings) ohne Berücksichtigung der Groß-/Kleinschreibung:
    "Firefox", "Opera", "Mozilla", "Netscape", "MSIE"
  2. Liste der Betriebssysteme und deren Häufigkeiten, absteigend sortiert nach Häufigkeit, prüfen Sie dabei folgende Betriebsysteme(strings) ohne Berücksichtigung der Groß-/Kleinschreibung:
    "Linux", "Windows", "Mac"
  3. Liste der Subnetze (xx.xx.xx.*) mit deren Häufigkeiten, absteigend sortiert nach Häufigkeit (Verwenden Sie zur Unterscheidung der Netze einfach die ersten drei Teile der IP-Adresse am Zeilenanfang).
  4. Liste der IP-Adressen mit deren Häufigkeiten, absteigend sortiert nach Häufigkeit.
  5. Zugriffe pro Tag mit den Häufigkeiten, sortiert nach Datum (sortieren Sie einfach die Datumstrings – z.B. 27/May/2007).
Die nötigen Informationen sind im Klartext im Logfile enthalten. Am Anfang jeder Zeile steht die IP-Adresse (z.B. 66.249.73.115). Die weiteren Informationen bekommt man durch (Sub-)Stringvergleich bzw. reguläre Ausdrücke.
Ausschnitt aus einem Logfile (↵ bedeutet Zeilenumbruch):
66.249.73.115 - - [27/May/2007:06:50:10 +0200] "GET /r.simon/html/Unterlagen/mysql_de/mysql-2.html HTTP/1.1" 200 3358 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"↵
66.249.73.115 - - [27/May/2007:06:50:15 +0200] "GET /d03011/Art_of_War_Forum/board.php?boardid=17&sid=c8422d9b444a6ca37c37194516d35532 HTTP/1.1" 200 6297 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"↵
81.52.143.15 - - [27/May/2007:06:51:11 +0200] "GET /robots.txt HTTP/1.1" 404 379 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.1) VoilaBot BETA 1.2 (http://www.voila.com/)"↵
81.52.143.15 - - [27/May/2007:06:51:12 +0200] "GET /d00050/http:/213.133.115.133/p/www.alpennic.com/zp_at.html HTTP/1.1" 403 550 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.1) VoilaBot BETA 1.2 (http://www.voila.com/)"↵
66.249.73.115 - - [27/May/2007:06:52:02 +0200] "GET /d03011/Art_of_War_Forum/thread.php?postid=227 HTTP/1.1" 200 20545 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"↵
193.170.149.156 - - [27/May/2007:06:52:24 +0200] "GET / HTTP/1.0" 200 209 "-" "check_http/1.99 (nagios-plugins 1.4.6)"↵
Laden Sie sich eine Beispieldatei von http://edvowww.htlwrn.ac.at/HP/blosxom/C/beispiele/access.log herunter (Achtung 14MB).
Schreiben Sie eine Klasse LogAnalyse mit einem Konstruktor, dem der Dateiname übergeben wird. In der Klasse muss eine Methode implementiert werden, welche obige Aufgabenstellung löst.
Im Hauptprogramm muss ein Objekt der Klasse angelegt werden und diese Methode aufgerufen werden. Der Dateiname kommt aus der Kommandozeile.

Hinweise:

Verwenden Sie einen Hash zum Speichern der Zählinformation.
Verwenden Sie die Sortiermethode sort von Arrays, um die entsprechende Liste zu erzeugen.
Abgabe am Ende der zweiten Übungsstunde:
Nennen Sie das Projekt 5xd-name-ruby-logfile-i
Dabei gilt:
Geben Sie auch das Ergebnis als Textdatei innerhalb des Projekts ab: ergebnis.txt. Das Projekt enthält also 2 Dateien:
  1. loganalyse.rb
  2. ergebnis.txt

Labels: , ,


Freitag, 15. Oktober 2010

 

Listings (Source-Code drucken)

Sollten Sie Source Code in gedruckter Form abliefern (müssen), dann sollte ein Listing etwa so aussehen:
Das Listing sollte folgende Eigenschaften aufweisen:
Beispiel mit Zeilennummern:

Beispiel mit Zeilennummern, nur jede 5. Zeile bekommt eine Nummer:

Unter Linux können Sie den Befehl a2ps (ASCII to PostScript, 2 = two = to, daher a2ps) verwenden.

Beispiele:
a2ps -Phpg1 dreieck.py
Die Datei dreieck.py wird auf dem Drucker hpg1 ohne Zeilennummern gedruckt.

a2ps --line-numbers=1 -Phpg2 dreieck.py newprog.py
Die Dateien dreieck.py und newprog.py werden am Drucker hpg2 mit Zeilennummern gedruckt.

a2ps --line-numbers=5 -Phpg3 dreieck.py newprog.py
Die Dateien dreieck.py und newprog.py werden am Drucker hpg3 mit Zeilennummern gedruckt, wobei nur jede fünfte Zeilennummer gedruckt wird.

Weitere Optionen zu a2ps finden Sie in der man-Page (man a2ps).

Die meisten IDEs (z.B. eclipse oder netbeans) und viele Editoren bieten ähnliche Möglichkeiten, jedoch passt bei a2ps der Ausdruck praktisch immer (abgesehen davon, dass a2ps nur ASCII-versteht und damit Probleme mit Umlauten hat, aber damit kann man leben).

Labels: , , ,


Montag, 4. Oktober 2010

 

Ruby lernen (PR: 5AD, 5BD)

Lernen Sie Ruby anhand der Vorgangsweise 15 Exercises for Learning a new Programming Language.

Machen Sie ein Projekt 5ad-name-learn-ruby bzw. 5bd-name-learn-ruby, in dem Sie die Lösungen der 15 Aufgaben dokumentieren (in erster Linie getesteter und kommentierter Source-Code).

Achtung: die erste Aufgabe ist ziemlich schwierig, da das Prüfen, ob eine Taste gedrückt wurde, systemabhängig ist. Lösen Sie diese Aufgabe sowohl für Windows als auch für Linux.

Informationen:

Labels: , ,


Donnerstag, 9. September 2010

 

Entwicklungsumgebung für veschiedene Programmiersprachen einrichten (PR: 5AD, 5BD)


Richten Sie sich eine Entwicklungsumgebung ein, mit der Sie Java, C, C++, Ruby, Groovy und Python programmieren können. Weiters sollte es möglich sein, Projekte in einem CVS-Repository abzulegen.

Der Schwerpunkt - auch im Hinblick auf die Reife- und Diplomprüfung - liegt auf Java, aber im Laufe des Jahres werden die anderen Sprachen verwenden.
Für Ihre Projekte (Programmbeispiele) sollten Sie sowieso eine Versionsverwaltung einsetzen. In der Schule haben wir einen CVS-Server eingerichtet. Auf diesem System müssen Sie außerdem Ihre Beispiele abgeben.
Ich empfehle eclipse oder netbeans als Entwicklungsumgebung (wobei ich mir nicht sicher bin, ob netbeans auch ein Groovy-Plugin hat).
Falls Sie noch keinen Zugang zum CVS-Server haben, melden Sie sich bei mir.

"Abgabe": erstellen Sie folgende Projekte und checken Sie diese auf dem CVS-Server ein.
  • 5ad-name-java-helloworld
  • 5ad-name-c-helloworld
  • 5ad-name-cpp-helloworld
  • 5ad-name-ruby-helloworld
  • 5ad-name-groovy-helloworld
  • 5ad-name-python-helloworld
name ersetzen Sie bitte durch Ihren Familiennamen. "5ad" ist für die 5B natürlich durch "5bd" zu ersetzen.  Die Projekte sollen jeweils "Hello World"-Programme in der gegebenen Programmiersprache enthalten.

Termin: 
Jeweils Beginn der 2. Übungseinheit.

Labels: , ,


Donnerstag, 3. Juni 2010

 

IDEs und Projektverzeichnisse und wie Maturanten damit umgehen

Ich versuche gerade, die Programmierarbeiten der Projektwoche der Reife- und Diplomprüfung zu korrigieren. Die Maturanten haben mich da vor ziemlich großes Problem gestellt, denn ich muss von 17 Abgegebenen Projekten
Zur Erläuterung habe ich ein paar Screenshots gemacht. Das erste zeigt die Paket-Struktur eines richtig abgegebenen Netbeans-Projekt:
Das nächste Bild zeigt ein falsch abgegebenes Projekt. Es gibt in den Paket-Verzeichnissen keine *.java-Dateien!
Die *.java-Dateien waren im übergeordneten Verzeichnis zu finden. Die Verzeichnisse mit den Sourcen beginnen alle mit einem Leerzeichen und müssen erst wieder umbenannt werden:
Das Verzeichnis, das mit "-brz" endet, enthält das Projekt, aber eben ohne die Sourcen.

Ich denke, man muss schon ziemlich Hand anlegen, um aus den Projektverzeichnissen diese kaputten Projekte zu erzeugen.

Ich frage mich, was so kompliziert ist, wenn es heißt: "Das gesamte Projekt-Verzeichnis (Name-brz) in eine Jar-Datei (Name-brz.jar) packen und ins Abgabeverzeichnis kopieren (Name-brz.jar enthält also u.a. die „ausführbare“ Datei brz.jar)."

Die Angabe für das Projekt war so:
"Verwenden Sie als Standard-Encoding UTF-8 (Unicode)!
Nennen Sie das Projekt Name-brz, wobei Name Ihr Familienname ist.
Erstellen Sie eine geeignete Paket-Struktur.
Erstellen Sie die Verzeichnisse build und doc. Im Verzeichnis build muss das fertige Programm brz.jar abgelegt werden. In doc muss die generierte JavaDoc abgelegt werden. Legen Sie im Projektverzeichnis eine Datei readme.txt an, die Hinweise zum Erzeugen von brz.jar enthält.
brz.jar soll alle nötigen Informationen zum Betrieb mit Ausnahme der Datenbankdateien enthalten, d.h. brz.jar soll in jedem beliebigen Verzeichnis aufgerufen werden können.
Programmaufruf: über ein Argument der Kommandozeile soll festgelegt werden, ob das Programm als Server (Argument server), Client (Argument client) oder AdminClient (Argument admin) läuft.
Erstellen Sie ein Ant-Script, mit dem die Applikation und die Dokumentation (neu) erstellt werden kann."

Es durfte frei zwischen Eclipse und Netbeans gewählt werden.

Die Leute haben 5 oder mehr Jahre Programmieren hinter sich. Und immer wieder Tests in dieser Arbeitsumgebung (Java unter Linux). Ich verstehe das nicht!

Diese Umstände kosteten bis jetzt ein paar Stunden Arbeit (überhaupt, wenn man diesen Blog-Eintrag mitrechnet). Wirklich korrigiert habe ich noch keine der Arbeiten.

Labels: , , , ,


This page is powered by Blogger. Isn't yours?

Abonnieren Posts [Atom]