Mittwoch, 28. Januar 2009

 

Algorithmus zum Umwandeln einer Infix- in eine Postfix-Notation

Folgender Algorithmus beschreibt die Umwandlung von Infix- in Postfix-Notation (für UPN).






















Zeichen aus Eingabe

Aktion

'('

push('(')

')'

Solange Stapel nicht leer:
zeichen = pop()
Wenn zeichen nicht '(':
schreibe zeichen in die Ausgabe
Verlasse Schleife wenn zeichen '(' ist

neuer Operator (Variable neuerOp)

Wenn Stapel leer:
push(neuerOp)
sonst:
Solange Stapel nicht leer ist:
zeichen = pop()
Wenn zeichen '(' ist:
lege zeichen wieder auf den Stapel (push(zeichen))
Wenn zeichen ein Operator opTop ist und
wenn opTop < neuerOp:
push(opTop)
Wenn opTop >= neuerOp:
schreibe opTop in die Ausgabe
Verlasse die Schleife, wenn opTop < neuerOp oder
wenn das zeichen '(' ist
push(neuerOp)

EOF (keine weiteren Zeichen)

Solange Stapel nicht leer ist:
zeichen = pop()
schreibe zeichen in die Ausgabe


Die Vergleiche opTop >= neuerOp bzw. opTop < neuerOp bedeuten einen Vergleich der Priorität (Auswertungsreihenfolge, Präzendent, Vorrang, Priorität). Es gilt Präzedenz:

  1. (
  2. + und -
  3. * und /
  4. alles Andere (z.B. sin, cos usw.)

Labels: , ,


Montag, 26. Januar 2009

 

GUI mit Netbeans erstellen und mit Eclipse weiterbearbeiten

Ich kenne keinen brauchbaren GUI-Designer für eclipse. Der GUI-Designer von Netbeans funktioniert ganz gut, dennoch verwende ich sonst zum Arbeiten lieber eclipse. eclipse compiliert immer im Hintergrund und ist daher in der Bedienung schneller. Außerdem hat es das Scrap Book, wo man mal schnell ein paar Zeilen probieren kann ohne gleich eine ganz Klasse schreiben zu müssen. Aber das meiste ist einfach Geschmacksache.

Netbeans verwendet eine eigene Klassen-Library für die graphischen Oberflächen. Die muss man auch im eclipse-Projekt verwenden. Es sind das die Libraries appframework-1.0.3.jar und swing-worker-1.1.jar, zu finden im Verzeichnis java2/modules/ext/ unterhalb des Netbeans-Installationsverzeichnisses (bei mir /usr/local/netbeans-6.1/).

Also zunächst ein "Java Desktop Application"-Projekt anlegen, das GUI erstellen und den About-Dialog ändern.

Als nächstes legt man ein einfaches Java-Projekt in eclipse an und kopiert die Pakete (Klassendateien) in das entprechende Projektverzeichnis, z.B.
hh@turing:~/netbeans/Gui2Eclipse/src> ls gui2eclipse/
Gui2EclipseAboutBox.form  Gui2EclipseAboutBox.java  Gui2EclipseApp.java  Gui2EclipseView.form  Gui2EclipseView.java  resources
hh@turing:~/netbeans/Gui2Eclipse/src> cp -r gui2eclipse/ ~/workspace/Gui2Eclipse/src/
hh@turing:~/netbeans/Gui2Eclipse/src> cd ~/workspace/Gui2Eclipse/
In eclipse muss man natürlich ein "refresh" machen. Dann erscheinen die Pakete mit den Klassen im Package Explorer, allerdings mit Fehlern, da ja die Libraries fehlen. Um dieses Problem zu beheben, legen wir im Quell-Ordner des Projekts (src/) ein lib/-Verzeichnis an, in die wir die nötigen Libraries kopieren:
hh@turing:~/workspace/Gui2Eclipse/src> mkdir lib
hh@turing:~/workspace/Gui2Eclipse/src> cd lib
hh@turing:~/workspace/Gui2Eclipse/src/lib> cp /usr/local/netbeans-6.1/java2/modules/ext/appframework-1.0.3.jar .
hh@turing:~/workspace/Gui2Eclipse/src/lib> cp /usr/local/netbeans-6.1/java2/modules/ext/swing-worker-1.1.jar .

Wo sich diese Jar-Dateien befinden ermittelt man aus dem "Properties"-Dialog von Netbeans:


Im eclipse muss man wieder ein "refresh" machen und anschließend die Libraries "aktivieren": Build Path->Add External Achives

Dass die Jar-Files eingebunden sind, sieht man im Projekt-Explorer:

Die Applikation lässt sich dann starten und ändern.

Aber Achtung: der von Netbeans erzeugte Code ist nicht so einfach. Man muss sich vorher schon ziemlich genau überlegen, wie man sein System gestaltet und dann erst das GUI dazu machen.

Labels: , , , ,


Freitag, 23. Januar 2009

 

Postels Gesetz (Postels Law) - Robustheitsprinzip

Jonathan Postel (1943-1998), ist am besten bekannt als Herausgeber der Requests for Comments (RFC), eine Aufgabe die er fast dreißig Jahre lang ausführte. Während dieser Zeit verfasste er auch über 200 RFC, unter anderem grundlegende wie RFC 791 bis RFC 793 (IP, ICMP und TCP) und RFC 2223 (Instructions to RFC Authors).

Sein vielleicht bekanntestes Vermächtnis stammt aus RFC 793, welches das Robustheitsprinzip beinhaltet und oft als Postels Gesetz bezeichnet wird: „be conservative in what you do, be liberal in what you accept from others“ („sei konservativ in dem was Du tust, sei tolerant in dem was Du von anderen akzeptierst“, oft formuliert als „sei konservativ in dem was Du sendest, sei tolerant in dem was Du empfängst“).

Dieses Prinzip klingt ziemlich vernünftig und wurde bei der TCP/IP-Implementierung und bei der Implementierung von Webbrowsern umgesetzt. Bei Protokollen bedeutet es, "halte dich genau an die Spezifikation beim Senden, sei aber tolerant beim Parsen der empfangenen Daten". Solange sich jeder an das Prinzip hält, funktioniert das prima. Wehe aber, wenn sich die Sender nicht alle an das Prinzip halten. Eine Zeit lang wird das funktionieren, das ist ja der Sinn dieses Prinzips.

Problematisch wird die Sache jedoch, wenn es Änderungen oder Erweiterungen des Protokolls gibt. Wie gesagt, kein Problem, wenn sich alle daran halten. Wenn sich nicht alle daran halten, es dann vielleicht aufgrund des Marktes ergibt, dass sich eine nicht-Standard-konforme Software weit verbreitet, kann es passieren, dass vieles nicht mehr funktioniert.

Ein Beispiel ist das World Wide Web mit HTML (ist zwar kein Protokoll sondern eine Auszeichnungssprache, das Protokoll ist HTTP): die Webbrowser waren sehr tolerant gegenüber dem empfangenen HTML-Code und versuchten eine "möglichst gute" Darstellung des Inhalts. Manche Browser waren da sehr tolerant (IE), andere sehr viel weniger (Netscape, Mozilla).

Ein Beispiel


<b>das ist fett und das ist <i>fett-und-kursiv</b></i>

das ist für den Menschen kein Problem, es sollte etwa so aussehen:

das ist fett und das ist fett-und-kursiv

Viele Browser haben solche kleinen Fehler toleriert. Die Folge war, dass es total viele schlechte Web-Seiten gab, da sie ja "funktionierten" (im IE). Sogar WYSIWYG-Tools produzierten syntaktisch fehlerhaften Code, nicht so offensichtlich wie mein Beispiel aber doch fehlerhaft. Übrigens wird das Programmierern wesentlich seltener passieren, denn die sind ja gewöhnt, dass ein Compiler jeden kleinen falschen Punkt, Beistrich oder Strichpunkt "bemeckert".
Offensichtlich führt hier die zu große Toleranz zu großen Problemen. Diese Probleme sind für jeden Web-Entwickler ganz deutlich, er muss 23 verschiedene Browser testen, 17 verschiedene Tricks anwenden, damit die Web-Seite überall "richtig" dargestellt wird. Dabei ist natürlich dazugekommen, dass nicht nur HTML unterschiedlich tolerant interpretiert wird, sondern auch CSS und natürlich JavaScript (auf einem Browser läuft's, auf einem andern geht gar nichts, weil ein Syntaxfehler in einem Zweig drinnen ist, der praktisch nie erreicht wird).

Das Robustheitsprinzip von Postel hat uns ein Riesen-Deployment-Problem beschert - echt schade, es klingt wirklich logisch und gut, ich hätte lieber mehr positives zum Robustheitsprinzip geschrieben.

Wir hätten weniger Probleme, wenn sich die Browser nicht so tolerant gegenüber fehlerhaftem HTML-Code verhalten hätten. Vielleicht hätten schon viel früher Normungsbestrebungen einsetzen müssen. Allerdings ist es fraglich, ob sich das Web so schnell und so weit entwickelt hätte, wenn HTML von anfang an genormt gewesen wäre bzw. wenn sich die Browser intoleranter verhalten hätten. Wahrscheinlich würden dann nur Programmierer Web-Sites erstellen (können), es wären viel weniger "Kreative" auf den WWW-Zug gesprungen. Alles wäre viel einfacher - nicht?

Man kann dieses Prinzip auch (na ja ein bisschen) auf das Lernen umsetzen (bei HTML haben viele Web-Designer nie ordentlich HTML gelernt, weil es ja nie Fehler gab):
Man lernt am meisten durch Fehler, das wissen viele, wenn aber nie Fehler angezeigt werden, lernt man nichts oder man lernt etwas ganz falsches (wobei es natürlich für das Selbstwertgefühl schon sehr gut ist, wenn man auch Feedback für richtige Sachen bekommt).

Quellen: Eigene Erfahrung, Wikipedia, Joel On Software: Martian Headsets (sehr lesenswert, ich wünschte, ich könnte so schreiben)

Labels:


 

Anzeigen und Laden von Bildern mit Java

Folgendes Beispiel ermöglicht es Bilder anzuzeigen. Die Bilder werden in etwa auf die Fenstergröße skaliert (beim Öffnen). Weiters kann man das angezeigte Bild immer um 90 Grad drehen. Das Beispiel soll nur gewisse Grundfunktionalität zeigen. Es beinhaltet auch einige grafische Hilfsmethoden, die ich gegoogelt habe (rotieren mit rotateImage(), skalieren).
Das Beispiel ist nicht ausgereift sondern ein bisschen buggy (das Skalieren sollte über Menü ausgewählt werden können, das Format des Steuerelements/Bildes passt nach dem Rotieren nicht ganz). Als Basis für weitere Arbeiten ist's jedoch ganz brauchbar.

/**
* Beispiel für Bilderanzeige. Kann Bilddateien öffnen. Bild wird in etwa auf
* Fenstergröße skaliert.
* Rotieren eines Bildes ist möglich.
* Alle nötigen Klassen in dieser Datei.
* FIXME: Ist noch ein bisschen Buggy: Skalieren sollte man extra aufrufen
* können, ImageComponent passt nicht ganz nach rotieren (oder die Grafik?).
*
* @author Harald R. Haberstroh
*/
package image;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.PixelGrabber;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JScrollPane;
import javax.swing.KeyStroke;
import javax.swing.filechooser.FileFilter;

/**
* Bildkomponente, kann Bilder zeichnen, skalieren und rotieren.
*
* @author Harald R. Haberstroh (hh)
*
*/
class ImageComponent extends JComponent {
private static final long serialVersionUID = 8055865896136562197L;

private BufferedImage image; // gelesenes Bild

private Image scaled; // Hilfsvariable für skaliertes Bild

/**
* über diese Methode kann man eine Datei angeben, die geffnet und angezeigt
* wird.
*
* @param file
* Bilddatei
*/
public void setImage(File file) {
try {
if ((image = ImageIO.read(file)) != null) {
// Skalieren, unterscheide Hoch- und Querformat
double scale = 1.0;
if (image.getWidth() > image.getHeight()) { // Querformat
scale = (double)getParent().getWidth() / image.getWidth();
} else { // Hochformat
scale = (double)getParent().getHeight() / image.getHeight();
}
scaled = image.getScaledInstance((int)(image.getWidth() * scale),
(int)(image.getHeight() * scale), Image.SCALE_FAST);
image = toBufferedImage(scaled);
setPreferredSize(new Dimension(image.getWidth(), image.getHeight()));
repaint();
}
} catch (IOException e) {
e.printStackTrace();
}
}

/**
* rotieren der aktuellen Grafik.
*
* @param degrees
* um soviele Grad wird gedreht (im Uhrzeigersinn).
*/
public void rotate(double degrees) {
image = rotateImage(image, degrees);
setPreferredSize(new Dimension(image.getWidth(), image.getHeight()));
repaint();
}

/**
* Hilfsmethode, die eine Grafik (BufferedImage) drehen kann. Verwendet
* AffineTransform zum Drehen. Wichtig ist, den Drehpunkt in die Mitte der
* Grafik zu legen.
*
* @param src
* Grafik
* @param degrees
* um soviele Grad wird gedreht
* @return neue gedrehte Grafik
*/
private static BufferedImage rotateImage(BufferedImage src, double degrees) {
AffineTransform affineTransform = AffineTransform.getRotateInstance(Math
.toRadians(degrees), src.getWidth() / 2, src.getHeight() / 2);
BufferedImage rotatedImage = new BufferedImage(src.getWidth(), src
.getHeight(), src.getType());
Graphics2D g = (Graphics2D)rotatedImage.getGraphics();
g.setTransform(affineTransform);
g.drawImage(src, 0, 0, null);
return rotatedImage;
}

/**
* hier wird gezeichnet..
*/
@Override
protected void paintComponent(Graphics g) {
if (image != null) {
g.drawImage(image, 0, 0, this);
}
}

/**
* Hilfsmethode, die ein Image in ein BufferedImage umwandelt, da direktes
* Umwandeln nicht möglich. Benötigt man z.B. für Saklieren, da dort ein Image
* geliefert wird, sonst aber BufferedIMage benötigt wird.
*
* @param image
* Image
* @return BufferedImage
*/
public static BufferedImage toBufferedImage(Image image) {
if (image instanceof BufferedImage) {
return (BufferedImage)image;
}

// wirklich alle Pixel des image laden:
image = new ImageIcon(image).getImage();

// Gibt es transparente Pixel?
boolean hasAlpha = hasAlpha(image);

BufferedImage bimage = null;
// Erzeuge buffered image mit dem default color model
int type = BufferedImage.TYPE_INT_RGB;
if (hasAlpha == true) {
type = BufferedImage.TYPE_INT_ARGB;
}
bimage = new BufferedImage(image.getWidth(null), image.getHeight(null),
type);

// Kopiere image zu buffered image über den Graphic-Kontext:
Graphics g = bimage.createGraphics();

// Zeichne nun das Image auf den neuen Gracphic-Kontext (und damit auf das
// buffered image)
g.drawImage(image, 0, 0, null);
g.dispose();

return bimage;
}

/**
* Hilfsmethode, die bestimmt, ob Transparenz vorhanden.
*
* @param image
* @return hat Alpha (Transparenz)
*/
public static boolean hasAlpha(Image image) {
// Wenn das image bereits ein buffered image, dann gibt's schon ein color
// model
if (image instanceof BufferedImage) {
return ((BufferedImage)image).getColorModel().hasAlpha();
}

// Verwende einen pixel grabber um das color model (Farbmodell) zu
// bestimmen.
// Ein einzelnes Pixel genügt.
PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false);
try {
pg.grabPixels();
} catch (InterruptedException e) {
}

// von dem Pixel wird nun das color model bestimmt und zurückgeliefert
return pg.getColorModel().hasAlpha();
}
}

/**
* Aktion für Dateiauswahl.
*
* @author Harald R. Haberstroh (hh)
*
*/
class FileOpenAction implements ActionListener {
private final ImageComponent viewComponent;

public FileOpenAction(ImageComponent viewComponent) {
this.viewComponent = viewComponent;
}

/**
* Dateiauswahldialog anzeigen.
*/
public void actionPerformed(ActionEvent e) {
JFileChooser d = new JFileChooser();
d.setFileFilter(new FileFilter() {
@Override
public boolean accept(File f) {
return f.isDirectory() || f.getName().toLowerCase().endsWith(".jpg")
|| f.getName().toLowerCase().endsWith(".gif");
}

@Override
public String getDescription() {
return "*.jpg;*.gif";
}
});

d.showOpenDialog(null);
File file = d.getSelectedFile();

if (file != null)
viewComponent.setImage(file);
}
}

/**
* Aktion für Rotieren.
*
* @author Harald R. Haberstroh (hh)
*
*/
class RotateAction implements ActionListener {
private final ImageComponent viewComponent;

public RotateAction(ImageComponent viewComponent) {
this.viewComponent = viewComponent;
}

public void actionPerformed(ActionEvent e) {
viewComponent.rotate(90.0);
}

}

/**
* Einfacher Bildbetrachter.
*
* @author Harald R. Haberstroh (hh)
*
*/
public class ImageViewer {
public static void main(String[] args) {
JFrame f = new JFrame("Bildbetrachter");

ImageComponent viewComponent = new ImageComponent();
f.add(new JScrollPane(viewComponent)); // zum Scrollen, falls Bild zu groß

JMenuBar mbar = new JMenuBar();
JMenu menu = new JMenu("Datei");
JMenuItem item = new JMenuItem("Öffnen");
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O,
InputEvent.CTRL_MASK));
item.addActionListener(new FileOpenAction(viewComponent));
menu.add(item);
item = new JMenuItem("Drehen");
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R,
InputEvent.CTRL_MASK));
item.addActionListener(new RotateAction(viewComponent));
menu.add(item);
mbar.add(menu);
f.setJMenuBar(mbar);

f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(600, 400);
f.setLocationRelativeTo(null); // zentrieren auf Bildschirmmitte
f.setVisible(true);
}
}

Labels: ,


Mittwoch, 21. Januar 2009

 

Verzeichnisse mit Java durchsuchen

Die File-Klasse (java.io.File) dient nicht nur dazu, einzelne Dateien zu behandeln, man kann ein Objekt vom Typ File auch für Verzeichnisse verwenden. Die Methode listFiles() ist dafür geeignet. Folgende Beispielklasse liefert eine Liste von Dateien, auf die ein bestimmtes Muster passt. Dazu wird ein regulärer Ausdruck verwendet (java.util.regex.Pattern). Die Klasse durchsucht nicht rekursiv auch alle Unterverzeichnisse und verwendet dazu einen Stack (java.util.Stack). Die Schreibweise List<File> beschränkt die Liste auf den (Objekt-)Typ File:

/**
* java-directories/utils.dir.FileFinder.java
* @author (c) 2009, Harald R. Haberstroh
* 21.01.2009
*/
package utils.dir;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import java.util.regex.Pattern;

/**
* Suchen aller Dateien in einem Verzeichnis bzw. Unterverzeichnissen nach einem
* bestimmten Muster (entnommen aus "Java ist eine Insel").
*
* Ergänzt um Logging, da NullPointerException bei Verzeichnis mit seeeeehr
* vielen Dateien und Unterverzeichnissen.
* Tatsächliche Fehlerursache waren symbolische Links auf Verzeichnisse, auf
* die ich keine Leserechte besaß.
*
* @author Harald R. Haberstroh (hh)
*
*/
public class FileFinder {
private static Logger logger = Logger.getAnonymousLogger();
/**
* initialisiere Klasse, setze Logging aus
*/
static {
logger.setLevel(Level.OFF); // default kein Logging
}

/**
* Setze Logging Level
*
* @param level
*/
public static void setLogLevel(Level level) {
logger.setLevel(level);
}

/**
* Logging zusätzlich in Datei (*.txt Text, XML sonst)
*
* @param filename
* Logfilename
*/
public static void setLogFile(String filename) {
try {
if (filename.endsWith("txt")) { // lesbarer Text in Datei
Handler handler = new FileHandler(filename);
handler.setFormatter(new SimpleFormatter());
logger.addHandler(handler);
} else { // sonst XML (default)
logger.addHandler(new FileHandler(filename));
}
} catch (IOException e) {
e.printStackTrace(); // kann Logfile nicht schreiben, seeeehr schlimm
}
}

/**
* suche alle Dateien mit gegebenem Muster.
*
* @param start
* Startverzeichnis
* @param extensionPattern
* Dateimuster (regulärer Ausdruck)
* @return Liste von Dateien
*/
public static List find(String start, String extensionPattern) {
List files = new ArrayList(1024);
Stack dirs = new Stack();
File startdir = new File(start);
Pattern p = Pattern.compile(extensionPattern, Pattern.CASE_INSENSITIVE);

if (startdir.isDirectory()) {
logger.info(startdir + " ist ein Verzeichnis");
dirs.push(startdir); // Startverzeichnis auf den Stack
}

while (dirs.size() > 0) { // solange ein Verzeichnis am Stack
try {
File[] listFiles = dirs.pop().listFiles();
logger.info("prüfe " + listFiles.length + " Dateien");
for (File file : listFiles) { // alle Dateien des Verzeichnisses
if (file.isDirectory()) {
logger.info(file + " ist ein Verzeichnis");
dirs.push(file);
} else {
logger.info(file + " ist eine Datei");
if (p.matcher(file.getName()).matches()) {
files.add(file); // passt das Muster, dann in die Liste
}
}
}
} catch (Exception e) {
logger.severe(e.getLocalizedMessage());
e.printStackTrace();
System.exit(1);
}
}
return files;
}
}

Die Klasse verwendet Logging, welches aber standardmäßig abgeschaltet ist. Beachten Sie das Codestück

static {
logger.setLevel(Level.OFF); // default kein Logging
}

welches beim Laden der Klasse ausgeführt wird (fast immer beim Starten des Programmes). Diese Anweisungen sind nicht an ein bestimmtes Objekt gebunden.

Verwenden kann man die Klasse z.B. zum Ermitteln aller Bilddateien in gegebenen Verzeichnissen:

/**
* listet alle Bilddateien der gegebenen Verzeichnisse.
*
* @param args
* Verzeichnisse, aktuelles Verzeichns, falls leer
*/
public static void main(String[] args) {
// Logging aktivieren durch entfernen der Kommentarzeichen:
// FileFinder.setLogFile("filefinder.txt");
// FileFinder.setLogLevel(Level.ALL);
String[] dirs = null;
if (args.length > 0) {
dirs = args;
} else {
dirs = new String[1];
dirs[0] = new File(System.getProperty("user.dir")).getAbsolutePath();
}
for (String dir : dirs) {
List files = FileFinder.find(dir,
"(.*\\.gif$)|(.*\\.png$)|(.*\\.jpg$)"); // *.gif, *.png, *.jpg
System.out.printf("Verzeichns %s - %s Datei%s:\n", dir,
files.size() == 0 ? "keine" : "" + files.size(),
files.size() == 1 ? "" : "en");
for (File file : files) {
System.out.println(" " + file.getAbsolutePath());
}

}
}

Labels: ,


Dienstag, 20. Januar 2009

 

Java Bildbetrachter

Erstellen Sie in Java einen Bildbetrachter, der folgende Eigenschaften aufweist:
  1. Ein angegebenes Verzeichnis soll rekursiv bearbeitet werden.
  2. Zu allen Bilddateien in dem angegebenen Verzeichnis sollen Meta-Daten erfasst werden (Stichworte, Titel, Ort, Datum der Aufnahme, Kameraeinstellungen etc.).
  3. Die Meta-Daten sollen in eigenen Dateien gespeichert werden, wobei der Name der Bilddatei mit einer anderen passenden Endung (z.B. xml) verwendet werden soll.
  4. Existiert zu einer Bilddatei keine Meta-Datei, so soll diese automatisch erzeugt werden (als Titel wird der Dateiname eingesetzt, als Datum das Datum der Bilddatei, eventuell können diese Grunddaten aus den Bilddateien ermittelt werden).
  5. Es soll durch alle Bilder geblättert (Normalansicht) werden können und die Metadaten erfasst/geändert werden.
  6. Eine Diaschau soll möglich sein.
  7. Seine Suchfunktion soll implementiert werden (suchen in allen Meta-Daten), dabei werden dann nur jene Bilder angezeigt, auf die das Suchkriterium passt (sowohl in der Normalansicht als auch in der Diaschau).

Aufgaben

Beginnen Sie mit dem Anlegen des Projekts mit dem vorgegebenen Namen (klasse-picture-name). Erstellen Sie eine Datei index.html. In dieser Datei muss eine Beschreibung des Projekts zu finden sein. Es muss deutlich der Autor erkennbar sein. Schreiben Sie in diese Datei eine grobe Zeitschätzung. In weiterer Folge soll in dieser Datei der Entwurf beschrieben werden.
Wenn der Entwurf einigermaßen steht, machen Sie eine Zeitschätzung für jede der Komponenten (Klassen).
Diese Datei dient auch als Projekttagebuch, d.h. Sie vermerken jedesmal, wenn Sie am Projekt arbeiten einen Eintrag (einchecken!!).

Ihre erste Komponente soll eine Hilfsklasse für das Bearbeiten von Verzeichnissen und Dateien sein. Diese Klasse soll eine ArrayList von Dateinamen erstellen, indem sie den Verzeichnisbaum durchläuft. In weiterer Folge soll sie eine Liste von Bildobjekten erzeugen. Ein Bildobjekt enthält den Dateinamen des Bildes sowie die Meta-Informationen.

Verwenden Sie Logging, um die Abläufe leichter testen zu können. Außerdem können Sie Fehlermeldungen an den Logger weitergeben (zusätzlich zu Fehlermeldungen, die dann in der graphischen Oberfläche dem Benutzer angezeigt werden).

Weitere Informationen an dieser Stelle.

Labels: , ,


 

Logging mit Java

Folgendes Beispiel demonstriert verschiedene Logging-Mechanismen des in Java integrierten Loggers java.util.logging.Logger. Die Einstellungen des Loggers werden in logging.properties (liegt normalerweise im lib-Verzeichnis der JRE, also z.B. /usr/lib/jvm/java-1.6.0.u11/jre/lib/logging.properties).
Diese Datei sieht etwa so aus:

############################################################
# Default Logging Configuration File
#
# You can use a different file by specifying a filename
# with the java.util.logging.config.file system property.
# For example java -Djava.util.logging.config.file=myfile
############################################################

############################################################
# Global properties
############################################################

# "handlers" specifies a comma separated list of log Handler
# classes. These handlers will be installed during VM startup.
# Note that these classes must be on the system classpath.
# By default we only configure a ConsoleHandler, which will only
# show messages at the INFO and above levels.
handlers= java.util.logging.ConsoleHandler

# To also add the FileHandler, use the following line instead.
#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler

# Default global logging level.
# This specifies which kinds of events are logged across
# all loggers. For any given facility this global level
# can be overriden by a facility specific level
# Note that the ConsoleHandler also has a separate level
# setting to limit messages printed to the console.
.level= INFO

############################################################
# Handler specific properties.
# Describes specific configuration info for Handlers.
############################################################

# default file output is in user's home directory.
java.util.logging.FileHandler.pattern = %h/java%u.log
java.util.logging.FileHandler.limit = 50000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter

# Limit the message that are printed on the console to INFO and above.
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter


############################################################
# Facility specific properties.
# Provides extra control for each logger.
############################################################

# For example, set the com.xyz.foo logger to only log SEVERE
# messages:
com.xyz.foo.level = SEVERE


Will man eine andere Konfiguration verwenden, so kann sie beim Java-Aufruf angegeben werden:

java -Djava.util.logging.config.file=/pfad/zur/datei/logging.properties StartKlasse

Es folgt das Beispiel, bei dem in einer Klasse in einer Methode div() verschiedene Log-Aufrufe gezeigt werden. Die Konfiguration des Loggers erfolgt über den Konstruktor.

Normalerweise verwendet man einen Logger für ein Programm. Das enstpricht private Logger jlog = Logger.getLogger("at.haberstroh");, wobei man den Logger gleich static macht, da getLogger() ohnedies das selbe Objekt liefert.

/**
* java-logging/log.LogDemo.java
* @author (c) 2009, Harald R. Haberstroh
* 20.01.2009
*/
package log;

import java.io.IOException;
import java.util.logging.FileHandler;
import java.util.logging.Filter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

/**
* Demonstration von Logging.
*
* @author Harald R. Haberstroh (hh)
*
*/
public class LogDemo {
/*
* ein Logger für alle Instanzen - in der Logdatei sollten ALLE div()-Aufrufe
* sein
*/
// private Logger jlog = Logger.getLogger("at.haberstroh");
/*
* einLogger pro Instanz - in der Logdatei sollten nur die beiden
* div()-Aufrufe sein
*/
private Logger jlog = Logger.getAnonymousLogger();

/**
* Objekt mit Default-Logger
*/
public LogDemo() {
jlog.info("erzeuge LogDemo-Objekt");
}

/**
* @param filter
* Log-Filter
*/
public LogDemo(Filter filter) {
this("log.txt");
jlog.setLevel(Level.ALL);
jlog.setFilter(filter);
jlog.info("erzeuge LogDemo-Objekt");
}

/**
* @param level
* Level, der angezeigt wird
*/
public LogDemo(Level level) {
jlog.setLevel(level);
jlog.info("erzeuge LogDemo-Objekt");
}

/**
* Log in Datei, *.txt -> lesbar, sonst XML.
*
* @param filename
* Name der Log-Datei
*/
public LogDemo(String filename) {
try {
if (filename.endsWith("txt")) { // lesbarer Text in datei
Handler handler = new FileHandler(filename);
handler.setFormatter(new SimpleFormatter());
jlog.addHandler(handler);
} else { // sonst XML (default)
jlog.addHandler(new FileHandler(filename));
}
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
jlog.info("erzeuge LogDemo-Objekt");
}

/**
* unsere Demo-Methode, dividier a durch b.
*
* @param a
* @param b
* @return a/b
*/
public double div(int a, int b) {
jlog.fine("super, bin in div");
jlog.entering("LogDemo", "div"); // verschiedene Log-DingsDas
jlog.info("entering div");
if (b == 0) {
jlog.log(Level.SEVERE, "Division durch 0 ");
}
double result = 0.0;
result = (double)a / b;
jlog.exiting("LogDemo", "div", result);
return result;
}

/**
* @param args
*/
public static void main(String[] args) {
LogDemo demo;
double r;
System.out.println("*** LogDemo() ***");
demo = new LogDemo();
r = demo.div(5, 2);
System.out.printf("Ergebnis: %f\n", r);
r = demo.div(2, 0);
System.out.printf("Ergebnis: %f\n", r);

// Logdatei log.xml
System.out.println("*** LogDemo(\"log.xml\") ***");
demo = new LogDemo("log.xml");
r = demo.div(5, 2);
System.out.printf("Ergebnis: %f\n", r);
r = demo.div(2, 0);
System.out.printf("Ergebnis: %f\n", r);

// Logdatei log.txt
System.out.println("*** LogDemo(\"log.txt\") ***");
demo = new LogDemo("log.txt");
r = demo.div(5, 2);
System.out.printf("Ergebnis: %f\n", r);
r = demo.div(2, 0);
System.out.printf("Ergebnis: %f\n", r);

System.out.println("*** LogDemo(Level.OFF) ***");
// logging off siehe entsprechenden Konstruktor:
demo = new LogDemo(Level.OFF);
r = demo.div(5, 2);
System.out.printf("Ergebnis: %f\n", r);
r = demo.div(2, 0);
System.out.printf("Ergebnis: %f\n", r);

System.out.println("*** LogDemo(Level.ALL) ***");
demo = new LogDemo(Level.ALL); // logging alles
r = demo.div(5, 2);
System.out.printf("Ergebnis: %f\n", r);
r = demo.div(2, 0);
System.out.printf("Ergebnis: %f\n", r);

System.out.println("*** LogDemo(new Filter() ...) ***");
demo = new LogDemo(new Filter() {
@Override
public boolean isLoggable(LogRecord record) {
String msg = record.getMessage();
boolean log = msg.toLowerCase().contains("return");
// System.out.println(".isLoggable(" + msg + ") " + log);
if (log) {
return true;
} else {
return false;
}
}

});
r = demo.div(5, 2);
System.out.printf("Ergebnis: %f\n", r);
r = demo.div(2, 0);
System.out.printf("Ergebnis: %f\n", r);

}

}

Durch einen Filter kann zur Laufzeit bestimmt werden, was geloggt wird (in Abhängigkeit des Levels).

Labels: , ,


 

Informationen zu den Labels

Die Postings werden mit verschiedenen Labels versehen. Hier die Bedeutung:

Labels: , , ,


Montag, 12. Januar 2009

 

Suchen und markieren in der Eingabe - Musterlösung

Musterlösung zu Suchen und markieren in der Eingabe.
Der Kernpunkt ist das Suchen in Strings. Das kann z.B. mit der Methode indexOf() gemacht werden. indexOf() liefert einen Wert kleiner 0, wenn der Suchstring nicht gefunden wurde. Daher benötigt man eine Schleife über das Ergebnis von indexOf(), da der Suchbegriff mehrmals vorkommen kann. Man muss nur immer im noch nicht durchsuchten Rest weitersuchen.
In der Lösung wird immer ein String gesucht und markiert. Die Methode wird dann für jeden Suchbegriff aufgerufen. Effizienter wäre möglicherweise eine zeilenorientierte Bearbeitung, bei der immer alle Begriffe in einer Zeile geprüft werden.

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;

/**
* java-such-mark: .SuchMark.java
*
* Zweck: markiere alle Suchbegriffe in der Eingabe durch Großschreibung und
* Sternchen davor und danach.
*
* 10.01.2009, Harald R. Haberstroh
*/

/**
* TODO Zweck
*
* @author Harald R. Haberstroh (user)
*
*/
public class SuchMark {

private String[] suchbegriffe;

/**
* @param suchbegriffe
*/
public SuchMark(String[] suchbegriffe) {
this.suchbegriffe = suchbegriffe;
}

/**
* markiere alle Vorkommnisse der suchbegriffe im String content und liefere
* das Ergebnis zurück.
*
* @param content
* Eingabetext
* @return Text mit Markierungen
*/
public String mark(String content) {
for (String what : suchbegriffe) {
content = mark(content, what);
}
return content;
}

/**
* markiere alle Vorkommnisse des Suchbegriffs what im Text content. Suche die
* Position des Suchbegriffs mit indexOf() und verkleinere den content auf den
* Rest hinter den Suchbegriff, nachdem der String vor dem Suchbegriff und der
* markierte Suchbegriff an einen StringBuilder angehängt wurde.
*
* @param content
* Text
* @param what
* Suchbegriff
* @return Text mit Markierungen
*/
private String mark(String content, String what) {
StringBuilder sb = new StringBuilder();
int pos;
int len = what.length();
String wHAT = what.toUpperCase(); // der großgeschriebene Suchbegriff
while ((pos = content.indexOf(what)) >= 0) {
sb.append(content.substring(0, pos)); // alles vor Suchbegriff dazu
sb.append('*'); // Markierung...
sb.append(wHAT);
sb.append('*');
content = content.substring(pos + len); // alles vor Suchbegriff und
// Suchbegriff selbst entfernen
}
sb.append(content); // den Rest noch anhängen
return sb.toString();
}

/**
* liest eine ganze Textdatei.
*
* @param in
* geöffneter BufferedReader (Inputstream)
* @return gelesener String
* @throws IOException
*/
public String readStream(BufferedReader in) throws IOException {
StringBuilder sb = new StringBuilder();
String line;
while ((line = in.readLine()) != null) {
sb.append(line);
sb.append("\n");
}
return sb.toString();
}

/**
* schreibt den String in Textdatei.
*
* @param out
* PrintStream zum Schreiben
* @param content
* Text zum Speichern
*/
public void writeStream(PrintWriter out, String content) {
out.write(content);
out.flush();
}

/**
* Hilfe ausgeben und Ende
*/
private static void help() {
System.out.println("Markieren der Suchbegriffe in der Eingabe.\n"
+ "Aufruf: java SuchMark [-h] [-o out] suchbegriffe...\n"
+ " -h ... Hilfe\n" + " -o out ... Ausgabe in die Datei out\n"
+ " suchbegriffe ... ein oder mehrere Suchbegriffe");
System.exit(0);
}

/**
* Ausgeben einer Fehlermeldung und optional beenden des Programms.
*
* @param exitStatus
* Programm beenden, wenn != 0
* @param format
* Formatstring (Text)
* @param objects
* Weiterer Parameter
*/
private static void fehler(int exitStatus, String format, Object... objects) {
System.err.print("FEHLER in SuchMark: ");
System.err.printf(format, objects);
if (exitStatus != 0) {
System.exit(exitStatus);
}
}

/**
* @param args
*/
public static void main(String[] args) {
if (args.length > 0) {
PrintWriter out = new PrintWriter(System.out);
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
if (args[0].equals("-h")) {
help();
} else if (args[0].equals("-o")) {
// Ausgabedatei
if (args.length > 2) { // -o out such...
try {
out = new PrintWriter(args[1]);
} catch (FileNotFoundException e) {
fehler(2, "Kann Datei %s nicht für Ausgabe öffnen! (%s)\n",
args[1], e.getLocalizedMessage());
}
// Erzeuge neues args nur mit den Suchbegriffen...
String[] argumente = new String[args.length - 2];
for (int i = 0; i < argumente.length; i++) {
argumente[i] = args[i + 2];
}
args = argumente;
} else {
fehler(1,
"Es muss bei Option -o der Name einer Datei und mindestens "
+ "\nein Suchbegriff angegeben werden!\n");
}
}
// Suchbegriffe bearbeiten
SuchMark suchmark = new SuchMark(args);
String content = null;
try {
content = suchmark.readStream(in);
} catch (IOException e) {
fehler(3, "Ein-/Ausgabefehler: %s\n", e.getLocalizedMessage());
}
String markedContent = suchmark.mark(content);
suchmark.writeStream(out, markedContent);
out.close();
} else {
fehler(0, "mindestens einen Suchbegriff angeben. Hilfe mit -h\n");
}
}

}

Labels: , ,


Samstag, 10. Januar 2009

 

Suchen und markieren in der Eingabe

Allgemeines

Erstellen Sie ein Projekt klasse-suchmark-name, z.B. 2cd-suchmark-mueller. Das Programm soll Strings in der Eingabe suchen und markieren, indem sie großgeschrieben werden (alle Buchstaben) und mit Sternchen vor und nach dem Suchstring markiert werden (siehe Aufrufbeispiele).

Aufgabe 1 – SuchMark, readStream(), writeStream()

Erstellen Sie eine Klasse SuchMark mit einem Konstruktor SuchMark(String[] such). such ist ein Array mit Suchbegriffen, die markiert werden sollen.

public String readStream(BufferedReader in) liest bis zu EOF alles in einen String. Implementieren Sie die Methode möglichst effizient (Stichwort "Shlemiel the Paitner").

public void writeSream(PrintWriter out, String content) schreibt den gesamten String content auf den Stream out (Datei oder stdout).

Aufgabe 2 – mark()

public String mark(String content) markiert die im Konstruktor angegebenen Worte im Text content,indem alle Buchstaben groß geschrieben werden und davor und danach ein Sternchen steht.

Der String mit den markierten Worten wird zurückgeliefert.

Aufgabe 3 – main()

Erstellen Sie eine main()-Methode, sodass die Klasse als Programm verwendet werden kann.

Das Programm soll immer von stdin lesen, die Ausgabe soll entweder auf stdout oder in eine Datei erfolgen. Die Suchbegriffe sind auf der Kommandozeile anzugeben.

Aufruf: java SuchMark [-h] [-o out] suchbegriff...

-h ... Hilfe ausgeben und Programm beenden

-o out ... optionale Ausgabe in Datei out

suchbegriff ... ein oder mehrere Suchbegriffe

Aufrufbeispiele:


hp@linux $ cat test.txt
Viel Erfolg im neuen Jahr!
Übung macht den Meister!
Erfolg ist das was folgt.
hp@linux $ java SuchMark folg neu < test.txt
Viel Er*FOLG* im *NEU*en Jahr!
Übung macht den Meister!
Er*FOLG* ist das was *FOLG*t.
hp@linux $ java SuchMark -o ausgabe.txt folg neu < test.txt
hp@linux $ cat ausgabe.txt
Viel Er*FOLG* im *NEU*en Jahr!
Übung macht den Meister!
Er*FOLG* ist das was *FOLG*t.
hp@linux $ java SuchMark -h
Markieren der Suchbegriffe in der Eingabe.
Aufruf: java SuchMark [-h] [-o out] suchbegriffe...
-h ... Hilfe
-o out ... Ausgabe in die Datei out
suchbegriffe ... ein oder mehrere Suchbegriffe

Labels: , ,


Freitag, 9. Januar 2009

 

Integer aus Eingabestrom in Array, formatierte Ausgabe

Allgemeines

Erstellen Sie ein Projekt klasse-intNumbers-name, z.B. 2ad-intNumbers-mueller.

Schreiben Sie ein Programm zum Auffinden von ganzen Zahlen in Textdateien.

Die gefundenen Zahlen speichern Sie in einem int-Array und geben Sie am Ende der Verarbeitung in Tabellenform aus.

Aufgabe 1 – FindIntNumbers(), find(), fill()

Erstellen Sie eine Klasse FindIntNumbers mit einem Konstruktor FindIntNumbers(int value). value ist die, aus der Kommandozeile eingelesene Größe eines anzulegenden Arrays zum Speichern der Zahlen.

public void find(BufferedReader in) liest pro Datei bis zu EOF alle Zeichen des Streams ein und sucht nach ganzen Zahlen. Ganze Zahlen bestehen nur aus zusammenhängenden Ziffern (1-n, begrenzt durch den Wertebereich vom integer-Datentyp). Die Zahlen werden durch beliebige Zeichen getrennt. Die gefundenen Zahlen werden in einem StringBuffer zusammengestellt und mit der Methode fill() im angelegten Array abgespeichert.

public void fill(StringBuffer sb)füllt die Zahl aus dem StringBuffer in das nächste freie Arrayelement. (maximal size Zahlen!). Bei Array-Überlauf bitte eine Fehlermeldung auf stderr ausgeben.

Aufgabe 2 – toString()

public String toString() erstellt aus dem Array die Ausgabe in Tabellenform.

Die Formatierung der Zahlen (Spaltenbreite) wird durch die größte gefundene Zahl bestimmt, damit die Zahlen in der Tabelle linksbündig ausgerichtet untereinander stehen. In einer Zeile der Tabelle dürfen nicht mehr als 80 Zeichen stehen!

Beispiel:

Tabelle der gefundenen Zahlen

43 515 45 43 3567 24 36 23

478 6 59 346 1356887656 266 57 3624

487 346 57 345 245 6 6 3

3567 4 2134 934 193 4 934 934

931 193 934 3498 1943 190843 314 143

4 4 43 13

zugehörige Eingabedatei:

43 515 45 43 3567 24 36 23 478 6 59 346 1356887656 266 57 3624 487 346 57 345

245 6 gfr 6z3 qg tg rtg3567 4g

sadjfhalsd 2134h c934h fcnc193h4c nx934hc934ncjkd c931hqnc193hcudc 934

3498f 1943j190843jx n314dj143..4.4.43c13

Aufgabe 3 – main()

Erstellen Sie eine main()-Methode, sodass die Klasse als Programm verwendet werden kann.

Das Programm soll beliebig viele Dateien durchsuchen oder von der Standardeingabe lesen, wenn kein Dateiname angegeben ist. Die Ausgabe soll immer auf Standardausgabe erfolgen.

Aufruf:

java FindIntNumbers [-h] size [dat1 ... datn]

-h ... Hilfe ausgeben und Programm beenden

size ... Zahl, welche die Größe des Arrays angibt

dat1,datn ... aus diesen Dateien wird gelesen

Aufrufbeispiele:

java FindIntNumbers -h ... gibt Hilfe aus und sonst nichts

java FindIntNUmbers 200 dat.txt text.txt ... versucht alle Zahlen in den Dateien dat.txt und text.txt zu finden und gibt maximal die ersten 200 aus..

java FindIntNUmbers 100 ... liest aus der Standardeingabe und sucht nach allen Zahlen und gibt maximal 100 aus.


Lösungsansatz:
Die Klasse FindIntNumbers benötigt folgende Informationen (Eigenschaften):


private static final int zeilenLaenge = 80;
private int[] array;
private int arraySize = 0;
private int fillTo = 0;
private int max = 0;

Im Konstruktor wird das Array angelegt.
Die Methode find() liest aus dem BufferedReader zeichenweise und ermittelt die Zahlen (in einem StringBuffer zusammengebaut) und füllt das Array mittels fill(). find() implementiert einen einfachen Automaten mit zwei Zuständen inZahl und nicht inZahl um zu unterscheiden, ob gerade eine Zahl aus den Eingabezeichen "zusammengebaut" wird oder nicht:

public void find(BufferedReader in) throws IOException {
int c;
boolean inZahl = false;
StringBuffer sb = new StringBuffer();
while ((c = in.read()) != EOF) {
if (inZahl) {
if (!Character.isDigit((char) c)) {
inZahl = false;
fill(sb);
sb = new StringBuffer();
} else {
sb.append((char) c);
}
} else {
if (Character.isDigit((char) c)) {
inZahl = true;
sb.append((char) c);
}
}
}
}

fill() prüft, ob noch Platz im Array ist und konvertiert den StringBuffer in einen int und ermittelt gleichzeitig das Maximum für die Formatierung der Ausgabe (längste Zahl).

public void fill(StringBuffer sb) {
if (fillTo < arraySize) {
array[fillTo] = Integer.parseInt(sb.toString());
if (array[fillTo] > max) {
max = array[fillTo];
}
//System.out.printf("filled %d into array[%d]\n", array[fillTo], fillTo);
fillTo++;
}
}


Die Methode toString() macht die gesamte Formatierungsarbeit, indem zunächst ein Formatstring erstellt wird, bei dem die Länge mittels der Funktion maxlen() eingetragen wird. Die Anzahl der Zahlen pro Zeile ergibt sich aus der Division aus zeilenLaenge / maxlen(). maxlen() liefert die Länge der größten Zahl plus eins (für den Zwischenraum).

public String toString() {
StringBuffer sb = new StringBuffer();
String format = String.format("%%-%dd", maxlen());
int anzZahlenProZeile = zeilenLaenge / maxlen();
int spaltennummer = 0;
for (int i = 0; i < fillTo; i++) {
int zahl = array[i];
sb.append(String.format(format, zahl));
spaltennummer++;
if (spaltennummer >= anzZahlenProZeile) {
spaltennummer = 0;
sb.append("\n");
}
}
if (spaltennummer > 0) {
sb.append("\n");
}
return sb.toString();
}


Das gesamte Programm könnte folgendermaßen aussehen:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;

/**
* lehrer-intNumbers-haberstroh: .FindIntNumbers.java
*
* 17.12.2008, Harald R. Haberstroh
*/

/**
* Auffinden von ganzen Zahlen in Dateien und speichern in einem Array.
*
* @author Harald R. Haberstroh (hp)
*
*/
public class FindIntNumbers {

private static final int EOF = -1;
private static final int zeilenLaenge = 80;
private int[] array;
private int arraySize = 0;
private int fillTo = 0;
private int max = 0;

/**
* @param arraySize
*/
public FindIntNumbers(int arraySize) {
this.arraySize = arraySize;
this.array = new int[this.arraySize];
this.fillTo = 0;
this.max = Integer.MIN_VALUE;
}

/**
* default Konstruktor
*/
public FindIntNumbers() {
this(10);
}

/**
* liest bis EOF und ermittelt alle ganzen Zahlen und speichert sie im Array.
*
* @param in
* Eingabestrom
* @throws IOException
*/
public void find(BufferedReader in) throws IOException {
int c;
boolean inZahl = false;
StringBuffer sb = new StringBuffer();
while ((c = in.read()) != EOF) {
if (inZahl) {
if (!Character.isDigit((char) c)) {
inZahl = false;
fill(sb);
sb = new StringBuffer();
} else {
sb.append((char) c);
}
} else {
if (Character.isDigit((char) c)) {
inZahl = true;
sb.append((char) c);
}
}
}
}

/**
* liefert maxmale Länge einer Zahl in Zeichen.
*
* @return maximale Länge + 1
*/
private int maxlen() {
return Integer.toString(max).length() + 1;
}

/**
* füllt die Zahl im Stringbuffer in die nächste freie Stelle von array.
*
* @param sb
*/
public void fill(StringBuffer sb) {
if (fillTo < arraySize) {
array[fillTo] = Integer.parseInt(sb.toString());
if (array[fillTo] > max) {
max = array[fillTo];
}
System.out.printf("filled %d into array[%d]\n", array[fillTo], fillTo);
fillTo++;
}
}

/**
* liefert schön formatierte Tabelle als String.
*
* @return Tabella als String.
*/
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
String format = String.format("%%-%dd", maxlen());
int anzZahlenProZeile = zeilenLaenge / maxlen();
int spaltennummer = 0;
for (int i = 0; i < fillTo; i++) {
int zahl = array[i];
sb.append(String.format(format, zahl));
spaltennummer++;
if (spaltennummer >= anzZahlenProZeile) {
spaltennummer = 0;
sb.append("\n");
}
}
if (spaltennummer > 0) {
sb.append("\n");
}
return sb.toString();
}

/**
* @param args
*/
public static void main(String[] args) {
if (args.length >= 1) {
if (args[0].equals("-h")) {
System.out.println("Aufruf: java FindIntNumber [-h] size [dateien...]");
} else {
try {
int size = Integer.parseInt(args[0]);
BufferedReader in;
FindIntNumbers fn = new FindIntNumbers(size);
if (args.length > 1) { // Dateien
for (int i = 1; i < args.length; i++) {
in = new BufferedReader(new FileReader(args[i]));
fn.find(in);
in.close();
}
} else { // stdin
in = new BufferedReader(new InputStreamReader(System.in));
fn.find(in);
}
System.out.print(fn.toString());
} catch (NumberFormatException e) {
System.err.println("ungültige Größe angegeben, Hilfe mit -h");
} catch (IOException e) {
System.err.println("Ein-/Ausgabefehler: " + e.getLocalizedMessage());
}
}
} else {
System.err.println("keine Größe angegeben, Hilfe mit -h");
}
}

}

Labels: , , ,


Donnerstag, 8. Januar 2009

 

Caesar-Verschlüsselung

Allgemeines

Erstellen Sie ein Projekt klasse-caesar-name, z.B. 2cd-caesar-mueller.

Schreiben Sie ein Programm zum Knacken einfacher Caesar-Chiffren (z.B. A->C, B->D, C->E,..., Y->A, Z->B).

Verschlüsseln: Der Klartext wird zunächst in Kleinbuchstaben umgewandelt, wobei alle Zeichen, die keine Buchstaben sind, entfernt werden. Dann wird der über die Kommandozeile angegebene Schlüssel Zeichen für Zeichen angewendet. Zum Schluss soll eine zufällige Auswahl an Buchstaben wieder in Großbuchstaben umgewandelt werden und Leerzeichen eingefügt werden.

Entschlüssen: Der verschlüsselte Text wird wieder komplett in Kleinbuchstaben umgewandelt, danach wird der Schlüssel auf der Kommandozeile genau umgekehrt zeichenweise angewendet.

In beiden Fällen wird der (de-)kodierte Text ausgegeben

Knacken des Codes: Folgendes Verfahren funktioniert nur bei längeren sinnvollen Texten. Ermittle die Häufigkeiten der Zeichen. Das häufigste Zeichen entspricht dem E (zweithäufigstes ist das A), d.h. der Schlüssel muss entsprechend angesetzt werden.


Aufgabe 1 – Caesar, readStream(), writeStream()

Erstellen Sie eine Klasse Caesar mit einem Konstruktor Caesar(int key). key ist der Schlüssel (Verschiebung der Zeichen für das De-/Kodieren.

public String readStream(BufferedReader in) liest bis zu EOF alles in einen String. Implementieren Sie die Methode möglichst effizient (Stichwort "Shlemiel the Paitner").

public void writeSream(PrintWriter out, String content) schreibt den gesamten String content auf den Stream out (Datei oder stdout).



Aufgabe 2 – prepare()

public String prepare(String content) entfernt aus content alle Zeichen, die nicht Buchstaben von A bis Z sind und wandelt alle verbleibenden Buchstaben in Kleinbuchstaben um.



Aufgabe 3 – crypt()

public String crypt(String content) verschlüsselt den Text content mit dem im Konstruktor angegebenen key.

Ist key == 0, so bleibt der Text wie er ist, bei key == 1, wird aus jedem A ein B, aus jedem B ein C usw. ist key == 10, so wird aus dem A ein K, aus dem B ein L usw. aus dem Z wird I.



Aufgabe 4 – disguise()

public String disguise(String content) verschleiert den Text durch zufälliges Einstreuen von Leerzeichen bzw. durch zufälliges Umwandlen in Großbuchstaben. Verwenden Sie die Hilfsmethode aus der Datei disguise (vgl. Hinweise).



Aufgabe 5 – decrypt()

public String decrypt(String content) entschlüsselt den Text content mit dem aus dem Konstruktor angegebenen key (bzw. dem aus Aufgabe 6 ermittelten key). Z.B. bei key == 3 wird aus einem A ein X, aus B ein Y, aus C ein Z, aus D ein A usw.



Aufgabe 6 – crack()

public String crack(String content) entschlüsselt den Text, indem das häufigste Zeichen ermittelt wird. Dieses Zeichen entspricht also statistisch gesehen dem E. Ist zum Beispiel das häufigste Zeichen ein K, so ist der Schlüssel key = K-E, also 6. Dann wird die Methode decrypt() verwendet, um den Text zu dekodieren.



Aufgabe 7 – main()

Erstellen Sie eine main()-Methode, sodass die Klasse als Programm verwendet werden kann.

Das Programm soll immer aus einer Datei lesen, die Ausgabe soll entweder auf stdout oder in eine Datei erfolgen.

Verschlüsseln bedeutet immer die Kombination prepare(), crypt() und disguise(), Entschlüsseln entsprechend prepare(), decrypt().

Aufruf: java Caesar [-h] key eingabedatei [ausgabedatei]

-h ... Hilfe ausgeben und Programm beenden

key ... besteht aus dem Zeichen c für crypt, d für decrypt und k für crack() sowie im Falle von c und d aus einer Zahl, welche die Verschiebung angibt.

eingabedatei ... aus dieser Datei wird gelesen

ausgabedatei ... in diese Datei wird geschrieben, wird sie nicht angegeben, so ist auf stdout auszugeben.

Aufrufbeispiele:

java Caesar -h ... gibt Hilfe aus

java Caesar c5 text geheim ... verschlüsselt den Text aus Datei text mit dem Schlüssel 5 und speichert das Ergebnis in Datei geheim.

java Caesar d5 geheim ... entschlüsselt die Datei geheim mit dem Schlüssel 5 und gibt das Ergebnis auf stdout aus.

java Caesar k geheim ... knackt die Datei geheim und gibt das (hoffentlich richtig entschlüsselte) Ergebnis auf stdout aus.


Lösungsansatz:

Zunächst ein Lösungsansatz für das Ver- und Entschlüsseln durch Erzeugung eines Strings mit dem verschobenen Alphabet. Dazu verwenden wir einen String zeichensatz, der alle gültigen Zeichen enthält (Kleinbuchstaben 'a' bis 'z'). Dann erzeugen wir mit dem Schlüssel einen String shiftText, der den um den Schlüssel verschobenen Zeichensatz enthält.

Beispiel für Schlüssel 3:
String text = "abcdefghijklmnopqrstuvw";
String shiftText = "defghijklmnopqrstuvwxyzabc"; // Schlüssel 3
Das Verschlüsseln (bzw. Entschlüsseln) funktioniert dann so:

public String crypt(String text) {
StringBuffer sb = new StringBuffer(text.length());
for (char c : text.toCharArray()) {
sb.append(shiftText.charAt(zeichensatz.indexOf(c)));
}
return sb.toString();
}

Der Index eines Zeichens wird mittels der Stringfunktion indexOf() im String zeichensatz ermittelt. Dieser Index ist dann das gesuchte (verschlüsselte) Zeichen in shiftText.

Eine Klasse Code, die obige Methode implementiert finden Sie im Anschluss, sie erfüllt noch nicht die gesamten Anforderungen der Aufgabe!

/**
* Code: code.Code.java
*
* 08.01.2009, Harald R. Haberstroh
*/

package code;

/**
* Caesarverschlüsselung.
*
* @author Harald R. Haberstroh (hp)
*
*/
public class Code {

private final String zeichensatz = "abcdefghijklmnopqrstuvwxyz"; // Zeichensatz
private String shiftText; // verschobener Zeichensatz

/**
* @param code
* Verschiebung des Alphabets
*/
public Code(int code) {
int len = zeichensatz.length();
StringBuffer sb = new StringBuffer(len);
if (code < 0) {
code += len;
}
for (int i = 0; i < len; i++) {
int idx = (i + code) % len;
sb.append(zeichensatz.charAt(idx));
}
shiftText = sb.toString();
}

/**
* verschlüsselt (entschlüsselt) Text mit gegebenen Code, indem Zeichenweise
* verschoben wird (Caesar-Code).
*
* @param text
* zu verschlüsselnder Text (nur Kleinbuchstaben ohne Umlaute und
* 'ß')
* @return verschlüsselter Text
*/
public String crypt(String text) {
StringBuffer sb = new StringBuffer(text.length());
for (char c : text.toCharArray()) {
sb.append(shiftText.charAt(zeichensatz.indexOf(c)));
}
return sb.toString();
}

/**
* vorbereiten des Texts, indem alles auf Kleinbuchstaben und nur mehr Zeichen
* aus Zeichensatz 'zeichensatz'.
*
* @param text
* Rohtext
* @return vorbereiteter Text
*/
public String prepare(String text) {
StringBuffer sb = new StringBuffer(text.length());
for (char c : text.toCharArray()) {
c = Character.toLowerCase(c);
if (zeichensatz.indexOf(c) >= 0) { // gültig
sb.append(c);
}
}
return sb.toString();
}

/**
* @param args
*/
public static void main(String[] args) {
Code code = new Code(3);
System.out.println(code.zeichensatz);
System.out.println(code.shiftText);
Code decode = new Code(-3);
String text = "Hallo, das ist streng geheim!";
String ctext = code.crypt(code.prepare(text));
System.out.println(ctext);
text = decode.crypt(ctext);
System.out.println(text);
}
}

Im folgenden finden Sie eine Lösung der gesamten Aufgabenstellung, wobei zum Ver-/Entschlüsseln jeweils ein Array als Kodierungstabelle verwendet wird. Der Index wird erzeugt, indem das Zeichen in einen int-Wert umgewandelt wird und davon der int-Wert vom 'a' abgezogen wird.

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Random;

/**
* lehrer-caesar-haberstroh/.Caesar.java
* @author (c) 2008, Harald R. Haberstroh
* 16.12.2008
*/

/**
* @author Harald R. Haberstroh (hh)
*
*/
public class Caesar {
private int key;

/**
* default für's Knacken.
*/
public Caesar() {
key = 0;
}

/**
* @param key
*/
public Caesar(int key) {
this.key = key;
}

/**
* liest eine ganze Textdatei.
*
* @param in
* geöffneter BufferedReader (Inputstream)
* @return gelesener String
* @throws IOException
*/
public String readStream(BufferedReader in) throws IOException {
StringBuilder sb = new StringBuilder();
String line;
while ((line = in.readLine()) != null) {
sb.append(line);
sb.append("\n");
}
return sb.toString();
}

/**
* schreibt den String in Textdatei.
*
* @param out
* PrintStream zum Schreiben
* @param content
* Text zum Speichern
*/
public void writeStream(PrintWriter out, String content) {
out.write(content);
out.flush();
}

/**
* entfernt alles, was kein Buchstabe und macht die dann zu Kleinbuchstaben.
*
* @param content
* zu konvertierender Text
* @return konvertierter Text
*/
public String prepare(String content) {
StringBuilder sb = new StringBuilder();
for (char c : content.toCharArray()) {
if ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') {
sb.append(Character.toLowerCase(c));
}
}
return sb.toString();
}

/**
* veschlüsselt Text mit Caesar-Chiffre und key.
*
* @param content
* zu verschlüsselnder Text
* @return verschlüsselter Text
*/
public String crypt(String content) {
StringBuilder sb = new StringBuilder();
char[] table = new char[26];
for (int i = 0; i < table.length; i++) {
char cryptChar = (char)((int)'a' + (i + key) % table.length);
table[i] = cryptChar;
}
for (char c : content.toCharArray()) {
sb.append(table[(int)c - (int)'a']);
}
return sb.toString();
}

/**
* verschleiert Text, indem zufällige Groß-/Kleinschreibung verwendet wird und
* zufällig Leerzeichen eingefügt werden.
*
* @param content
* zu verschleiernder Text
* @return verschleierter Text
*/
public String disguise(String content) {
StringBuilder sb = new StringBuilder();
for (char c : content.toCharArray()) {
sb.append(disguise(c));
}
return sb.toString();
}

/**
* für's disguise, immer selbe Folge von Zufallszahlen.
*/
private Random rand = new Random(23);

/**
* @param c
* Zeichen
* @return zufällig Groß/Klein, zufällig Leerezichen dazu
*/
private String disguise(char c) {
if (rand.nextBoolean()) {
c = Character.toUpperCase(c);
}
if (rand.nextBoolean()) {
return Character.toString(c) + " ";
} else {
return Character.toString(c);
}
}

/**
* entschlüsseln mit Caesar-Chiffre.
*
* @param content
* zu entschlüsselnder Text.
* @return entschlüsselter Text
*/
public String decrypt(String content) {
StringBuilder sb = new StringBuilder();
char[] table = new char[26];
for (int i = 0; i < table.length; i++) {
int shift = (i - key) < 0 ? (i - key) + table.length : i - key;
char cryptChar = (char)((int)'a' + shift);
table[i] = cryptChar;
}
for (char c : content.toCharArray()) {
sb.append(table[(int)c - (int)'a']);
}
return sb.toString();
}

/**
* versucht den key zu finden, indem das häufigste Zeichen ermittelt wird,
* welches dem 'E' entspricht.
*
* @param content
* verschlüsselter Text
* @return hoffentlich Klartext
*/
public String crack(String content) {
int[] hauf = new int[26];
for (char c : content.toCharArray()) {
hauf[(int)c - (int)'a']++;
}
char e = 'e';
int max = 0;
for (int i = 0; i < hauf.length; i++) {
if (hauf[i] > max) {
max = hauf[i];
e = (char)((int)'a' + i);
}
}
key = (int)e - (int)'e';
System.out.println("key = " + key);
String ret = decrypt(content);
return ret;
}

/**
* Hilfe ausgeben
*/
private static void help() {
System.out
.println("Aufruf: java Crypt [-h] key eingabedatei [ausgabedatei]");
System.out
.println("key besteht aus Zeichen plus Zahl, c...crypt, d...decypt, "
+ "k...cracken");

}

/**
* @param args
* [-h] key eingabedatei [ausgabedatei]
*/
public static void main(String[] args) {
if (args.length >= 2) { // scheint OK
if (args[0].equals("-h")) {
help();
} else {
try {
BufferedReader in = new BufferedReader(new FileReader(args[1]));
PrintWriter out = new PrintWriter(System.out);
if (args.length == 3) {
out = new PrintWriter(args[2]);
}
Caesar caesar;
String eingabe;
String ausgabe;
switch (args[0].charAt(0)) {
case 'c': // crypt
caesar = new Caesar(Integer.parseInt(args[0].substring(1)));
eingabe = caesar.readStream(in);
ausgabe = caesar.disguise(caesar.crypt(caesar.prepare(eingabe)));
caesar.writeStream(out, ausgabe);
break;
case 'p': // prepare
caesar = new Caesar(Integer.parseInt(args[0].substring(1)));
eingabe = caesar.readStream(in);
ausgabe = caesar.prepare(eingabe);
caesar.writeStream(out, ausgabe);
break;
case 'd': // decypt
caesar = new Caesar(Integer.parseInt(args[0].substring(1)));
eingabe = caesar.readStream(in);
ausgabe = caesar.decrypt(caesar.prepare(eingabe));
caesar.writeStream(out, ausgabe);
break;
case 'k': // crack
caesar = new Caesar();
eingabe = caesar.readStream(in);
ausgabe = caesar.crack(caesar.prepare(eingabe));
caesar.writeStream(out, ausgabe);
break;
default:
System.err.println("Ungültiger Aufruf (key), Hilfe mit -h");
}
} catch (FileNotFoundException e) {
System.err
.println("Datei nicht gefunden: " + e.getLocalizedMessage());
} catch (IOException e) {
System.err.println("Ein-/Ausgabefehler: " + e.getLocalizedMessage());
} catch (NumberFormatException e) {
System.err.println("Ungültiger Aufruf (key), Hilfe mit -h");
}
}
} else if (args.length >= 1 && args[0].equals("-h")) { // Hilfe
help();
} else { // Fehler
System.err.println("Ungültiger Aufruf, Hilfe mit -h");
}
}

}



Sinnvollerweise wird man bei dieser Lösung noch das Erzeugen der Tabelle in eine eigene Methode auslagern, die dann im Konstruktor bzw. in crack() aufgerufen wird.

Labels: , , ,


Mittwoch, 7. Januar 2009

 

Top 10 Liste mit Java

Schreiben Sie ein Javaprogramm, welches eine Top 10 Liste verwaltet (z.B. von einem Wettbewerb, Spiel o.ä.).

Die Teilnehmer werden eingegeben, z.B.


Hansi 56
Kurt 90
Jürgen Bauer 76

Jede Zeile muss mindestens einen Namen (evtl. mehrere) haben und durch ein Leerzeichen getrennt die ganzzahlige Punktezahl.
Nach jedem Eintrag soll die Top 10 Liste angezeigt werden, z.B.



*** Top 10 ***
Kurt 90
Jürgen Bauer 76
Hansi 56

Wird die Eingabe beendet (EOF), so wird die aktuelle Liste in einer Datei gespeichert (die Datei enthält also die Top 10 oder weniger Zeilen).


Bei einem neuerlichen Start des Programms wird die Datei eingelesen und die Liste gleich angezeigt. Neue Einträge können wie schon vorher vorgenommen werden. Bei EOF wird die geänderte Liste gespeichert.

Labels: , ,


Montag, 5. Januar 2009

 

C ist Programmiersprache des Jahres 2008

TIOBE erstellt monatlich einen TIOBE Programming Community Index, in dem die populärsten Programmiersprachen ermittelt werden. Die Reihung erfolgt aufgrund der Anzahl der qualifizierten Ingenieure weltweit, Kursen und Drittanbietern. Die Suchmaschinen Google, MSN, Yahoo und YouTube werden zur Berechnung der Reihung herangezogen. In dieser Reihung geht es nicht um die beste Programmiersprache und auch nicht und jene Sprache in der die meisten Zeilen geschrieben wurden.

C wurde von TIOBE deshalb als "Programmiersprache des Jahres 2008" gewählt, weil die Verbreitung dieser Sprache im Jahr 2008 am meisten gestiegen ist. Die am häufigsten verwendete Sprache nach diesem Index ist nach wie vor Java. Laut Prognosen von TIOBE wird sie das auch weiter bleiben.

Die Gründe, warum C immer noch so populär ist, sind möglicherweise:
Java und C zu lernen wird also auch bezüglich der Popularität der Sprachen weiterhin sinnvoll sein. Für C gibt es von Joel Spolsky ganz gute Argumente (englisch, deutsch gibt's leider nicht).

Hier die TOP 20 (Jänner 2009):

Rang
Jänner 2009
Rang
Jänner 2008
Änderung des Rangs
ProgrammierspracheVerbreitung
Jänner 2009
Vergleich
Jänner 2008
Status
1 1 Java 19.022% -1.83% A
2 2 C 15.931% +2.01% A
3 5 C++ 10.116% +1.39% A
4 3 (Visual) Basic 9.161% -1.80% A
5 4 PHP 8.882% -0.31% A
6 8 C# 5.609% +0.75% A
7 6 Python 4.731% -0.81% A
8 7 Perl 4.303% -0.94% A
9 10 JavaScript 3.360% +0.16% A
10 9 Delphi 3.303% -0.03% A
11 11 Ruby 3.149% +0.80% A
12 14 D 1.022% -0.15% A
13 12 PL/SQL 1.006% -0.22% A
14 13 SAS 0.797% -0.41% A
15 18 Pascal 0.661% +0.21% B
16 20 Logo 0.632% +0.25% B
17 15 COBOL 0.579% -0.35% B
18 28 ABAP 0.537% +0.34% B
19 17 FoxPro/xBase 0.477% -0.03% B
20 21 ActionScript 0.455% +0.11% B
(Status A...Mainstream, siehe Erklärungen von TIOBE).

Die weiteren TOP 50 (ab 21):
RangProgrammierspracheVerbreitung
21 RPG (OS/400) 0.451%
22 Lua 0.445%
23 Lisp/Scheme 0.433%
24 MATLAB 0.430%
25 Ada 0.327%
26 Fortran 0.324%
27 LabVIEW 0.251%
28 Prolog 0.221%
29 Erlang 0.195%
30 Awk 0.189%
31 NXT-G 0.184%
32 PowerShell 0.172%
33 Transact-SQL 0.172%
34 Scratch 0.164%
35 Haskell 0.162%
36 Euphoria 0.152%
37 Objective-C 0.138%
38 Groovy 0.135%
39 Alice 0.132%
40 ML 0.131%
41 Focus 0.124%
42 CL (OS/400) 0.123%
43 Tcl/Tk 0.120%
44 Smalltalk 0.117%
45 Scala 0.113%
46 Bourne shell 0.112%
47 Q 0.104%
48 Forth 0.101%
49 Caml 0.092%
50 Natural 0.088%

Ich habe die Tabellen aus TIOBE Programming Community Index kopiert, da über diesen Link nur die aktuellen Daten verfügbar sind.

Labels: , , , , ,


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

Abonnieren Posts [Atom]