Sonntag, 29. März 2009

 

Robo*Stars qualifizerten sich für den RoboCup

Robo*Stars
Wir können stolz sein auf unser Robotik-Team Robo*Stars - Gabriel Grill, Stefan Walker, Daniel Grübler und Bianca Deinhammer haben den 4. Platz im RoboCupJunior Austria Open im Bewerb Dance Secondary geschafft. Damit haben sie sich für den internationalen RoboCup in Graz qualifiziert.
Der Wettbewerb fand vom 28.3. bis zum 29.3.2009 in der Fachhochschule Technikum Wien statt. Leider konnte Frau Deinhammer gar nicht und Herr Grübler nur am Samstag teilnehmen.
Ich möchte dem Team ganz herzlich zu der großartigen Leistung gratulieren, insbesonders den Herren Gabriel Grill und Stefan Walker, die als einzige diesen Bewerb nur zu zweit bestritten.

Hier das Video des "Auftritts" am Sonntag:


Bericht in Futurezone.

Eindrücke zum Bewerb gibt's hier:

Labels: , , , ,


Mittwoch, 25. März 2009

 

JTable

Erstellen Sie ein einfaches Java-Programm, welches einfache CSV-Dateien erzeugen, darstellen und ändern kann. Verwenden Sie zur Darstellung der CSV-Datei eine JTable. Beim Neuanlegen wir die Anzahl der Spalten festgelegt. Beim Laden einer CSV-Datei wird die Anzahl der Zeilen/Spalten durch die Datei gegeben.
Das Programm soll es ermöglichen, jede Zelle (definiert durch Zeile und Spalte) zu ändern.
Sehen Sie eine Möglichkeit zum Einfügen neuer Zeilen vor.
Eine markierte Zeile soll gelöscht werden können.

Nennen Sie das Projekt klasse-tabelle-name (klasse ist Ihre Klasse, name ist Ihr Familienname, z.B. 3bd-tabelle-mueller)

Hier finden Sie zwei Beispielklassen, TableFrame für das GUI und MyTableModel für das Tabellenmodell.


/**
* JTableDemo/at.haberstroh.table/TableFrame.java
*
* Einfaches Tabellenbeispiel mit eigenem Modell. Einfügen/Löschen von Zeilen,
* Ändern von Zeilen.
*
* (c) 2007 Harald R. Haberstroh, www.haberstroh.at
*
* 15.01.2007
*/

package at.haberstroh.table;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;

/**
* @author hp
*
*/
public class TableFrame extends JFrame {

private static final long serialVersionUID = 9118987994549780691L;
private JTable table;
private MyTableModel model;

public TableFrame() {
setLayout(new BorderLayout());
table = new JTable();
model = new MyTableModel();
table.setModel(model);
table.setPreferredScrollableViewportSize(new Dimension(550, 100));
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
add(new JScrollPane(table), BorderLayout.CENTER);
JButton eBut = new JButton("Einfügen");
JButton lBut = new JButton("Löschen");
JButton aBut = new JButton("Anzeigen");
JPanel pan = new JPanel();
pan.add(eBut);
pan.add(aBut);
pan.add(lBut);
eBut.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
int zeile = table.getSelectedRow();
if (zeile > -1)
model.insertRow(zeile);
}
});
aBut.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.print(model);
}
});
lBut.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
int zeile = table.getSelectedRow();
if (zeile > -1)
model.deleteRow(zeile);
}
});
add(pan, BorderLayout.SOUTH);
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
pack(); // GUI so klein als möglich machen
}

/**
* @param args
*/
public static void main(String[] args) {
new TableFrame().setVisible(true);
}

}



/**
* JTableDemo/at.haberstroh.table/MyTableModel.java
*
* Implementiert ein einfaches Modell einer 3-spaltigen Tabelle.
*
* (c) 2007 Harald R. Haberstroh, www.haberstroh.at
*
* 15.01.2007
*/
package at.haberstroh.table;

import javax.swing.table.AbstractTableModel;

/**
* @author hp
*
*/
public class MyTableModel extends AbstractTableModel {

private static final long serialVersionUID = -8833545818864673421L;

// Kopf
private Object[] name = { "Name", "Erfindung", "geboren" };

// (Anfangs-)Daten
private Object[][] daten = {
{ "C. Babbage", "The Analytical Engine", new Integer(1792) },
{ "N. Chomsky", "Die Chomsky-Grammatiken", new Integer(1928) },
{ "G.W. Leibniz", "Das Dualsystem", new Integer(1646) },
{ "A.M. Turing", "Die Turing-Maschine", new Integer(1912) },
{ "K. Zuse", "Der 1. Relaisrechner Z1", new Integer(1910) } };

/**
* @return Beschriftung
*/
public String getColumnName(int col) {
return (String) name[col];
}

/**
* @return liefert Klasse der Spalte
*/
public Class getColClass(int col) {
if (col == 0 || col == 1)
return String.class;
else
return Integer.class;
}

/**
* @return liefert Anzahl der Spalten
*/
public int getColumnCount() {
return 3;
}

/**
* @return liefert Anzahl der Zeilen
*/
public int getRowCount() {
return daten.length;
}

/**
* @param arg0 Zeile
* @param arg1 Spalte
* @return liefert Wert der Zelle
*/
public Object getValueAt(int arg0, int arg1) {
return daten[arg0][arg1];
}

/**
* @param aValue Wert der gesetzt werden soll
* @param arg0 Zeile
* @param arg1 Spalte
*/
public void setValueAt(Object aValue, int row, int col) {
daten[row][col] = aValue;
fireTableCellUpdated(row, col);
}

/**
* @return ist die Zelle änderbar?
*/
public boolean isCellEditable(int row, int col) {
return row < daten.length && col < 3;
}

/**
* Einfügen einer Zeile
* @param row die Zeilennummer vor der eingefügt wird
*/
public void insertRow(int row) {
Object[][] tmp = new Object[daten.length + 1][3];
for (int i = 0; i < row; i++) {
for (int j = 0; j < 3; j++) {
tmp[i][j] = daten[i][j];
}
}
// neue Zeile
tmp[row][0] = "";
tmp[row][1] = "";
tmp[row][2] = new Integer(0);
// Rest
for (int i = row + 1; i < tmp.length; i++) {
for (int j = 0; j < 3; j++) {
tmp[i][j] = daten[i - 1][j];
}
}
// neue daten
daten = tmp;

// update
fireTableDataChanged();
}

/**
* löschen der Zeile. Bei der letzten Zeile wird eine leere
* Zeile angelegt.
* @param row Zeilennummer
*/
public void deleteRow(int row) {
if (row >= 0 && row < daten.length && daten.length > 1) {
Object[][] tmp = new Object[daten.length - 1][3];
for (int i = 0; i < row; i++) {
for (int j = 0; j < 3; j++) {
tmp[i][j] = daten[i][j];
}
}
for (int i = row; i < tmp.length; i++) {
for (int j = 0; j < 3; j++) {
tmp[i][j] = daten[i+1][j];
}
}
// neue daten
daten = tmp;

} else { // leere Zeile
daten = new Object[][] { { "", "", new Integer(0) } };
}

// update
fireTableDataChanged();
}

/**
* @return liefert Tabelle als String
*/
public String toString() {
StringBuffer buf = new StringBuffer(1000);
for (int i = 0; i < daten.length; i++) {
for (int j = 0; j < 3; j++) {
buf.append(daten[i][j]);
}
buf.append("\n");
}
return buf.toString();
}

}

Labels: , , ,


 

Verkettete Listen

Folgendes Bild stellt das Prinzip der verketteten Listen dar. Wir benötigen einen Anker (pAnker), der auf das erste Element "zeigt". Jedes Element "zeigt" auf einen Nachfolger. Nur das letzte Element hat keinen Nachfolger und enthält daher null.

In Java könnte das etwa so formuliert werden:

class Element {
Element next;
int data;
Element(int data) {
this.data = data;
}
}

Element anker = null; // leere Liste

Element n = new Element(1);
n.next = anker;
anker = n;

n = new Element(2);
n.next = anker;
anker = n;

n = new Element(3);
n.next = anker;
anker = n;

System.out.println("liste");
for (Element e = anker; e != null; e = e.next) {
System.out.println(e.data);
}

// 1. Element entfernen
anker = anker.next;
System.out.println("liste");
for (Element e = anker; e != null; e = e.next) {
System.out.println(e.data);
}


Folgendes Listing zeigt eine mögliche Implementierung einer einfach verketteten Liste sowie die Verwendung einer solchen Liste als Stack. Neue Elemente werden einfach vorne eingefügt:

public class Liste {

private class Element {
Element next;
int data;

Element(int data) {
this.data = data;
}
}

private Element anker = null;

// private Element ende = null;

public void append(int data) {
if (anker == null) {
anker = new Element(data);
} else {
// suche Ende
Element e = anker;
while (e.next != null) {
e = e.next;
}
// e ist das letzte Element (Ende)
e.next = new Element(data);
}
}

public void insertFirst(int data) {
Element n = new Element(data);
n.next = anker;
anker = n;
}

public void deleteFirst() {
assert (anker != null);
anker = anker.next;
}

public void printList() {
System.out.print("[");
for (Element e = anker; e != null; e = e.next) {
System.out.print(e.data + " ");
}
System.out.println("]");
}

public void push(int data) {
insertFirst(data);
}

public int pop() {
assert (anker != null);
Element e = anker;
anker = anker.next;
return e.data;
}

public boolean isEmpty() {
return anker == null;
}

/**
* @param args
*/
public static void main(String[] args) {
Liste liste = new Liste();
liste.printList();
// liste.insertFirst(1);
// liste.insertFirst(2);
// liste.insertFirst(3);
liste.append(1);
liste.append(2);
liste.append(3);
liste.printList();
liste.deleteFirst();
liste.printList();

Liste stack = new Liste();
stack.push(23);
stack.push(3);
while (!stack.isEmpty()) {
System.out.println(stack.pop());
}
}

}

Labels: ,


 

UrlyBird - Hinweis zu Synchronisierung

Sollten die Locks für die Datenbank in einer Collection (z.B. HashMap, TreeMap) gehalten werden. So muss beachtet werden, dass die Collections üblicherweise nicht synchronisiert sind. Man kann nun entweder jeden Zugriff selbst mit synchronized schützen oder einen Wrapper aus java.utils.Collections verwenden. Hier die Verwendung mit TreeMap:

/**
* handle locks (lockcookies)
*/
private SortedMap<Long, Integer> locks;

//...
locks = Collections.synchronizedSortedMap(new TreeMap<Long, Integer>());

Durch den Aufruf Collections.synchronizedSortedMap(new TreeMap<Long, Integer>()) wird jeder Zugriff auf die locks synchronisiert.

Labels: ,


Donnerstag, 19. März 2009

 

Kleines Java-Quiz

Hier finden Sie ein paar Java-Quiz:
Ich werde die Liste im Laufe der Zeit vervollständigen.

Viel Spaß!

Labels: , , ,


Mittwoch, 18. März 2009

 

Suchen von Mustern mit Hilfe eines Automaten

Erstellen Sie ein Programm, welches Textdateien (auf Kommandozeile angegeben oder stdin) liest und die gefundenen C Prototypen ausgibt. Das Programm soll aus (mindestens) zwei Klassen bestehen. Die Hauptklasse macht die Parameteranalyse und behandelt die Ein- und Ausgabe. Die zweite Klasse implementiert den Automaten (weitere Klassen bzw. enums nach Bedarf). Um andere Suchmuster zu bearbeiten, muss dann nur die Automatenklasse getauscht werden.
Die Automatenklasse soll einen Automaten implementieren, der die Prototypen (siehe unten) erkennt.
Arbeiten Sie in Zweiergruppen. Jede Klasse wird von einer Person implementiert und getestet.
Die einzeln getesteten Klassen sollen dann (nachdem der Testcode auskommentiert wurde) zum fertigen Programm verbunden werden.

Die Syntax der Prototypen darf vereinfacht werden (im Prinzip können auch Java-Methoden ohne Rumpf verwendet werden, jedoch ohne private, public etc.):

Beispiele gültiger Prototypen:

int getX();
long setVal ( long x , long y ) ;
char zeichen23(int i);

Typen und Namen sind Worte. Worte müssen mit einem Buchstaben oder Unterstrich (_) beginnen und können dann mit beliebig vielen Buchstaben Ziffern oder Unterstrichen fortgesetzt werden.
Zwischen Worten muss mindestens ein Leerzeichen sein, vor und nach (, ), , und ; dürfen beliebig viele Leerzeichen (auch keine) sein.

Erstellen Sie eine alternative Automatenklasse, welche (einfache) Variablendeklarationen erkennt.

Nennen Sie das Projekt klasse-automat-name (klasse...2ad, 2bd, 2cd, name Ihr Name).

Labels: , ,


 

JavaScript steigt, Perl fällt

TIOBE Programming Community Index vom März 2009: "March Headline: All time high for JavaScript, all time low for Perl".

Interessant. Java und C führen weiterhin die Liste an. JavaScript wird in der Schlagzeile erwähnt, weil JavaScript noch nie so weit oben war. Bei Perl ist es genau umgekehrt.

Die 5-Jahresübersicht zeigt den "Absturz" von Perl ganz deutlich:

Labels: ,


 

einfache erkennende Automaten, implementiert mit Zustandstabelle

Erstellen Sie jeweils eine Klasse, die einen Automaten zur Erkennung von
imlpementiert. Implementieren Sie die Automaten mit Hilfe der Zustandstabelle.
Die Automaten sollen jeweils eine Methode

boolean accept(String input)
implementieren, welche true liefert, wenn der Automat den String input akzeptiert.

Labels: , ,


 

einfache erkennende Automaten, implementiert mit switch

Erstellen Sie jeweils eine Klasse, die einen Automaten zur Erkennung von
imlpementiert. Implementieren Sie die Automaten mit Hilfe eines switch-Statements.
Die Automaten sollen jeweils eine Methode

boolean accept(String input)
implementieren, welche true liefert, wenn der Automat den String input akzeptiert.

Labels: , ,


Dienstag, 17. März 2009

 

Execptions aus Threads bearbeiten

Wird innerhalb eines Trheads eine Exception geworfen, so wird der Thread abgebrochen, wenn der Thread nicht selbst die Exception behandelt. Der startende Thread bekommt die Exception nicht mit. Nehmen Sie an, Sie hätten folgende Klasse für den Thread:

/** Throws an exception 50% of the time */
class NaughtyThread implements Runnable {
public void run() {
try {
if (Math.random() > .5) {
throw new RuntimeException("badness "
+ Thread.currentThread().getName());
}
} finally {
System.out.println("ran " + Thread.currentThread().getName());
}
}
}

Würden Sie ihn einfach ganz normal starten, würde der startende Thread nichts von der Exception mitbekommen:

Thread[] threads = { new Thread(new NaughtyThread(), "bad thread 1"),
new Thread(new NaughtyThread(), "bad thread 2"),
new Thread(new NaughtyThread(), "bad thread 3"), };
for (Thread t : threads) {
t.start();
}
try {
for (Thread t : threads) {
t.join();
}
} catch (InterruptedException e) {
System.err.println("interrupted: " + e.getMessage());
}

Die Lösung ist die Verwendung eines Proxies, der die Exception auffängt und speichert und auf Wunsch einen Callback aufruft.

/**
* Catches exceptions thrown by a Runnable, so you can check/view them later
* and/or deal with them from some callback.
*/
class RunnableCatch implements Runnable {

/** Proxy we will run */
private final Runnable _proxy;

/** Callback, if any */
private final RunnableCallback _callback;

/** @guarded-by(this) */
private Exception _exception;

public RunnableCatch(final Runnable proxy) {
this(proxy, null);
}

public RunnableCatch(final Runnable proxy, RunnableCallback target) {
_proxy = proxy;
_callback = target;
}

public void run() {
try {
_proxy.run();
} catch (Exception e) {
synchronized (this) {
_exception = e;
}
if (_callback != null) {
_callback.handleException(_proxy, e);
}
}
}

/** @return any exception that occured, or NULL */
public synchronized Exception getException() {
return _exception;
}
}

Der erzeugende Thread muss im Falle eines Callbacks das Interface RunnableCallback implementieren:

/** Called when an exception occurs */
interface RunnableCallback {
void handleException(Runnable runnable, Exception exception);
}

Unser NaughtyThread kann nun auf folgende Arten aufgerufen/gestartet werden. safe1 (t1) ist der Thread ohne Callback, safe2 (t2) ist jener mit Callback:

final Runnable bad = new NaughtyThread();
// safe1 doesnt have a callback
final RunnableCatch safe1 = new RunnableCatch(bad);
// safe2 DOES have a callback
final RunnableCatch safe2 = new RunnableCatch(bad, new RunnableCallback() {
public void handleException(Runnable runnable, Exception exception) {
System.out.println("Callback handled: " + exception.getMessage());
exception.printStackTrace();
}

});
final Thread t1 = new Thread(safe1, "bad thread 1");
final Thread t2 = new Thread(safe2, "bad thread 2");
t1.start();
t2.start();
t1.join();
t2.join();
if (safe1.getException() != null) {
System.out.println("thread 1 finished with exceptions");
safe1.getException().printStackTrace();
}
System.out.println("done");


Quelle: What is the best approach to handling exceptions thrown in a separate thread? auf www.stackoverflow.com

Labels:


 

Testklasse für UrlyBird

Die Testklasse DataTest.java (beim Speichern die Versionskennung entfernen) funktioniert nur, wenn die Datenbankdatei db-1x2.db im Basisverzeichnis des Projekts liegt, die Data-Klasse sowie die entsprechenden Exceptions im vorgegebenen Package liegen.
Man kann die Testklasse als Programm starten:

java -cp ../vendor/junit.jar:. test.DataTest

Dabei ist zu beachten, dass man JUnit 3.8 im CLASSPATH hat (Parameter -cp ../vendor/junit.jar:. für Linux, wenn junit.jar im angegebeenen Verzeichnis ist). Mit der Option --gui kann man die Tests auch mit einer graphischen Oberfläche starten:

java -cp ../vendor/junit.jar:. test.DataTest --gui

In Eclipse kann man die Klasse natürlich auch als JUnit-Test starten.

Die Testklasse legt bei jedem Test eine Kopie der Datenbank (db-1x2.db.sav) an, damit die Originaldatenbank erhalten bleibt. Nach jedem Test wird die Datenbank wieder hergestellt.

Achtung: Die Testklasse geht davon aus, dass zusätzlich zum geforderten Interface zwei Methoden implementiert wurden:
  1. getCntRecords() ... liefert die Anzahl der Datensätze (incl. jener, die als gelöscht markiert sind).
  2. getNumFields() ... liefert die Anzahl der Felder.
In der Angabe wurde nicht genau spezifiziert, wie man das Ende der Datei erkennen kann, denn es steht einerseits explizit drinnen, dass das Lesen eines gelöschten Datensatzes eine RecordNotFoundException bewirkt, gelöschte Datensätze für neue verwendet werden sollen und es andererseits keinen EOF-Mechanismus gibt. Man könnte diesen Unit-Test diesbezüglich an die gegebene Datei anpassen und die Anzahl der Datensätze fix definieren.
Die Anzahl der Felder ist eigentlich auch vorgegeben und könnte daher im Test fix definiert werden.

Wollen Sie sich genau an das geforderte Interface halten, so müssen Sie die Testklasse entsprechend anpassen.

Die Angabe ist diesbezüglich ungenau.

Labels: , ,


Mittwoch, 11. März 2009

 

Internationalisierung in Java

Im Beispiel Internationalisierung.zip wird demonstriert, wie man die Ein-/Ausgabe sowie eigene Texte internationalisiert.


private String resource = "international.texte"; // Package + Prefix
private ResourceBundle bundle;
private Locale locale = Locale.getDefault();
public International() {
this.bundle = ResourceBundle.getBundle(this.resource);
System.out.println(this.locale.getDisplayName());
}
public International(Locale locale) {
this.bundle = ResourceBundle.getBundle(this.resource, locale);
this.locale = locale;
System.out.println(this.locale.getDisplayName());
}

public void sayHello() {
System.out.println(this.bundle.getString("hallo"));
}

Dieses Code-Fragment zeigt einen Ausschnitt aus der Beipielklasse International. Beim Defaultkonstruktor wird als Sprache die Systemeinstellung verwendet, der zweite Konstruktor wird mit einer Locale aufgerufen:


International i = new International(new Locale("de", "AT"));
i.sayHello();

ResourceBundle.getBundle(this.resource, locale) versucht eine Datei international.texte_locale.properties zu lesen (international ist hier das Package). locale setzt sich in obigem Beispiel aus de_AT zusammen.
sayHello() gibt dann die Übersetzung zum String hallo aus, falls diese Property existiert.

Eine solche Property-Datei enthält Zeilen der Art

 msg = Übersetzung

Z.B. international.texte_de_AT.properties enthält:


# österreichische Texte
hallo = Servus
fehler = Des is foisch
zahl = Zoi
parse = des kaunst net umwaundln
io = des geht net eine oder aussi
null = Nix geht mehr (null pointer exception)

Die Methoden readNumber() bzw. printNumber() zeigen, wie man Zahlen lokalisiert einliest bzw. ausgibt.

Siehe auch Internationalisierung (Softwareentwicklung)

Labels: ,


 

CRISP, ANT und JAR

Zubereitung von CRISP Builds

Ein Build ist eine Reihe von Schritten, die Ihre kreativen Artefakte (Source Code) in auslieferbare Software transformieren.

CRISP bedeutet

vollständig (engl. Complete)
wiederholbar (engl. Repeatable)
informativ (engl. Informative)
planbar (engl. Schedulable)
portabel (engl. Portable)

Ein CRISP Build versetzt uns in die Lage jederzeit automatisch den Quellcode in auslieferbare Software zu transferieren.

Complete - vollständig heißt, dass alles von Grund auf nach dem Rezept neu erstellt wird. Es braucht nichts vor oder nach dem Build hinzugefügt werden.
Repeatable wiederholbar heißt, dass jederzeit auch frühere Softwareversionen erzeugt werden können. Der Schlüssel dazu liegt in der Versionsverwaltung (CVS).
Informative - informativ bedeutet, dass wir sofort wissen, wenn der Build schief gegangen ist. Der Build war erfolgreich, wenn die Software fehlerfrei übersetzt werden konnte und die automatischen Test bestanden hat.
Schedulable - planbar oder zeitgesteuert - ist ein Build vollständig und wiederholbar, so kann er auch automatisch z.B. zu bestimmten Zeiten angestoßen werden. Der Vorteil ist, dass man nebenher am Quellcode weiterarbeiten kann.
Portable - portable Builds bedeuten weniger, dass Builds, die auf einem Unix durchgeführt werden, auch unter Windows durchgeführt werden können. Jedoch soll der Build ohne Probleme auf einem andern Unix-System laufen. Abhängigkeiten von bestimmten IP-Adressen oder IDE sind zu vermeiden.

Der "Compile"-Knopf ist kein Build Prozess

So verlockend es auch sein mag, ihn als solchen zu benutzen, der "Compile"-Knopf ist kein Build-Prozess, der einen CRISP-Build erzeugt. Sie müssten sich auf eine einzige IDE mit einer Standardkonfiguration einigen (Einigung durch Armdrücken? - was ist die beste IDE?). Das erfordert Disziplin, denn jede Änderung der Einstellung in der IDE müsste an alle verteilt werden (CVS).

Wie automatisiert man den Build-Prozess nun mit der IDE? Ein Programmierer drückt auf den Knopf, wenn ihm eine Banane (oder ein Bier :-) hingehalten wird?

Mehr Flexibilität erhält man, durch die Verwendung eines aus jeder IDE ausgelagerten Build-Prozesses.

ANT

another neat tool - ANT ist ein auf Java-Projekte spezialisiertes Open-Source-Build-Werkzeug, welches XML-Dateien zur Steuerung verwendet. Einige IDEs können ANT-Dateien erzeugen bzw. ANT für den Build-Prozess verwenden ("Compile"-Knopf mit ANT im Hintergrund). ANT versetzt uns in die Lage CRISP-Builds zu machen.

Vorteile

- ANT-Dateien sind portabel und da ANT in Java geschrieben wurde, ist auch der Build-Prozess portabel.
- ANT verfolgt Abhängigkeiten (ähnlich make). Es ruft javac nur auf, wenn sich eine Datei geändert hat (das macht make auch, aber es bei Java effizienter, da alles in einer JVM läuft, bei make würde für jden javac -Aufruf eine JVM gestartet werden müssen).
- ANT beinhaltet neben dem Kompilieren noch eine Reihe von Tasks zur Erledigung verschiedener Dinge (z.B. JUnit -Tests). Außerdem können eigene in Java geschriebene Tasks verwendet werden.

Nachteile

Das Build-Rezept muss in einer XML-Datei ausgedrückt werden. All die eckigen XML-Klammern können verschleiern, was wirklich vor sich geht.

Eine ANT-Build Datei build.xml


<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="build" name="AntDemo">
<property name="JUNIT_HOME" value="vendor">
<property name="build.dir" location="build">
<property name="doc.dir" location="doc">
<property name="src.dir" location="src">
<property name="vendor.dir" location="vendor">

<path id="project.classpath">
<pathelement location="bin">
<pathelement location="build">
<pathelement location="${JUNIT_HOME}/junit.jar">
</path>

<!-- Initialisierungen -->
<target name="init">
<mkdir dir="bin">
<mkdir dir="build">
</target>

<!-- löschen der binaries -->
<target name="clean">
<delete dir="bin">
<delete dir="build">
</target>

<!-- Übersetzen -->
<target depends="init" name="compile" description="Übersetzen">
<javac destdir="bin">
<src path="src">
<classpath refid="project.classpath">
</javac>
</target>

<!-- Unit Tests, vorher evtl. Übersetzen -->
<target depends="init,compile" name="test" description="Testen">
<junit fork="yes" printsummary="withOutAndErr">
<formatter type="plain">
<test name="antdemo.DatumTest">
<classpath refid="project.classpath">
</junit>
</target>

<!-- erzeuge jar-File, vorher evtl. übersetzen, Test-Klassen werden nicht
ausgeliefert -->
<target depends="init,compile" name="build" description="Build jar">
<jar destfile="build/antdemo.jar" basedir="bin" excludes="**/*Test.class">
<manifest>
<!-- erzeuge Minifest ... -->
<attribute name="Main-Class" value="antdemo.Datum">
</manifest>
</jar>
</target>
</project>

junit.jar (die für die Unit-Tests nötige Bibliothek) ist in diesem Beispiel im Verzeichns vendor zu finden. Die jar-Datei wird im Verzeichnis build erstellt. Die übersetzten Klassendateien sind im Verzeichnis bin.

Aufruf: ant [task]

Wird kein task angegeben, so wird der Default-Task ausgeführt (im Projekt definiert: <project basedir="." default="build" name="AntDemo">). Abhängigkeiten werden durch depends= definiert.

Erzeugen von jar-Dateien und einbinden fremder jar-Dateien

Oft benötigt man weitere jar Dateien. Diese müssten beim ausgelieferten Programm entweder im Class-Path sein oder man packt die jar Datei aus und im Ziel- jar wieder ein.


<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="build" name="AntDemo">
<property name="JUNIT_HOME" value="vendor">
<property name="build.dir" location="build">
<property name="doc.dir" location="doc">
<property name="src.dir" location="src">
<property name="vendor.dir" location="vendor">

<path id="project.classpath">
<pathelement location="bin">
<pathelement location="build">
<pathelement location="${JUNIT_HOME}/junit.jar">
</path>

<!-- Initialisierungen -->
<target name="init">
<mkdir dir="bin">
<mkdir dir="build">
</target>

<!-- löschen der binaries -->
<target name="clean">
<delete dir="bin">
<delete dir="build">
</target>

<!-- Übersetzen -->
<target depends="init" name="compile" description="Übersetzen">
<javac destdir="bin">
<src path="src">
<classpath refid="project.classpath">
</javac>
</target>

<!-- Unit Tests, vorher evtl. Übersetzen -->
<target depends="init,compile" name="test" description="Testen">
<java fork="yes" classname="antdemo.DatumTest">
<classpath refid="project.classpath">
</java>
<!--
<junit fork="yes" printsummary="withOutAndErr">
<formatter type="plain">
<test name="antdemo.DatumTest">
<classpath refid="project.classpath">
</junit>
-->
</target>

<!-- erzeuge jar-File, vorher evtl. übersetzen, Test-Klassen werden nicht
ausgeliefert -->
<target depends="init,compile" name="build" description="Build jar">
<jar destfile="build/antdemo.jar" basedir="bin" excludes="**/*Test.class">
<manifest>
<!-- erzeuge Manifest ... -->
<attribute name="Main-Class" value="antdemo.Datum">
</manifest>
</jar>
</target>

<!-- erzeuge jar-File incl. junit, vorher evtl. übersetzen, Test-Klassen werden nicht
ausgeliefert
junit.jar wird zuerst ins bin ausgepackt und dann ins jar wieder
eingepackt -->
<target depends="init,compile" name="testbuild" description="Build jar">
<unjar src="vendor/junit.jar" dest="bin">
<jar destfile="build/antdemo.jar" basedir="bin">
<manifest>
<!-- erzeuge Manifest ... -->
<attribute name="Main-Class" value="antdemo.DatumTest">
</manifest>
</jar>
</target>
</project>

Das Ziel testbuild (das letzte) enthält zuerst einen Task , der junit.jar auspackt. Bei diesem Beispiel wird eingenommen, dass im Projektverzeichnis ein Unterverzeichnis vendor existiert, in dem eine Kopie von junit.jar zu finden ist.

junit.jar wird nach bin ausgepackt (dort landen alle *.class Dateien) und dann gemeinsam mit den eigenen Klassen zu build/antdemo.jar verpackt. <manifest...> erstellt das nötige Manifest, um die Datei mit java -jar antdemo.jar aufrufen zu können.

Beim Ziel build (das vorletzte) werden die Testklassen exkludiert und eine andere Startklasse ins Manifest eingetragen.

Das gesamte Beispielprojekt finden Sie hier

Die Verzeichnisstruktur ist an Projektstruktur angelehnt.

Weitere Informationen

ant.apache.org
Wikipedia Ant
projektstruktur.zip

Labels: ,


Freitag, 6. März 2009

 

Literaturverwaltung

Jemand will Zeitschriftenartikel, Bücher und Internet-Links für seine Recherchen verwalten. Schreiben Sie dazu eine Java-Klasse (plus eventuelle Hilfsklassen), welche es ermöglicht diese Daten in einer RandomAccess-Datei zu speichern. Folgende Daten sollen in der Klasse Eintrag erfasst werden:
  1. autor - String(100)
  2. titel - String(100)
  3. erscheinungsjahr - int
  4. verlag - String(30)
  5. zeitschrift - String(30)
  6. seiten - String(10)
  7. link - String(60)
String(100) bedeutet, dass dafür maximal 100 (genau 100) Zeichen gespeichert werden soll. Sie können das so implementieren, dass kürzere Strings hinten mit Leerzeichen gefüllt werden. Längere Strings müssen abgeschnitten werden. Für eine eventuelle Anzeige/Ausgabe müssen aber die Füllzeichen entfernt werden. Die Klasse Eintrag ist mit entsprechenden Getter- und Setter-Methoden auszustatten (die bei den Strings das Abschneiden und Auffüllen bewerkstelligen).

Die Klasse Literatur für die Verwaltung der Datensätze soll folgende Methoden bereitstellen:
Der Konstruktor der Klasse Literatur soll eine geöffnete RandomAccess-Datei als Parameter haben:

public Literatur(RandomAccessFile file)

Implementieren Sie das Löschen so, dass der jeweilige Eintrag als gelöscht markiert wird (zusätzliches Feld) und diese Änderung in der Datei gespeichert wird. Der Datensatz ist also physisch noch in der Datei. Bei der Methode lese() muss also im Falle eines gelöschten Eintrags null geliefert werden.
Umgekehrt muss bei neuerEintrag() zunächst ein gelöschter Eintrag gesucht werden. Wird ein solcher gefunden, so wird er durch den neuen ersetzt (Lösch-Markierung muss wieder zurückgesetzt werden). Wird kein gelöschter Datensatz gefunden, so ist der neue an die Datei anzuhängen.

Schreiben Sie ein kleines Testprogramm zum Testen Ihrer Klassen.

Anmerkung: Die Daten sind für eine echte Anwendung nicht ausreichend und müssten unterteilt werden, da es z.B. mehrere Autoren für einen Artikel gibt.

Hinweis: hier finden Sie eine Testklasse zum Testen der Klasse Literatur. Die Klasse Literatur muss wie diese Testklasse im Package literatur sein. Weiters muss die Library für JUnit 3 angegeben werden. Diese Testklasse trifft gewissen Annahmen zur Implementierung von Literatur und Eintrag. Bitte passen Sie entweder die Testklasse an Ihre Implementierung an oder Sie ändern Ihre Literatur bzw. Eintrag-Klassen entsprechend (z.B. nimmt der Test an, dass es eine read() und eine write()-Methode gibt, die einen Datensatz ("in sich hinein") list bzw. einen Datensatz ("sich selbst") schreibt (jeweils an die Position, die in Literatur bestimmt wurde).

Nennen Sie das Projekt klasse-literatur-name (klasse ... 2ad, 2bd, 2cd, name ... Ihr Familienname), z.B. 2ad-literatur-haberstroh.

Labels: , ,


Mittwoch, 4. März 2009

 

CVS - "cvs commit: nothing known about ..."

Ein Schüler fragte mich, was die Fehlermeldung "cvs commit: nothing known about ..." beim commit aus Netbeans bedeutet. Ich konnte das nicht beantworten, also googeln: Diese Fehlermeldung kommt, wenn man eine Datei "commiten" will, die dem CVS noch nicht bekanntgegeben wurde (cvs add file). Der Schüler hatte die Datei schon gelöscht. Also ist auch kein cvs add nötig. Aber aus einem mir (noch) unbekannten Grund versucht Netbeans doch ein "commit" auf diese (nicht existierende) Datei zu machen.

Folgenden Workaround habe ich gefunden:
  1. Alle einzelnen Dateien des Projekts händisch commiten.
  2. Das Projekt in einem neuen Verzeichnis auschecken.
  3. Prüfen, ob alles da ist.
  4. Das alte/originale Projekt löschen.
  5. Das neue verwenden.
Wenn man CVS von der Shell aus verwenden würde, müsste man sich immer selbst um jedes cvs add kümmern. Da würde man verstehen, warum man eine nicht existierende Datei nicht "commiten" kann. Hier war aber die Datei offensichtlich nicht vorhanden und trotzdem versuchte Netbeans ein "commit".

Grundsätzlich vereinfacht aber Netbeans (und auch eclipse) die Verwendung von CVS - schon alleine die umständlichen Schritte beim Anlegen (import) entfallen. cvs add braucht man nicht machen.

Scheinbar ein Bug im Netbeans.

Links:

Labels: , , ,


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

Abonnieren Posts [Atom]