Incorporating speech in a Swing application

1) First you need to download a speech engine. For example, you can download for free the Microsoft’s Speech SDK here. (Beware: version 5.1 is 68MB). Use the tools available to make sure the SDK is properly installed and trained to your voice.

2) Then get an implementation of Sun’s Java Speech API (JSAPI). The one I used in this example is from the CloudGarden. Follow the instructions on this page to find out how to install: copy the files jsapi.dll and jsapi.jar to JRE_HOME/lib/ext and adjust your classpath so that it includes jsapi.jar, necessary to compile following sample program.

3) This sample program was written by looking at the Cloud Garden examples. I dictated the following text to the app: “Last night, after a productive day of work, I joined my girlfriend in bed and quoted a very famous poet, who’s name escapes me. I said: ‘I feel great sexual desire for you right now.’. She said she’s not into poetry.”. The result (see window) was better than I expected.

Main.java:

import javax.speech.recognition.*;
import javax.speech.synthesis.*;
import javax.speech.*;
 
import javax.swing.text.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.*;
 
public class Main extends JFrame
{ 
   TextComponentSpeechEnabler tcse;
 
   public Main() {
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent we) {
            tcse.deallocate();
            System.exit(1);
         }
      });
 
      getContentPane().setLayout(new BorderLayout(10, 10));
      JTextArea textArea = new JTextArea();
      textArea.setLineWrap(true);
 
      try {
         tcse = new TextComponentSpeechEnabler(textArea);
         getContentPane().add(BorderLayout.CENTER, tcse.getPanel());
      }
      catch(Exception e) {
         e.printStackTrace();
      }
   }
 
   public static void main(String []args) {
      Main main = new Main();
      main.setSize(500, 500);
      main.setVisible(true);
   }
}
 
class TextComponentSpeechEnabler
{
   static Recognizer recognizer;
 
   JPanel         textComponentPanel;
   JTextComponent textComponent;
   JTextField     speakerName;
   JTextArea      statusArea;
 
   public TextComponentSpeechEnabler(JTextComponent textComponent) throws Exception {
      this.textComponent = textComponent;
      
      createPanel();
      initializeRecognizer();
   }
  
   public void initializeRecognizer() throws Exception {
      recognizer = Central.createRecognizer(null);
      recognizer.addResultListener(new MyResultListener());
      recognizer.allocate();
      recognizer.waitEngineState(Recognizer.ALLOCATED);
 
      SpeakerManager speakerManager = recognizer.getSpeakerManager();
      SpeakerProfile[] speakers = speakerManager.listKnownSpeakers();
      for (int i=0; i<speakers.length; i++) {
         addStatusText("Found profile of " + speakers[i].getName());
      }
 
      addStatusText("Current profile is " +
                                   speakerManager.getCurrentSpeaker().getName());
      speakerName.setText(speakerManager.getCurrentSpeaker().getName());
      speakerManager.setCurrentSpeaker(speakers[0]);
 
      DictationGrammar dictation = recognizer.getDictationGrammar("dictation");
      dictation.setEnabled(true);
 
      recognizer.commitChanges();
 
      recognizer.requestFocus();
      recognizer.resume(); 
   }
 
   public void deallocate() {
      System.out.println("deallocated!");
      try {
         recognizer.deallocate();
      }
      catch(Exception e) {
         e.printStackTrace();
      }
   }
 
   private void createPanel() {
      textComponentPanel = new JPanel(new BorderLayout(10, 10));
      JPanel northPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
      northPanel.add(new JLabel("Speaker name:  "));
      speakerName = new JTextField(25);
      northPanel.add(speakerName);
      textComponentPanel.add(BorderLayout.NORTH, northPanel);
      textComponentPanel.add(BorderLayout.CENTER, new JScrollPane(textComponent));
 
      statusArea = new JTextArea(10, 50);
      textComponentPanel.add(BorderLayout.SOUTH, new JScrollPane(statusArea));   
   }
  
   public JPanel getPanel() {
      return textComponentPanel;
   }
 
   public void addStatusText(final String s) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            statusArea.append(s + "n");
            statusArea.setCaretPosition(statusArea.getDocument().getLength());
         }
      });
   }
 
   public void addText(final String s) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            textComponent.setText(textComponent.getText() + s);
            textComponent.setCaretPosition(textComponent.getDocument().getLength());
         }
      });
   }
  
   class MyResultListener extends ResultAdapter {
      public void resultRejected(ResultEvent e) {
      }
 
      public void resultCreated(ResultEvent e) {
      }
 
      public void resultUpdated(ResultEvent e) {
      }
 
      public void resultAccepted(ResultEvent e) {
         FinalResult finalResult = (FinalResult)(e.getSource());
         ResultToken tokens[] = null;
         tokens = finalResult.getBestTokens();
 
         StringBuffer sb = new StringBuffer();
         for (int i=0; i<tokens.length; i++) {
            sb.append(tokens[i].getSpokenText() + " ");  
         }
         addText(sb.toString());
         addStatusText("" + finalResult);
      }
   }
}

Feel free to mail me any improvements!

Sending an email using JavaMail

This example assumes you have JavaMail1.2 installed. It can be downloaded from http://java.sun.com/products/javamail/. Add both mail.jar and activation.jar to your classpath.

Set the property mail.smtp.host with your SMTP host, create a session, tie it to your message and send it with the Transport class. It’s that simple!

SendMessage.java:

import javax.mail.internet.*;
import javax.mail.*;
import java.util.*;
 
public class SendMessage
{
   public static void main(String []args) {
      try {
         String host = "fill in your SMTP host here";
         String from = "FROM email address";
         String to   = "TO email address";
 
         Properties properties = new Properties();
         properties.put("mail.smtp.host", host);
 
         Session session = Session.getDefaultInstance(properties);
 
         MimeMessage message = new MimeMessage(session);
         message.setFrom(new InternetAddress(from));
         message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
 
         message.setSubject("Hi!");
         message.setText("I send you this file in order to have your advice");
 
         Transport.send(message);
      }
      catch(MessagingException e) {
         e.printStackTrace();
      }
   }
}

Saving the contents of a JPanel in a JPG image

The following example saves the content of the JPanel in the file test.jpg when the JFrame is closed. Play around with the saveComponent method that takes any sort of Component as a parameter.

Main.java:

import com.sun.image.codec.jpeg.*;
import javax.swing.border.*;
import java.awt.image.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.*;
import java.io.*;
 
public class Main extends JFrame 
{
   JPanel panel;
 
   public Main() {
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent we) { 
            saveComponent(panel, "test.jpg");
            System.exit(0);
         }
      });
 
      panel = createContactPanel();
 
      JScrollPane sp = new JScrollPane();
      sp.setViewportView(panel);
 
      getContentPane().setLayout(new BorderLayout());
      getContentPane().add(sp, BorderLayout.CENTER); 
   }
 
   public JPanel createContactPanel() {
      JLabel titleLbl      = new JLabel("Title");
      JLabel firstNameLbl  = new JLabel("First name");
      JLabel lastNameLbl   = new JLabel("Last name");
      JLabel addressLbl    = new JLabel("Address");
      JLabel cityLbl       = new JLabel("City");
      JLabel zipLbl        = new JLabel("Postal code");
      JLabel countryLbl    = new JLabel("Country");
      JLabel phoneLbl      = new JLabel("Phone number");
      JLabel faxLbl        = new JLabel("Fax number");
      JLabel emailLbl      = new JLabel("E-mail");
      JLabel birthdayLbl   = new JLabel("Birthdate");
      JLabel pickchoiceLbl = new JLabel("Pick a choice");
      JLabel creditCardTypeLbl     = new JLabel("Credit card type");
      JLabel creditCardNumberLbl   = new JLabel("Credit card number");
      JLabel expirationLbl         = new JLabel("Expiration date");
 
      JComboBox titleCombo = new JComboBox(
             new String[] { "-", "Mr", "Mrs", "Miss" });
      JTextField firstNameTf    = new JTextField();
      JTextField lastNameTf     = new JTextField();
      JTextField address1Tf     = new JTextField();
      JTextField address2Tf     = new JTextField();
      JTextField cityTf         = new JTextField();
      JTextField zipTf          = new JTextField();
      JTextField countryTf      = new JTextField();
      JTextField phoneTf        = new JTextField();
      JTextField faxTf          = new JTextField();
      JTextField emailTf        = new JTextField();
      JComboBox bd1Combo = new JComboBox();
      for (int i=1; i<=12; i++) bd1Combo.addItem(""+i);
      JComboBox bd2Combo = new JComboBox();
      for (int i=1; i<=31; i++) bd2Combo.addItem(""+i);
      JComboBox bd3Combo = new JComboBox();
      for (int i=1900; i<2000; i++) bd3Combo.addItem(""+i);
      JComboBox referCombo = new JComboBox();
      referCombo.addItem("Friend");
      referCombo.addItem("Search engine");
      referCombo.addItem("Print Media");
      referCombo.addItem("Banner Add");
      referCombo.addItem("Other");
      JComboBox creditCardTypeCombo = new JComboBox();
      creditCardTypeCombo.addItem("VISA");
      creditCardTypeCombo.addItem("MasterCard");
      creditCardTypeCombo.addItem("American Express");
      JTextField creditCardNumberTf = new JTextField();
      JComboBox expiration1Combo = new JComboBox();
      for (int i=1; i<=12; i++) expiration1Combo.addItem(""+i);
      JComboBox expiration2Combo = new JComboBox();
      for (int i=1; i<=31; i++) expiration2Combo.addItem(""+i);
      JComboBox expiration3Combo = new JComboBox();
      for (int i=1900; i<=2000; i++) expiration3Combo.addItem(""+i);
 
      JPanel panelGeneral = new JPanel();
      panelGeneral.setLayout(null);
      panelGeneral.add(titleLbl);
      panelGeneral.setBorder(new TitledBorder("General Information"));
      titleLbl.setBounds(20, 20, 150, 20);
      panelGeneral.add(firstNameLbl);
      firstNameLbl.setBounds(20, 50, 150, 20);
      panelGeneral.add(lastNameLbl);
      lastNameLbl.setBounds(20, 80, 150, 20);
      panelGeneral.add(addressLbl);
      addressLbl.setBounds(20, 110, 150, 20);
      panelGeneral.add(cityLbl);
      cityLbl.setBounds(20, 170, 150, 20);
      panelGeneral.add(zipLbl);
      zipLbl.setBounds(20, 200, 150, 20);
      panelGeneral.add(countryLbl);
      countryLbl.setBounds(20, 230, 150, 20);
      panelGeneral.add(phoneLbl);
      phoneLbl.setBounds(20, 260, 150, 20);
      panelGeneral.add(faxLbl);
      faxLbl.setBounds(20, 290, 150, 20);
      panelGeneral.add(emailLbl);
      emailLbl.setBounds(20, 320, 150, 20);
      panelGeneral.add(birthdayLbl);
      birthdayLbl.setBounds(20, 350, 150, 20); 
      panelGeneral.add(titleCombo);
      titleCombo.setBounds(150, 20, 60, 20);
      panelGeneral.add(firstNameTf);
      firstNameTf.setBounds(150, 50, 200, 20);
      panelGeneral.add(lastNameTf);
      lastNameTf.setBounds(150, 80, 200, 20);
      panelGeneral.add(address1Tf);
      address1Tf.setBounds(150, 110, 200, 20);  
      panelGeneral.add(address2Tf);
      address2Tf.setBounds(150, 140, 200, 20);  
      panelGeneral.add(cityTf);
      cityTf.setBounds(150, 170, 200, 20);
      panelGeneral.add(zipTf);
      zipTf.setBounds(150, 200, 200, 20);
      panelGeneral.add(countryTf);
      countryTf.setBounds(150, 230, 200, 20);
      panelGeneral.add(phoneTf);
      phoneTf.setBounds(150, 260, 200, 20);
      panelGeneral.add(faxTf);
      faxTf.setBounds(150, 290, 200, 20);
      panelGeneral.add(emailTf);
      emailTf.setBounds(150, 320, 200, 20);
      panelGeneral.add(bd1Combo);
      bd1Combo.setBounds(150, 350, 60, 20);
      panelGeneral.add(bd2Combo);
      bd2Combo.setBounds(220, 350, 60, 20);
      panelGeneral.add(bd3Combo);
      bd3Combo.setBounds(290, 350, 60, 20);
 
      JPanel panelReferral = new JPanel();
      panelReferral.setLayout(null);
      panelReferral.setBorder(new TitledBorder("Where did you hear about us?"));
      panelReferral.add(pickchoiceLbl);
      pickchoiceLbl.setBounds(20, 20, 150, 20);
      panelReferral.add(referCombo);
      referCombo.setBounds(150, 20, 100, 20);
 
      JPanel panelCreditCard = new JPanel();
      panelCreditCard.setLayout(null);
      panelCreditCard.setBorder(new TitledBorder("Payment method"));
      panelCreditCard.add(creditCardTypeLbl);
      creditCardTypeLbl.setBounds(20, 20, 150, 20);
      panelCreditCard.add(creditCardNumberLbl);
      creditCardNumberLbl.setBounds(20, 50, 150, 20);
      panelCreditCard.add(expirationLbl);
      expirationLbl.setBounds(20, 80, 150, 20);
 
      panelCreditCard.add(creditCardTypeCombo);
      creditCardTypeCombo.setBounds(150, 20, 100, 20);
      panelCreditCard.add(creditCardNumberTf);
      creditCardNumberTf.setBounds(150, 50, 150, 20);
      panelCreditCard.add(expiration2Combo);
      expiration2Combo.setBounds(220, 80, 60, 20);
      panelCreditCard.add(expiration3Combo);
      expiration3Combo.setBounds(290, 80, 60, 20);
 
      JPanel panel = new JPanel();
      panel.setLayout(null);
      panel.add(panelGeneral);
      panelGeneral.setBounds(10, 20, 370, 400);
      panel.add(panelReferral);
      panelReferral.setBounds(10, 430, 370, 50);
      panel.add(panelCreditCard);
      panelCreditCard.setBounds(10, 490, 370, 120);
      
      panel.setPreferredSize(new Dimension(380, 620)); 
 
      return panel;
   }
 
   public void saveComponent(Component c, String filename) {
      Dimension d = c.getPreferredSize();
      BufferedImage bi = new BufferedImage((int) d.getWidth(), (int) d.getHeight(),
                                           BufferedImage.TYPE_INT_RGB);
 
      Graphics2D g2 = bi.createGraphics();
      g2.setClip(0, 0, (int) d.getWidth(), (int) d.getHeight());
      c.paint(g2);
      try {
         BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filename));
         JPGWrite(bi, bos);
      }
      catch(Exception e) {
         e.printStackTrace();
      }
   }
 
   public static void JPGWrite(BufferedImage bi, OutputStream out) throws IOException {
      JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
      JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(bi);
      param.setQuality(1f, true);
      encoder.encode(bi, param);
   }
 
   public static void main(String []args) {
      Main main = new Main();
      main.setSize(400, 400);
      main.setVisible(true);
   }
}

Changing the default session timeout with Tomcat

You can specify the session timeout in your web.xml deployment descriptor. The <session-timeout%gt; tag specifies the number of minutes of inactivity that the container will allow before the HttpSession object becomes invalid.

eg.

    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>

You can also set your own timeout interval in your servlet or jsp:

<html>
<body>
<% 
   session.setMaxInactiveInterval(30);
%>
</body>
</html>

Don’t change filename textfield when a directory is selected in a JFileChooser

Here’s some code that solves that problem.

Main.java:

import java.awt.event.*;
import javax.swing.*;
import java.beans.*;
import java.io.*;
 
public class Main extends JFrame
{
   private String fileSelected = "autoexec.bat";
 
   public static void main(String []args) {
      Main main = new Main();
      main.show();
   }
  
   public Main() {
      JButton fileButton = new JButton("Select File");
      fileButton.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            File file = getFileFromUser();
            if (file != null)
               System.out.println(file.getName());
         }
      });
      getContentPane().add(fileButton);
  
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent e) {
            System.exit(0);
         }
      });
 
      pack();
   }
  
   public File getFileFromUser() {
      JFileChooser fc = new JFileChooser();
 
      // use current directory
      fc.setCurrentDirectory(new File("c:\"));
 
      // set default name
      fc.setSelectedFile(new File(fileSelected));
 
      fc.addPropertyChangeListener(new PropertyChangeListener() {
         public void propertyChange(PropertyChangeEvent pce) {
            String property = pce.getPropertyName();
            if (property.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)) {
               File dir = (File) pce.getNewValue();
               if (dir.isDirectory()) {
                  ((JFileChooser) pce.getSource()).setSelectedFile(new File(dir, fileSelected));
               }
               else {
                  fileSelected = ((JFileChooser) pce.getSource()).getSelectedFile().getName();
               }
            }
            else if (property.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY)) {
               File dir = (File) pce.getNewValue();
               ((JFileChooser) pce.getSource()).setSelectedFile(new File(dir, fileSelected));
            }
         }
      });
 
      // show dialog for opening files
      int result = fc.showSaveDialog(this);
 
      if (result != fc.APPROVE_OPTION) 
         return null;
       
      return fc.getSelectedFile();
   }
}

Creating an SWT DirectoryDialog

Main.java:

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.ColorDialog;
import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
 
public class Main { 
   static Display display;
   static Shell shell;
   static Color color;
    
   public static void main(String[] args) {
      display = new Display();
      shell = new Shell(display);
 
      // pos x, pos y, width, height
      shell.setBounds(200, 200, 400, 200);
      shell.setText("SWT DirectoryDialog Demonstration");
      shell.setLayout(new GridLayout());
 
      final Group buttonGroup = new Group(shell, SWT.NONE);
      GridLayout gridLayout = new GridLayout();
      gridLayout.numColumns = 3;
      buttonGroup.setLayout(gridLayout);
      buttonGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
       
      final Button directoryDialogButton = new Button(buttonGroup, SWT.PUSH);
      directoryDialogButton.setText("Open DirectoryDialog");
      Label label1 = new Label(buttonGroup, SWT.NONE);
      label1.setText("Selected:");
      final Label label2 = new Label(buttonGroup, SWT.NONE);      
      label2.setText("");
 
      SelectionListener selectionListener = new SelectionAdapter () {
         public void widgetSelected(SelectionEvent event) {
            DirectoryDialog dialog = new DirectoryDialog (shell, SWT.MODELESS);
            dialog.setMessage ("Please select the desired directory.  Notice network neighborhood!");
            dialog.setText("Select the desired directory");
            String result = dialog.open();
            label2.setText("" + result);
            buttonGroup.layout();
         };
      };   
       
      directoryDialogButton.addSelectionListener(selectionListener);
       
      shell.open();
 
      while (!shell.isDisposed()) {
         if (!display.readAndDispatch()) {
            display.sleep();
         }
      }
      if (color != null && !color.isDisposed()) {
         color.dispose();
      }
      display.dispose();
   }
}

Creating an SWT table with the lines visible

Main.java:

import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
  
public class Main {
   public static void main(String[] args) {
      Display display = new Display();
      Shell shell = new Shell(display);
  
      // pos x, pos y, width, height
      shell.setBounds(200, 200, 300, 200);
      shell.setText("SWT Table Demonstration");
      shell.setLayout(new GridLayout());
  
      Table table;
      String[] columnTitles = { "Wine", "Vintage", "Price" };
 
      Object[][] tabledata = {
            { "Chateau Meyney, St. Estephe",       new Integer(1994), "$18.75"},
            { "Chateau Montrose, St. Estephe",     new Integer(1975), "$54.25" },
            { "Chateau Gloria, St. Julien",     new Integer(1993), "$22.99" },
            { "Chateau Beychevelle, St. Julien",   new Integer(1970), "$61.63" },
            { "Chateau La Tour de Mons, Margeaux", new Integer(1975), "$57.03" },
            { "Chateau Brane-Cantenac, Margeaux",  new Integer(1978), "$49.92" },
      };
  
      Group tableGroup = new Group(shell, SWT.NULL);
      GridLayout gridLayout = new GridLayout();
      gridLayout.numColumns = 1;
      tableGroup.setLayout(gridLayout);
      tableGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
      tableGroup.setText("Table");
  
      table = new Table(tableGroup, SWT.FULL_SELECTION);
      table.setHeaderVisible(true);
      table.setLinesVisible(true);
        
      for (int i=0; i<columnTitles.length; i++) {
         TableColumn tableColumn = new TableColumn(table, SWT.NULL);
         tableColumn.setText(columnTitles[i]);
      }  
        
      for (int i=0; i<tabledata.length; i++) {
         TableItem tableItem = new TableItem (table, SWT.NULL);
         for (int j=0; j<columnTitles.length; j++) {
            tableItem.setText(j, ""+tabledata[i][j]);
         }
      }
  
      for (int i=0; i<columnTitles.length; i++) {
         TableColumn tableColumn = table.getColumn(i);
         tableColumn.pack();
      }      
        
      shell.open();
  
      while (!shell.isDisposed()) {
         if (!display.readAndDispatch()) {
            display.sleep();
         }
      }
      display.dispose();
   }
}

Creating a scale in SWT

Main.java:

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Scale;
import org.eclipse.swt.widgets.Shell;
 
public class Main {
   public static void main (String [] args) {
      Display display = new Display ();
      Shell shell = new Shell (display);
   
      shell.setBounds(200, 200, 250, 250);   
       
      final Scale scale = new Scale (shell, SWT.VERTICAL);
      scale.setMinimum(100);
      scale.setMaximum(200);
      scale.setBounds(10, 10, 30, 200);
      final StyledText styledText = new StyledText(shell, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL | SWT.WRAP);
      styledText.setBounds(50, 10, 180, 200);
       
      scale.addListener(SWT.Selection, new Listener () {
         public void handleEvent(Event event) {
            styledText.append(scale.getSelection() + "n");
            int offset = styledText.getOffsetAtLine(styledText.getLineCount()-1);
            styledText.setCaretOffset(offset);
            styledText.showSelection();
         }
      });
 
      shell.open ();
      while (!shell.isDisposed()) {
         if (!display.readAndDispatch ()) display.sleep ();
      }
   }
} 

Canonicalizing an XML file

The canonical form of an XML document is a normalized version of that XML document. Two XML documents that are physically different can still be logically the same. For example, consider an XML tag with two attributes. The order in which the attributes appear is of no importance:

(1)
<tag attrA="123" attrB="456"/>
  
is logically the same to 
 
(2) 
<tag attrB="456" attrA="123"/>

The canonical form of an XML document is important when you look at signing. Signing an XML document consists of calculating a message digest (hash) to ensure message integrity and signing the message and the hash with the private key of the sender. The receiver would then use the public key to verify.

The verification procedure should go successful regardless of the physical representation of the XML document. This is where the problem comes in: the digest of example (1) is different than the digest of example (2), even though the information is the same.

It is important to calculate the message digest on the canonical form of the XML document.

More information about canonical XML can be found here.

The following example uses the Canonicalizer class from the XML Security project at apache.org. Download it here and place the following libraries in your classpath:

bc-jce-jdk13-118.jar   (bouncycastle library)
log4j-1.2.5.jar
xalan.jar
xercesImpl.jar
xml-apis.jar
xmlsec.jar

Main.java:

import org.apache.xml.security.c14n.Canonicalizer;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import org.w3c.dom.*;
 
import java.io.ByteArrayInputStream;
 
public class Main
{
   static String input1 = "<doc><field1 attr1="123" attr2="456"/><field2>abc</field2></doc>";
   static String input2 = "<doc><field1  attr1="123" attr2="456"   /><field2   >abc</field2></doc>";
   static String input3 = "<doc><field1 attr2="456" attr1="123"  /><field2>abc</field2></doc >";
 
   public static void main(String args[]) throws Exception 
   {
      org.apache.xml.security.Init.init();
 
      DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
      docFactory.setNamespaceAware(true);
      docFactory.setValidating(true);
 
      DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
 
      // This is to throw away all validation warnings
      docBuilder.setErrorHandler(new org.apache.xml.security.utils.IgnoreAllErrorHandler());
 
      System.out.println("Input:");
      System.out.println("  " + input1);
      System.out.println("  " + input2);
      System.out.println("  " + input3);
 
      byte[] output1 = canonicalize(docBuilder, input1);
      byte[] output2 = canonicalize(docBuilder, input2);
      byte[] output3 = canonicalize(docBuilder, input3);
 
      System.out.println("nOutput:");
      System.out.println("  " + new String(output1));
      System.out.println("  " + new String(output2));
      System.out.println("  " + new String(output3));
   }
 
   public static byte[] canonicalize(DocumentBuilder docBuilder, String input) throws Exception {
      byte inputBytes[] = input.getBytes();
      Document doc = docBuilder.parse(new ByteArrayInputStream(inputBytes));
      
      Canonicalizer c14n = Canonicalizer.getInstance(
        "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments");
 
      return c14n.canonicalizeSubtree(doc);
   }
}

outputs:

Input:
  <doc><field1 attr1="123" attr2="456"/><field2>abc</field2></doc>
  <doc><field1  attr1="123" attr2="456"   /><field2   >abc</field2></doc>
  <doc><field1 attr2="456" attr1="123"  /><field2>abc</field2></doc >

Output:
  <doc><field1 attr1="123" attr2="456"></field1><field2>abc</field2></doc>
  <doc><field1 attr1="123" attr2="456"></field1><field2>abc</field2></doc>
  <doc><field1 attr1="123" attr2="456"></field1><field2>abc</field2></doc>

Get a private key from a keystore

This example extracts a private key from a keystore and prints it out in PEM format.

Main.java:

import sun.misc.BASE64Encoder;
import java.security.cert.Certificate;
import java.security.*;
import java.io.File;
import java.io.FileInputStream;
 
class Main {
   public static void main(String args[]) throws Exception{
      if (args.length != 3) {
         System.out.println("Extracts private key in PEM format from a JKS keystore");
         System.out.println("Usage: java Main <keystore> <alias> <passphrase>");
         System.exit(1);
      }
 
      String keystore = args[0];
      String alias = args[1];
      String password = args[2];      
 
      KeyStore ks = KeyStore.getInstance("JKS");
      char[] passphrase = password.toCharArray();
      BASE64Encoder base64Encoder = new BASE64Encoder();
 
      ks.load(new FileInputStream(keystore), passphrase);
 
      PrivateKey privateKey = getPrivateKey(ks, alias, passphrase);
 
      String sPrivateKey = base64Encoder.encode(privateKey.getEncoded());
 
      System.out.println("-----BEGIN PRIVATE KEY-----");
      System.out.println(sPrivateKey);
      System.out.println("-----END PRIVATE KEY-----");
   }
 
   public static PrivateKey getPrivateKey(KeyStore keystore, String alias, char[] password) {
      try {
         Key key = keystore.getKey(alias, password);
         if (key instanceof PrivateKey) {
            Certificate cert = keystore.getCertificate(alias);
            PublicKey publicKey = cert.getPublicKey();
 
            KeyPair kp = new KeyPair(publicKey, (PrivateKey)key);
 
            return kp.getPrivate();
         }
      } catch (UnrecoverableKeyException e) {
         e.printStackTrace();
      } catch (NoSuchAlgorithmException e) {
         e.printStackTrace();
      } catch (KeyStoreException e) {
         e.printStackTrace();
      }
 
      return null;
   }
}