The Source for Java Technology Collaboration
User: Password:



Fabrizio Giudici's Blog

Community: JavaDesktop Archives


A little desktop quiz

Posted by fabriziogiudici on May 02, 2008 at 03:26 PM | Permalink | Comments (4)

This blueMarine screenshot has been just taken on Mac OS X. Do you note something different than usual? Clicking the image you can see the original capture. I'll pay a beer at JavaOne to the first guy that guesses what's going on (*).





(*) This is the second beer I'll have to offer, the first being due to Kohsuke.

Dreams Beans (Part 1)

Posted by fabriziogiudici on February 01, 2008 at 07:58 AM | Permalink | Comments (3)

One of the most hated things in Swing programming is the management of the infamous Event Dispatcher Thread (a.k.a. AWT Thread). The state-of-the-art solutions such as SwingWorker work fine but are quite verbose and cumbersome to implement. Indeed there are much better solutions, at least for most cases, that are just at hand.

What's the "perfect bean" like in my perspective? It's something like this (taken from a real world case, of course):
package it.tidalwave.metadata.iptc.viewer;

import it.tidalwave.metadata.iptc.IPTCContent;
import it.tidalwave.metadata.viewer.MetadataItemPaneSupport;

public class IPTCContentPane extends MetadataItemPaneSupport<IPTCContent> // might be extends JPanel
  {
    public IPTCContentPane()
      {
        super(IPTCContent.class);
        initComponents();
      }
   
    /***************************************************************************
     *
     * This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     *
     **************************************************************************/
    // Matisse-generated code hidden                         
  }
That's all; all the hidden parts are chunks of code that have been generated by the Visual Designer of NetBeans and that I can indirectly edit by means of proper wizards.

Now, if you're already complaining about that extends, well you can avoid it if you wish. Indeed my specific requirements ask for polymorphic behaviour of my panels, hence that inheritance, otherwise I could easily make it by means of composition. Details later.

The above panel matches with this class (a bit long, but if you look at it you'll only see getters and setters):
package it.tidalwave.metadata.iptc;

import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.CopyOnWriteArrayList;
import java.io.Serializable;

public class IPTCContent
  {
    private String headline;
    private boolean headlineAvailable;
    private String caption;
    private boolean captionAvailable;
    private Collection<String> iptcSubjectCodes;
    private boolean iptcSubjectCodesAvailable;
    private String descriptionWriter;
    private boolean descriptionWriterAvailable;
    private String category;
    private boolean categoryAvailable;
    private Collection<String> otherCategories;
    private boolean otherCategoriesAvailable;

    public String getHeadline()
      {
        return headline;
      }

    public void setHeadline (final String headline)
      {
        this.headline = headline;
      }

    public boolean isHeadlineAvailable()
      {
        return headlineAvailable;
      }

    public void setHeadlineAvailable (final boolean headlineAvailable)
      {
        this.headlineAvailable = headlineAvailable;
      }

    public String getCaption()
      {
        return caption;
      }

    public void setCaption (final String caption)
      {
        this.caption = caption;
      }

    public boolean isCaptionAvailable()
      {
        return captionAvailable;
      }

    public void setCaptionAvailable (final boolean captionAvailable)
      {
        this.captionAvailable = captionAvailable;
      }

    public Collection<String> getIPTCSubjectCodes()
      {
        return (iptcSubjectCodes == null) ? null : new CopyOnWriteArrayList(iptcSubjectCodes);
      }

    public void setIPTCSubjectCodes (final Collection<String> iptcSubjectCodes)
      {
        if (iptcSubjectCodes == null)
          {
            this.iptcSubjectCodes = null;
          }
       
        else
          {
            if (this.iptcSubjectCodes == null)
              {
                this.iptcSubjectCodes = new ArrayList<String>();
              }
           
            this.iptcSubjectCodes.clear();
            this.iptcSubjectCodes.addAll(iptcSubjectCodes);
          }
      }

    public boolean isIPTCSubjectCodesAvailable()
      {
        return iptcSubjectCodesAvailable;
      }

    public void setIPTCSubjectCodesAvailable (final boolean iptcSubjectCodesAvailable)
      {
        this.iptcSubjectCodesAvailable = iptcSubjectCodesAvailable;
      }

    public String getDescriptionWriter()
      {
        return descriptionWriter;
      }

    public void setDescriptionWriter (final String descriptionWriter)
      {
        this.descriptionWriter = descriptionWriter;
      }

    public boolean isDescriptionWriterAvailable()
      {
        return descriptionWriterAvailable;
      }

    public void setDescriptionWriterAvailable (final boolean descriptionWriterAvailable)
      {
        this.descriptionWriterAvailable = descriptionWriterAvailable;
      }

    public String getCategory()
      {
        return category;
      }

    public void setCategory (final String category)
      {
        this.category = category;
      }

    public boolean isCategoryAvailable()
      {
        return categoryAvailable;
      }

    public void setCategoryAvailable (final boolean categoryAvailable)
      {
        this.categoryAvailable = categoryAvailable;

      }

    public Collection<String> getOtherCategories()
      {
        return (otherCategories == null) ? null : new CopyOnWriteArrayList(otherCategories);
      }

    public void setOtherCategories (final Collection<String> otherCategories)
      {
        if (otherCategories == null)
          {
            this.otherCategories = null;
          }
       
        else
          {
            if (this.otherCategories == null)
              {
                this.otherCategories = new ArrayList<String>();
              }
           
            this.otherCategories.clear();
            this.otherCategories.addAll(otherCategories);
          }
      }

    public boolean isOtherCategoriesAvailable()
      {
        return otherCategoriesAvailable;
      }

    public void setOtherCategoriesAvailable (final boolean otherCategoriesAvailable)
      {
        this.otherCategoriesAvailable = otherCategoriesAvailable;
      }

    @Override
    public boolean equals (final Object obj)
      {
         ...
      }

    @Override
    public int hashCode()
      {
         ...
      }
  }

Now you can just call:
IPTCContentPane contentPane = new IPTCContentPane();
// add contentPane to a Swing component

IPTCContent iptcContent1 = contentPane.getBean();
String headline = iptcContent1.getHeadline();
iptcContent1.setCaption("My caption");

// alternatively

IPTCContent iptcContent2 = new IPTCContent();
contentPane.bind(iptcContent2);
Now, every change made in the UI is automatically reflected in the bean properties, and each change in the bean properties is propagated to the pane, properly delivered by means of the Swing EventDispatcherThread. As you can see, this is just looking like plain Java code, no inner classes, no hassles; and the bean is just a POJO, no event listener stuff and whatever.

How did I make it? Well, mostly with existing pieces of software and tools. When I've understood some residual issues, I could even throw away some code of mine and revert to existing software only.

The fully working code from which I took the above examples is available here:
svn co https://bluemarine.dev.java.net/svn/bluemarine/trunk/src/Metadata/IPTC/IPTC --username guest
svn co https://bluemarine.dev.java.net/svn/bluemarine/trunk/src/Metadata/IPTC/IPTCViewer --username guest
If you want to compile everything, you need to check out the whole Metadata subproject.


Let's see things in details. There are three problems to address:
  1. We need something compliant with JavaBeans (I mean events, listener, whatever) even if we just write a POJO.
  2. We need a quick way to bind the properties in our bean with the components in the UI.
  3. We need a way to deal with the EventDispatcherThread.

Getting JavaBeans behaviour (almost) for free - and handling the EventDispatcherThread stuff


Turning a POJO in a fully compliant JavaBean just requires a few steps, most notably wrapping the construction of the bean:
JavaBeanEnhancer enhancer = new JavaBeanEnhancer();
IPTCContent content = (IPTCContent)enhancer.createEnhancedBean(new IPTCContent(), JavaBeanEnhancer.EDT_COMPLIANT);
The JavaBeanEnhancer dinamically changes the bytecode by means of the CGLIB library (details below). At this point, the bean can be managed as usual, but it also supports this:
PropertyChangeListener listener = new addPropertyChangeListener() { ... };
((JavaBean)content).addPropertyChangeListener(listener);
Of course, I must accept a trade-off: dynamically changing the code can't change the static definitions by default, so the explicit cast is required. Beyond the explicit use of the listeners, you can use your objects with the BeansBindings API (in this case with no need for casting, as BeansBinding works with plain objects and resolves references by introspection).

The details about how JavaBeanEnhancer has been implemented will go in another post, or I won't ever been able to finish this. In any case, you can look at the code by checking out from here:

svn co https://openbluesky.dev.java.net/svn/openbluesky/trunk/src/OpenBlueSky/Beans -r 153 --username guest

The code is a refinement of some base code that was provided by pupmonster and jarppe2 as a response to a previous blog post of mine - thank you guys, you saved me a lot of time.

I must point you to the Spin project (thanks to guette for suggesting it): indeed it does most of the things I need, but in my specific case I'm using CGLIB also for other aspects of my problem. Up to now I've found that I can't run CGLIB to enhance a bean that has already been enhanced by CGLIB. If I am able to fix this, I could be using Spin very soon. In the meantime, it might already be good for you.


Binding Properties

I really don't want to repeat stuff about BeansBinding that is already available in tutorials. I'll only shortly repeats which steps I used for my code:
  1. Use Matisse as usual for designing the form.
  2. Open the Inspector and select the "Other Components" element.
  3. Select the menu "Add From Palette / Beans / Choose Bean".
  4. At this point you have to enter the fully qualified name of the bean that you want to bind (in my case it.tidalwave.metadata.iptc.IPTCContent). Please be aware that you must have compiled at least once the bean, or Matisse won't allow you to go on.
  5. Rename the automatically generated name for the bean reference to something that you like (in my case iptcContent).


Matisse has added your bean to the others already declared and initialized by the auto-generated code. Now you have to open the "Customize Code" menu.



As you can see in the picture below, you can now customize the code for instantiating the bean. Instead of a direct instantiation, we must wrap it with the JavaBeanEnhancer stuff.



There are many ways to do this, I suggest wrapping the thing in a method - getBean()) in my case, which is inherited (but an alternate approach based on composition might be used):
public abstract class MetadataItemPaneSupport<Bean> extends JPanel
  {
    private static final JavaBeanEnhancer JAVA_BEAN_ENHANCER = new JavaBeanEnhancer();
    private final Class<Bean> beanClass;
    private final Bean edtBean;
    private final BindingGroup bindingGroup = new BindingGroup();

    protected MetadataItemPaneSupport (final Class<Bean> beanClass)
      {
        this.beanClass = beanClass;   

        try
          {
            edtBean = (Bean)JAVA_BEAN_ENHANCER.createEnhancedBean(beanClass.newInstance(), JavaBeanEnhancer.EDT_COMPLIANT);
          }
        catch (InstantiationException e)
          {
            throw new RuntimeException(e);
          }
        catch (IllegalAccessException e)
          {
            throw new RuntimeException(e);
          }
      }

    public final Bean getBean()
      {
        return edtBean;   
      }

    public void bind (final Bean externalBean)
      throws IllegalArgumentException
      {
         // see below for details
      }
  }

At this point you can just use Matisse to bind each component to the properties of your beans as shown below:



The bind() method is implemented with a combination of the standard Java introspection capabilities and the BeansBindings API:

    private static final List<String> JAVA_BEAN_ASPECT_PROPERTIES = 
Arrays.asList("callback", "callbacks", "class", "propertyChangeListeners", "vetoableChangeListeners");

public void bind (final Bean externalBean)
throws IllegalArgumentException
{
logger.info(String.format("bind(%s)", externalBean));
validateBean(externalBean);

bindingGroup.unbind();

for (final Binding binding : bindingGroup.getBindings())
{
bindingGroup.removeBinding(binding);
}

try
{
final PropertyDescriptor[] descriptors = Introspector.getBeanInfo(externalBean.getClass()).getPropertyDescriptors();

for (final PropertyDescriptor descriptor : descriptors)
{
if (!JAVA_BEAN_ASPECT_PROPERTIES.contains(descriptor.getName()))
{
final Property property = BeanProperty.create(descriptor.getName());
logger.finest(String.format(">>>> binding %s", property));
final Binding binding = Bindings.createAutoBinding(READ_WRITE, externalBean, property, edtBean, property);
bindingGroup.addBinding(binding);
}
}
}
catch (IntrospectionException e)
{
throw new RuntimeException(e);
}

bindingGroup.bind();
}
It basically perform a "global binding" of all the properties available (with the exclusion of some infrastructural stuff): after a call to the method, the two beans are kept in complete synchrony.

If Shannon Hickey and the other BeansBindings guy are around: what about wrapping the above code (cleaned up, of course) into a method such as:

BindingGroup bindingGroup = Bindings.createAutoBindings(READ_WRITE, bean1, bean2);


That's all for today. Next time I'll show you more details about the JavaBeanEnhancer

Good advice: buy that book

Posted by fabriziogiudici on September 19, 2007 at 06:29 AM | Permalink | Comments (4)

I received my copy of "Filthy Rich Clients" by Chet Haase and Romain Guy a few days ago (now in good company with "Rich Client Programming"), but until this morning I didn't find the time to read it.

So, just after waking up, in the usual last ten minutes before getting to work, I had the idea to check whether the book could help me in fixing a problem that was around since a few days (actually I was working on it only during breaks, but you get the point). If you read my latest blog post, you know that I'm working on mixing the Visual Library with JXMapViewer from SwingLabs. The two components should be rendered one over the other, and for this kind of stuff there's JLayeredPane. Unfortunately, it didn't worked well for me, I presumed for something related to the LayoutManager. So I developed a customized LayoutManager and went for some customized code extending JPanel (quite a common (anti)pattern for people working with Swing). Yesterday I discovered that the stuff wasn't working properly on Linux (don't know if because of the different operating system or because I tested it with Java 6); more code changes followed; then I added a third layer to render the navigation controls and the stuff got completely broken. Despair: this stuff must be demoed next week, so I scheduled a "massive revert" thinking of giving up with some parts of the demo.

This morning I took the book, went to the "Layered Panes" chapter, "Layered Panes and Layouts" paragraph. Here's the illuminating excerpt:

The only efficient way to use a layout with a layered pane are to write your own or to use the least-known layout of all time, java.awt.OverlayLayout.

Yep, it was indeed the least-known of all time, I often navigate inside Java sources in search of inspiration but I always missed it! The result is that these lines work for me:

        setLayout(new OverlayLayout(this));
        add(backgroundComponent, new Integer(100));
        add(sceneComponent, new Integer(200));
        add(foregroundComponent, new Integer(300));

Got rid of the code bloat, now everything is simpler, it works everywhere, it can be demoed next week ;-) Thanks Chet and Romain!


Technorati Tags: ,






Photo courtesy of Geertjan Wielenga


Towards a "unified community" for Java imaging developers?

Posted by fabriziogiudici on June 08, 2007 at 03:06 AM | Permalink | Comments (0)

In the latest week I've written a few times about the world of Java imaging. I've anticipated one of my points, that is we lack an "unified community" dedicated to imaging Java programmers, which spans over all the Java imaging technologies (Java2D, JAI, others), focusing only on imaging themes (e.g. dropping rendering stuff in Java2D).

Starting up a community is not easy. I talked with a few persons and we share the same opinion: we would like to see such a community. But we need other people to reach a critical mass and start it up.

We will talk about this in a BoF at Jazoon: "Towards a "unified community" for Java imaging developers" (without the question mark :-) ). We will just talk a bit about the problem, above all we hope to find many attendees and to exchange some ideas. If there is enough consensus (and volunteers), the thing could start as soon as this summer.

See you in Zurich!



Feeling like the Buridan's Donkey

Posted by fabriziogiudici on May 12, 2007 at 01:24 AM | Permalink | Comments (0)

I don't know whether the expression "Buridan's Donkey" has the same meaning in english as in italian (I've just recenly discovered that the latin "qui(d) pro quo" has nowadays different meanings in english and romance languages). In any case, the history is about a donkey that starved and died since it had a lot of alternate foods to eat and it couldn't decide which was the most tasty to start eating with.

I feel in a similar way as I've found a lot of new Java toys to play with! :-)

  • NASA World Wind - and Geertjan will soon tell us how to include it in NetBeans RPC apps
  • With the latest NetBeans update I've found a Sun Grid plugin that allows to create and start jobs on the grid
  • ... and I just discovered that now the Sun Grid is accessible from 24 countries other than USA, including Italy!!

Well, maybe the plugin was already there since some time, but I didn't notice it - knowing that the grid was not available for Italy it could not have attracted my attention. Now I've just subscribed to it, hopefully in a few days I'll have my account working. This is important, since the USA-only access really slowed down the development of the Grid plugin for Mistral. I can't promise anything... but maybe there will be something ready at the blueMarine / Mistral demo at Jazoon. ;-)

Wow Sun: I looooove you sooooooooo! ;-)

Picture 3.png



Some small quirks in Ubuntu .deb installation

Posted by fabriziogiudici on May 10, 2007 at 11:59 AM | Permalink | Comments (0)

A few days ago I blogged about the way we can now deliver a more robust .deb installation of our application on Ubuntu Feisty Fawn. Now that I've released a first non-snapshot .deb package, I ran some more tests and discovered a little quirk.

The good thing is that by just downloading a .deb file you can automatically invoke the package installer (and this is the default choice).

Screenshot1-600.jpg

If for some reasons there's not the Sun Java version you need, and "multiverse" is not enabled, you get a good error message. It's better to block the installation here instead of going on and having your application to malfunction, as in this way it's clear that there's a missing resource problem.

Screenshot2-600.jpg

If you have the "multiverse" already enabled, the installer is so smart to figure out what you need - it will be automatically downloaded and installed. The user can optionally look at the details before going on.

Screenshot3-600.jpg

And now the small-but-annoying problem. I noted that the installer progress bar "installing dependencies" got stuck around the middle. I first thought there was a temporary connection problem, but as it didn't resume I opened the "Terminal" tab. Aaargh.... The licensing stuff! You have to go to the bottom of the page and manually insert "y" and press return. I hope that Sun and Ubuntu will be soon able to get rid of this. In the meantime, I strongly suggest you give this hint in the installation notes of your application.

Screenshot4-600.jpg


Let's discuss about Java imaging

Posted by fabriziogiudici on March 28, 2007 at 05:46 AM | Permalink | Comments (16)

I was happy to see that Slav Boleslawski's article about a specialized component for rendering photos made its way to the featured articles of this week. Indeed if Java wants to gain attention on the desktop, the whole media segment can't be ignored.

Looking around I think we can notice some early warnings that people is indeed working on this area. Today I finished editing a blog post about a component for photo management and rendering, and I included in the article a quick panorama of some existing Java imaging applications (both commercial and opensource) and of the most important Java imaging APIs. I think it would be a good thing if people working both on commercial and opensource imaging application could meet in some specialized forum - not aimed at a particular technology (so I'm not thinking of the Java2D or JAI standard forums), but for sharing the experience and the open problems.

Yes, because in my opinion there are some open problems (I discussed some of them in my post). Apart from a general lack of information (or a difficulty in retrieving that information) about how to use the imaging APIs (a recurring problem is about high-quality image resizing), there are some inconsistencies in how the APIs behave in different systems. For instance, take this table with a few compared performance results about some basic operations performed with Java2D (keep them with a pinch of salt, but I'm starting to be confident with them):

Test Quality
    Mac OS X Linux Ubuntu   Win XP  
J2DUtils.ScaleWithAffineTransform
INTERMEDIATE 1214328 1169925 1236672
J2DUtils.ScaleWithAffineTransform (opt) INTERMEDIATE 1859 FAILS  1921
J2DUtils.ScaleWithDrawImage INTERMEDIATE 1844
1044457  1095032
J2DUtils.ScaleWithDrawImage (opt)
INTERMEDIATE 3991 3230  2062
ScaleJ2DOp INTERMEDIATE  1755 1161022 1220625
ScaleJ2DOp (opt) INTERMEDIATE  1818 2950 1906
OptimizeJ2DOp INTERMEDIATE  5146 4053 4062
OptimizeJ2DOp (opt) INTERMEDIATE  1663 2940  1734
RotateJ2DOp INTERMEDIATE 24805 4040127 4300516
RotateJ2DOp (opt)
INTERMEDIATE 57435 28843 18188

The performance has been measured on a single Mac Mini Dual Core with a triple-boot, so you can actually compare results from the different operating systems. As you can see there are some wild differences, pointed out by the red color. Operations marked with "opt" are performed on optimized images, that is an image made with GraphicsConfiguration.createCompatibleImage(). A "compatible" image "has a layout and color model that is closest to this native device configuration and can therefore be optimally blitted to this device" (from the javadoc of GraphicsConfiguration) and using them is one of the good practices of working with Swing. In facts, as you can see, using them leads to dramatic performance improvements on Linux and Windows (OTOH the bad result is several magnitudos worse, and it's clearly related to some bugs). But one operation miserably fails on Linux with an exception. And... surprise, on Mac OS X the optimized image is not always faster (BTW, the test image is a JPEG converted from a 6 megapixels image taken with a Nikon D100 and loaded by standard Image I/O - both the test image and test sources are in Mistral unit tests). On Mac OS X, by default the used imaging core is not the same as Sun, as this partially explains the difference.

I talked about this only twice with other persons on some random forums, and each time I had the impression that this is part of a dubious, common but unshared knowledge. So, my point is: why don't we setup a specific forum where we can discuss this? An excellent opensource project would be to set up a well-written set of tests similar to the one I've just presented, so that they can be authoritative - it would be a great value for all the imaging Java programmers!



Duke the Photographer wants you!

Posted by fabriziogiudici on March 19, 2007 at 07:25 AM | Permalink | Comments (11)

When you are forced to stop or slow down, as I'm being forced to due to my illness still evolving slowly, you have more time to spend just thinking (yep, this is just a try to catch the good in bad moments). For instance, you can look at the big picture of what you're doing, instead of focusing at a single activity, and see whether things are going in the (supposed) proper direction. In these days I realized that in the latest years I started up a lot of projects (that later went into opensource). Their primary purpose was to experiment with some technologies, and they were successful in this. But as they reach a critical mass and can be useful to others, it's a shame when they starve. And there's a project of mine that's really starving - being its latest release a 1.0.RC5 dated to November 2006; and it has been in RC stage for one year now... It's jrawio and it's a Java Image I/O plugin for camera raw files. With this blog, I'm explicitly calling for volunteers to join it.

Continue Reading...



The Cream on the Cake: free Duke graphics

Posted by fabriziogiudici on November 13, 2006 at 09:38 AM | Permalink | Comments (0)

As part of the opensourcing programme of Java, Sun has also made Duke graphics open sourced. This means that, complying with the BSD License, everybody can include Duke on his/her application graphics, even making changes (and redistributing them).

So, guys, c'mon! Let's show our Java pride and let's say to everybody that our applications are Java-powered. This is the newest splash screen of blueMarine, and Duke is really happy to be there.

SplashScreen.png


You need to build blueMarine from sources to see Duke, as it has committed as build 1397. Or wait for the next official release.



It's because of that flattening Moore's Law curve

Posted by fabriziogiudici on November 10, 2006 at 05:28 AM | Permalink | Comments (2)

After the short parenthesis on the Java / GPL stuff - looks like Sun will clarify everything in a matter of days - I'm switching back to my original topic.

As I told you a few days ago, one of the purposes of the blueMarine cluster of projects is to research in new technologies and understand some possible future scenarios of computing. One of these is parallel-computing-made-easier. I'm getting convinced that in the next few years parallel computing (that is the capability of performing intense computation tasks in a shorter time by distributing the load on multiple CPUs) will become more pervasive than today - not because I've dreamt about it after eating too much onions, but because I'm seeing some techy people talking more and more about it.

Continue Reading...



First, the context

Posted by fabriziogiudici on November 06, 2006 at 02:27 PM | Permalink | Comments (4)

I think that it's a good thing that I first describe the context where my next posts will live. Well, it's a cluster of opensource projects mainly for the desktop, about photo processing:

  • blueMarine is a desktop application for supporting the workflow of a digital photographer;
  • jrawio is a Java Image I/O plugin for dealing with the "camera raw" file formats produced by the medium-top camera bodies from manufacturers such as Nikon, Canon, Sony, etc...
  • Mistral is the image processing engine of blueMarine (which is an abstraction layer over JAI or ImageJ)

Everything started in Summer 2003 when I bought my first digital single-lens reflex camera (for the record a Nikon D100) and I was intrigued by the "camera raw" world, as it gave you the opportunity to "develop" your own photos (for people not aware of it, in a few words "camera raw" formats are the raw dump of data from the camera sensor - they require extensive processing to deliver a viewable image and in this processing there's a lot of flexibility and control from the photographer).

But as the project evolved, it turned out to be also a very good platform to experiment with cool Java technologies. It's a way to kill two birds with one stone: my professional interest in Java and my passion about photography.





Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds