mail us  |  mail this page

contact us
training  | 
tech stuff  | 

Tech Stuff - Java

Plenty of resources for Java folks on the web. This page documents a number of problems for which we failed to find (or steal, snaffle, purloin as you choose) a solution on the web and a trivial (X)SSI expansion utility. If this stuff is useful feel free to use (steal, snaffle or purloin as you choose).

Note: We are not Java guys, necessity, as always, however, is the mother of invention. There may be be BQF (better, quicker, faster) and in the case of Java "purer" ways of doing this stuff that we failed to find. If so write us off as the Java neophytes that we willingly admit to being.

Contents - Notes

  1. Editing HTML Forms
    1. Finding the INPUT tags for the Triggers
    2. Handling The Triggers
    3. Form Submission (Submit button)
  2. Capturing Current Data from an HTML Form (Programmatic Submit)
  3. Sending System.out (Std.out) to a Log File
  4. Creating an Apple Bundle for a Java App
    1. Getting Acces to Files in a Bundle
  5. (X)SSI Expansion Utility

Editing HTML Forms

We wanted to be able to intercept editing of certain INPUT tags on HTML FORMs within a JEditorPane and call specialized editors that provide unique formatting for the value(s) being edited immediately the user showed an intention of wishing to edit these fields (right click in field for example). In our case possibly any one of 5 - 10 specialized editors are called, depending on the type of field being edited. These editors typically popup a Modal JDialog and offer content specific editing support, formatting and validation. Some fields we ignore, such as short text editing and SELECT tags and let the default editor handle them. While the processes described reflects our specific requirement they should work for anything with some mods which we note in the code snippet comments where sensible.

When using JEditorPane with HTML Forms (FORM tag) the editing function for INPUT tags with type="text" is handled in a JTextField, type="password" in a JPasswordField (SELECT tags use JComboBox and TEXTAREA uses JTextArea and yet others for check boxes and radio buttons). These editing components are not visible using the object hierarchy obtained from the HTMLDocument interface of JEditorPane. The HTMLDocument hierarchy terminates in a Document object. The Document interface supports documentListeners - all of which are triggered after the edit has been completed. Specifically, the Document interface does not support mouseListeners which would allow notification immediately the user enters a field or double clicks (or however you want to trigger the edit interface).

However, the JTextField, JPasswordField (and JComboBox, JTextArea and others) components do allow multiple listener types and specifically (in our case) mouseListeners. Interestingly, they also terminate in the Document object. Meaning that we can find the final Document by descending the HTMLDocument interface, and if we can find and set appropriate triggers on the JTextField (and others as required) we could get to the same Document.

The only remaining problem is the Document interface has no references back up its hierarchy to the HTMLDocument(for example, a getParent method), so if we reach the Document using the JTextField object we have no idea which INPUT tag it relates to and since it's the INPUT tag which contains interesting things like the name attribute which we want to use to invoke our selective editors.

However, all things are finally possible and the key to tying up the two access routes (via the HTMLDocument and separately via the JTextField/JPasswordField) lies in the Document object's get/putProperty methods. Read on for the gory details.

HTML Form Analysis and Set Triggers

The first step is to find the various INPUT tags in the FORM and track them down to the Document. In the same fragment we show finding the various JTextFields (using an incredibly useful scraper utility) that we want to use as mouseListener triggers. Here's the commented fragment showing what we did:

....
// somewhere on a distant planet we set up the JEditorPane and HTMLEditorKit
// and introduced them to each other
private JEditorPane editor = new JEditorPane();
// Custom HTMLEditorKit (see last snippet)
private SubmitHTMLForm htmlEditor = new SubmitHTMLForm(this, viewer);

editor.setEditorKitForContentType("text/html", htmlEditor);
editor.addHyperlinkListener(hyperlinkListener);
editor.setEditable(false);
editor.setContentType("text/html");
....

// some private/public method that adds a new HTML page 
private void setHTMLPageData(String htmlText){
 try
 {
   // htmlText may or may not contain a FORM
   // but is a fully formed HTML document
	 
   editor.setText(htmlText);
                
   // process the resultant HTML document
	  
   HTMLDocument hd = (HTMLDocument)editor.getDocument();
   // get a reference to the FORM tag if present starting from the document root
   Element elem = hd.getElement(hd.getDefaultRootElement(), 
              StyleConstants.NameAttribute, HTML.Tag.FORM);
   if(elem != null){
       // we have a form tag
       // now find all the input tags of type=text/password
       ElementIterator it = new ElementIterator(elem);
       Element input = null;
       while((input = it.next()) != null ){
	            		
         HTML.Tag tag = (HTML.Tag)input.getAttributes().getAttribute(StyleConstants.NameAttribute);
         // we are only interested in INPUT tags - add SELECT/TEXTAREA as required
         // normally tons of TABLE, TR and TD tags as well cluttering up the layout
         if (tag == HTML.Tag.INPUT) {
            // get the type HTML attribute     		
            String type = (String)input.getAttributes().getAttribute(HTML.Attribute.TYPE);
            // filter out the types we are interested in
            if(type.equals("text") || type.equals("password")){
                		   
                AttributeSet attr = input.getAttributes();
                // now get the name= HTML attribute which is the unique data we are interested in
                // but could be , say, the value= HTML attribute
                String name = (String)attr.getAttribute(HTML.Attribute.NAME);
                // oops - no name lets get out of here PDQ
                if (name != null) {
                     // get the Document object for this INPUT tag         
                     Document doc = (Document)attr.getAttribute(StyleConstants.ModelAttribute);
                     // add name= of INPUT tag to the document - will be retrieved by mouseListener 
                     // property is key and value (both objects - so lots of flexibility)
                     doc.putProperty(HTMLID, name); 		       
                }
            }
         }  
      }
      // Now re-process the form looking for editing components
      // Find all the JTextFields/JPasswordFields in the current HTML document
      List<JTextField> tf = SwingUtils.getDescendantsOfType(JTextField.class, editor);
      for(JTextField f: tf){
         // you could use any event type supported by JTextField
         // we wanted a double-click-to-edit interface        	
	       f.addMouseListener(this); 	                	
     }  
   }            
 }
 catch (Exception e) 
 {
   // yes it can all go horribly wrong
 }
}

Note: SwingUtils (written by Darryl Burke - no copyright claimed) is a fantastic 10 line function that will find any class in its container (JEditorPane). In this case we are using the SwingUtils.getDescendantsOfType to find the JTextField (also finds JPasswordField) from the fully rendered HTML document where we found the FORM tag. No other way to find these components (that we could find). Having found the target objects we simply add a mouseListener to them. The trivial mouse handling code is shown next.

Handle the JTextField Triggers

A nothing-special-mouseListener defined, obviously, in the same class as the above fragment:

    /**
     * mouseListener inherited methods
     * only interested in the mouseClicked event all the 
     * rest are null methods
     */

    public void mouseClicked(MouseEvent e) {
      // click threshold reached?
      if(e.getClickCount() == 2){
  		  
         // get the event source
         JTextField tf = (JTextField) e.getSource();
         // and derive its Document interface
         Document doc = tf.getDocument();
         // extract the original HTML atttribute name
         String name = (String)doc.getProperty(HTMLID);
         if(name != null){
				
           // do some interesting stuff based (in our case) on the name
        
        }
      }
    }

    public void mousePressed(MouseEvent e) {

    }

    public void mouseReleased(MouseEvent e) {

    }

    public void mouseEntered(MouseEvent e) {

    }

    public void mouseExited(MouseEvent e) {

    }

Form Submission (Submit Button)

Plenty of stuff about this scattered round the web but it's included here for the sake of completeness since the final handling of all the edits above comes only when the user submits the form.

The key to intercepting the Submit button in the HTML FORM is to extend the HTMLFactory of HTMLEditorKit and use this, in turn, to extend FormView which hooks the submitData method. A commented fragment is shown below:

// to get at HTMLFactory we need HTMLEditorKit
public class SubmitHTMLForm extends HTMLEditorKit
{
    private final ViewFactory viewFactory;
    
    public SubmitHTMLForm(...whatever construction params you need ...)
    {
        super();
        //trigger creating the factory class
        viewFactory = new SubmitHTMLFactory(.....);
    }

    // method returns our extented HTMLFactory
    public ViewFactory getViewFactory()
    {
        return viewFactory; 
    }        
    
    // extended HTMLFactory
    public static class SubmitHTMLFactory extends HTMLEditorKit.HTMLFactory 
    {
        
        public SubmitHTMLFactory(.. construction params as required ..)
        {          
          super();      	
        }
    
        /** overload the create method to serve up our own version of FormView
         *  (optionally ImageView if required)
         */
        public View create(Element elem) 
        {
            // let the superclass do the hard work
            View v = super.create(elem);
            
            if (v instanceof FormView){
              // INPUT, SELECT and TEXTAREA tags generate FormView
              // we only require type=submit
              String type = (String)elem.getAttributes().getAttribute(HTML.Attribute.TYPE);
              if(type.eqaulsIgnoreCase("submit"){
                 v = new HTMLFormView(elem, .. other params as required...); 
              }
            }
            return v;
        }
    }
		
		class HTMLFormView extends FormView
   {
    public HTMLFormView(Element elem, .. other params as required by app ...) 
    {
        super(elem);       
    }

    /**
     *    This method over-rides the
     *    method in the standard class which would try to submit data to a web server
     *    @param data = HTML PUT query String
     *    Notes:
     *    1. Query string is in escaped HTML format (may need to decode to UTF-8 etc.)
     *    2. Series of name=value pairs for the INPUT, SELECT and TEXTAREA tags delimited by &
     *    3. The Submit button also appears in the Query String!
     */
     
    protected void submitData(String data) 
    {
        // do your stuff with the Query String, but decode first...maybe
        // ns = URLDecoder.decode(data, "UTF-8");
    }

GO UP

Capturing Current Data from an HTML Form (Programatic Submit)

We wanted to capture the current state of an HTML Form from a JEditorpane control including all current edits. The trigger, in our case, was when the user toggled to a different editing view (we allow both HTML and a Table editing format) at anytime during the editing process and without forcing the user to click the submit button. The trigger conditions are not important - could be any event that you choose, including periodic saves, the principle remains the same. We tried the JEditorpane.getDocument() interface but the attribute values (HTML.Attribute.VALUE) reflect those that were used to set up the HTML page not their current (possibly changed) state.

Conclusion, we needed to programatically force a submit button operation. Turns out this was trivial in the extreme.

Note: It probably goes without saying that you do need to have Submit button on your HTML Form for this solution to work but we'll say it all the same. This technique can be used (and indeed we do use it) in conjunction with the intercepted Submit or not as you choose.

When the HTML Form is rendered by JEditorpane/HTMLEditorkit the HTML <input type="submit" ... is converted into a JButton control. That's the good news. The bad news is that the JEditorpane.getDocument() does not return a reference to the JButton control. It just gives all the HTML tag/attribute thingies. Instead using the excellent SwingUtils (written by Darryl Burke - no copyright claimed) we need to scan the container for all JButton references and save a reference to our target (the Submit in our case though the solution would work for any button). When we need to invoke a submit programmatically we simply use the JButton.doClick() method inherited from the AbstractButton class. Here's the code snippets for your delight and edification:


// HTML Form snippet
<html>
// appropriate head stuff - css, title and so on
....
<body>
// Eye candy or essential HTML
....
// The serious stuff starts ....
<form name="someformname">
  // table formatting HTML, editing input tags and other good stuff
  ....
  // input submit button for form
  <input type="submit" name="Submit" value="Submit"/>
  // other buttons may be added as appropraite to application
	....
       
</form>
</body>
</html>

// Java code snippets
....
// somewhere on a distant planet we set up the JEditorPane and HTMLEditorKit
// and introduce them to each other
private JEditorPane editor = new JEditorPane();
....
private JButton submitBtn = null;

// set an HTML page which contains and HTML form in an appropriate galaxy
  try
  {
    editor.setText(someHTMLText);
    ....
    // use swingUtils to get all JButton references in container
    List<JButton> tb = SwingUtils.getDescendantsOfType(JButton.class, editor);
    for(JButton f: tb){
      // At this point the JButton is generic so the only identifier is text on the button face
             	
      if("submit".equalsIgnoreCase(f.getText())){
        // save a reference to our target JButton
	      submitBtn = f;
      }      
    }                  
  }
  catch (Exception e) 
  {
     // panic here            
  }
	
  // trigger condition (eventListener, state change or in-line code as required)
  // tests for condition as appropriate
  // fire submit
  if(submitBtn != null){
    submitBtn.doClick();
    // if you are using the intercepted Submit Form wheeze then the
    // submitData function is immediately called
  }
  ....

That's it. You can fire the button as many times as required, say, as an automated save feature every n minites or once to capture users edited data and save a user prompt or as in our case when we switch from one editing view of the data (in HTML format) to another editing view (a table format). Whatever.

GO UP

Sending System.out (Std.out) to a Log File

There are times when it is useful to be able to capture all relevant diagnostic information into a log file, either during debug or more especially when trying to analyze site-specific user problems. The application generates plenty of log messages using the standard Java Logger object and it is relativly trivial to capture these to a file using FileHandler. In our case, however, problems can arise using TLS/SSL connections. We can selectively turn on SSL tracing (using the javax.net.debug property) but these (voluminous) messaages are sent to System.out (Std.out) and are not captured by the standard ConsoleHandler from which log-to-file is sourced.

The only way to do this is to hook System.out directly by subclassing OutputStream and feed it directly to the FileHandler. To avoid having System.out messages formatted as if they were log messages we also need to subclass SimpleFormatter and recognize our Standard.out messages. We adopted a very crude technique of using a currently unused LogLevel (Level.CONFIG in our case) for this purpose.

Note: The technique below involves hooking System.out which affects the whole VM. It will get any and all outputs to Std.out from any active program. as will be sen from the code sample below we only do this when we trigger log-to-file and (in our case) SSL tracing is also active. Bottom line. Use this technique with extreme caution.

import java.io.OutputStream;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.LogRecord;

public class LogOutputStream extends OutputStream
{
	/**
	 * Trivial class to hook system.out to a log file - only invoked if TLS tracing active
	 * NOTE: Whie there are 3 write functions in the OutputStream interface
	 * only write(int b) must be supported and supporting any other can lead to serious grief.
	 */
	private static final int MAX_BUFFER = 512;
	private int count = 0;
	private byte[] ba = new byte[MAX_BUFFER];
	private FileHandler fh = null;

	public LogOutputStream(FileHandler fh)
	{
		this.fh = fh;
	}
	/**
	 * minimal requirement for OutputStream
	 * take low order 8 bits only from 32 bit integer value
	 * add to buffer and periodically flush buffer (MAX_BUFFER)
	 * @param b is a single 8 bit byte
	 */
	public void write(int b)
	{
		if(count == MAX_BUFFER){
			flush();
		}
		
		ba[count++] = (byte)b;	// add to buffer
	}
	
	/**
	 * write to output destination and reset buffer count to 0
	 * NOTE: we use a modest 512 byte buffer and periodically flush it 
	 * internally. The alternative strategy would be to allocate a huge buffer 
	 * (20 - 50K) and only flush when System.out invokes flush() at the 
	 * end of a sequence
	 */
	public void flush()
	{
		LogRecord lr;
	
		if(count == 0) return;
		
		if(count != MAX_BUFFER){
			byte[] tba = new byte[count];
			System.arraycopy(ba, 0, tba, 0, count);
			// indicate System.out data by use of otherwise unused (in our case) Level.CONFIG
			lr = new LogRecord(Level.CONFIG, new String(tba));
		}else{
			// indicate System.out data by use of otherwise unused (in our case) Level.CONFIG
			lr = new LogRecord(Level.CONFIG, new String(ba));
		}
		
		fh.publish(lr);
		
		count = 0;
	}
}

import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.SimpleFormatter;

/**
 * Vestigial class to handle formatting of Std.out (System.out) when logging to file
 * by extending SimpleFormatter and overriding its only method - sometimes
 * 
 *
 */

public class StdOutFormatter extends SimpleFormatter{

	public StdOutFormatter()
	{
		
	}
	
	@Override
	public String format(LogRecord record)
	{
		// uses an otherwise unused Level value to recognize System.out data 
		// (set by LogOutputStream)
		if(record.getLevel() != Level.CONFIG){
			// handle as normal log message
			return super.format(record);
		}else{
			// return raw message data only to fileHandler
			return record.getMessage();
		}
	}
}

// code to invoke log-to-file and hook System.out conditionally
// in some suitable place
try {  
 
  FileHandler fh = new FileHandler("mylog.log",204800, 3);  
  
  // this just initiates log-to-file  		        
  StdOutFormatter fmat = new StdOutFormatter();  
  fh.setFormatter(fmat);  
  log.addHandler(App.fh);
  
  // hook System.out (std.out) only if some extra condition true
  
  if(trace){
    LogOutputStream los = new LogOutputStream(fh);
    PrintStream ps = new PrintStream(los);
    System.setOut(ps);
  }
    		        
} catch (Exception e) { 
  // panic appropriately here
  
}

GO UP

Creating an Apple Bundle for a Java App

It appears in the dim and distant past there was a standard Java tool called appbundler. This has been discontinued for some time. We are too young to remember such nonsense.

There are now approximately 6.5 thousand variants of appbundler on github (we exaggerate slightly) each of which does something slightly different. All very laudable and essential if you want to do something special during the startup process - but jolly confusing.

We turned our back on this stuff because it smelled vaguely of work, thinking and learning and all kinds of horrible stuff like that. And besides we just wanted Apple to load our App not reconfigure the entire system and download and install goodness how many version of the JVM.

We handcrafted a Apple bundle. Now we just build the .jar under Windows (Eclipse), slap it into the bundle, update resource files as required, zip the bundle, highjack a friendly Mac (sometimes willingly, sometimes less so) to test load the bundle and that's it. Here is what we did (with a lot of experimentation and a lot of help from various page 12 google reults (nothing ever useful on the first page in this case) too numerous to mention. If you recognize your contribution please let us know - we're more than happy to give you the credit, we just didn't note where we looked and it was a while ago).

There are only three things to get a handle on (well, 4 if you need to create custom .icns files) and all of which can be run on a Windows or *nix environment. First, the layout of the bundle, generically (with some specifics), looks like this (comments start with #, bolded items are directories - else files):

# targetApp.app
Contents
  # extended commands in Info.plist determined by JavaAppLauncher
  Info.plist
  PkgInfo
  # location of this directory depends on 
  # JavaAppLauncher
  Java
    TargetApp.jar
    # our app resouce directories
    TargetAppResourceDir
      app-resource-files
      app-resource-directories      
  MacOS
     # binary magic
     JavaAppLauncher
  Resources
    # replace as required with your snazzy custom logo
    # and referenced in the Info.plist file
    GenericApp.icns

Second, the Info.plist file describes the Application, its version and its environment:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>CFBundleDevelopmentRegion</key>
  <string>English</string>
  <key>CFBundleExecutable</key>
  <!-- name of binary bootstrap - see notes -->
  <string>JavaAppLauncher</string>
  <key>CFBundleIconFile</key>
  <string>GenericApp</string>
  <key>CFBundleIdentifier</key>
  <string>TargetApp</string>
  <key>CFBundleDisplayName</key>
  <string>Pretty TargetApp</string>
  <key>CFBundleInfoDictionaryVersion</key>
  <string>6.0</string>
  <key>CFBundleName</key>
  <string>TargetApp Again</string>
  <key>CFBundlePackageType</key>
  <string>APPL</string>
  <key>CFBundleSignature</key>
  <string>????</string>
  <key>CFBundleShortVersionString</key>
  <string>0.7.3</string>
  <key>CFBundleVersion</key>
  <string>0.7.3.1</string>
  <key>JVMMainClassName</key>
  <!-- full class path to .java module containing main -->
  <string>class.path.to.main</string>
</dict>
</plist>

The above are a pretty standard Info.plist command set. Note in particular <key>CFBundleExecutable</key> which points to JavaAppLauncher.

Third, (the real magic) is JavaAppLauncher which determines any extensions to the standard Info.plist command set and the placement of files and directories. Look at it as a bootstrap for Jars. Many of the appbundler forks write a file with the same name, other use different names to avoid confusion (the <key>CFBundleExecutable</key> allows this) but they do different things and can involve different layout requirements. We have no idea where we got this JappAppLauncher bootstrap from or who produced it (even a quick look wth a Hex editor gives no real clues) but it works reliably on Java 1.8, and now 1.9. If it ever stops working we'll throw a serious wobbly and have to find out more about JavaAppLauncher but right now it's auto-magical. A veritable black box. Long may it continue.

If you want a copy (and you trust us don't you) of the JavaAppLauncher that we use in our Apple bundles. Good luck.

Getting Access to Files in a Bundle

We do all our Java developement on windows (OK, feel sad for us if you must). There is copious Apple documentation on how to get a reference to resource files usinng various classes available on the Apple Java VM. We build a single .jar under windows (Eclipse in our case) and simply copy it into the appropriate Windows installer, *nix RPM or Apple Bundle and it works. The application uses a number of resource files (config info, themes, help and other good stuff) which are packaged into various installation systems including the .app bundle. To get a cross-platform reference to access these files we use the following code:

// This line gets a reference to the absolute path of the 
// directory in which the jar is located - gives same result on all platforms
// For an Apple bundle typically /Users/logged-in-user/app-name.app/Contents/Java (see above)

String jar = ClassLoader.getSystemClassLoader().getResource(".").getPath();

// the bundle contains the resource files under this path and 
// we construct appropriate references and access using File and its variant classes.

GO UP

(X)SSI Expansion Utility

We are addicted to the use of (X)SSI includes. IOHO it both allows us to minimise HTML/CSS development and to maximise our ability to change page format and layout quickly. By changing included files only we immediately propagate changes to all HTML files. We use it for meta data, stylesheets, page headers, page footers, left and right hand menus and navigation bars. Essentially everything except the main content block. Great stuff.

The bad news is what to do with HTML viwers (such as Java) or web servers that do not support (X)SSI. These cases require complete, fully expanded HTML/CSS files. Life is too short to do this manualy for more than one file.

We slapped together (wrote would be an exageration) this trivial (and limited) (X)SSI expansion tool. It processes all .html, .htm and .shtml files in a source directory and for each file:

  1. Expands all Apache <!--#include directives from their relative addresses and includes the contents into the html, htm or shtml file.

  2. Copies all image files (referenced in <img> tags) to a target/images folder.

  3. Changes the image reference in the image tag to point to the new location.

  4. All other Apache (X)SSI directives are left unmodified.

  5. Writes the expanded and/or modified file to a target directory (silently overwriting files with the same name).

The net result being files that will load on any server or HTML viewer. Eureka.

We claim no copyright for the utitlity or its source code. Feel free to use in any way you see fit (subject only to the normal fitness for use/purpose caveats). If you develop it further we'd be very happy if you send us the changes but we do not insist on it. The utility may be dowloaded as a jar (20K) for direct execution. However, if you don't trust us or wish to develop it further then download (20K) the source (single XSSI.java module) and build the jar in your favorite Java IDE having thoroughly checked the source code.

Documentation for this utility consists of a pop-up window when the jar is initially loaded which explains what to do. Want more extensive documentation. Tough.

GO UP



Problems, comments, suggestions, corrections (including broken links) or something to add? Please take the time from a busy life to 'mail us' (at top of screen), the webmaster (below) or info-support at zytrax. You will have a warm inner glow for the rest of the day.

Tech Stuff

RSS Feed Icon

If you are happy it's OK - but your browser is giving a less than optimal experience on our site. You could, at no charge, upgrade to a W3C standards compliant browser such as Firefox

Search

web zytrax.com

Share

Icons made by Icomoon from www.flaticon.com is licensed by CC 3.0 BY
share page via facebook tweet this page

Page

email us Send to a friend feature print this page Display full width page Decrease font size Increase font size

Resources

Main Ruby site
The Book
ruby-doc.org
RubyGems
Ruby on Rails

Useful Stuff

Ruby PLEAC

Our Pages

our ruby pages
glossary

Site

CSS Technology SPF Record Conformant Domain
Copyright © 1994 - 2024 ZyTrax, Inc.
All rights reserved. Legal and Privacy
site by zytrax
hosted by javapipe.com
web-master at zytrax
Page modified: January 20 2022.