Skip navigation

I recently wanted to add some performance measurements to an application. To avoid duplicating code everywhere I needed to make measurements, I coded up a small helper class.

What I wanted to measure was the amount of time taken by various operations within the application. For each operation, I wanted to know various statistics about it: how many times it was called, the average time it took, and also the minimum, maximum, and standard deviation.

The basic skeleton for timing code is this:

    long startTime = System.nanoTime();
    doTheOperation();
    long elapsedTime = System.nanoTime() - startTime;

The System.nanoTime() method was introduced in the J2SE 5.0 release. Its intended use is exactly for timing measurements such as this. As the name implies, the result is in nanoseconds, though it will not usually be accurateto the nearest nanosecond.

If we want to accumulate statistics aboutdoTheOperation(), then we'll need more code than that. It'll be more like:

    long startTime = System.nanoTime();
    doTheOperation();
    long elapsedTime = System.nanoTime() - startTime;
    synchronized (something) {
        nCalls++;
        sumTime += elapsedTime;
        minTime = Math.min(minTime, elapsedTime);
        maxTime = Math.max(maxTime, elapsedTime);
    }

Not only does this pollute the doTheOperation()call with lots of distracting code, but we'll also be obliged to duplicate code around every other operation we want to measure. So obviously we'll want to pull this code out into a class. We'd like the measurement code to look something like this:

public class ClassContainingTheOperationToBeMeasured {
    private final CallStats operationStats = new CallStats();

    ...
        CallStats.Call call = operationStats.newCall();
        doTheOperation();
        call.end();
    ...
}

For each collection of timing statistics, we'll need an instance of CallStats to record the statistics. The use of this instance is just the line before and the line afterdoTheOperation(). We probably can't simplify more than that.

So here's the class I coded for this:

import java.beans.ConstructorProperties;
import java.io.Serializable;

public class CallStats implements Serializable {
    private int nCalls;
    private long minTime;
    private long maxTime;
    private long sumTime;
    private double sumSquaredTime;
    
    /** Creates a new instance of CallStats */
    public CallStats() {
    }
    
    @ConstructorProperties({"nCalls", "minTime", "maxTime", "sumTime",
                            "sumSquaredTime"})
    public CallStats(int nCalls, long minTime, long maxTime, long sumTime,
                     double sumSquaredTime) {
        this.nCalls = nCalls;
        this.minTime = minTime;
        this.maxTime = maxTime;
        this.sumTime = sumTime;
        this.sumSquaredTime = sumSquaredTime;
    }
    
    public int getNCalls() {
        return nCalls;
    }
    
    public long getMinTime() {
        return Math.max(0, minTime);
    }
    
    public long getMaxTime() {
        return maxTime;
    }
    
    public long getSumTime() {
        return sumTime;
    }
    
    public double getSumSquaredTime() {
        return sumSquaredTime;
    }
    
    public long getMeanTime() {
        if (nCalls == 0)
            return 0;
        else
            return sumTime / nCalls;
    }
    
    public long getStdDevTime() {
        if (nCalls == 0)
            return 0;
        double mean = getMeanTime();
        double meanSquared = mean * mean;
        double meanOfSquares = sumSquaredTime / nCalls;
        double variance = meanOfSquares - meanSquared;
        if (variance < 0)
            return 0;  // might happen due to rounding error
        return (long) Math.sqrt(variance);
    }
    
    public String toString() {
        return "nCalls = " + nCalls + ";\n" +
                "sumTime = " + getSumTime() + ";\n" +
                "minTime = " + minTime + ";\n" +
                "maxTime = " + maxTime + ";\n" +
                "meanTime = " + getMeanTime() + ";\n" +
                "stdDevTime = " + getStdDevTime();
    }
    
    public Call newCall() {
        return new Call();
    }
    
    public class Call {
        private final long startTime = System.nanoTime();
        private Call() {}
        
        public void end() {
            long elapsed = System.nanoTime() - startTime;
            synchronized (CallStats.this) {
                nCalls++;
                if (elapsed < minTime || nCalls == 1)
                    minTime = elapsed;
                if (elapsed > maxTime)
                    maxTime = elapsed;
                sumTime += elapsed;
                double elapsedFP = elapsed;
                sumSquaredTime += elapsedFP * elapsedFP;
            }
        }
    }
}

A few remarks are in order. First, the @ConstructorProperties annotation is new in the Java SE 6 release, so if you're using an earlier release you'll need to comment out that line. The purpose of the annotation is to allow us to use CallStats directly as the type of an attribute in an MXBean, like this:

public interface MeasurementMXBean {
    public CallStats getFooStats();
    public CallStats getBarStats();
}

The presence of the @ConstructorPropertiesannotation allows us to reconstruct a CallStatsinstance for an MXBean proxy, like this:

    MeasurementMXBean proxy =
        JMX.newMXBeanProxy(mbeanServer, name, MeasureMXBean.class);
    CallStats stats = proxy.getFooStats();

Second, there are a lot of nanoseconds in a second. So we have to be on the lookout for overflow. A long is enough to hold 292 years' worth of nanoseconds without overflowing, so you won't usually have problems if you use longeverywhere. But we're also accumulating squarednanoseconds so that we can compute the standard deviation. We would get overflow if we squared any value over 3 seconds' worth of nanoseconds as a long. So instead we square the values as doubles and also sum them as doubles. This means the standard deviation might not be as accurate as if we had used BigInteger, but I'm not sure it matters. I think there's a small possibility that rounding errors would lead us to compute a negative variance when the actual variance is very close to zero, so I've guarded against that case.

Finally, you might have noticed an asymmetry between theminTime and maxTime statistics.minTime needs a special check for the first call, because otherwise the initial default value of 0 would never change. maxTime doesn't, because the first measurement will be greater than the initial zero value. We could correct the asymmetry by initializing minTime toLong.MAX_VALUE, but at the expense of reporting a very odd minTime for a CallStats that hasn't recorded any measurements yet.

Defining an equals(Object) method in a public class is not always straightforward. One reason it might not be is that the answer to the question "are these objects equal?" might be "who's asking?".

In a very interesting comment on my previous blog entry, rullrich suggests that the real problem is with MBeanInfo.equals. I'm inclined to disagree, but the issue is a subtle one.

One frequent difficulty with implementing anequals(Object) method is what to do in subclasses. Josh Bloch has an exhaustive discussion of this question in Effective Java, so I won't go into it in any more detail, except to say that there is a blemish in the JMX API because it doesn't follow Bloch's advice. The class OpenMBeanInfoSupport is a subclass of MBeanInfo and overrides its equals method. In addition to the requirements of MBeanInfo.equals, it requires that its argument be an instance of OpenMBeanInfo. This means that this method violates the symmetry property guaranteed by the contract for Object.equals. If I have an object mbeanInfo of class MBeanInfo and an objectopenMBeanInfo of classOpenMBeanInfoSupport, thenmbeanInfo.equals(openMBeanInfo) might be true whileopenMBeanInfo.equals(mbeanInfo) is false. Ugh.

But the question at hand is whether two MBeanInfoobjects should be equal if (for example) they contain the same attributes but in a different order. This is where I say we need to know: who's asking?

I can see at least two use cases for callers ofMBeanInfo.equals:

  1. A client has been coded to interact with a particular sort of MBean, say the MemoryMXBean from java.lang.management. It wants to check that the interface of the MBean it is interacting with is indeed the expected interface.

  2. A graphical client (such as JConsole) has created a GUI for a certain MBean and wants to know if the MBean's interface has changed so it can change the GUI.

JConsole GUI for MemoryMXBean 

The snapshot shows the JConsole GUI for the MemoryMXBean. If the order of the attributes changed, and assuming we think it is useful to preserve the order from the MBeanInfo, then we would want to change the order in the GUI too. You could imagine JConsole callingmbeanServer.getMBeanInfo(memoryMXBeanName) and comparing the MBeanInfo it gets back with the one it previously used to construct this view.

In this second use case, MBeanInfo.equals should return false if the order of the attributes has changed, or if any other detail has changed, such as the description of an attribute. But in the first use case, MBeanInfo.equals should ignore these details because they don't change the set of valid client operations. Obviously we can't make MBeanInfo.equals conform to both use cases so we must pick one.

We chose the second use case because it is precise. Two MBeanInfo objects are different if they differ in any detail, including the order of attributes. The first use case is much less precise:

  • Changing the type of an attribute from int to Integer has no effect on clients so should we consider two MBeanInfo objects equal if that is their only difference?
  • What about changes to the Descriptor of an MBeanInfo or a contained MBeanAttributeInfo?
  • Adding a new attribute to an MBean does not invalidate existing clients, but we can't consider two MBeanInfo objects equal if one of them has an attribute that the other doesn't. (It would violate symmetricity again.)

A secondary reason for preferring the definition where order is significant is that it makes for a faster equalsmethod. If order were not significant then we would need either a quadratic algorithm or the creation of extra objects inside the method. This is very much a secondary reason; efficiency concerns should almost never be the primary motivation for API design choices. Correctness and simplicity trump efficiency!

A summary of the choice we made, then, is that it allows me to answer the question: Is this MBeanInfo object the same as one I got earlier for the same MBean? If the answer is yes, then the MBean interface definitely hasn't changed. If the answer is no, then the MBean interface might or might not have changed, but I must assume it has.

The choice does not in general allow you to use MBeanInfo.equals to answer the incompatible question: Is this MBeanInfo object the same as the one I expected?

So the moral is that when you define an equalsmethod you must know what question you want it to answer.

But what about the order of methods in a Standard MBean interface?

rullrich also makes some other interesting points. First, in principle calling getMBeanInfo() twice on the same MBean might return a different object each time, with a different attribute order, even if the MBean has not changed in any way. This is not a problem with the JDK, but you could imagine an implementation that (a) does not cache the MBeanInfo for a Standard MBean interface (as the JDK does), and where (b) the order of the methods returned by Class.getMethods() can vary at different times for the same Class object (which it cannot in the JDK). Well, I think a JMX implementation running on a JVM with property (b) had better ensure that it does in fact cache the MBeanInfo. We could certainly modify the spec so it says this explicitly, but I think we would be addressing a very theoretical problem.

Second, I am also inclined to think we should preserve the order returned by Class.getMethods(). The implication for the regression test is that it should use reflection to determine what it thinks the order of attributes and operations in the MBeanInfo should be, and compare this with the order that is actually in the MBeanInfo returned by the JMX API. That test is then imposing a stronger restriction on the JDK's implementation of the API than is required by the spec, but that's all right. The bug in the test that I actually coded is that it hardwired this expected order instead of getting it from Class.getMethods().

An inadvertent change in JDK 6 means that MBean attributes and operations no longer appear in the order they were in a Standard MBean interface. I wanted to fix this, but now I'm not so sure.

Here's the background. Suppose you have a Standard MBean with this interface:

public interface PlanetMBean {
    public int getMercury();

    public String getVenus();
    public void setVenus(String x);

    public BigInteger getEarth();
    public void setEarth(BigInteger x);

    public boolean isMars();

    public double getJupiter();

    public byte getSaturn();

    public short getUranus();
    public void setUranus(short x);

    public long getNeptune();

    public void neptune();
    public void uranus(int x);
    public int saturn(int x, int y);
    public short jupiter(int x, long y, double z);
    public void mars(boolean x);
    public BigInteger earth();
    public String venus();
    public int mercury();
}

As you can see, it defines a set of attributes and operations with random types, but names in a very definite and familiar order.

If we create a Standard MBean like this, using JDK 5.0, then it will look like this in JConsole.

Attributes and operations appear in the same order as in the interface 

However, if we run the same program on JDK 6, then it looks like this:

Attributes and operations appear in alphabetical order 

Observe that the planets are no longer in astronomical order, but in alphabetical order.

Aside: what about Pluto? You might have noticed that I omitted Pluto from my list. That's because Pluto is not a Planet. A rare victory in the ongoing and hopeless war against Stupidism was won earlier this year when the International Astronomical Union voted to recognize logic and common sense and ignore inertia, nostalgia, and welearneditinschoolsoitmustbetrueforeveria. More details than anyone could possibly want to know are in Wikipedia. Beyond just astronomical reality, computer scientists will surely be happier that there are eight planets, divided into four big ones and four small ones, and with each group of four further divisible into two big and two small. Or maybe that's just me.

It turns out that this reordering is an unintended side-effect of my rewrite of the MBean Server internals. I had intended to preserve the order from the MBean interface, but in a moment of distraction I used a TreeMapinstead of a LinkedHashMap.

Nothing in the JMX specification requires that attributes and operations in a Standard MBean appear in the same order in the MBeanInfo as they do in the MBean interface, so none of our automated tests check that. We only discovered it recently, as a side-effect of some unrelated tests being coded by one of our Quality Engineers, Sandra Lions. Sandra logged bug 6486655 to track this.

This looks like something we would obviously want to fix, right? Perhaps you thought carefully about the order you want to present your attributes in, and followed that order in your MBean interface. Perhaps you want to group related operations together, for example addThingy() followed immediately byremoveThingy() (example due to Daniel Fuchs).

So, since I broke it, I started the process of fixing it. The fix itself is completely trivial - just replace twoTreeMaps with LinkedHashMaps. But then things got interesting.

The JDK has a clearly-defined engineering process. One rule in that process is that every time we fix a bug, there must be a regression test that fails without the fix and passes with the fix.

So I duly coded a regression test. It was pretty much what you would expect. In outline:

public class MethodOrderTest {
    public static interface PlanetMBean {
        ...same as above...
    }

    private static final String[] planets = {
        "mercury", "venus", "earth", "mars", "jupiter", "saturn",
        "uranus", "neptune"
    };

    public static void main(String[] args) throws Exception {
        ...create a Standard MBean using the PlanetMBean interface...
        MBeanInfo mbi = ...the MBeanInfo of this MBean...;
        MBeanAttributeInfo[] mbais = mbi.getAttributes();
        ...check that mbais[] is in the same order as planets[]...
        MBeanOperationInfo[] mbois = mbi.getOperations();
        ...check that mbois[] is in the opposite order as planets[]...
    }
}

After fixing the usual stupid mistakes in the test, the attribute check passed. But the operation check failed! What was different about operations? I went back and looked at the introspection code but could see nothing.

What's more, the order the operations were appearing in was surprising. I would have understood if they were in alphabetical order. That would have meant that something else besides theTreeMaps I had removed was causing them to be sorted.

But the operations were in astronomical order, from Mercury to Neptune, even though the interface declared them in reverse astronomical order, from Neptune to Mercury. What could be reversing them?

I won't get into the details of the painstaking investigation that followed. Suffice it to say that I ended up boiling the test failure down to this program:

public class MethodOrderTest {
    public static interface PlanetMBean {
        ...same as above...
    }

    private static final String[] planets = {
        "mercury", "venus", "earth", "mars", "jupiter", "saturn",
        "uranus", "neptune"
    };

    public static void main(String[] args) throws Exception {
        System.out.println(Arrays.asList(PlanetMBean.class.getMethods()));
    }
}

Notice that there's no JMX stuff at all here. It's just a call to the Reflection API. And it shows the methods in the following order:

[public abstract int MethodOrderTest$PlanetMBean.mercury(),
public abstract java.lang.String MethodOrderTest$PlanetMBean.venus(),
public abstract java.math.BigInteger MethodOrderTest$PlanetMBean.earth(),
public abstract void MethodOrderTest$PlanetMBean.mars(boolean),
public abstract short MethodOrderTest$PlanetMBean.jupiter(int,long,double),
public abstract int MethodOrderTest$PlanetMBean.saturn(int,int),
public abstract void MethodOrderTest$PlanetMBean.uranus(int),
public abstract void MethodOrderTest$PlanetMBean.neptune(),
public abstract int MethodOrderTest$PlanetMBean.getMercury(),
public abstract java.lang.String MethodOrderTest$PlanetMBean.getVenus(),
public abstract void MethodOrderTest$PlanetMBean.setVenus(java.lang.String),
public abstract java.math.BigInteger MethodOrderTest$PlanetMBean.getEarth(),
public abstract void MethodOrderTest$PlanetMBean.setEarth(java.math.BigInteger),
public abstract boolean MethodOrderTest$PlanetMBean.isMars(),
public abstract double MethodOrderTest$PlanetMBean.getJupiter(),
public abstract byte MethodOrderTest$PlanetMBean.getSaturn(),
public abstract short MethodOrderTest$PlanetMBean.getUranus(),
public abstract void MethodOrderTest$PlanetMBean.setUranus(short),
public abstract long MethodOrderTest$PlanetMBean.getNeptune()]

OK. So the getters and setters appear in the same order as in the interface, but the other methods have been reordered and appear earlier. Does reflection treat getters and setters differently from other methods? What on earth...what the devil is going on?

The next, surprising step is to comment out the definition of the planets[] array. Lo and behold, the methods are now printed out in the same order as they appear in the interface!

Further investigation shows that if I include just the strings"mercury", "venus", "earth"in the planets[] array, then the three methodsmercury, venus, earth appear first in the list, and the remaining methods appear in the same order as in the interface.

If we go back and look at the documentation for Class.getMethods(), we learn that "the elements in the array returned are not sorted and are not in any particular order". I had read that text long ago, but I had also observed that in practice the elements were in the order that the methods were declared in the class. So I wasn't too uneasy about assuming they would be.

But the investigation above shows that this assumption is false. In fact, the order of methods is the order in which the JVM first encountered the method name strings. If your interface has methods called mercury and venus, then that will often be the first time the JVM is seeing those strings, especially in small test programs trying to determine howClass.getMethods() behaves. So theMethod[] array will have the methods in the same order as in the interface. But if you have previously loaded another class that also has those method names, or even that happens to contain those names as string constants, then the order will be determined by that other class and could be completely different!

The implications are wide-ranging. In particular, JUnit runs the varioustestFoo() methods in a class in the order that they show up in Class.getMethods(). It's good practice to write tests so that they work no matter what order they are run in - in fact it's vital because you don't know what that order will be.

It turns out that this surprising order is recorded if you look very hard. For example bug 4789197 includes the text "In the HotSpot VM, the order is actually dependent on the order in which the VM's UTF-8 constants are created, i.e., order will be different if a program loads other classes which uses the same name-type id's in random order."

Anyway, now I'm faced with a dilemma. I'd quite like to restore the previous behaviour of Standard MBeans, where attributes and operations show up in the same order as the methods in the MBean interface. Code that depends on this is surely broken, but code like JConsole that presents the attributes and operations in a user interface would surely be better served by this arrangement. Except that now I know that that order can be perturbed by unrelated classes that happen to be loaded earlier. Is it still worth trying to preserve this fragile order? I'd be interested in what people think.

The JMX API includes the possibility to create "Dynamic MBeans", whose management interface is determined at run time. When might that be useful? Here's an example.

In the JMX forum on the Sun Developer Network, Athar asks how to load a properties file in a dynamic MBean. I think that's an excellent question, because it's exactly the example I usually use for "Runtime" Dynamic MBeans.

What I call a "Runtime" Dynamic MBean is one whose management interface you cannot determine by looking at the source code. Obviously Standard MBeans aren't like this, because you can just look at WhateverMBean.java to see what the management interface is going to be. This is still true for MBeans constructed using the StandardMBean class, and it's also true for MXBeans.

It isn't necessarily true for Dynamic MBeans. A Dynamic MBean is a Java object of a class that implements the DynamicMBean interface. This interface includes a method getMBeanInfo(). A class that implements DynamicMBean can construct the MBeanInfo object that it returns from getMBeanInfo() however it likes. It can even return a different MBeanInfo every time it is called!

This flexibility is almost never necessary. Nearly always, when you create a Dynamic MBean, it is because you want to add extra information to the MBeanInfo, or because you want to implement the logic to get an attribute or call an operation in some particular way. Just like dynamic code generation, my advice if you are considering making a Runtime Dynamic MBean is to think really hard about whether you couldn't redesign things so that the interface is known at compile time. The problem with an MBean interface only known at run time is that it's hard for a client to interact with it. Suppose your client wants to call getAttribute on your MBean. The only way it can know what attributes are available is to call getMBeanInfo beforehand. If the MBean's interface can change as it is running, even this isn't guaranteed to work!

However, there are some cases where it makes a certain amount of sense to have a Runtime Dynamic MBean, and Athar's question suggests one of them. Suppose you have a properties file containing configuration for your application, and you'd like to expose its contents for management, so that you can see the values of configuration items, and perhaps change them as the application is running. The obvious way to do this is to have a ConfigurationManagementMBean that is linked to the properties file.

Every time you change your app to add a new configuration item, you'll need to add it to the initial configuration file, and you'll need to add code to interpret it. But it would be a pain to have to add a new attribute explicitly to the ConfigurationManagementMBean as well. So this argues for one of two approaches:

  1. The ConfigurationManagementMBean has one big attribute that is a Properties, say, or aMap<String,String> if you're using MXBeans. This is workable, but in practice it is very clumsy. In order to change a configuration item, you'll have to get the complete set of items, change the item within it, and write the result back to the MBean. Furthermore, JConsole doesn't currently support changing an item in the middle of a Properties or Mapattribute.
  2. The ConfigurationManagementMBean has one attribute per configuration item, or in other words per property. It determines these attributes at run time by reading the properties file. So it's a Runtime Dynamic MBean!

If you adopt the second approach, then JConsole looking at your ConfigurationManagementMBean might look like this:

JConsole looking at a Runtime Dynamic MBean 

I'll present the code to implement this below. A few things are worth noting. First of all, the DynamicMBean interface is a little bit clunky, in particular the getAttributes and (especially) setAttributes methods. The problem that generates this clunkiness is what to do if one of the attributes to be set produces an error. Should you throw an exception? If so, have any of the other attributes been set? The cleanest solution would be to say that setAttributes is an all-or-nothing operation: either it sets all of the given attributes, or it sets none of them and throws an exception. However, the designers of the JMX API felt that this was a harsh constraint to put on MBean writers. What's more it is not at all obvious how it should apply to Standard MBeans. So instead, setAttributes returns an AttributeList containing the attributes that were actually set. The caller needs to check that this contains all the values that were supposed to be set, and react appropriately if not.

The code doesn't let you set a value for a property that was not already present. The MBean Server does not check that the attribute name in setAttribute is present in the MBeanInfo. It is up to the MBean to do that. An MBean could choose to accept such a name, which in this case would allow you to define new properties. But I think it would be better to achieve that in some other way, for example an explicit addProperty operation.

In addition to one attribute per property, I've defined an operation reload which reloads the properties from the file. If there are properties in the file that were not present before, then they will appear as new attributes. Notice that adding an operation requires you both to mention it ingetMBeanInfo and to recognize it ininvoke. If there are many operations, you might want to consider getting the StandardMBean class to do some of the work for you.

Finally, every time you change a property the code updates the configuration file. The way it does this is intended to be a safe way to update a file. It writes a new properties file in the same directory, then renames it over the original. On most operating systems, renaming is atomic, so even if your app is interrupted in the middle of this operation, you will end up with either the old file or the new file, but not with a missing or partially-written file.

package propertymanager;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.Properties;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.DynamicMBean;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.ReflectionException;

public class PropertyManager implements DynamicMBean {
    private final String propertyFileName;
    private final Properties properties;
    
    public PropertyManager(String propertyFileName) throws IOException {
        this.propertyFileName = propertyFileName;
        properties = new Properties();
        load();
    }

    public synchronized String getAttribute(String name)
    throws AttributeNotFoundException {
        String value = properties.getProperty(name);
        if (value != null)
            return value;
        else
            throw new AttributeNotFoundException("No such property: " + name);
    }

    public synchronized void setAttribute(Attribute attribute)
    throws InvalidAttributeValueException, MBeanException, AttributeNotFoundException {
        String name = attribute.getName();
        if (properties.getProperty(name) == null)
            throw new AttributeNotFoundException(name);
        Object value = attribute.getValue();
        if (!(value instanceof String)) {
            throw new InvalidAttributeValueException(
                    "Attribute value not a string: " + value);
        }
        properties.setProperty(name, (String) value);
        try {
            save();
        } catch (IOException e) {
            throw new MBeanException(e);
        }
    }

    public synchronized AttributeList getAttributes(String[] names) {
        AttributeList list = new AttributeList();
        for (String name : names) {
            String value = properties.getProperty(name);
            if (value != null)
                list.add(new Attribute(name, value));
        }
        return list;
    }

    public synchronized AttributeList setAttributes(AttributeList list) {
        Attribute[] attrs = (Attribute[]) list.toArray(new Attribute[0]);
        AttributeList retlist = new AttributeList();
        for (Attribute attr : attrs) {
            String name = attr.getName();
            Object value = attr.getValue();
            if (properties.getProperty(name) != null && value instanceof String) {
                properties.setProperty(name, (String) value);
                retlist.add(new Attribute(name, value));
            }
        }
        try {
            save();
        } catch (IOException e) {
            return new AttributeList();
        }
        return retlist;
    }

    public Object invoke(String name, Object[] args, String[] sig)
    throws MBeanException, ReflectionException {
        if (name.equals("reload") &&
                (args == null || args.length == 0) &&
                (sig == null || sig.length == 0)) {
            try {
                load();
                return null;
            } catch (IOException e) {
                throw new MBeanException(e);
            }
        }
        throw new ReflectionException(new NoSuchMethodException(name));
    }
    
    public synchronized MBeanInfo getMBeanInfo() {
        SortedSet<String> names = new TreeSet<String>();
        for (Object name : properties.keySet())
            names.add((String) name);
        MBeanAttributeInfo[] attrs = new MBeanAttributeInfo[names.size()];
        Iterator<String> it = names.iterator();
        for (int i = 0; i < attrs.length; i++) {
            String name = it.next();
            attrs[i] = new MBeanAttributeInfo(
                    name,
                    "java.lang.String",
                    "Property " + name,
                    true,   // isReadable
                    true,   // isWritable
                    false); // isIs
        }
        MBeanOperationInfo[] opers = {
            new MBeanOperationInfo(
                    "reload",
                    "Reload properties from file",
                    null,   // no parameters
                    "void",
                    MBeanOperationInfo.ACTION)
        };
        return new MBeanInfo(
                this.getClass().getName(),
                "Property Manager MBean",
                attrs,
                null,  // constructors
                opers,
                null); // notifications
    }

    private void load() throws IOException {
        InputStream input = new FileInputStream(propertyFileName);
        properties.load(input);
        input.close();
    }

    private void save() throws IOException {
        String newPropertyFileName = propertyFileName + "$$new";
        File file = new File(newPropertyFileName);
        OutputStream output = new FileOutputStream(file);
        String comment = "Written by " + this.getClass().getName();
        properties.store(output, comment);
        output.close();
        if (!file.renameTo(new File(propertyFileName))) {
            throw new IOException("Rename " + newPropertyFileName + " to " +
                    propertyFileName + " failed");
        }
    }
}