Java ist auch eine Insel

Samstag, März 15, 2008

Insel: Service-Factory, IoC, Lookup, Generics, Services und alles zusammen

Je größer eine Java-Anwendung wird, desto größer werden die Abhängigkeiten zwischen Klassen und Typen. Um die Abhängigkeiten zu reduzieren, ist zunächst gewünscht, sich nicht so sehr an Implementieren zu binden, sondern an Schnittstellen. (Das gelobte „Programmieren geben Schnittstellen und nicht gegen eine Implementierung.“) Eine Schnittstelle beschreibt dann Dienste, so genannte Services, auf die an anderer Stelle zurückgegriffen werden kann. Die nächste Frage ist, wie ein Service mit Geschäftslogik zu der Stelle kommt, an denen er benötigt wird, etwa auf der grafischen Oberfläche als Aktion hinter einer Schaltfläche. Hier haben sich zwei Wege herausgestellt:

  • Service-Fabriken. Eine Service-Fabrik ist eine Zentrale, an die sich Interessenten wenden, wenn sie einen Service nutzen wollen. Die Fabrik liefert eine passende Implementierung, die immer eine Service-Schnittstelle implementiert. Welche Realisierung – also konkrete Klasse – die Fabrik liefert, soll den Nutzer nicht interessieren; eben Programmieren gegen Schnittstellen.
  • Dependency Injection/Inversion of Control (IoC). Nach diesem Prinzip fragen die Interessenten nicht aktiv über eine zentrale Service-Fabrik nach den Diensten, sondern den Interessenten wird der Service über eine übergeordnete Einheit gegeben (injiziert). Die magische Einheit nennt sich IoC-Container. In der Vergangenheit hat sich das Spring-Framework als De-facto-Standard eines IoC-Containers herauskristallisiert.
Arbeiten mit dem ServiceLoader

Java SE bietet bisher keine Bibliothek für Dependency Injection aber mit der Klasse java.util.ServiceLoader eine einfache Realisierung für Service-Fabriken. Ein eigenes Programm soll auf einen Grüß-Dienst zurückgreifen, aber welche Implementierung das sein wird, soll an anderer Stelle entschieden werden.

ServiceLoader<Greeter> greeterServices = ServiceLoader.load( Greeter.class );

for ( Greeter greeter : greeterServices )

  System.out.println( greeter.getClass() + " : " + greeter.greet( "Chris" ) );

ServiceLoader erfragt mit load() eine Realisierung, die die Schnittstelle Greeter implementieren soll. Die Realisierung ist der Service-Provider. Greeter deklariert eine greet()-Operation:

package com.tutego.insel.services;

public interface Greeter

{

  String greet( String name );

}

Der Service liefert aber eine konkrete Klasse. Demnach muss es irgendwo eine Zuordnung geben, die einen Typnamen (Greeter) mit einer konkreten Klasse, der Service-Implementierung, verbindet. Dazu ist im Wurzelverzeichnis des Klassenpfades ein Order META-INF mit einem Unterordner services anzulegen. In diesem Unterordner ist eine Textdatei (provider-configuration file) zu setzen, die den gleichen Namen wie die Service-Schnittstelle besitzt:

META-INF/

  services/

    com.tutego.insel.services.Greeter

Diese Textdatei, die keine Dateiendung aufweist, enthält Zeilen mit voll qualifizierten Klassenamen (binary name genannt) für die Implementierung, die später hinter diesem Service stehen. Es kann eine Zeile oder durchaus mehrere Zeilen für unterschiedliche Implementierungen angegeben sein. In der Datei META-INF/services/com.tutego.insel.services.Greeter steht:

com.tutego.insel.services.FrisianGreeter

FrisianGreeter ist demnach unsere letzte Klasse und eine tatsächliche Implementierung des Services:

package com.tutego.insel.services;

public class FrisianGreeter implements Greeter

{

public String greet( String name ) {

  return "Moin " + name + "!";

}

}

Utility-Klasse Lookup als ServiceLoader-Fassade

So nett der ServiceLoader auch ist, die API könnte ein wenig kürzer sein. Denn oftmals gibt es nur eine Service-Implementierung und nicht gleich mehrere. Daher soll eine Fassade eine knackigere API anbieten. Eine kurze Methode lookup() liefern genau den ersten Service (oder null) und lookupAll() gibt alle Service-Klassen in einer Sammlung zurück. (Das Listing nutzt mehrere Dinge, die die Insel bisher nicht vorgestellt hat! Dazu zählen Generics, Datenstrukturen, Iterator, Meta-Objekte.)

public class Lookup

{

public static <T> T lookup( Class<T> clazz )

{

  Iterator<T> iterator = ServiceLoader.load( clazz ).iterator();

  return iterator.hasNext() ? iterator.next() : null;

}

public static <T> Collection<? extends T> lookupAll( Class<T> clazz )

{

  Collection<T> result = new ArrayList<T>();

  for ( T e : ServiceLoader.load( clazz ) )

   result.add( e );

  return result;

}

}

Die Nutzung vereinfacht sich damit:

System.out.println( Lookup.lookup( Greeter.class ).greet( "Chris" ) ); // Moin Chris!

System.out.println( Lookup.lookupAll( Greeter.class ).size() ); // 1

Unverkennbar ist natürlich der Einfluss der NetBeans-Klasse http://bits.netbeans.org/dev/javadoc/org-openide-util/org/openide/util/Lookup.html.

Labels:

AddThis Social Bookmark Button

Dienstag, März 11, 2008

Inselupdate: Quantifizierer mit Wiederholungen, nicht gierige Operatoren

Wiederholungen

Neben Quantifizierern ? (einmal oder keinmal), * (keinmal oder beliebig oft) und + (einmal oder beliebig oft), gibt es drei weitere Quantifizierer, die es erlauben, die Anzahl eines Vorkommens genauer zu beschreiben.

  • X{n}. X muss genau n-mal vorkommen
  • X{n,}. X kommt mindestens n-mal vor
  • X{n,m}. X kommt mindestens n aber maximal m-mal vor

Beispiel   Eine E-Mail Adresse endet mit einem Domain-Namen, der 2 oder 3 Zeichen lang ist.

static Pattern p = Pattern.compile( "[\\w|-]+@\\w[\\w|-]*\\.[a-z]{2,3}" );

 

Gierige und nicht gierige Operatoren

Die drei Operatoren ?, * und + haben die Eigenschaft, die längste mögliche Zeichenfolge abzudecken – das nennt sich gierig (engl. greedy). Deutlich wird diese Eigenschaft, bei dem Versuch, in einem HTML-Strings alle fett gesetzten Teile zu finden. Gesucht ist also ein Ausdruck, der im String

String string = "Echt <b>fett</b>. <b>Cool</b>!";

die Teilfolgen <b>fett</b> und <b>Cool</b> erkennt. Der erste Versuch für ein Programm:

Pattern pattern = Pattern.compile( "<b>.*</b>" );

Matcher matcher = pattern.matcher( string );

while ( matcher.find() )

System.out.println( matcher.group() );

Nun ist die Ausgabe aber <b>fett</b>. <b>Cool</b>! Das verwundert nicht, denn mit dem Wissen, dass * gierig ist, passt <b>.*</b> auf die Zeichenkette vom ersten <b> bis zum letzten </b>.

Die Lösung ist der Einsatz eines nicht gierigen Operators (auch „genügsam“, „zurückhaltend“, „non-greedy“, „reluctant“ genannt). Dieser Operator bekommt einfach hinter dem Qualifizierer ein Fragezeichen gestellt.

Mit diesem nicht-gierigen Operator lösen wir einfach das Fettproblem:

Pattern pattern = Pattern.compile( "<b>.*?</b>" );

Matcher matcher = pattern.matcher( "Echt <b>fett</b>. <b>Cool</b>!" );

while ( matcher.find() )

  System.out.println( matcher.group() );

Die Ausgabe ist:

<b>fett</b>

<b>Cool</b>

Labels: ,

AddThis Social Bookmark Button

Donnerstag, Februar 21, 2008

Java Closures : Inselupdate für Java 7

Mit den Closures nach Gilad Bracha, Neal Gafter, James Gosling, Peter von der Ahé.

Innere Klassen, insbesondere anonyme innere Klassen, sind bisher die einzige Möglichkeit, um Programmteile an Methoden zu übergeben. Nehmen wir einen Timer als Beispiel. Dem eigentlichen Zeitgeber muss ein Stücken Code übergeben werden, sodass der Timer weiß, was er zu tun hat. Mit dem Zeitgeber, der Klasse Timer, und der abstrakten Basisklasse TimerTask zur Beschreibung der Aufgaben, ist schnell ein Beispiel programmiert, das wie eine Uhr in jeder Sekunde die Zeit auf dem Bildschirm ausgibt:


public class TimerExample
{
public static void main( String[] args )
{
class MyTimer extends java.util.TimerTask {
@Override public void run() {
System.out.println( new java.util.Date() );
}
}

new java.util.Timer().scheduleAtFixedRate( new MyTimer(), 0, 1000 );
}
}

Für die Beschreibung des Programmcodes ist extra eine eigene Klasse erforderlich. Über eine innere anonyme Klasse lässt sich der Programmcode jedoch noch etwas weiter verkürzen:

 public class TimerExample
{
public static void main( String[] args )
{
new java.util.Timer().scheduleAtFixedRate( new java.util.TimerTask() {
@Override public void run() {
System.out.println( new java.util.Date() );
}
}, 0, 1000 );
}
}

Aus dem Programm ist deutlich abzulesen, dass zum "Transport" der println()-Anweisung einiges an Schreibarbeit nötig ist. Wünschenswert ist es aber, wenn der Programmcode leichter an die Funktion scheduleAtFixedRate() zu übergeben wäre. Da die bereitgestellte Funktion bisher nicht so programmiert ist, ist das Ziel, dieses nachzuprogrammieren.

Deklaration eines Closures

Ein Closure repräsentiert einen Block Java-Code. Die allgemeine Schreibweise ist

{ formal parameters => statements expression }

Die Schreibweise definiert eine anonyme Funktion. Vergleichen wir einer bekannten Funktionsdeklaration

void out()
{
System.out.printn("Hallo Welt");
}

mit der Deklaration eines Closures:

{ => System.out.printn("Hallo Welt"); }

Während normale Funktionen mit ihrem Namen aufgerufen werden, steht eine Closure für ein Objekt, welches eine invoke()-Methode anbietet.

public class Closures
{
public static void main( String[] args )
{
{ => System.out.println("Hallo Welt"); }.invoke();
}
}

Aus der allgemeinen Schreibweise

{ formal parameters => statements expression }

lässt sich absehen, dass formale Parameter wie bei normalen Funktionsdeklarationen möglich sind. So lassen sich in den Closure-Block Daten einführen. Vergleichen wir wieder eine Funktions- mit einer Closure-Deklaration:

void quote( String s )
{
System.out.println("'" + s + "'");
}

Der Closure mit Beispiel:

public class Closures
{
public static void main( String[] args )
{
{ String s => System.out.println("'" + s + "'"); }.invoke( "tutego" );
}
}

Zwei Dinge fallen an dem Beispiel auf:
• Formale Parameter haben einen Typ (links vom Pfeil steht String s ).
• Die invoke()-Funktion nimmt immer so viele Argumente an, wie es formale Parameter in der Closure-Deklaration gibt.

Die Beispiele bisher zeigen Closures, die in ihrem Rumpf eine Anweisung tragen. Closures können aber auch Ausdrücke enthalten.

public class Closures
{
public static void main( String[] args )
{
System.out.println( { int a, int b => (a + b) / 2 }.invoke( 10, 20) ); // 15
}
}

Im Rumpf endet die Ausdruck nicht mit einem Semikolon, denn es wäre ja auch verboten,

System.out.println( (a + b) / 2; );

zu schreiben.

Ein auffälliger Unterschied zur Funktion ist die fehlende return-Anweisung. Closures selbst sind quasi an die Aufrufstelle eingesetzte Programmteile und ein return würde die Funktion verlassen!

Funktions-Typ

Closures, wie { int a, int b => (a + b) / 2 }, besitzen einen so genannten Funktions-Typ, der durch die Typen der Parameter und der Rückgabe bestimmt ist. Die allgemeine Notation ist

{ formal parameters => return type }

Gibt eine Funktion nicht zurück, so steht rechts vom Pfeil void. Für unsere drei bisherigen Closures sind die Typen:

{ => void }
{ String => void }
{ int, int => int }

Da in einer Funktionsdeklaration ohne Parameter ja auch kein void steht – void out(void) ist falsch – steht auch im ersten Fall links vom Pfeil nichts.
Mit diesem Funktions-Typ lassen sich die Closures ausgezeichnet referenzieren:

{ => void } printHelloWorld = { => System.out.println("Hallo Welt"); }; 

{ String => void } printQuoted = { String s => System.out.println("'" + s + "'"); };

{ int, int => int } avg = { int a, int b => (a + b) / 2 };

printHelloWorld.invoke(); // Hallo Welt

printQuoted.invoke( "tutego" ); // 'tutego'

System.out.println( avg.invoke( 10, 20 ) ); // 15

Mit diesem Wissen lassen sich Funktionen schreiben, die ein Closure entgegennehmen. Eine Funktion repeat() soll dabei einen Programmcode so oft wie gewünscht aufrufen:

public class Repeater
{
public static void repeat( int times, { => void } block )
{
for ( int i = 0; i < times; i++ )
block.invoke();
}

public static void main( String[] args )
{
repeat( 2, { => System.out.println("Hallo"); } );
}
}

Das Hallo kommt also zweimal auf den Bildschirm.

In der Java-Bibliothek sind nicht alle Funktionen so parametrisiert, so dass Closures als Parameter erlaubt sind. Schreiben wir für den Timer eine eigene Methode scheduleAtFixedRate(), die einen Codeblock entgegennimmt und ausführt:

public class TimerExample
{
public static void scheduleAtFixedRate( { => void } task, long delay, long period )
{
new java.util.Timer().scheduleAtFixedRate( new java.util.TimerTask() {
@Override public void run() {
task.invoke();
}
}, delay, period );
}
public static void main( String[] args )
{
scheduleAtFixedRate(
{ => System.out.println( new java.util.Date() ); }
, 0, 1000 );
}
}

Mit Closures sind auch Funktionszeiger leicht zu realisieren:

public class FunctionPointerWithClosures
{
static void invoker( { => void } block )
{
block.invoke();
}

public static void main( String[] args )
{
{ => void } method1 = { => System.out.println("Hello www.tutego.com"); };
{ => void } method2 = { => System.out.println("Hallo www.tutego.com"); };

invoker( Math.random() > 0.5 ? method1 : method2 );
}
}

Natürlich lassen sich jetzt die Methoden auch in eine Datenstruktur setzen:

Map<String, { => void }> methods = new HashMap<String, { => void }>();
methods.put( "German", { => System.out.println("Hallo www.tutego.com"); } );
methods.put( "English", { => System.out.println("Hello www.tutego.com"); } );

methods.get( "German" ).invoke();

for ( { => void } method : methods.values() )
invoker( method );

In ein Feld können Closures nicht gesetzt werden! Der Grund liegt an der internen Generics-Umsetzung.

Nehmen wir an, wir wollen eine Funktion each() schreiben, die einen String mit einem gegebenen Delimiter zerlegt und dann eine Operation auf den einzelnen Token ausführt. Soll weiterhin die Operation einen String zurückgeben, so kann das in herkömmlichem Java etwa so implementiert werden:

class StringUtils
{
public static interface StringInStringOutBlock
{
String execute( String in );
}

public static String each( String source, String delimiter, StringInStringOutBlock block )
{
StringBuilder result = new StringBuilder();

for ( String token : source.split( delimiter ) )
result.append( block.execute( token ) );

return result.toString();
}
public static void main( String[] args )
{
class QuoterBlock implements StringInStringOutBlock {
@Override public String execute( String in ) {
return "'" + in + "'";
}
}

String s = each( "Hallo Welt!", " ", new QuoterBlock() );
System.out.println( s );
}
}

Closures machen das viel kompakter:

class StringUtils
{
public static String each( String source, String delimiter, { String => String } block )
{
StringBuilder result = new StringBuilder();

for ( String token : source.split( delimiter ) )
result.append( block.invoke( token ) );

return result.toString();
}

public static void main( String[] args )
{
String s = each( "Hallo Welt!", " ", { String in => "'" + in + "'" } );
System.out.println( s );
}
}

Closure mit Variablenzugriff

Closures haben einige Eigenschaften, die innere anonyme Klassen so erst einmal nicht haben. Eine davon ist, dass ein Closure-Block auf auch nicht-finale Werte lesend und schreiben zugreifen kann.

int i = 0;
repeat( 3, { => System.out.println(i); } ); // 0 0 0

@Shared int j = 0;
repeat( 3, { => System.out.println(j++); } ); // 0 1 2
System.out.println( j ); // 3

Auffällig ist die Annotation @Shared. Annotiert sie nicht die Variablen, auf die der Closure schreibend zugreift, gibt es eine Compiler-Warnung (kein Fehler!): "warning: [shared] captured variable j not annotated @Shared".

Closure Conversion

Damit automatisch Java-Entwicklung ohne Anpassung der Bibliotheken von den Closures profitieren, haben die Sprachentwickler einen speziellen Mechanismus eingebaut: Ein Closure kann einem Interface mit einer Operation zugewiesen werden, wenn die Rückgabe- und Parametertypen übereinstimmen. Die Schnittstelle Runnable und ActionListener sind wie folgt deklariert:

public interface Runnable {
void run();
}
public interface ActionListener extends EventListener {
void actionPerformed(ActionEvent e);
}

Kompatible Closures sind:

Runnable run = { => System.out.println("Nebenläufig!"); };
ActionListener listener = { ActionEvent l => System.out.println("Gedrückt!"); };


Ohne Zuweisung an Variablen ist ein nebenläufiges Programm schnell gestartet und ein Ereignisbehandler ohne viel Programmcode an einer Schaltfläche festgemacht:



new Thread( { => System.out.println("Nebenläufig!"); } ).start();
JButton b = new JButton();
b.addActionListener( { ActionEvent l => System.out.println("Gedrückt!"); } );

Diese automatische Konvertierung ist wirklich sehr praktisch, denn viele wichtige Java-Schnittstellen schreiben nur eine Operation vor.

Nach einem Vormittag mit Java-Closures kann ich sagen, dass ich gut mit der Syntax leben kann. Ich mag's! Schauen wir mal, ob sich noch groß etwas ändert.

Labels: ,

AddThis Social Bookmark Button

Sonntag, Februar 17, 2008

Inselupdate: Applets und HTML-Converter

Applets in der Wiege von Java

Applets sind kleine Java-Programme, die in einem Web-Browser ablaufen. Sie gehören zu den Java-Programmen der ersten Stunde. Obwohl Applets Java an die Spitze der Programmiersprachen brachte, sind die heute nur noch selten zu sehen. Es gibt zwar Ausnahmen, wie den Routenplaner http://map24.de/, doch im Allgemeinen sind in öffentlichen Webseiten Applets weitestgehend verschwunden. Der Grund, warum Java-Applets weniger attraktiv für den Konsumenten sind liegt nicht daran, dass die Client-seitige Darstellung und Logik unwichtig geworden ist, doch vielmehr an ande-ren Gründen:

  • Mit HTML, CSS sowie JavaScript lassen sich heutzutage viele Aufgaben lösen, die 1995 unlösbar waren. Dagegen wirken kompilierte Java-Programme wie Raketentechnik. Während bei Java-Applets erst eine JVM gestartet werden muss, was natürlich eine gewisse Zeit kostet, ist JavaSc-ript und HTML sofort bereit. Starke JavaScript-Bibliotheken ermöglichen tolle Effekte und schnelle Verarbeitung.
  • Ist Java installiert, steht auf den Rechnern eine moderne und schnelle Java-Laufzeitumgebung für Applets über ein Brower-Plugin zur Verfügung. Möchte der Anwender Applets nutzen, aber kein JVM ist installiert, ist der Bezug langwierig und viele Megabyte Daten müssen vom Sun-Server geladen werden. Sun arbeitet aktuell an einem System, mit dem nur relevante Bibliotheken bezogen werden, doch die Umsetzung ist noch Zukunftsmusik. (Microsoft lieferte für Windows XP immerhin noch eine eigene JVM aus, obwohl sie auf dem Stand von Java 1.1 stehen bleib. Wegen immer wieder aufkehrenden Sicherheitsproblemen sollten Anwender die Microsofts Java VM jedoch deinstallieren. Microsoft liefert für Vista kein Java mit aus und empfiehlt daher, Suns JVM zu installieren. )
  • Java ist als allgemeine Programmiersprache entworfen worden, aber nicht als einfache Programmiersprache für grafische Effekte. Hier liegt der Vorteil von Adobe Flash. Mit starken Tools kön-nen Designer großartige Oberflächen entwerfen und die Verbreitung des Flash-Players ist phäno-menal . Zudem erweitert Adobe die Multimedia-Techologie Flash, die Programmiersprache ActionScript sowie die Produktpalette zur Entwicklung kontinuierlich. Möglicherweise ändert sich dass, wenn Java Laufzeitumgebungen stark verbreitet sind und sich Suns neue Programmierspra-che JavaFX (http://java.sun.com/javafx/) verbreitet hat.

Hinweis   Wir wollen im Folgenden davon ausgehen, dass nicht die Java-Laufzeitumgebung 1.1 von Microsoft installiert ist, sondern ein vollwärtiges Java von Sun. Ist Suns Version von Java installiert, ersetzt es die JVM von Microsoft, und aktuelle Java-Programme lassen sich ausführen.

HTML Converter

Läuft der Browser auf ein <applet>-Tag, so startet er die Java Laufzeitumgebung, die das Java-Programm ausführt. Problematisch ist nur, wenn für den Browser kein Java (oder die falsche Version) installiert ist, und der Browser keine Idee hat, war er mit dem <applet>-Tag anfangen soll. Die Lö-sung ist, auf das <applet>-Tag zu verzichten, und eine Browser-spezifische Alternative zu verwen-den, um dem Benutzer zur Installation einer richten JVM zu verhelfen. Der HTML-Code dafür ist recht kryptisch, sodass Sun ein Tool vorsieht, welches das <applet>-Tag ersetzt. Das Hilfsprogramm heißt HTML Converter und befindet sich im bin-Verzeichnis vom JDK. Nach dem Start öffnet sich eine grafische Oberfläche, mit der die HTML-Datei oder ein Ordner mit Applet-referenzierenden HTML-Dateien angegeben wird.
<neupic: htmlconverter.png, „Der HTML Converter“>
Aus dem bedächtigen

<html><body>
<applet code="HelloWorldApplet.class" width="200" height="100"></applet>
</body></html>

erzeugt der Generator anschließend (wenn die Schablonendatei „Erweitert“ angegeben ist):

<html><body>
<!--"CONVERTED_APPLET"-->
<!-- HTML CONVERTER -->
<script language="JavaScript" type="text/javascript"><!--
    var _info = navigator.userAgent;
    var _ns = false;
    var _ns6 = false;
    var _ie = (_info.indexOf("MSIE") > 0 && _info.indexOf("Win") > 0 && _info.indexOf("Windows 3.1") < 0);
//--></script>
    <comment>
        <script language="JavaScript" type="text/javascript"><!--
        var _ns = (navigator.appName.indexOf("Netscape") >= 0 && ((_info.indexOf("Win") > 0 && _info.indexOf("Win16") < 0 && java.lang.System.getProperty("os.version").indexOf("3.5") < 0) || (_info.indexOf("Sun") > 0) || (_info.indexOf("Linux") > 0) || (_info.indexOf("AIX") > 0) || (_info.indexOf("OS/2") > 0) || (_info.indexOf("IRIX") > 0)));
        var _ns6 = ((_ns == true) && (_info.indexOf("Mozilla/5") >= 0));
//--></script>
    </comment>

<script language="JavaScript" type="text/javascript"><!--
    if (_ie == true) document.writeln('<object classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93" WIDTH = "200" HEIGHT = "100"  codebase="http://java.sun.com/update/1.6.0/jinstall-6u30-windows-i586.cab#Version=6,0,0,5"><noembed><xmp>');
    else if (_ns == true && _ns6 == false) document.writeln('<embed ' +
        'type="application/x-java-applet;version=1.6" \
            CODE = "HelloWorldApplet.class" \
            WIDTH = "200" \
            HEIGHT = "100" ' +
        'scriptable=false ' +
        'pluginspage="http://java.sun.com/products/plugin/index.html#download"><noembed><xmp>');
//--></script>
<applet  CODE = "HelloWorldApplet.class" WIDTH = "200" HEIGHT = "100"></xmp>
    <PARAM NAME = CODE VALUE = "HelloWorldApplet.class" >
    <param name="type" value="application/x-java-applet;version=1.6">
    <param name="scriptable" value="false">

</applet>
</noembed>
</embed>
</object>

<!--
<APPLET CODE = "HelloWorldApplet.class" WIDTH = "200" HEIGHT = "100">

</APPLET>
-->
<!--"END_CONVERTED_APPLET"-->

</body></html>

Labels:

AddThis Social Bookmark Button

Mittwoch, August 29, 2007

Use JavaMail API to reveive all Google mails

package com.tutego.insel.mail;

import java.util.*;
import javax.mail.*;
import javax.mail.internet.ContentType;
import javax.swing.JOptionPane;

public class GetEMails
{
public static void getMail( final Properties props ) throws Exception
{
Session session = Session.getInstance( props, new javax.mail.Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication( props.getProperty( "mail.pop3.user" ),
props.getProperty( "mail.pop3.password" ) );
}
} );
session.setDebug( true );

Store store = session.getStore( "pop3" );
store.connect();

Folder folder = store.getFolder( "INBOX" );
folder.open( Folder.READ_ONLY );

Message message[] = folder.getMessages();

for ( int i = 0; i < message.length; i++ )
{
Message m = message[i];

System.out.println( "-------------------------\nNachricht: " + i );
System.out.println( "Von: " + Arrays.toString(m.getFrom()) );
System.out.println( "Betreff: " + m.getSubject() );
System.out.println( "Gesendet am: " + m.getSentDate() );
System.out.println( "Content-Type: " + new ContentType(m.getContentType()) );

if ( m.isMimeType("text/plain") )
System.out.println( m.getContent() );
}
folder.close( false );
store.close();
}

public static void main( String[] args ) throws Exception
{
Properties props = new Properties();
props.setProperty( "mail.pop3.host", "pop.gmail.com" );
props.setProperty( "mail.pop3.user", JOptionPane.showInputDialog( "user" ) );
props.setProperty( "mail.pop3.password", JOptionPane.showInputDialog( "pass" ) );
props.setProperty( "mail.pop3.port", "995" );
props.setProperty( "mail.pop3.auth", "true" );
props.setProperty( "mail.pop3.socketFactory.class", "javax.net.ssl.SSLSocketFactory" );

getMail( props );
}
}

Labels:

AddThis Social Bookmark Button

Dienstag, August 28, 2007

Inselupdate: Passwort-geschützte Seiten mit Basic Authentication/Proxy-Authentifizierung

URL-Verbindungen können durch die Basic Authentication, also durch ein Passwort, geschützt sein. Anwender bemerken dies, wenn sich ein Eingabedialog öffnet, der die Eingabe eines Namens und eines Passworts erzwingt. Die Webseite http://www.rahul.net/joeuser/ demonstriert diesen Eingabedialog. Der Benutzername »joeuser« und das Passwort »a.b.C.D« zeigen eine Webseite erst nach der Identifizierung.

Ohne das Login kommt auch ein Java-Programm nicht an die Seite. Daher muss der Java-Client der Authentifizierungsbitte nachkommen, und Benutzername und Passwort schicken. Glücklicherweise geht das in Java mit der Klasse java.net.Authenticator ganz einfach:



Authenticator.setDefault( new Authenticator()
{
protected PasswordAuthentication getPasswordAuthentication()
{
System.out.printf( "url=%s, host=%s, ip=%s, port=%s%n",
getRequestingURL(), getRequestingHost(),
getRequestingSite(), getRequestingPort() );

return new PasswordAuthentication( "joeuser", "a.b.C.D".toCharArray() );
}
} );

URL url = new URL( "http://www.rahul.net/joeuser/" );
System.out.println( new Scanner( url.openStream() ).useDelimiter( "\\Z" ).next() );

Die Anweisung Authenticator.setDefault() setzt einen neuen Authenticator, den die URL-Klasse immer dann nutzt, wenn eine Verbindung aufgebaut wird. Dann ruft die Java-Bibliothek unsere überschriebene Funktion getPasswordAuthentication() auf, in der wir ein PasswordAuthentication-Objekt liefert, welches die Benutzernamen und Passwort kodiert. Da getPasswordAuthentication() eine überschriebene Methode ist, kann sie über diverse getXXX()-Methoden auf Zustände zurückgreifen, und das sind die Verbindungsdaten wie Host, usw. Diese Daten sind nicht unwichtig, denn wir wollen ja mitunter für unterschiedliche Webseiten unterschiedliche Benutzer und Passwörter verwenden wollen.

Proxy-Authorization

Um nicht nur eine Benutzer-Authentifizierung, sondern auch eine Authentifizierung für den Proxy zu realisieren gibt es zwei Möglichkeiten.

System.setProperty( "http.proxyUserName", proxyUser );
System.setProperty( "http.proxyPassword", proxyPass );

Eine andere Variante ist, die Header-Variable „Proxy-Authorization“ zu setzen:

URLConnection conn = url.openConnection();
String base64 = "Basic " + new sun.misc.BASE64Encoder().encode((user + ":" + passwd).getBytes() );
conn.setRequestProperty( "Proxy-Authorization", "Basic " +
new sun.misc.BASE64Encoder().encode((proxyUser + ":" + proxyPass).getBytes()) );
conn.connect();
InputStream in = conn.getInputStream();

Labels:

AddThis Social Bookmark Button

Dienstag, Juli 31, 2007

revalidate(), invalidate(), validate(), repaint() -- Inselupdate

Jede Swing-Komponente liegt zwangsläufig in einem Container. Die Ausmaße der meisten Container sind von der Größe der Kinder abhängig, sodass sie mitbekommen müssen, wenn sich die Größe der Kinder verändert. Wenn etwa ein JLabel einen Text mit einer anderen Länge bekommt, so muss auch der Container sich neu darstellen und seine Kinder neu ausrichten. Diese Mitteilung sendet eine Komponente über die Methode revalidate().

Aus javax.swing.JLabel
public void setText( String text ) {
...
revalidate();
repaint();
...
}
Das revalidate() ist eine geerbte Funktion aus JComponent. Sie ruft im AWT-Event-Thread invalidate() auf und setzt die aktuelle Komponente auf einer Liste der invaliden Komponenten, die beim nächsten Zeichen aktualisiert werden müssen.
Aus javax.swing.JComponent
public void revalidate() {
...
invalidate();
RepaintManager.currentManager( this ).addInvalidComponent( this );
...
}
Das invalidate() ist eine aus Container geerbte Funktion, die allen übergeordneten Swing-Container nach oben und allen Kindern unten mitteilt, dass das Layout nicht mehr aktuell ist. Die Aufforderung zur Neudarstellung übernimmt addInvalidComponent(). Die Methode legt ein Event in die Event-Queue, in der alle invaliden Komponenten vermerkt sind. Beim Neuzeichnen ruft der Event-Thread auf allen diesen Komponenten die Berechnungsfunktion validate() auf, damit das Layout wieder stimmt. Der Container überschreibt validate() und ruft die protected-Methode validateTree() auf, was die Komponenten des Containers zur Neuberechnung auffordert. Halten wir noch drei Aussagen fest:

• Eine Neuzeichnung ist mit invalidate() nicht verbunden. Die Funktion
markiert nur Komponenten.

• Eine Neuberechnung ist mit invalidate() nicht verbunden, denn invalidate()
ruft nicht validate() auf.

• Das validate() führt ebenfalls nicht zur Neudarstellung, sondern nur zur
Neuberechung der Größen.

Mit diesem Vorgehen führt das revalidate() nach einem Markieren der Komponenten im Baum über invalidate() zu einem Repaint-Event, was über validate() die Größen neu berechnet und zur korrekten Neudarstellung führt. Die Funktion validate() können wir auch selbst zur Neuberechung des Layouts aufrufen – doch dürfen wir nicht vergessen, ein repaint() aufzurufen.

Labels:

AddThis Social Bookmark Button

Sonntag, Juli 29, 2007

Inselupdate: Dicke und Art der Linien von Formen bestimmen über java.awt.Stroke

Eine noch fehlende Eigenschaft ist die der Umrisslinie, Stroke genannt. Zu den Eigenschaften einer Umrisslinie zählten:

• die Dicke (engl. width),
• die Art, wie Liniensegment beginnen und enden (engl. end caps),
• die Art, wie aufeinander treffende Linien verbunden werden (engl. line joins) und
• ein Linien-Pattern (engl. dash attributes).

Die Stroke-Schnittstelle

Die Umrisseigenschaften bestimmen Objekte vom Typ java.awt.Stroke; die Methode setStroke(Stroke) auf dem Graphics2D-Kontext setzt sie. Alle nachfolgenden Methoden wie draw(), drawLine(), usw. berücksichtigen diese Umrisslinie anschließend.
Die Schnittstelle Stroke schreibt nur eine Funktion vor:

interface java.awt.Stroke

  • Shape createStrokedShape( Shape p )
    Liefert die Umrandung für ein Shape p.

Bisher gibt es in Java nur eine Standardimplementiert der Schnittstelle: BasicStroke.

Beispiel Zeichne die folgenden Formen mit einer Dicke von zehn Pixel:

g2.setStroke( new BasicStroke( 10 ) );

Linienenden (end caps)

Besonders bei breiten Linien ist es interessant, wie eine allein stehende Linie endet. Sie kann einfach aufhören oder auch abgerundet sein. Drei Konstanten bestimmen diesen Linienende-Typ:

• CAP_BUTT. , Belässt das Ende so wie es ist.
• CAP_ROUND. Rundet das Ende mit einem Halbkreis ab
• CAP_SQUARE. bestimmen diesen Linienende-Typ. Setzt einen rechteckigen Bereich an.

Die Typen CAP_ROUND und CAP_SQUARE erweitern die Linie um ein Stück, was halb so groß wie die Dicke der Linie ist.

@Override
protected void paintComponent( Graphics g )
{
Graphics2D g2 = (Graphics2D) g;

g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);

g2.setStroke( new BasicStroke( 20,
BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER ) );
g2.drawLine( 30, 50, 200, 50 );

g2.setStroke( new BasicStroke( 20,
BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER ) );
g2.drawLine( 30, 150, 200, 150 );

g2.setStroke( new BasicStroke( 20,
BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER ) );
g2.drawLine( 30, 100, 200, 100 );
}

Zwar gibt es von BasicStroke fünf Konstruktoren, aber nur einen einfachen, der Linienenden (immer unterschiedlich in dem Beispiel) und Linienverbindungen (hier BasicStroke.JOIN_MITER) gleichzeitig bestimmt haben möchte.

Linienverbindungen (line joins)

Wenn Linien nicht alleine stehen, sondern etwa wie in einem Dreieck oder Rechteck verbunden sind, stellt sich die Frage, wie diese Verbindungspunkte gezeichnet werden. Das bestimmen ebenfalls drei Konstanten:

  • JOIN_ROUND rundet die Ecken ab.
  • JOIN_BEVEL zieht eine Linie zwischen den beiden äußeren Endpunkten.
  • JOIN_MITER erweitert die äußeren Linien soweit, bis sie sich treffen.

@Override
protected void paintComponent( Graphics g )
{
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);

BasicStroke stroke = new BasicStroke( 20, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL );
g2.setStroke( stroke );

Path2D shape = new GeneralPath();
shape.moveTo( 25, 25 ); shape.lineTo( 50, 100 ); shape.lineTo( 75, 25 );
g2.draw( shape );

//

stroke = new BasicStroke( 20, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER );
g2.setStroke( stroke );

shape = new GeneralPath();
shape.moveTo( 25+100, 25 ); shape.lineTo( 50+100, 100 ); shape.lineTo( 75+100, 25 );
g2.draw( shape );

//

stroke = new BasicStroke( 20, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND );
g2.setStroke( stroke );

shape = new GeneralPath();
shape.moveTo( 25+200, 25 ); shape.lineTo( 50+200, 100 ); shape.lineTo( 75+200, 25 );
g2.draw( shape );
}

Falls der Typ der Linienverbindungen JOIN_MITER ist, kann mit einem spitzen Winkel die Verbreiterung sehr lang werden. Die Variable miterlimit beim Konstruktor kann die maximale Länge beschränken, sodass über einer gewissen Größe die beiden Linien mit JOIN_BEVEL enden.

Füllmuster (dash)

Auch die Muster, mit denen die Linien oder Kurven gezeichnet werden, lassen sich ändern. Dazu erzeugen wir vorher ein Feld und übergeben dies einem Konstruktor. Damit auch die Muster abgerundet werden, muss CAP_ROUND gesetzt sein.
Die folgenden Zeilen erzeugen ein Rechteck mit einem einfachen Linienmuster. Es sollen zehn Punkte gesetzt und zwei Punkte frei sein.

float[] dash = { 10, 2 };
BasicStroke stroke = new BasicStroke( 2,
BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER,
1,
dash, 0 );

g2.setStroke( stroke );
g2.draw( new Rectangle2D.Float( 50, 50, 50, 50 ) );

Als letztes Argument am Konstruktor von BasicStroke hängt noch eine Verschiebung. Dieser Parameter bestimmt, wie viele Pixel im Muster übersprungen werden sollen. Geben wir dort für unser Beispiel etwa 10 an, so beginnt die Linie gleich mit zwei nicht gesetzten Pixeln. Eine 12 ergibt eine Verschiebung wieder an den Anfang. Bei nur einer Zahl im Feld ist der Abstand der Linien und die Breite einer Linie genauso lang, wie diese Zahl angibt. Bei gepunkteten Linien ist das Feld also 1. Hier eignet sich ein anonymes Feld ganz gut, wie die nächsten Zeilen zeigen:

stroke = new BasicStroke( 1,
BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL,
1, new float[]{ 1 }, 0 );

Bei feinen Linien sollten wir das Weichzeichnen besser ausschalten.

Labels:

AddThis Social Bookmark Button

Sonntag, Juli 22, 2007

Inselupdate: API-Dokumentation im HTML-Help Format

Die Sun-Dokumentation als Loseblattsammlung hat einen Nachteil, der sich im Programmieralltag bemerkbar macht: Die Dokumentation lässt sich nur ungenügend durchsuchen. Da die Webseiten statisch sind, lässt sich nicht einfach nach Methoden forschen, die auf »listener« enden. Franck Allimant (http://tutego.com/go/allimant) übersetzt regelmäßig die HTML-Dokumentation von Sun in das Format Windows HTML-Help (CHM-Dateien), das auch unter Unix und Mac OS X mit der Open-Source Software http://xchm.sourceforge.net/ gelesen werden kann. Neben den komprimierten Hilfe-Dateien lassen sich auch Sprach- und JVM-Spezifikation sowie die API-Dokumentation der Enterprise Edition und der Servlets im Speziellen beziehen.

Labels:

AddThis Social Bookmark Button

Dienstag, Mai 29, 2007

Insel: Eine Kopie von java.awt.Graphics erstellen

Das Zeichensystem übergibt an die paintXXX()-Methoden ein Graphics-Objekt, das wir zum Zeichnen oft verändern, etwa um einen Farbe für nachfolgende Operationen zu setzen. Stehen eigene Zeichenfunktion jedoch nicht am Ende der Zeichenfolge, ist es wichtig, den Grafikkontext so zu restaurieren, wie er am Anfang war, um nachfolgende Zeichenoperationen nicht zu beeinflussen. Wichtig ist dies etwa bei Swing-Komponenten, wo paint() die Methoden

  • protected void paintComponent( Graphics g )
  • protected void paintBorder( Graphics g )
  • protected void paintChildren( Graphics g )

aufruft. Nun lässt sich leicht ausmalen, was passiert, wenn unsere Funktion paintComponent() ein verhunztes Graphics-Objekt hinterlässt.

Die erste Lösung ist, sich die alten Zustände zu merken und zurückzusetzen:

Color oldColor = g.getColor();

g.setColor( newColor );

g.setColor( oldColor ); // Farbe zurücksetzten

Bei wenigen Eigenschaften funktioniert das gut, doch werden es mehr, ist es sinnvoller, eine Kopie aller Zustände des Grafik-Kontexts vorzunehmen. Dazu dient die Funktion create(). Von großer Wichtigkeit ist hier, diesen temporäreren Kontext auf jeden Fall mit dispose() wieder freizugeben, um keinen Speicherplatz zu blockieren.

@Override protected void paintComponent( Graphics g )

Graphics gcopy = g.create(); // Kopie erfragen

try {

gcopy.draw… // Alle Zeichenopertationen über gcopy

}

finally {

gcopy.dispose(); // Kopie freigeben

}

Ist definitiv keine Ausnahme zu erwarten, kann der try/catch-Block entfallen.

Labels:

AddThis Social Bookmark Button

Samstag, Mai 12, 2007

Insel: Ist eine Datei eine Verknüpfung (.lnk-Datei unter Windows)?

Bisher ist die File-Klasse noch nicht so ausgestattet, dass sie alle Fragen ohne Hacks beantwortet. Mit der JSR 203: „More New I/O APIs for the Java Platform“ ist hoffentlich Besserung auf dem Weg, aber heute kann die Frage nach der Dateiverknüpfung nur eine interne sun.awt.shell.ShellFolder beantworten. Die Klasse liefert bei .lnk-Dateien unter Windows mit isLink() ein klares true/false, gibt die Zieladresse mit getLinkLocation() und auf Anfrage mit getIcon() das assoziierte Datei-Icon als Image dazu.

String s = "C:\\Dokumente und Einstellungen\\All Users\\Startmenü\\Programmzugriff und -standards.lnk"
ShellFolder folder = ShellFolder.getShellFolder( new File( s ) );
System.out.println( folder.getFolderType() ); // Verknüpfung
if ( folder.isLink() )
System.out.println( folder.getLinkLocation() ); // C:\WINDOWS\system32\control.exe

Labels:

AddThis Social Bookmark Button

Freitag, April 27, 2007

Inselupdate: Properties einer Bean erfragen

Eine Bean besitzt Properties (Eigenschaften), die in Java (bisher) durch Setter und Getter ausgedrückt werden, also Methoden, die einer festen Namenskonvention folgen. Gibt es Interesse an den Properties, lässt sich natürlich getMethods() auf dem Class-Objekt aufrufen und nach den Methoden filtern, die der Namenskonvention entsprechen. Die Java-Bibliothek bietet aber im Paket java.beans eine einfachere Lösung für Beans: einen PropertyDescriptor.

Beispiel Gebe alle Properties – es gibt nur lesbare – von Color aus:

Listing 21.9 com/tutego/insel/meta/PropertyDescriptors.java, main()

BeanInfo beanInfo = Introspector.getBeanInfo( Color.class );
for ( PropertyDescriptor pd : beanInfo.getPropertyDescriptors() )
System.out.println( pd.getDisplayName() + " : " +
pd.getPropertyType().getName() );

Die Ausgabe ist

RGB : int
alpha : int
blue : int
class : java.lang.Class
colorSpace : java.awt.color.ColorSpace
green : int
red : int
transparency : int

Interessanter sind vom PropertyDescriptor die Methoden getReadMethod() und getWriteMethod(), die beides ein Method-Objekt liefern – wenn das denn verfügbar ist – um so die Methode gleich aufrufen zu können.

Labels:

AddThis Social Bookmark Button

Montag, April 16, 2007

Inselupdate: AUTO_RESIZE-Mode von JTable

Verändert der Anwender die Breite einer Spalte, ändert er entweder die Gesamtbreite einer Tabelle oder ändert automatisch die Breite der anderen Spalten, um die Gesamtbreite nicht zu verändern. Hier gibt es für die JTable unterschiedliche Möglichkeiten, die eine Methode setAutoResizeMode(int mode) bestimmt. Erlaubte Modi sind Konstanten aus JTable und AUTO_RESIZE_OFF, AUTO_RESIZE_NEXT_COLUMN, AUTO_RESIZE_SUBSEQUENT_COLUMNS, AUTO_RESIZE_LAST_COLUMN, AUTO_RESIZE_ALL_COLUMNS. Sinnvoll sind drei:

  • AUTO_RESIZE_SUBSEQUENT_COLUMNS. Der Standard. Verändert gleichmäßig die Breiten aller rechts liegenden Spalten.
  • AUTO_RESIZE_NEXT_COLUMN. Ändert nur die Breite der nachfolgenden Spalte.
  • AUTO_RESIZE_OFF. Ändert die Größe der gesamten Tabelle. Ist nur sinnvoll, wenn die JTable in einer JScrollPane liegt.

Labels:

AddThis Social Bookmark Button

Montag, März 26, 2007

parse()-Fehler bei DateFormat und setLenient(false)

Unsinnige Werte meckert parse() standardmäßig nicht an, wie die folgenden Zeilen darlegen:

DateFormat formatter = new SimpleDateFormat( "dd-MM-yyyy" );
System.out.println( formatter.parse( "29-02-2008" ) ); // Fri Feb 29 00:00:00 CET 2008
System.out.println( formatter.parse( "29-02-2007" ) ); // Thu Mar 01 00:00:00 CET 2007
System.out.println( formatter.parse( "33-02-2008" ) ); // Tue Mar 04 00:00:00 CET 2008

An den letzten beiden Beispielen lässt sich ablesen, dass das Datum auf den nächsten Monat „rollt“. Um das abzusichern, bietet DateFormat eine Funktion setLenient(boolean), die den „Mildmodus“ mit setLenient(false) ausschaltet.

DateFormat formatter = new SimpleDateFormat( "dd-MM-yyyy" );
formatter.setLenient( false );
System.out.println( formatter.parse( "29-02-2007" ) );

Jetzt gibt es für den 29.02.2007 eine ParseException mit dem Text „Unparseable date: "29-02-2007"“. Der 29.02.2008 ist in Ordnung, weil 2008 ein Schaltjahr ist.

Labels:

AddThis Social Bookmark Button

Donnerstag, März 15, 2007

Insel: Negative Rückgabe bei Math.abs(int)

Es gibt genau einen Wert, auf den kann Math.abs(int) keine positive Rückgabe liefern. Das ist -2147483648. Der Grund ist, dass -2147483648 die kleinste darstellbare int-Zahl (Integer.MIN_VALUE) ist, aber +2147483648 gar nicht in ein int int! Die größte darstellbare int-Zahl ist 2147483647 (Integer.MAX_VALUE). Was sollte hier abs() machen?

Labels:

AddThis Social Bookmark Button

Freitag, Februar 17, 2006

Insel: Geschütze Passwort-Eingaben mit der Klasse Console

Neu in Java 6 ist die Klasse java.io.Console, von der System.console() ein aktuelles Exemplar liefert – oder null bei einer Gui-Anwendung. Das Console-Objekt ermöglicht übliche Ausgaben und Eingaben und insbesondere mit readPassword() eine Möglichkeit zur Eingabe ohne Echo der eingegeben Zeichen.

Beispiel Lese ein Passwort ein und gib es auf der Konsole aus:
PasswordFromConsole.java
if ( System.console() != null )
{
String passwd = new String( System.console().readPassword() );
System.out.println( passwd );
}

Labels:

AddThis Social Bookmark Button

Freitag, Januar 13, 2006

Insel überarbeitet: nCopies()

Die statische Funktion nCopies() erzeugt eine Liste mit der gewünschten Anzahl von Elementen aus einem Objekt.

class java.util.Collections
- static List nCopies( int n, T o )
Erzeugt eine unveränderbare Liste mit n Elementen o.

Die Liste besteht nicht aus einer Anzahl Kopien des Elements (mit clone()), sondern aus einer Liste, die ein Element immer wiederholt. Daher sind auch nur Leseoperationen wie get() oder contains() erlaubt, doch keine Veränderungen. Infolgedessen ist der Einsatzbereich der Liste beschränkt, jener der Funktion aber nicht. Denn die Elemente der Liste können als Ausgang für eine modifizierbare Datenstruktur gelten, der sich eine Liste übergeben lässt. Das gilt zum Beispiel für eine ArrayList, die im Konstruktor eine andere Liste akzeptiert, der sie die Werte entnimmt.

Beispiel Initialisiere eine Liste mit 10 Leer-Strings und hänge an eine Liste 2 Zeichenketten mit ».« an.

List list = new ArrayList( Collections.nCopies(10, "") );
list.addAll( Collections.nCopies(2, ".") );
System.out.println( list ); // [, , , , , , , , , , ., .]

Labels:

AddThis Social Bookmark Button