Wednesday, December 23, 2015

Cast Away

Java Generics are neat and all, but one situation I find them highly annoying goes like this:
  1. An interface IBar and an interface IFoo with a method:

    public Set<? extends IBar> getBars ();
     
  2. Bar is a concrete implementation of IBar, and Foo is a concrete implementation of IFoo that gives a covariant return for the same method:

    public Set<Bar> getBars ();
     
  3. Another method that declares it returns a Set<? extends IBar> where I know the Set actually holds instances of Bar.
     
  4. The implementation of Foo.getBars() calls this other method and intends to return the Set.
     
  5. The compiler balks because this is not acceptable:

    Set<? extends IBar>() bars = x();return (Set<Bar>) bars;
Indignantly I balk because this cast should be fine! But in reality, the nature of how Java Generics works does not allow downcasting of parameterized types. The bytecode has no knowledge of these types and no way to check them at runtime.

An easy hack would be to subvert the Java Generics compiler checks, such as:

Set<? extends IBar>() bars = x();
return (Set<Bar>)(Set<?>) bars;

For those who don't care about type-safety, I'm sure you will jump at that solution. For those who treasure type-safety, though, this option is highly unpalatable.

For a type-safe solution, a direct cast is not acceptable, so a copy with the correct type must be generated. To provide a single method that would satisfy this requirement for any Collection type took a bit of finagling of both Java Generics and reflection. This is what I finally came up with:

public static <T,
               U extends T,
               C extends Collection<? extends T>,
               D extends Collection<U>>
 D specializedCollection (C generalized, Class<? extends U> type) {
    try {
        D specialized = (D) generalized.getClass().newInstance();
        for (T item : generalized) {
            if (type.isInstance(item)) {
                specialized.add((U)item);
            }
            else {
                throw new ClassCastException("Element not castable to " + type.getClass() + ": " + item);
            }
        }
        return specialized;
    } catch (IllegalAccessException | InstantiationException e) {
        throw new IllegalArgumentException("Could not create a " + generalized.getClass().getName(), e);
    }

}
  • The U parameterized type had to be passed into the method because it cannot be discovered via the D type, or the C instance provided.
  • Collections are supposed to provide a public no-argument constructor, so the use of reflection to create the new instance is acceptable.
  • The cast of item from a type T to a type U is an acceptable cast because by the types declared, U is a subtype of T. However, this cast does not occur in the bytecode since parameterized types are deleted upon compilation.
  • In order to ensure type safety, Class.isInstance() is explicitly called using the U parameterized type in order to generate the expected ClassCastException.
Now I can maintain type safety with a single method call such as:

Set<? extends IBar>() bars = x();
return specializedCollection(bars, Bar.class);

Less efficient, sure, but now I am type safe!

This post is also available at Quantum Strides, and while you're there check out my Java Fundamentals course!

Monday, December 7, 2015

What I Learned From My Commodore 64

Today, retired to a box in my garage, my beloved Commodore 64 awaits my decision on its ultimate fate. That contraption, archaic by today's standards, changed my fate.

Engaging a child in any endeavor requires grabbing attention and stimulating the imagination. Whereas computers are ubiquitous today, 30 years ago they were novel. Remember how smart phones were rather uncommon a few years ago? And how everyone wanted one, first just to have one, then to use them for what they could do: games, videos, and everything ENTERTAINING! Now consider how most of those writing programs for smart phones are in general much younger than the average software engineer. It's not accident. These young whelps found their imaginations engaged by the new and exciting smart phones, so they dove in to learn more about them and to figure out how to make the phones do what they wanted them to do.

That was me 30 years ago. Does that make me an old fogey now? In some ways, I suppose, since my imagination did not get as an engaged with smart phones. That is partly due to my age, but also partly due to not having the time to indulge since I also have children and other activities that I actively pursue. But back then, I did.

I loved how I could play my favorite games on the Commodore 64, games like Jumpman, M.U.L.E., Olympic games, SportTime Soccer, and the amazing Alternate Reality series. Those certainly engaged my imagination. Then I discovered that BASIC was built right into the computer. I could write my own programs and make the computer do what I wanted it to do! Those small programs would mean nothing to anyone else, but they meant everything to me.

This is what I learned from my Commodore 64:
  • Save frequently.
  • Make backup copies.
  • A bug is not so bad, but if that bug ruins my work or fun then you and I are done.
  • Copy protection reduces customer value and satisfaction.
  • Plan for future change. Leave gaps between your line numbers.
  • Any program of significance cannot be kept entirely in your head.
  • REM only useful comments. Fix them if they are wrong.
  • A hole punch can do amazing things.
  • Know your variables.
  • Debug statements might be the only way to figure out what is going on.
  • Good graphics do not make a good game.
  • Good music can make a good game great!
  • Read other people's code.
  • Adjust to how other people write their code.
  • Adapt other people's techniques to your own style.
  • You can create any data structure in any programming language.
  • Memory is precious.
  • Be very careful where and what you poke.
  • If all else fails, power off and start all over.

Friday, November 13, 2015

Arrays

Don't reinvent the wheel when there is a racecar available for free!

I have the amusing difficulty of having been in Java too long. Back in version 1.0, I was writing my own serialization mechanism because none existed, however in version 1.1 Java serialization was introduced. In 1.1 and 1.2, I was working on the Voyager ORB where we were generating proxy classes on the fly. Lo and behold, in version 1.3 dynamic proxy generation was introduced.

The Java Core APIs and Java Extension APIs now have many capabilities that I am still realizing are there. I find that solving these small problems can be personally satisfying, but I still would rather have used one already available. I will document some of the useful Java Core APIs that I like to use.

Beginning with:

java.util.Arrays

The java.util.Arrays class has really grown up. Many methods are overloaded to support every primitive type as well as Java Generics parameterized Object references. In these cases, TYPE is used as a placeholder to indicate there is a method for every such type.

public static <T> List<T> asList(T... a)

Create a List front-end to an array. Any actions taken on items in the List affect the Objects in the underlying Array.


    public static void main (String [] args)
    {
        List<String> list = Arrays.asList(args);

    }

Since a java.util.List can only hold Object references and not primitive types, there are no primitive variants for this method.

public static boolean equals(TYPE [] a, TYPE [] a2)

Performs a shallow equals on the two arrays. For example, for primitive types the algorithm would be like:

return (a[0] == a2[0] && a[1] == a2[1] && ...)

whereas for Object types (assuming no null references):

return (a[0].equals(a2[0]) && a[1].equals(a2[1]) && .... )

public static boolean deepEquals(Object [] aObject [] a2)
public int deepHashCode(Object [] a)
public String deepToString(Object[] a)

The deep* methods recursively step into any array elements that are themselves arrays.

deepEquals() performs a deep equals on the two arrays. That is, if an element in the arrays is yet another array, then the contents of those arrays is recursively compared via deepEquals.

deepHashCode() calculates a hashCode value for the array by stepping into its contents and the contents of any arrays contained therein.

deepToString() creates a String value for the array from the String values of the array's contents and the contents of any arrays contained therein.

public static void sort (TYPE [] array, int fromIndex, int toIndex)
public static void sort (T[] array, Comparator<? super T> c)
public static void sort (T[] array, int fromIndx, int toIndex, Comparator<? super T> c)

The sort() methods perform an in-place quicksort on the given array. If the indices are given, then the sort is only performed between those indices. For Object references, a Comparator can be given. Once an array is sorted, the corresponding binarySearch() method can be used to efficiently search the data in the array.

Other methods include functions to copy part or all of the contents of an array and to fill an array. There area also methods to process the elements of an array in parallel for various behaviors including sort and set.

Monday, November 2, 2015

Accidentally

It was an accident. I swear! I got into Java because my manager had heard a new buzzword: "Java", and she wanted someone to look into it. I just happened to be that someone she chose, so off researching I went. I was just a year out of college. The latest Java version was 1.0.2. Server-side Java applications did not exist. All GUI actions went through the Component.action() method. Applets were ALL the rage. So Applets it had to be. I wrote an Applet corollary to one or our Xwindows apps. The only piece of functionality I added was the ability to change the font type and size for the display. As luck would have it, that was the feature that our customers LOVED! From there I became the Java Guy and several other apps were targeted for Java redesigns. That led to job offers doing even more Java work for much higher pay. Yes, this was me:
Today, I can now claim I have been doing Java full time for over 18 years. I will admit, I'm still learning the new additions introduced in JSE 1.8. I will get there. If for no other reason, then because I will have to fix a bug in code someone wrote using the newest Java features. Or in other words, by accident.

This blog entry also appears at Quantum Strides.

Thursday, January 23, 2014

The Method To My Madness

Just about everyone has a preferred coding style. It generally manifests in formatting choices. For example, although the Java standard for braces is to have open braces at the end of a line, I refused to follow that standard. My reason is because I actually learned C and C++ before Java came on the scene, and I learned to format my C and C++ code with braces on their own lines and indented in pairs. As a result, I learned to pair braces according to their matched indentation, and that familiarity is why I continue to do so. Sure, I sometimes get remarks from other Java programmers at times, and I have occasionally clashed with official company styles. To them I say: let me do what I know; I am good at it! To be fair, of course, I never demand a particular style from anyone else either -- though reviewing the code of someone that is inconsistent in their own style will get complaints from me. Be consistent!

There is another aspect to my coding style which gets a lot more questions, and that is because it is more than just simple formatting: I write my code specifically to avoid break statements, continue statements, and nested returns. As an example, a double nested loop in my code will resemble:

boolean closed = false;

for (int i=0; !closed && i < fooList.size(); ++i)
{
    List<Bar> barList = foolist.get(i).getBarList();
    for (int j=0; !closed && j < barList.size(); ++j)
    {
        closed = barList.get(j).isClosed();
    }
}

return closed;

Colleagues reviewing my code often ask why I don't just use the nested return such as:

for (Foo foo : fooList)
{
    for (Bar bar : foo.getBarList())
    {
        if (bar.isClosed())
        {
            return true;
        }
    }
}

return false;

This is my answer:

The looping style I use is related to what I learned in one of my graduate-level courses in Computer Engineering when we studied formal proofs of computer programs. I happen to be very mathematically minded and even got a minor in Mathematics as an undergrad, so this particular subject appealed to me. I was also curious how IBM had done some significant development with software validation via formal proofs and no testing and found the results to be at least as high in quality (fewer bugs) as software developed with testing.

Formally proving the correctness of a loop requires stating the invariant of that loop and the condition of the loop. The way I write the condition in the for loop, the invariant and condition can be easily determined by examination. In this case the outer loop invariant is i <= fooList.size() and the condition is as written: !closed && i < fooList.size(). The inner loop is similar.

Since the nested return is logically equivalent, the invariants and conditions are the same, but determining them from examination is extremely difficult. I do not expect anyone to formally prove my loop, but if desired it could be. For myself, I find it easier to validate my style when reviewing code since I tend to think more mathematically.

That is how I got to writing my loops this way. After I had been doing this a short while, I discovered a particular advantage that is even more appealing to me: the single exit point. By having a single exit point, I have a single clear location where I can set a breakpoint or insert a log statement when debugging. I also have an obvious variable to watch (closed in this case). I also have a clear delineation for the beginning and ending of a try block in the event that an exception needs to be caught. This has led me to write my if statements without nested returns as well. All in all, it feels more disciplined to me.

Since this style has paid such dividends to me over the years, I continue to use it. That’s why you don’t find nested return, break, or continue statements in my code. I don’t usually mind if others use them — unless I see a continue statement with a label then I do complain rather loudly.

Sunday, December 23, 2012

A Bit of Regex

Every time I want to use a regular expression in a Java program, I always seem to go through a lot of iterations developing an expression to get the specific results I need. As a result, this is a small Java program that allows me to use command line arguments to specify text and expressions on the command line.

import java.util.*;
import java.util.regex.*;

public class Regex
{
    public static void main (String [] args) throws Exception
    {
        String source = args[0];
        System.out.println("Source string: " + source);
        Pattern p = Pattern.compile(args[1]);
        System.out.println("Pattern: " + args[1]);
        Matcher m = p.matcher(source);
        System.out.println("match? " + m.matches());
        for (int i=1; i <= m.groupCount(); ++i)
        {
            System.out.println("\tGroup " + i + ":\t" + m.group(i));
        }
    }
}

Tuesday, July 31, 2012

Providing Map Context In Log4J Output

Although Log4J's PatternLayout provides a way to output specifically named values from the mapped diagnostic context (MDC), there is no mechanism in any of Log4J's layouts to simply display all of the values from the MDC. In fact, the LoggingEvent does not allow access to the MDC's map or even a copy of it.

This shortcoming was particularly annoying because at various points in my code I would want to add and remove arbitrary key/value pairs from the MDC at certain points specifically to make the information available in log files. This was why I decided to extend LoggingEvent to provide access to the set of keys in the MDC's map, thus allowing other classes aware of this functionality to access the full contents of the MDC and even to display it in layouts.

This implementation is in multiple classes provided below:
  1. OpenLoggingEvent which adds getMDCKeys() to the LoggingEvent.
  2. OpenLogger which extends Logger and changes the forcedLog() method to create an OpenLoggingEvent instead of a LoggingEvent.
  3. OpenLoggerFactory which is a LoggerFactory that will attempt to install itself as the default provider via LogManager.
  4. OpenLoggerConfigurator which can be specified by the log4j.configuratorClass system property in order for OpenLoggers to be used by default during Log4J during configuration.
  5. MDCAwareHTMLLayout which extends HTMLLayout with behavior to display the MDC map's keys and values in a HTML table.
  6. MDCAwareXMLLayout which extends XMLLayout with behavior to provide the MDC map's contents via <log4j:mdc> and <log4j:mdcEntry> tags.

 package log4j.util;

import java.util.Arrays;
import java.util.Collection;
import java.util.Hashtable;

import org.apache.log4j.*;
import org.apache.log4j.spi.*;

public class OpenLoggingEvent extends LoggingEvent
{
    private transient Category logger;
    private String [] mdcKeys;

    public OpenLoggingEvent(String fqnOfCategoryClass, Category logger,
                            Priority level, Object message, Throwable throwable)
    {
        super(fqnOfCategoryClass, logger, level, message, throwable);
        this.logger = logger;
    }

    public OpenLoggingEvent(String fqnOfCategoryClass, Category logger,
                            long timeStamp, Priority level, Object message,
                            Throwable throwable)
    {
        super(fqnOfCategoryClass, logger, timeStamp, level, message, throwable);
        this.logger = logger;
    }


    // Should this be synchronized?  The superclass doesn't do it.
    public void getMDCCopy()
    {
        if (mdcKeys == null)
        {
            Hashtable mdc = MDC.getContext();
            if (mdc == null)
            {
                mdcKeys = new String [0];
            }
            else
            {
                mdcKeys = (String [])mdc.keySet().toArray(new String[mdc.size()]);
            }
        }
        super.getMDCCopy();
    }

    public Collection getMDCKeys ()
    {
        if (mdcKeys == null)
        {
            getMDCCopy();
        }
        return Arrays.asList(mdcKeys);
    }

    protected Category getLogger ()
    {
        return logger;
    }
}




package log4j.util;

import org.apache.log4j.Logger;
import org.apache.log4j.Priority;

class OpenLogger extends Logger
{
    protected OpenLogger (String name)
    {
        super(name);
    }

    protected void forcedLog (String fqcn, Priority level, Object message, Throwable t)
    {
        callAppenders(new OpenLoggingEvent(fqcn, this, level, message, t));
    }
}




package log4j.util;

import org.apache.log4j.*;
import org.apache.log4j.spi.*;

public class OpenLoggerFactory implements LoggerFactory
{
    public static void install ()
    {
        try
        {
            LogManager.setRepositorySelector(new RepositorySelector()
                {
                    private Hierarchy repository = null;

                    {
                        OpenLoggerFactory factory = new OpenLoggerFactory();
                        Logger root = factory.makeNewLoggerInstance("root");
                        root.setLevel(LogManager.getLoggerRepository().getRootLogger().getLevel());
                        repository = new FactoryConfigurableHierarchy (factory, root);
                    }

                    public LoggerRepository getLoggerRepository ()
                    {
                        return repository;
                    }
                },
                                             null);  // no guard
        }
        catch (IllegalArgumentException ohWell)
        {
            // Guard prevented new repository
            Logger.getLogger(OpenLoggerFactory.class).info("Could not set RepositorySelector", ohWell);
        }
    }

    public Logger makeNewLoggerInstance (String name)
    {
        return new OpenLogger(name);
    }
}




package log4j.util;

import java.net.URL;

import org.apache.log4j.LogManager;
import org.apache.log4j.PropertyConfigurator;
import org.apache.log4j.helpers.OptionConverter;
import org.apache.log4j.spi.Configurator;
import org.apache.log4j.spi.LoggerRepository;
import org.apache.log4j.xml.DOMConfigurator;

/**
   Define system property log4j.configuratorClass to log4j.util.OpenLoggerConfigurator
   in order for the Log4J startup process to use OpenLoggers during configuration.
 */

public class OpenLoggerConfigurator implements Configurator
{
    static
    {
        OpenLoggerFactory.install();
    }

    public OpenLoggerConfigurator () {}

    public void doConfigure (URL url, LoggerRepository repository)
    {
        OptionConverter.selectAndConfigure(url, null, LogManager.getLoggerRepository());
    }
}



package log4j.util;

import java.util.*;

import org.apache.log4j.*;
import org.apache.log4j.or.RendererMap;
import org.apache.log4j.spi.*;
import org.apache.log4j.helpers.Transform;

public class MDCAwareHTMLLayout extends HTMLLayout
{
    public static final Logger LOGGER = Logger.getLogger(MDCAwareHTMLLayout.class);

    private RendererSupport rendererSupport;

    public MDCAwareHTMLLayout ()
    {
        super();
        if (LOGGER.getLoggerRepository() instanceof RendererSupport)
        {
            rendererSupport = (RendererSupport)LOGGER.getLoggerRepository();
        }
    }

    public String getRendererSupportClass ()
    {
        return (rendererSupport == null)
            ? null
            : rendererSupport.getClass().getName();
    }

    public void setRendererSupportClass (String className)
    {
        try
        {
            Class c = Class.forName(className, false, Thread.currentThread().getContextClassLoader());
            if (RendererSupport.class.isAssignableFrom(c))
            {
                rendererSupport = (RendererSupport)c.newInstance();
            }
            else
            {
                LOGGER.warn(className + " does not implement " + RendererSupport.class.getName());
            }
        }
        catch (ClassNotFoundException cnfe)
        {
            LOGGER.warn("Could not find class " + className, cnfe);
        }
        catch (IllegalAccessException illEx)
        {
            LOGGER.warn("Could not create instance of class " + className, illEx);
        }
        catch (InstantiationException isAbstract)
        {
            LOGGER.warn("Can not create instance of abstract class " + className, isAbstract);
        }
        catch (ExceptionInInitializerError uhOh)
        {
            LOGGER.warn("Could not initialize class " + className, uhOh);
        }
        catch (SecurityException secEx)
        {
            LOGGER.warn("Not allowed to create instance of class " + className, secEx);
        }
    }

    public String format (LoggingEvent event)
    {
        String formatted = super.format(event);
        if (event instanceof OpenLoggingEvent)
        {
            OpenLoggingEvent openEvent =(OpenLoggingEvent)event;
            Collection mdcKeys = new TreeSet(openEvent.getMDCKeys());
            if (mdcKeys.size() > 0)
            {
                StringBuffer buf = new StringBuffer(formatted);

                buf.append("<tr><td bgcolor=\"#EEEEEE\" class=\"MDC\" colspan=\"6\" title=\"Mapped Diagnostic Context\">")
                    .append(Layout.LINE_SEP)
                    .append("<strong>Mapped Diagnostic Context</strong>")
                    .append(Layout.LINE_SEP)
                    .append("<table border=\"1\">")
                    .append("<tr><th>Key</th>")
                    .append(Layout.LINE_SEP)
                    .append("<th>Value Class</th>")
                    .append(Layout.LINE_SEP)
                    .append("<th>Rendered Value</th></tr>")
                    .append(Layout.LINE_SEP);

                RendererMap rendererMap = null;

                if (openEvent.getLogger() != null)
                {
                    LoggerRepository maybeRendererSupport = openEvent.getLogger().getLoggerRepository();
                    if (maybeRendererSupport instanceof RendererSupport)
                    {
                        rendererMap = ((RendererSupport)maybeRendererSupport).getRendererMap();
                    }
                }
                if (rendererMap == null && rendererSupport != null)
                {
                    rendererMap = rendererSupport.getRendererMap();
                }

                for (Iterator keys = mdcKeys.iterator(); keys.hasNext();)
                {
                    String nextKey = (String)keys.next();
                    Object nextValue = event.getMDC(nextKey);
                    String val = (rendererMap == null)
                        ? String.valueOf(nextValue)
                        : rendererMap.findAndRender(nextValue);
                    buf.append("<tr><td>")
                        .append(Transform.escapeTags(nextKey))
                        .append("</td>")
                        .append(Layout.LINE_SEP)
                        .append("<td>")
                        .append(nextValue.getClass().getName())
                        .append("</td>")
                        .append(Layout.LINE_SEP)
                        .append("<td>")
                        .append(Transform.escapeTags(val))
                        .append("</td></tr>")
                        .append(Layout.LINE_SEP);
                }

                buf.append("</table></td></tr>").append(Layout.LINE_SEP);
                formatted = buf.toString();
            }
        }
        return formatted;
    }
}





package log4j.util;

import java.util.*;

import org.apache.log4j.Logger;
import org.apache.log4j.or.RendererMap;
import org.apache.log4j.spi.*;
import org.apache.log4j.xml.XMLLayout;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.helpers.Transform;

public class MDCAwareXMLLayout extends XMLLayout
{
    public static final Logger LOGGER = Logger.getLogger(MDCAwareXMLLayout.class);

    private RendererSupport rendererSupport;

    public MDCAwareXMLLayout ()
    {
        super();
    }

    public String getRendererSupportClass ()
    {
        return (rendererSupport == null)
            ? null
            : rendererSupport.getClass().getName();
    }

    public void setRendererSupportClass (String className)
    {
        try
        {
            Class c = Class.forName(className, false, Thread.currentThread().getContextClassLoader());
            if (RendererSupport.class.isAssignableFrom(c))
            {
                rendererSupport = (RendererSupport)c.newInstance();
            }
            else
            {
                LogLog.warn(className + " does not implement " + RendererSupport.class.getName());
            }
        }
        catch (ClassNotFoundException cnfe)
        {
            LogLog.warn("Could not find class " + className, cnfe);
        }
        catch (IllegalAccessException illEx)
        {
            LogLog.warn("Could not create instance of class " + className, illEx);
        }
        catch (InstantiationException isAbstract)
        {
            LogLog.warn("Can not create instance of class " + className, isAbstract);
        }
        catch (ExceptionInInitializerError uhOh)
        {
            LogLog.warn("Could not initialize class " + className, uhOh);
        }
        catch (SecurityException secEx)
        {
            LogLog.warn("Not allowed to create instance of class " + className, secEx);
        }
    }

    public String format (LoggingEvent event)
    {
        String formatted = super.format(event);

        if (event instanceof OpenLoggingEvent)
        {
            OpenLoggingEvent openEvent =(OpenLoggingEvent)event;
            Collection mdcKeys = new TreeSet(openEvent.getMDCKeys());
            if (mdcKeys.size() > 0)
            {
                StringBuffer buf = new StringBuffer(formatted);
                buf.setLength(buf.indexOf("</log4j:event") + 1);
                buf.append("<log4j:mdc>\r\n");

                RendererMap rendererMap = null;
                if (openEvent.getLogger() != null)
                {
                    LoggerRepository maybeRendererSupport = openEvent.getLogger().getLoggerRepository();
                    if (maybeRendererSupport instanceof RendererSupport)
                    {
                        rendererMap = ((RendererSupport)maybeRendererSupport).getRendererMap();
                    }
                    else
                    {
                        if (LOGGER.getLoggerRepository() instanceof RendererSupport)
                        {
                            rendererMap = ((RendererSupport)LOGGER.getLoggerRepository()).getRendererMap();
                        }
                    }
                }
                if (rendererMap == null && rendererSupport != null)
                {
                    rendererMap = rendererSupport.getRendererMap();
                }

                for (Iterator keys = mdcKeys.iterator(); keys.hasNext();)
                {
                    String nextKey = (String)keys.next();
                    Object nextValue = event.getMDC(nextKey);
                    String val = (rendererMap == null)
                        ? String.valueOf(nextValue)
                        : rendererMap.findAndRender(nextValue);
                    buf.append("<log4j:mdcEntry key=\"")
                        .append(Transform.escapeTags(nextKey))
                        .append("\" valueClass=\"")
                        .append(nextValue.getClass().getName())
                        .append("\" renderedValue=\"")
                        .append(Transform.escapeTags(val))
                        .append("\"/>\r\n");
                }

                buf.append("</log4j:mdc>\r\n");
                buf.append("</log4j:event>\r\n\r\n");
                formatted = buf.toString();
            }
        }
        return formatted;
    }
}