Skip to content
accelerando

Category Archives: Java

Find all non “.java” files but not in .svn directories

19-Aug-10

I’m migrating some old projects to Maven and i need to move all resources out of the source tree.

find . -type d -name '\.svn' -prune -o -type f -not -iname "*.java" -print
  • Prune every directory named ‘.svn’
  • Or
  • Type is file and the name ends not with “*.java”

I tried to create the necessary directories within the same command but after about half an hour xargs trial and error, i’ll do it manually, the list of files is small enough ;)

J2SE: Cut/Copy/Paste Helper

09-Jul-10

You wouldn’t think that having a standard edit menü with Cut, Copy and Paste buttons would be much of a problem in the J2SE world, especially regarding the fact that most standard Swing components have TransferHandlers that support the 3 operations with the standard keyboard shortcuts.

First try was to user TransferHandler.getCopyAction() etc. and create menuitems from the actions. The actions will actually be called but they use the source of the action event to determine the component whose contents should be transfered.

After some searching i found this tutorial. The idea is to have a global property change handler on the global KeyboardFocusManager that tracks the current component that has the focus. The handler than updates the 3 actions with the new target.

I have changed the code a little bit. I removed the methods for adding special client parameters to determine whether cut/copy/paste should be enabled completely. Instead i check whether the component has a TransferHandler installed and if so, whether that handler supports the current action. By doing so my actions have the same behavior like the ones that are the swing components have.

The following code can be used to create menuitems or buttons that mimic the default cut/copy/paste actions on Swing components:

import java.awt.Component;
import java.awt.KeyboardFocusManager;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
 
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import javax.swing.TransferHandler;
 
public final class CutCopyPasteHelper {
	private static final FocusedAction CUT_INSTANCE;
	private static final FocusedAction COPY_INSTANCE;
	private static final FocusedAction PASTE_INSTANCE;
 
	private static final Clipboard CLIPBOARD;
 
	private static final List<FocusedAction> FOCUSED_ACTIONS = new ArrayList<FocusedAction>();
 
	static {
		Clipboard clipboard;
		try {
			clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
		} catch (SecurityException e) {
			// Don't have access to the clipboard, create a new one
			clipboard = new Clipboard("Sandboxed Clipboard");
		}
		CLIPBOARD = clipboard;
 
		FOCUSED_ACTIONS.add(CUT_INSTANCE = new CutAction());
		FOCUSED_ACTIONS.add(COPY_INSTANCE = new CopyAction());
		FOCUSED_ACTIONS.add(PASTE_INSTANCE = new PasteAction());		
		KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener(new PropertyChangeHandler());
	}
 
	private static Clipboard getClipboard() {
		return CLIPBOARD;
	}
 
	/**
	 * Returns an action to perform a cut operation.
	 *
	 * @return the cut action
	 */
	public static Action getCutAction() {
		return CUT_INSTANCE;
	}
 
	/**
	 * Returns an action to perform a copy operation.
	 *
	 * @return the copy action
	 */
	public static Action getCopyAction() {
		return COPY_INSTANCE;
	}
 
	/**
	 * Returns an action to perform a paste operation.
	 *
	 * @return the paste action
	 */
	public static Action getPasteAction() {
		return PASTE_INSTANCE;
	}
 
	private static void updateActions(Component focusedComponent) {
		for(FocusedAction action : FOCUSED_ACTIONS)						
			action.update(focusedComponent);					
	}
 
	private static final class PropertyChangeHandler implements PropertyChangeListener {
		public void propertyChange(PropertyChangeEvent e) {
			if (e.getPropertyName() == "permanentFocusOwner") {
				updateActions((Component)e.getNewValue());
			}
		}
	}
 
	private CutCopyPasteHelper() {
	}
 
 
	private static abstract class FocusedAction extends AbstractAction {        
		private static final long serialVersionUID = 9134168335741527777L;		
		protected JComponent focusedComponent;
 
		public FocusedAction(String name) {
			super(name);
		}		
 
		protected void update(Component permanentFocusOwner) {
			this.focusedComponent = (permanentFocusOwner instanceof JComponent) ? (JComponent)permanentFocusOwner : null; 			
			this.checkTarget();
		}
 
		public JComponent getFocusedComponent() {
			return focusedComponent;
		}		
 
		public boolean actionSupported(int actionMap, int action) {	
			return (actionMap & action) != 0;
		}
 
		abstract protected void checkTarget();	
	}
 
	protected static void export(final int exportAction, final JComponent component) {
		final Clipboard clipboard = getClipboard();		
		component.getTransferHandler().exportToClipboard(component, clipboard, exportAction);
	}
 
	private static final class CopyAction extends FocusedAction {		
		private static final long serialVersionUID = 1765740238724009255L;
 
		public CopyAction() {
			super("Kopieren");	
			super.setEnabled(false);
			super.putValue(ACTION_COMMAND_KEY, "a6f9f030-c408-4531-857f-dd3aad7b43f6");
			super.putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK));
		}
 
		@Override
		public void actionPerformed(ActionEvent e) {			
			export(TransferHandler.COPY, focusedComponent);
		}
 
		@Override
		public void checkTarget() {
			super.setEnabled(focusedComponent != null && focusedComponent.getTransferHandler()!= null && actionSupported(focusedComponent.getTransferHandler().getSourceActions(focusedComponent), TransferHandler.COPY));
		}		
	}
 
	private static final class CutAction extends FocusedAction {		
		private static final long serialVersionUID = 1765740238724009255L;
 
		public CutAction() {
			super("Ausschneiden");	
			super.setEnabled(false);
			super.putValue(ACTION_COMMAND_KEY, "aa33f95a-c741-407a-a0ca-0c3aedd33c4d");
			super.putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_DOWN_MASK));
		}
 
		@Override
		public void actionPerformed(ActionEvent e) {			
			export(TransferHandler.MOVE, focusedComponent);
		}
 
		@Override
		public void checkTarget() {
			super.setEnabled(focusedComponent != null && focusedComponent.getTransferHandler()!= null && actionSupported(focusedComponent.getTransferHandler().getSourceActions(focusedComponent), TransferHandler.MOVE));
		}		
	}
 
 
	private static final class PasteAction extends FocusedAction {		
		private static final long serialVersionUID = -2117467682323608417L;
 
		public PasteAction() {
			super("Einfügen");	
			super.setEnabled(false);
			super.putValue(ACTION_COMMAND_KEY, "2676413d-8b9e-4d64-b59a-800db6747d80");
			super.putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_DOWN_MASK));
		}
 
		public void checkTarget() {			
			super.setEnabled(focusedComponent != null && focusedComponent.getTransferHandler() != null && focusedComponent.getTransferHandler().canImport(focusedComponent, getClipboard().getAvailableDataFlavors()));			
		}
 
		public void actionPerformed(ActionEvent e) {
			Clipboard clipboard = getClipboard();
			JComponent target = getFocusedComponent();
			target.getTransferHandler().importData(target, clipboard.getContents(null));
		}		
	}
}

What’s missing: The actions aren’t localized and they use Ctrl+? on every system. One could use the default Swing bundles to determine the correct modifier.

An iterable array

12-Jan-10

Java has the nice Iterable interface (since Java 5, i guess) that allows object oriented loops like

List<String> strings = new ArrayList<String>();
for(String string : strings) 
	System.out.println(string);

but guess what, a simple array is not iterable…

In case you need one, feel free to use this one:

package ac.simons;
 
import java.util.Iterator;
 
public class IterableArray<T> implements Iterable<T> {
	private class ArrayIteratorImpl implements Iterator<T> {
		private int position = 0;
		@Override
		public boolean hasNext() {
			return data != null && this.position < data.length;
		}
 
		@Override
		public T next() {
			return data[position++];
		}
 
		@Override
		public void remove() {
			throw new UnsupportedOperationException();
		}		
	}
 
	private final T[] data;
 
	public IterableArray(T[] data) {
		this.data = data;
	}
 
	@Override
	public Iterator<T> iterator() {
		return new ArrayIteratorImpl();
	}	
}

Create ZIP Archives containing Unicode filenames with Java

05-Jan-10

It is harder than i thought to create a simple Zip Archive from within Java that contains entries with unicode names in it.

I’m actually to lazy to read all the specs, but it says something that the entries in a zip archive are encoded using “Cp437″. The buildin Java compressing api has nothing to offer for setting the encoding so i tried Apache Commons Compress.

The manual says the following about interop :

For maximum interop it is probably best to set the encoding to UTF-8, enable the language encoding flag and create Unicode extra fields when writing ZIPs. Such archives should be extracted correctly by java.util.zip, 7Zip, WinZIP, PKWARE tools and most likely InfoZIP tools. They will be unusable with Windows’ “compressed folders” feature and bigger than archives without the Unicode extra fields, though.

That didn’t work for me.

After some cursing, this is my solution:

final ZipArchiveOutputStream zout = new ZipArchiveOutputStream(new BufferedOutputStream(new FileOutputStream(fc.getSelectedFile())));
zout.setEncoding("Cp437");
zout.setFallbackToUTF8(true);
zout.setUseLanguageEncodingFlag(true);								
zout.setCreateUnicodeExtraFields(ZipArchiveOutputStream.UnicodeExtraFieldPolicy.NOT_ENCODEABLE);

I specifying explicitly the encoding but instead of using utf-8, which didn’t work for my utf-8 strings (wtf??), i’m using the Cp437 from the specs and some other magic options and it works for me in 7zip, WinZip and even Windows’ “compressed folders”.

Edit: Unfortunately, in Mac OS X’s Unzip utility, the non Cp437 are broken. If anyone has a good idea, feel free to leave a comment.

Today: Fun with Unicode, Regex and Java.

03-Nov-09

Some would say, i have 3 problems ;)

private final static Pattern placeholder = Pattern.compile("#\\{(\\w+?)\\}");

won’t match “Mot#{ö}rhead” for example.

To replace the word character \w you either need the list of possible unicodeblocks like [\p{InLatin}|\p{InEtc}] (you get the codes for the blocks through “Character.UnicodeBlock.forName” or you’re lazy like me and just use the dot:

private final static Pattern placeholder = Pattern.compile("#\\{(.+?)\\}");

Oh what a day… :/

Javas String.replaceAll

21-Jul-09

The following statement

"foo baz".replaceAll("baz","$bar");

will present you an java.lang.IllegalArgumentException: Illegal group reference exception as the replacement string can contain backreferences to the search pattern as stated in Mather#replaceAll:

This method first resets this matcher. It then scans the input sequence looking for matches of the pattern. Characters that are not part of any match are appended directly to the result string; each match is replaced in the result by the replacement string. The replacement string may contain references to captured subsequences as in the appendReplacement method.

Note that backslashes (\) and dollar signs ($) in the replacement string may cause the results to be different than if it were being treated as a literal replacement string. Dollar signs may be treated as references to captured subsequences as described above, and backslashes are used to escape literal characters in the replacement string.

Luckily, there is an easy solution:

"foo baz".replaceAll("baz",Matcher.quoteReplacement("$bar"));

Grails, Hibernate, Current Session Context

12-Feb-09

Graeme was so kind to help me with this problem. My app worked well with Grails 1.0.4 but not in 1.1-beta3 any more.

My first wild guess leading to a

HibernateException: contains is not valid without active transaction

exception was my use of annotated Hibernate classes together with additional constraints in Grails, but Graeme figured out that it was the current session context i configured in hibernate.cfg.xml like so:

<property name="hibernate.current_session_context_class">thread</property>

I used this fragment for various JUnit test and in one case in a J2SE application where the same hibernate classes are needed. Together with a JTA Manager, this fragment is not needed and in case of Grails it has to go.

To run my tests i added the following statement right before opening my session:

final Properties nonJtaEnv = new Properties();
nonJtaEnv.put("hibernate.current_session_context_class", "thread");
 
sessionFactory = new AnnotationConfiguration().configure("hibernate.cfg.xml").addProperties(nonJtaEnv).buildSessionFactory();

Grails startup parameter

09-Feb-09

Just some snippets from the doku that I tend to forget:

Run grails on a different port:

grails -Dserver.port=9090 run-app

Run grails with a different environment:

grails prod run-app // production
grails -Dgrails.env=mycustomenv run-app  // mycustomenv

Different day, same shit, today: Java 5 on Oracle Enterprise Linux 5

19-Jan-09

Worlds collide: Oracle and Sun JDK. Perfect start to ruin a not so bad Monday morning.

Background: Need to have a Tomcat Server deployed on a Oracle Enterprise Linux 5 system.

I was happy, when i saw a tomcat5 package in the repositories. Great, i thought. All i need. Well. Not.

Under Windows you’ll get between one and ten JVM versions installed with on Oracle product (slight exaggerated), within the OEL5 there was only one ancient 1.4.2 JDK. *sigh* Did i mention that the application that is supposed to run on that thing uses a buttload of Java 5 features?

“yum search java-”… No Java 5. WtF?

There is no Java 5.

Again, do it yourself:

This is a nice entry that describes howto build rpms for the “official” Sun Java 5 jdk.

I used the following steps to build my rpms:

  • Downloaded this rpm
  • Downloaded jdk-1_5_0_15-linux-i586.bin from the Sun JDK archive page
  • Put the later one into /usr/src/redhat/SOURCES/
  • Built the rpms with rpmbuild –rebuild java-1.5.0-sun-1.5.0.15-1jpp.nosrc.rpm. If rpmbuild is not installed, it’s hidden in the package rpm-build, not rpmbuild.
  • Installed missing libXp
  • Installed the rpms:
    rpm -Uvh /usr/src/redhat/RPMS/i586/java-1.5.0-sun-1.5.0.15-1jpp.i586.rpm
    rpm -Uvh /usr/src/redhat/RPMS/i586/java-1.5.0-sun-devel-1.5.0.15-1jpp.i586.rpm
    rpm -Uvh /usr/src/redhat/RPMS/i586/java-1.5.0-sun-src-1.5.0.15-1jpp.i586.rpm
    rpm -Uvh /usr/src/redhat/RPMS/i586/java-1.5.0-sun-demo-1.5.0.15-1jpp.i586.rpm
    rpm -Uvh /usr/src/redhat/RPMS/i586/java-1.5.0-sun-plugin-1.5.0.15-1jpp.i586.rpm
    rpm -Uvh /usr/src/redhat/RPMS/i586/java-1.5.0-sun-fonts-1.5.0.15-1jpp.i586.rpm
    rpm -Uvh /usr/src/redhat/RPMS/i586/java-1.5.0-sun-alsa-1.5.0.15-1jpp.i586.rpm
    rpm -Uvh /usr/src/redhat/RPMS/i586/java-1.5.0-sun-jdbc-1.5.0.15-1jpp.i586.rpm
  • Last step: Choose the right java version with alternatives –config java

After that, everything could be fine. Well, it wasn’t:

sun.misc.InvalidJarIndexException: Invalid index

Jehova! Finally not a NPE but something new, at least to me. Sometimes i wonder why i always run into bugs like these.

Some script changes and repackages all jar files in some weird ways so that a standard JDK has funny problems.

My solution to it: Reindex everything in /usr/share/java after you’ve chosen your newly installed java with the following command:

find /usr/share/java/ -iname "jakarta*commons*.jar" -exec jar -i {} \;

I opted to reindex only the jakarta commons files, that got Tomcat up and running with Java 5.

One last note: The /usr/bin/dtomcat5 is broken imho, at least when run from /etc/init.d/tomcat5. In ignores /etc/tomcat5/tomcat5.conf and therefore cannot stop Tomcat.

My solution: Replace

if [ -z "$CATALINA_HOME" ]; then
    TOMCAT_CFG="/etc/tomcat5/tomcat5.conf"
fi

(in line 55 on my setup) with

if [ -z "$CATALINA_HOME" ]; then
    TOMCAT_CFG="/etc/tomcat5/tomcat5.conf"
    [ -r "$TOMCAT_CFG" ] && . "${TOMCAT_CFG}"
fi

and remove

if [ -z "$CATALINA_HOME" ]; then
    [ -r "$TOMCAT_CFG" ] && . "${TOMCAT_CFG}"
fi

(in line 105 on my setup).

I wonder why simple things like these always are a pain in the ass. Stupid nuisances that keeps people from getting their work done. *sigh* Not a good start for the week.

Some random Grails thoughts and tipps

04-Dec-08

Lately i’ve been rambling and ranting a lot on twitter about the Grails framework.

To my surprise, many other developers actually read this tweets and helped me out on some problems. Thanks a lot gals and guys, i really appreciate that. Me rambling isn’t meant to be personal at any time, i guess you know how easily one gets frustrated with too less time and too much stuff to do.

Anyway, here are some shortcuts that could eventually be helpful. I’m gonna add more to this list the next days:

Enabling hibernate filters in the grails session

Took me a little digging through the source code, but i came up with the following idea:

import org.springframework.transaction.support.TransactionSynchronizationManager;
 
class SecurityFilters {
  def sessionFactory
  def filters = {
    login(controller:'*', action:'*') {
      before = {
        // get your user id somewhere
        def whatsoeveruserId = 0
        def sessionHolder =  TransactionSynchronizationManager.getResource(sessionFactory);     
        sessionHolder.getSession().enableFilter("filterByOwner").setParameter("currentUserId", whatsoeveruserId);
      }
    }
  }
}

I want to have some kind of rowlevel security through a Hibernate filter. Through dependency injection i get hold of the sessionFactory and through the TA Manager, i get the current session on which i can enable my filter.

Doing this in a Grails filter, i can combine this with some kinda login mechanism and i’m good to go.

Whitelisting attributes through bindData

To me it’s a bad idea using blacklisting on data binding as i can and will forget attributes that must not be updated through a webform.

With Marc i found the following solution:

bindData(entity, params, entity.properties.collect{it.key} - ['foo', 'bar'])

That way only attributes foo and bar gets updated.

Anyway, with Grails 1.1 this won’t be necessary anymore as Graeme anonced.

Graeme was so kind to comment on this: This feature is already in 1.0.x, i just didn’t find it, have a look at the docu at The Web Layer.

bindData(entity, params,  [include:['foo', 'bar']])

Updates on 2008/12/9

Adding custom errors to a domain class

The grails reference has a handy example for adding custom errors to domain classes, have a look here. This works quite well except that all other errors from databinding are mysteriously gone.

For me, the following steps worked to update a user (change some persistent attributes and the transient attributes password and passwordConfirmation):

bindData(anwender, params, [include:['name', 'vorname', 'password', 'passwordConfirmation']])
 
if(params.password != "" && params.password == params.passwordConfirmation)
  anwender.hashPassword() // As alway, never ever store plaintext passwords ;)
else if(params.password != "") {
  anwender.validate() // IMPORTANT without that step, possible other errors from bindData vanished
  anwender.errors.rejectValue('password', 'user.anwender.passwords_doesnotmatch')
}

Afterwords, hasErrors() show all errors, i.a. non nullable fields and the like.

More thoughts

I somewhat used to hibernate and come along very well with it, even though i’m actually a SQL fan. I guess if my inside into the Spring Framework would be a little bit deeper, some areas wouldn’t be hard to understand.

On the other hand i think that Grails does a great job for J2EE based development and it should do so even more. As always, there is the law of leaky abstractions, but the whole butload of stuff that is the J2EE stack should be abstracted away.

Updates on 2009/2/6

Grails 1.1-beta3

I use hibernate validator in my domain classes (that i created outside of rails as hibernate annotated classes) and i got

java.lang.NoSuchMethodError: org.hibernate.event.PreInsertEvent.getSource()Lorg/hibernate/engine/SessionImplementor

on every insert and update. Hibernate validator 3.0.0.GA is incompatible with the Hibernate version in Grails 1.1-beta3. Problem was gone after upgrading validator to 3.1.0.GA.

Some other stuff:

  • Installed plugins are obviously gone. i.e yui plugin is still in the application folder, it needs to be reinstalled after upgrade (grails install-plugin yui)

    Ok, i see this was done on purpose: “Plugins are now stored in your USER_HOME directory. You will need to re-install your plugins or run” (from the beta2 release note). Not a good decision making this a default imho. I like having my apps pinned to specific plugins.

  • The message method for doing I18n in controllers used to be available in filters. This method seems to be gone. No solution for that so far.
  • Values not bound in a form are not null anymore but 0 in case of numeric values. Bummer! Actually my bad.
  • Some problems solved.
Close
E-mail It