What is hybrid cryptography?

Symmetric and asymmetric ciphers each have their own advantages and disadvantages. Symmetric ciphers are significantly faster (Schneier states “at least 1000 times faster”) than asymmetric ciphers, but require all parties to somehow share a secret (the key). The asymmetric algorithms allow public key infrastructures and key exchange systems, but at the cost of speed.

A hybrid cryptosystem is a protocol using multiple ciphers of different types together, each to it’s best advantage. One common approach is to generate a random secret key for a symmetric cipher, and then encrypt this key via an asymmentric cipher using the recipient’s public key. The message itself is then encrypted using the symmetric cipher and the secret key. Both the encrypted secret key and the encrypted message are then sent to the recipient.

The recipient decrypts the secret key first, using his/her own private key, and then uses that key to decrypt the message. This is basically the approach used in PGP.

Using the class MessageFormat

MessageFormat is a useful class to format dynamic messages that depend on a number of variables. In other words, it allows you to replace certain parts of a String template.

For example, consider the following example template: (letter.txt)


				{0}, {1,date},
 
 
Dear {2}, 
 
	I regret to inform you that you have been eliminated from 
further contention to become the future Mrs. {3}. As you are 
probably aware, the competition was exceedingly tough this year and 
{4,number,integer} well-qualified canditates such as yourself also failed to 
make the final cut. I will, however, keep your name on file should 
an opening come available or I become extremely horny. 
  
					 Sincerely, 
 
					 {5} {3}

To be able to send this letter to many persons, we have created a template in which all things that are likely to vary are replaced by number of var, [type, [format of type]] enclosed in curly braces ({}). In our example, we’d like to replace {0} by the location, {1} by the date of today, {2} by the potential girlfriend that is about to be rejected :), {3} by the name of the person, {4} by the number of applicants, {5} by our first name and {3} by our last name.

Performing the replacement is simple, just instantiate an object of MessageFormat and initialize it with this text. Then call the method format and pass it an array in which the object at the index of the array refers to the variable with the same number.

      String letter = ... (initialize to above text) ...
 
      MessageFormat mf = new MessageFormat(letter);
 
      Object[] vars = { "Brussels",         // {0}
                        new Date(),         // {1, date}
                        "Ulla",             // {2}
                        "Doe",              // {3}
                        new Integer(12),    // {4, number, integer}
                        "John" };           // {5}
      String formattedLetter = mf.format(vars);

The result (formattedLetter) will be:

 
				Brussels, Jun 25, 2001,
 
 
Dear Ulla, 
 
	I regret to inform you that you have been eliminated from 
further contention to become the future Mrs. Doe. As you are 
probably aware, the competition was exceedingly tough this year and 
12 well-qualified canditates such as yourself also failed to 
make the final cut. I will, however, keep your name on file should 
an opening come available or I become extremely horny. 
  
					 Sincerely, 
 
					 John Doe

Here’s the complete program:

import java.text.*;
import java.util.*;
import java.io.*;
  
public class Main {
   public static void main(String []args) throws Exception {
      if (args.length != 1) {
         System.err.println("Usage: java Main <letter.txt>");
         System.exit(1);
      }
 
      String letter = readFile(args[0]);
      MessageFormat mf = new MessageFormat(letter);
 
      Object[] vars = { "Brussels",
                        new Date(),
                        "Ulla",
                        "Doe",
                        new Integer(12),
                        "John" };
      String formattedLetter = mf.format(vars);
 
      System.out.println(formattedLetter);
   }
 
   public static String readFile(String filename) throws Exception {
      BufferedReader br = new BufferedReader(new FileReader(filename));
      StringBuffer buffer = new StringBuffer();
 
      String ls = System.getProperty("line.separator");
      String line;
      while ((line = br.readLine()) != null) {
         buffer.append(line + ls);
      }
 
      return buffer.toString();
   }
}

You shouldn’t use MessageFormat as a report generator though. For one thing, you can use maximum 10 variables (from {0} to {9}) inside your template. Check out this entry in Sun’s bug parade to evaluate whether it is a bug or not.

Instead, use it for relatively small messages that need to be customized or internationalized, like localized error or status messages. Also, you don’t have to create a instance of MessageFormat; you can use its static method format. It looks much like the C function sprintf.

Main.java:

import java.text.*;
import java.util.*;
import java.io.*;
  
public class Main {
   public static void main(String []a) throws Exception {
      String s = MessageFormat.format("Price is {0,number,$##.#}, VAT included.", 
                                      new Object[] { new Float(8.46) });
      System.out.println(s);
   }
}

outputs:

Price is $8.5, VAT included.

MessageFormat and ChoiceFormat

For example, suppose we want to create a message to the user to show him information about the amount of new emails he’s got. We could have the following possibilities:

   You have no emails.    		[ if #emails == 0 ]
   You have one email.    		[ if #emails == 1 ]
   You have a couple of emails.    	[ if #emails > 1 and < 8 ]
   You have X emails.			[ if #emails >= 8 ]

There are two substrings that vary here depending on #emails:

   You have [VAR] email[VAR]

VAR2 is necessary to account for the missing “s” in the second case. This leads to the introduction of a class called ChoiceFormat, which is typically used in conjunction with MessageFormat to solve these types of problems. It allows you to “connect” an numeric interval to a result as a String. So, we want:

   "no"  		if #emails == 0
   "one"		if #emails == 1
   "a couple of"	if #emails in range [1, 8[
   #emails		if #emails in range [8, ...

Converting this to a ChoiceFormat:

#1   double[] limits0 = {0, 1, 2, 8};
#2   String[] choices0 = { "no", "one", "a couple", "{0, number}" };
 
#3   ChoiceFormat format0 = new ChoiceFormat(limits0, choices0);
#4   mf.setFormat(0, format0); 

Line number #1 encodes the intervals. According to the API, choices0[j] will be chosen if and only if limits0[j] < = #emails < limit[j+1]. Line number #4 tells the MessageFormat instance to use this ChoiceFormat when formatting the first variable (0 in this case means the first variable it encounters in the template “You have [VAR] email[VAR]“).

To format the second variable (“You have [VAR] email[VAR]“), we use the same technique:

   "s"		if #emails = 0
   ""		if #emails = 1
   "s"		if #emails > 1

Converting this to a ChoiceFormat:

#1    double[] limits1 = {0, 1, 2};
#2    String[] choices1 = { "s", "", "s" };
 
#3    ChoiceFormat format1 = new ChoiceFormat(limits1, choices1);
#4    mf.setFormat(1, format1);

Here’s the complete program: (Main.java)

import java.text.*;
import java.util.*;
import java.io.*;
  
public class Main {
   public static void main(String []args) throws Exception {
      MessageFormat mf = new MessageFormat("You have {0} email{0}.");
 
      double[] limits0 = {0, 1, 2, 8};
      String[] choices0 = { "no", "one", "a couple", "{0, number}" };
      ChoiceFormat format0 = new ChoiceFormat(limits0, choices0);
      mf.setFormat(0, format0);
 
      double[] limits1 = {0, 1, 2};
      String[] choices1 = { "s", "", "s" };
      ChoiceFormat format1 = new ChoiceFormat(limits1, choices1);
      mf.setFormat(1, format1);
 
      String message = mf.format(new Object[] {new Integer(0)});
      System.out.println(message);
 
      message = mf.format(new Object[] {new Integer(1)});
      System.out.println(message);
 
      message = mf.format(new Object[] {new Integer(7)});
      System.out.println(message);
 
      message = mf.format(new Object[] {new Integer(23)});
      System.out.println(message);
   }
}

outputs:

You have no emails.
You have one email.
You have a couple emails.
You have 23 emails.

Of course, you could also incorporate ResourceBundles as shown in following example.

LabelResourceBundle_en.java:

import java.util.*;
 
public class LabelResourceBundle_en extends ListResourceBundle
{
   private Object[][] contents = 
      { { "greeting", 		"Hello" } };
  
   public Object[][] getContents() {
      return contents;
   }
}

LabelResourceBundle_fr_BE.java:

import java.util.*;
 
public class LabelResourceBundle_fr_BE extends ListResourceBundle
{
   private Object[][] contents = 
      { { "greeting", 		"Bonjour" } };
  
   public Object[][] getContents() {
      return contents;
   }
}

Main.java:

import java.text.*;
import java.util.*;
import java.io.*;
  
public class Main {
   public static void main(String []a) throws Exception {
      ResourceBundle rb = ResourceBundle.getBundle("LabelResourceBundle");
 
      MessageFormat mf = new MessageFormat("{0}, {1}");
 
      // use default locale
      Object[] args = new Object[] { rb.getString("greeting"), "John Doe" };
      System.out.println(mf.format(args));
 
      // change locale
      rb = ResourceBundle.getBundle("LabelResourceBundle", new Locale("fr", "BE"));
      args = new Object[] { rb.getString("greeting"), "John Doe" };
      System.out.println(mf.format(args));
   }
}

outputs:

Hello, John Doe
Bonjour, John Doe

What is a locale?

A locale identifies a particular language and region. Optionally, you can specify a custom parameter called a variant. A locale object is useful for internationalization. For example: in the US the decimal symbol is point whereas in Belgium it’s a comma. To format the number correctly to the user, your application must consult the Locale object to determine the current language or region.

Whenever you create a locale object without specifying language or region, you’ll get the default one. This one is automatically determined as follows:

   System.getProperty("user.language");  // for language
   System.getProperty("user.region");    // for region

To get the default locale:

   Locale locale = Locale.getDefault();

You can create your own locale object as follows:

   Locale locale = new Locale("nl", "BE");

Optionally, you can specify a variant:

   Locale locale = new Locale("fr", "BE", "LINUX");

Classes that depend on a language (NumberFormat, DateFormat, …) or region are called locale-sensitive. Typically, you can call the static method getAvailableLocales on that class to find out the locales that work with that class. A resource bundle (see the category resourcebundles) also makes heavily use on locales, as it needs them to determine the correct class name or properties file.

Once you have a particular locale identified through one of the constructors, you have access to several methods to find out more about that locale. Look at the API for more information. Here’s an example:

import java.util.*;
 
public class Main {
   public static void main(String []args) {
      Locale locale = Locale.getDefault(); 
 
      System.out.println("Language         " + locale.getLanguage());
      System.out.println("Country          " + locale.getCountry());
      System.out.println("Variant          " + locale.getVariant());
      System.out.println("DisplayLanguage  " + locale.getDisplayLanguage());
      System.out.println("DisplayCountry   " + locale.getDisplayCountry());
      System.out.println("DisplayVariant   " + locale.getDisplayVariant());
      System.out.println("DisplayName      " + locale.getDisplayName());
      System.out.println("ISO3Country      " + locale.getISO3Country());
      System.out.println("ISO3Language     " + locale.getISO3Language());
   }
}

outputs:

Language         en
Country          US
Variant          
DisplayLanguage  English
DisplayCountry   United States
DisplayVariant   
DisplayName      English (United States)
ISO3Country      USA
ISO3Language     eng

Loading Java preferences

Specify the node.

Main.java:

import java.util.prefs.*;
 
public class Main
{
   public static final String APPLICATIONPATH = "applicationpath";
   public static final String USERFONT        = "userfont";
   public static final String LASTUSERLOGIN   = "lastuserlogin";
   public static final String LDAPURL         = "ldapurl";
 
   public static void main(String []args) {
      String nodename = "/com/esus/examples/preferences";
 
      Preferences preferences = Preferences.userRoot().node(nodename);
      System.out.println(preferences.get(APPLICATIONPATH, ""));
      System.out.println(preferences.get(USERFONT, ""));
      System.out.println(preferences.get(LASTUSERLOGIN, ""));
      System.out.println(preferences.get(LDAPURL, ""));
   }
}

outputs:

c:application
Courier New
Joris Van den Bogaert
ldap://localhost:389

Assuming you ran the example given in this answer and filled in the values.

Using a ResourceBundle

Suppose we have a .properties file called LabelsBundle_nl_BE and assume also our default locale is en_US. To get access to that bundle, you can invoke ResourceBundle.getBundle(“LabelsBundle”, “nl”, “BE”). Notice that a resource bundle consists of two parts: a family name and a locale part. The locale part contains a language code, a country code and an optional variant code.

In our example, it will try to locate the bundle in the following way:

	class LabelsBundle_nl_BE		NOTFOUND
	file LabelsBundle_nl_BE.properties      OK
	class LabelsBundle_nl			NOTFOUND
	file LabelsBundle_nl.properties		NOTFOUND
	class LabelsBundle			NOTFOUND
	file LabelsBundle.properties		NOTFOUND

Notice that once the .propreties file has been located, it will still continue to look for parent resourcebundles by shortening the locale part delimited by an underscore.

Now assume we only have a .properties file called LabelsBundle_en and we invoke ResourceBundle.getBundle(“LabelsBundle”, “nl”, “BE”). Notice here that it will start from our desired locale and build its way up the tree, including searching for a match with the default locale.

	class LabelsBundle_nl_BE		NOTFOUND
	file LabelsBundle_nl_BE.properties	NOTFOUND
	class LabelsBundle_nl			NOTFOUND
	file LabelsBundle_nl.properties		NOTFOUND
	class LabelsBundle_en_US		NOTFOUND
	file LabelsBundle_en_US.properties	NOTFOUND
	class LabelsBundle_en			NOTFOUND
	file LabelsBundle_en.propertes		OK
	class LabelsBundle			NOTFOUND
	file LabelsBundle.properties		NOTFOUND

Assume we have a .properties file called LabelsBundle_nl_BE.properties and a .properties file called LabelsBundle.properties.

	class LabelsBundle_nl_BE		NOTFOUND
	file LabelsBundle_nl_BE.properties	OK
	class LabelsBundle_nl			NOTFOUND
	file LabelsBundle_nl.properties		NOTFOUND
	class LabelsBundle			NOTFOUND
	file LabelsBundle.properties		OK

In this example, LabelsBundle.properties has become a parent resource bundle of LabelsBundle_nl_BE. You can use the property keys from both these files through the instance that you get back from invoking ResourceBundle.getBundle(…). Also notice that if the resource bundle has been found using the desired locale, the default locale will not be used in looking for a parent. The default locale is only used if locating a bundle with the desired locale failed.

As a last note, you must specify the fully qualified name for the family name (or baseclass) when requesting a resource bundle object. For example, if LabelsBundle_nl_BE.properties is in the package mypackage, you will have to say:

	ResourceBundle.getBundle("mypackage.LabelsBundle", "nl", "BE");

Decompressing using the GZIP algorithm at runtime

Use the standard API class GZIPInputStream. Internally, it uses Inflater to decompress data. The following example asks the user a file to decompress and writes the decompressed stream of data to file.def.

Main.java:

import java.util.zip.*;
import java.io.*;
 
public class Main {
   public static void main(String args[]) {
      if (args.length != 1) {
         System.out.println("Usage: java Main <file.gzip>");
         System.out.println("<file.gzip>.def will be created");
         System.exit(1);
      }
 
      try {
         FileInputStream fis = new FileInputStream(args[0]);
         FileOutputStream fos = new FileOutputStream(args[0] + ".def");
 
         GZIPInputStream gis = new GZIPInputStream(fis);
         byte []buffer = new byte[1024];
         int l;
         while ((l = gis.read(buffer)) >= 0) {
            fos.write(buffer, 0, l);
         }
  
         gis.close();
         fos.close();
      }
      catch(IOException e) {
         System.out.println(e);
      }
   }
}

Prevent the user from closing a JOptionPane with the X button

Embed your JOptionPane in a JDialog. Set the default close operation on that JDialog to DO_NOTHING_ON_CLOSE and add a PropertyChangeListener in which you listen for VALUE_PROPERTY and INPUT_VALUE_PROPERTY and hide the JDialog.

Main.java:

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
  
public class Main
{
   public static void main(String []args) {
      JFrame frame = new JFrame();
 
      final JOptionPane pane = new JOptionPane("Are you sure you want to import this file?n" +
                                               "(try presssing the x button!)", 
                                               JOptionPane.QUESTION_MESSAGE,
                                               JOptionPane.YES_NO_CANCEL_OPTION);
      final JDialog dialog = new JDialog(frame, "Question", true);
      dialog.setContentPane(pane);
 
      dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
 
      pane.addPropertyChangeListener(new PropertyChangeListener() {
         public void propertyChange(PropertyChangeEvent e) {
            String prop = e.getPropertyName();
            if (dialog.isVisible() 
             && (e.getSource() == pane)
             && (prop.equals(JOptionPane.VALUE_PROPERTY) ||
                 prop.equals(JOptionPane.INPUT_VALUE_PROPERTY)))
            {
               System.out.println("Caught, JOptionPane is ready to close!");
               dialog.setVisible(false);
            }
         }
      });
 
      dialog.pack();
      dialog.setVisible(true);
      Object value = pane.getValue();
      System.out.println("Option " + value + " selected!");
      
      System.exit(1);
   }
}

Displaying an image as the background of a JPanel

Override the JPanel’s method paintComponent, draw the image using g.drawImage and call the superclass’ paintComponent. Make sure the JPanel is opaque.

Main.java:

import java.awt.event.*;
import javax.swing.*;
import java.awt.*;
 
public class Main extends JFrame
{
   public Main() {
      getContentPane().setLayout(new FlowLayout());
 
      JPanel panel = createPanel();
 
      getContentPane().add(BorderLayout.CENTER, panel);
 
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent we) {
            System.exit(0);
         }
      });
   }
 
   public static JPanel createPanel() {
      JPanel panel = new JPanel() {
         public void paintComponent(Graphics g) {
            ImageIcon img = new ImageIcon("c:\mong.jpg");
            System.out.println(g.drawImage(img.getImage(), 0, 0, null));
            super.paintComponent(g);
         }
      };
      panel.setOpaque(false);
      panel.setLayout(new GridLayout(20, 4, 10, 10));
 
      for (int i=0; i<20; i++) {
         for (int j=0; j<4; j++) {
            JLabel label = new JLabel("label " + i + ", " + j);
            label.setFont(new Font("Helvetica", Font.PLAIN, 30));
            panel.add(label);            
         }
      }
 
      return panel;
   }
 
   public static void main(String [] args) {
      Main main = new Main();
      main.setSize(300, 300);
      main.setVisible(true);
   } 
} 

BTW, here’s the image I used:



Creating a nested JTabbedPane

The following example nests JTabbedPanes recursively until 10 levels deep:

import javax.swing.event.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.*;
import java.beans.*;
 
public class Main extends JFrame {
 
   public Main() {
      getContentPane().setLayout(new BorderLayout());
      JTabbedPane tabbedPane = new JTabbedPane();
 
      getContentPane().add(BorderLayout.CENTER, tabbedPane);
  
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent we) {
            System.exit(0);
         }
      });
 
      setTitle("JTabbedPane Nested Demonstration");
      setSize(new Dimension(400, 400));
 
      createTab(tabbedPane, 0);
   }
 
   protected void createTab(JTabbedPane jtp, int ct) {
      if (ct > 10) return;
      JTabbedPane tp = new JTabbedPane();
      tp.setTabPlacement(getNextOrientation(jtp.getTabPlacement()));
      jtp.addTab("TabLevel #" + ct, tp);
      createTab(tp, ct+1);
   }
 
   private int getNextOrientation(int tabPlacement) {
      if (tabPlacement == JTabbedPane.TOP) return JTabbedPane.RIGHT;
      if (tabPlacement == JTabbedPane.RIGHT) return JTabbedPane.BOTTOM;
      if (tabPlacement == JTabbedPane.BOTTOM) return JTabbedPane.LEFT;
      else return JTabbedPane.TOP;
   }
 
   public static void main(String []args) {
      Main main = new Main();
      main.show();
   }
}