21 Apr 2010 @ 13:18 

Java’s generics have undoubtedly made life easier in some cases by enforcing type safety on collections. One collection that frequently does not benefit from them is maps used in a heterogeneous way – that is, a java.util.Map (or equivalent) that contains several non-compatible values. Assuming the keys are of the same type, it’s still not possible to type the value of the map as anything other than a java.lang.Object – exactly what it would have been prior to generics.

Looking through the Jetbrains documentation on its API for IntelliJ plugin development, it’s interesting to note their workaround for enforcing type safety in a map containing heterogeneous values:

  • DataKey<T> – the access point to the map
  • DataContext – an interface that can be used to wrap a map

This approach externalises the type-safety of the map’s content by shifting the casts to DataKey.

The interesting part of DataKey, from this viewpoint, is

@Nullable
public T getData(@NotNull DataContext dataContext)
{
    return (T) dataContext.getData(myName);
}

Read access is via the key, and not directly on the map itself, e.g. for a key

DataKey<Foo> FOO = DataKey.create("foo")

a type-safe call without a cast can be made on the wrapped via:

Foo foo = MyKeys.FOO.getData(dataContext);

The map-based implementations of DataContext just does a regular lookup with the key provided by DataKey:

public class MyDataContext implements DataContext
{
    private final Map<String, ?> data = new HashMap<String, ?>();
    @Nullable Object getData(@NonNls String dataId)
    {
        return data.get(dataId);
    }
}

and the returned object is cast to the correct type on the way out of DataKey. Simple and elegant.

It’s worth noting that both DataKey and DataContext are read-only in function – there’s a getData(), but no setData().   It’s easy enough to extend the concept to make sure that what goes into the map is of the same type as what comes out by adding a couple of extra methods, one on DataKey:

    public void setData(DataContext dataContext, T value)
    {
        dataContext.setData(getName(), value);
    }

and another on DataContext:

    public void setData(String key, Object value)
    {
        data.put(key, value);
    }

I’ve seen a lot of code where keys to access values from maps have been declared as static final Strings. Using DataKey, you could replace these declarations with DataKeys that specify the key to access the value and the type of the value itself, so

public static final String USER = "user";
...
User user = (User)map.get(USER);

would become

public static final DataKey<User> USER = DataKey.create("user");
...
User user = USER.getData(dataContext);
Share and Enjoy: These icons link to social bookmarking sites where readers can share and discover new web pages.
  • Digg
  • del.icio.us
  • StumbleUpon
  • DZone
  • Facebook
Posted By: steve
Last Edit: 21 Apr 2010 @ 13:18

EmailPermalinkComments (0)
Tags
Tags: , , ,
Categories: general, java
 09 Nov 2009 @ 12:53 

When building a Struts2 application recently, I needed to add arbitrary parameters to a URL when creating the menu from dynamic content. The parameters were stored in a map, so I used the my standard bit of code for iterating over a map:

<s:url var="url" action="%{link}" >
  <s:iterator value="parameters.keySet()" var="key">
    <s:param name="%{key}" value="%{parameters.get(#key)}"/>
  </s:iterator>
</s:url>

…and nothing happened. No parameters at all appeared in the URL.

Odd.

I got rid of the iterator and used a single parameter, just to check:

<s:url var="url" action="%{link}" >
  <s:param name="test-name" value="test-value"/>
</s:url>

That worked fine. One quick debugging session later and I found the problem – the Struts2 org.apache.struts2.components.Param component parameterises its parent component. In this case, the parent component is an iterator and so it was absorbing the parameters and they were never getting as far as the URL.

I couldn’t find a way to do what I needed the core Struts2 components and tags and so I created my own.

IterableParam overrides Param’s findAncestor method to return the grandparent component in the case where the parent is an Iterator:

<s:url var="url" action="%{link}" >
  <s:iterator value="parameters.keySet()" var="key">
    <ob:iterable-param name="%{key}" value="%{parameters.get(#key)}"/>
  </s:iterator>
</s:url>

Result – works as required.

Despite the title of this blog entry, any Struts2 component that can be parameterised using the <s:param> tag can be parameterised using <ob:iterable-param>.

You can get the new tag and component from

Share and Enjoy: These icons link to social bookmarking sites where readers can share and discover new web pages.
  • Digg
  • del.icio.us
  • StumbleUpon
  • DZone
  • Facebook
Posted By: steve
Last Edit: 21 Apr 2010 @ 08:06

EmailPermalinkComments (0)
Tags
Tags: , , ,
Categories: java, open source, struts2
 30 Oct 2009 @ 0:32 

objectify-led, a Java library for binding properties at runtime has just been released at Objectify.

Instead of having chunks of code such as

public class Foo
{
    private static String BLAH = "default-value";

    private String myString;

    private int myInt = -1;

    public static void main(String[] args)
    {
        if (System.getProperty("blah.value) != null)
        {
            BLAH = System.getProperty("blah.value");
        }

        Foo foo = new Foo();
        if (System.getProperty("mystring.value) != null)
        {
            foo.myString = System.getProperty("mystring.value");
        }
        if (System.getProperty("myint.value) != null)
        {
            try
            {
                String intValue = System.getProperty("myint.value");
                foo.myInt = Integer.parseInt(intValue;
            }
            catch (NumberFormatException e)
            {
                ...
            }
        }
    }
...
}

you can instead use objectify-led to bind the properties for you :

public class Foo
{
    @Property("blah.value");
    private static String BLAH = "default-value";

    @Property("mystring.value");
    private String myString;

    @Property("myint.value");
    private int myInt = -1;

    public static void main(String[] args)
    {
        Foo foo = new Foo();
        new PropertySetter().process(foo);
    }
    ...
}

By default, Strings and all primitive/wrapper classes are handled.  If you wish to convert a property value into a specific class, you just need to plug in your own object factory.

Additionally, the source of the properties is completely up to you.  By default, properties are taken from the system environment and any Properties objects you may have plugged in; custom property contexts can be used to provide a facade to more complex value stores.

Static values are handled in two ways. If an instance of a class is processed, both its instance and static members are populated if necessary.  If a class is processed, just the static members are populated (what with the lack of an instance and everything…).

Final properties- despite the ability to do so via reflection – are not set.  This may or may not change depending on feedback.

If you want to try out objectify-led, you can get the binaries and sources from the links below, or directly from http://www.objectify.be.  They will be submitted to the well-known Maven repositories in the coming days.

Binaries:

Sources:

Share and Enjoy: These icons link to social bookmarking sites where readers can share and discover new web pages.
  • Digg
  • del.icio.us
  • StumbleUpon
  • DZone
  • Facebook
Posted By: steve
Last Edit: 21 Apr 2010 @ 08:06

EmailPermalinkComments (0)
Tags

 Last 50 Posts
Change Theme...
  • Users » 1
  • Posts/Pages » 10
  • Comments » 0
Change Theme...
  • VoidVoid « Default
  • LifeLife
  • EarthEarth
  • WindWind
  • WaterWater
  • FireFire
  • LightLight

About



    No Child Pages.