Thursday, 3 December 2009

Code Dump: Stripping out HTML

Two solutions for this seam to pop up - Regex and using HTMLEditorKit. The first is very broken and can not handle escape characters, the latter works well but does not maintain formatting. So here is my expanded version which also breaks when it encounters a tag that breaks the flow.



package ihaztehcodez.utils;
 
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.Arrays;
import java.util.Collection;
 
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.parser.ParserDelegator;
 
/** Strips HTML from text.
*
* @author mlk
*
*/
public final class StripHtml {
 
/** Utility class. */
private StripHtml() { }
 
/** Strips HTML from the content given.
*
* @param value The string to strip.
* @return A textual representation.
*/
public static String stripped(final String value) {
try {
return stripped(new StringReader(value));
} catch(final IOException ioe) {
// This should never happen as the StringReader can not throw an IOE.
throw new RuntimeException(ioe);
}
 
}
 
/** Strips HTML from the content given.
*
* @param value The content to strip.
* @return A textual representation.
*/
public static String stripped(final Reader value) throws IOException {
ParserDelegator delegator = new ParserDelegator();
StripOMatic matic = new StripOMatic();
delegator.parse(value, matic, true);
 
return matic.toString();
}
 
/** Read in the text file and adds a new line when a breaking tag is encountered.
*
* @author Michael Lee
*
*/
private static class StripOMatic extends HTMLEditorKit.ParserCallback {
/** The string value. */
private final StringBuilder value = new StringBuilder();
/** Some tags should be ignored. */
private static Collection<HTML.Tag> ignore = Arrays.asList(HTML.Tag.HTML,
HTML.Tag.BODY, HTML.Tag.HEAD);
 
/** If the tag breaks the flow then add a new line, otherwise ignore.
* {@inheritDoc}
*/
public void handleStartTag(final HTML.Tag t, final MutableAttributeSet a, final int pos) {
if (ignore.contains(t)) {
return;
}
if (t.breaksFlow()) {
value.append("\n");
}
}
 
/** Delegates to handleStartTag.
* {@inheritDoc}
*/
public void handleSimpleTag(final HTML.Tag t, final MutableAttributeSet a, final int pos) {
handleStartTag(t, a, pos);
}
 
/** Adds the following chunk of text.
*
* {@inheritDoc}
*/
public void handleText(final char[] text, final int pos) {
value.append(text);
}
 
/** @return The text version of the data parsed so far. */
public String toString() {
return value.toString();
}
}
}

Monday, 30 November 2009

Code Dump: Stubbing with a map

A year or so back I wrote a cool little utility class MyInvocationHandler. This is used by simply creating a map of the proprieties and their values simply as I felt that:
Map backing = new HashMap();
backing.put("title", "ihaztehcodez");
backing.put("getURL",
            "ihaztehcodez.michael-lloyd-lee.me.uk");
Blog thisHereBlog = create(backing, Blog.class);

was easier on the eyes that the EasyMock styled
Blog thisHereBlog = createNiceMock(Blog.class);
expect(thisHereBlog.getTitle())
   .andReturn("ihaztehcodez").anyTimes();
expect(thisHereBlog.getURL())
    .andReturn("ihaztehcodez.michael-lloyd-lee.me.uk")
    .anyTimes();
replay(thisHereBlog);

However I was wrong to use either method. If I was to do so again I'd use write a BlogBuilder. It is a good chunk more work up front, but it leads to significantly better reading code.

Blog thisHereBlog = new BlogBuilder()
     .withTitle("ihaztehcodez")
     .withURL( "ihaztehcodez.michael-lloyd-lee.me.uk")
     .build();

This does mean I now have a mini project to start. Write a JPA thingie to generate a builder.

Wednesday, 25 November 2009

Preventing multiple instances of an application from running.

This is a common question, with a very common answer - create a machine wide semaphore. The most common recommendation in Java is to use a socket. However this answer has a flaw. In a multi-user environment you do not want a machine-wide, you want a user-wide semaphore. I have seen some very complex solutions to this issue but there is a very simple answer. Use a file lock.
The common reason against using files is that they are not automatically cleaned when the application is forcible terminated (for example hitting the big red button). This is true if you use the existence of a file to determine if a previous instance is running, but there is a better option than this. Use the FileLock for a file in the users home directory. FileLocks are automatically cleaned by the OS should the application die without unlocking and can be placed in a location that will be local to the user allowing this solution to work on multi-user environment such as Windows Terminal Server.


import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.util.logging.Level;
import java.util.logging.Logger;

/** Prevents two instances of the application from starting at the same time.
 *
 * @author Mlk
 *
 */
public final class InstanceChecker {
    /** A singleton instance. */
    public static final InstanceChecker INSTANCE = new InstanceChecker();
    /** The file channel to be locked. */
    private FileChannel channel;
    /** The lock on the file. */
    private FileLock lock;
    /** The file to be locked. */
    private final File file = new File(new File(System.getProperty("user.home")), "/APPLICATION_NAME.lock");
    /** Logger. */
    private Logger log = Logger.getLogger(getClass().getName());

   
   
    /** ctor. */
    private InstanceChecker() {    }

    /** Is this the only instance of this application currently executing.
     *
     * @return should the application be allowed to start up.
     */
    public boolean onlyInstance() {
       
        try {
            channel = new RandomAccessFile(file, "rw").getChannel();
           
            try {
                lock = channel.tryLock();
            } catch (OverlappingFileLockException e) {
                return false;
            }
            if (lock == null) {
                return false;
            }
           
            Runtime.getRuntime().addShutdownHook(new Thread() {
                public void run() {
                    close();
                }
            });
        } catch (IOException e) {
            log.log(Level.WARNING, "Failed to lock file: " + file, e);
            return false;
        }
        return true;
    }

    /** Unlocks the file. */
    private void close() {
        try {
            if (lock != null) {
                lock.release();
            }
            if (channel != null) {
                channel.close();
            }
           
            file.delete();
        } catch (final IOException e) {
            System.err.println("Failed to unlock file!");
        }
    }
}

And you use it:
public static void main(String argv...) {
     if( !InstanceChecker.INSTANCE.onlyInstance()) {
     // Die!
  }
}

It currently lacks the inter application communication, but I think that would be easily added by also having a "ping" file that would be touched during the die section of code.