Thursday, December 04, 2008

Java : Axis2 client and Log4J

I was busy testing a client developed using the Eclipse plug-ins in the previous post.

This came up with the following error message.

log4j:WARN No appenders could be found for logger (org.apache.axis2.description.AxisService).
log4j:WARN Please initialize the log4j system properly.

To set this up inside an Eclipse project:

* Create a new folder in the project called e.g. "log4j".
* Add a file to that folder called log4j.properties which contains something like:


# Set root category priority to INFO and set its only appender to A1
log4j.rootCategory=INFO, A1

# A1 is set to be a ConsoleAppender (writes to system console).
log4j.appender.A1=org.apache.log4j.ConsoleAppender

# A1 uses PatternLayout.
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n


* In the project's "Run Dialog", click on the "Classpath" tab / "Advanced" / "Add folder" and then browse to the "log4j" folder previously created.
* That's it

Enjoy!

Java : Tomcat / Eclipse and Axis2

I needed to develop a Java web service that ran inside Tomcat and used Axis2 as the web server component.

I use Eclipse as my Java IDE and was looking around for plug-ins that would automate and simplify the whole process.

Came across this excellent tutorial - Developing Web Services Using Apache Axis2 Eclipse Plugins - Part 1

This uses two Eclipse plug-ins:

Apache Axis2 Service Archive Generator Wizard and
Apache Axis2 Code Generator Wizard

which generate the proxies and then wrap everything up for deployment to Tomcat.

I highly recommend working through the tutorial and then using that knowledge to develop and deploy your own.

Enjoy!

Java : Finding jars and classes

I seem to spend half my life looking for jars and classes to solve the ubiquitous "Class not defined" problem.

Found this very useful resource which allows you to find which jar files contain the required class and then download them if needed.

Enjoy!

Friday, November 28, 2008

Visual Studio: Intellisense XML files

I was playing with Rhino Mocks which comes with a .dll and a .xml file (for the Rhino Mocks Intellisense) but couldn't get the Intellisense to work.

You need to put the xml file in the same directory as the dll file and then restart VS.

Then all should be fine.

I did find bits and pieces that lacked Intellisense but I guess that that's a problem with the XML file?

Enjoy!

Wednesday, November 19, 2008

LINQ : Simple LINQ to XML

It's really simple to construct XML documents with LINQ. This simple construct (inside LINQPad):


XDocument simpleDoc =
  new XDocument(
    new XElement("TopLevel",
      new XElement("SecondLevel",
        new XElement("a", "b",
        new XAttribute("class", "level"))
      )
    )
  );

simpleDoc.Dump();


produces:


<TopLevel>
  <SecondLevel>
    <a class="level">b</a>
  </SecondLevel>
</TopLevel>


Enjoy!

Tuesday, November 18, 2008

LINQ : LINQPad with auto-completion / Intellisense

This is in beta as we speak.

If you can't wait, invoke as:

LINQPad -beta

Really unlocks the power of LINQ since you can now see what options are available.

Enjoy!

LINQ : LINQ samples and ObjectDumper

There's a good selection of LINQ samples here. I ran up most of them in LINQPad.

However, some of these samples use a method called ObjectDumper (which does much the same job as object.Dump() in LINQPad).

The source for ObjectDumper can be found in this zip file. Save the files in an ObjectDumper directory somewhere and load them as a Project under Visual Studio. (Note that the sample is for VS 2008 but I had no problems doing this with VS 2005. Just remember to click on the .csproj to open the project and not the .sln!).

Then build the project which will create a .dll and add this .dll to the LINQPad "snippet" using "Advanced Properties" (F4)", then "Add" and then "Browse" to where you created the .dll.

Enjoy!

LINQ : Use of elements of string array

Again, using LINQPad:


string[] digits = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" };

var getDigits =
from d in digits
where d[0] == 't'
select d;

getDigits.Dump();


This selects all elements in the array where the first letter is "t".

Result is:

two
three

Enjoy!

Wednesday, November 12, 2008

LINQ : ToArray() and Take()

Using the usual LINQPad and the Northwind DB:

So we first get the last names from the Employees table and put them into a string array using the ToArray() construct and then we get the first 4 using the Take() construct.


string [] allEmployees1 = Employees.Select (e => e.LastName).Take(4).ToArray();

allEmployees1.Dump();

foreach (string s in allEmployees1)
Console.WriteLine(s);


Enjoy!

LINQ : Some string handling

Based on an example by Eric White, here's some simple string handling (running as always inside LINQPad).


// Per Eric White. The following is a query to split a string into words, only
// allow words > 1 character, eliminate the ones that contain the letter
// "y", convert the words to lower case, count the number of occurrences of
// each word, and return the ten most used words.

string text = "Debugging LINQ queries can be problematic. One of the reasons is that quite often, you write a large query " +
"as a single expression, and you can’t set a breakpoint mid-expression. Is LINQ really that problematic? " +
"Queries can be fun as is debugging!";

var uniqueWords = text
.Split(' ', '.', ',', '?', '!')
.Where (i => i != "")
.Where (i => i.Count() > 1)
.Where (i => !i.Contains ("y"))
.Select(i => i.ToLower())
.GroupBy(i => i)
.OrderByDescending(i => i.Count())
.Select(i => new { Word = i.Key, Count = i.Count() })
.Take(10);

uniqueWords.Dump();


Returns:

Word Count
is 3
debugging 2
linq 2
queries 2
can 2
be 2
problematic 2
that 2
as 2
one 1

Enjoy!

Friday, November 07, 2008

LINQ : Some more on foreign keys

Continuing the series of LINQ to SQL using the Northwind DB:

Note: Can only be done if table has "Entity Set" relationship with another table i.e. green left-pointing arrow in LINQPad

Let's start with a compiled query:


var products = CompiledQuery.Compile ((TypedDataContext dc, decimal minUnitPrice) =>
from prod in Products
where prod.OrderDetails.Any (c => c.UnitPrice > minUnitPrice)
select prod
);

products (this, 20).Dump ("Unit price > $20");



OK - let's try that as a simple lambda without the compiled query:


var products1 =
from prod in Products
where prod.OrderDetails.Any (c => c.UnitPrice > 20)
select prod;

products1.Dump();


OK - let's try writing that again the "SQL" way with foreign key x = foreign key y:


var products2 =
(from prod in Products
from od in OrderDetails
where od.UnitPrice > 20 && prod.ProductID == od.ProductID
select prod).Distinct();

products2.Dump();


Hang on! There's far too many rows. That's because the relationship is duplicated so we need to add the "Distinct" keyword. You'd think that the construct would be:

select prod.Distinct();

but that doesn't work. If you look closely at the code snippet above, you'll see that the "Distinct" is applied to the whole clause (look at the brackets) rather than just the "Select" element.

Enjoy!

Thursday, November 06, 2008

LINQ : Compiled Queries

Continuing my playing with LINQPad, I found that you could put the query within a function that is precompiled (much like a stored procedure).

Using the Northwind DB,


var pp = CompiledQuery.Compile ((TypedDataContext dc, decimal minUnitPrice) =>
from p in Products
where p.UnitPrice > minUnitPrice
orderby p.UnitPrice
select p
);

pp (this, 10).Dump ("Products with unit price > 10");
pp (this, 100).Dump ("Products with unit price > 100");


This is equivalent to the "normal" form:


var pp1 =
from p in Products
where p.UnitPrice > 10
orderby p.UnitPrice
select p;

pp1.Dump();


Enjoy!

SQL Server : Installing the Northwind DB

As per previous posts, I'm playing with LINQPad and I needed a decent DB to run my queries on.

So I downloaded SQL Server 2008 Express but that doesn't come with any sample DB (apparently for security reasons).

The actual DB's are now hosted by Codeplex but they are just a pile of scripts - they don't actually include the DB.

So Mr. Google to the rescue and I eventually found the answer here.

The article is called "HOWTO: Install the Northwind and Pubs Sample Databases"
(by Brian Hart).

The article shows you how to install the DB using "SQL Server Management Studio Express" which is part of the Express download.

Enjoy!

LINQ : Some sample queries

Been paying around with LINQ to SQL using the excellent LINQPad and the Northwind DB.

Some samples I came up with:

(The "C# Expression" refers to the Type in LINQPad. Ensure the DB drop-down points to the Northwind DB. The lambda is generated during run time and can be viewed by clicking on the lambda symbol next to the Results tab. You can think of lambda as a function e.g. "a => a * 2" is a function that takes "a" as input and returns "a times 2".

The Dump statement just outputs the current object.)



// This works - C# Expression
// Get all rows in Products - sort by ProductName

from p in Products
orderby p.ProductName
select p

// This is the corresponding lambda - C# Expression

Products
.OrderBy (p => p.ProductName)

// C# Expression
// Only show rows that have an "A" in the ProductName

from p in Products
where p.ProductName.Contains("A")
select p



These are all C# Statement(s) - ";" are now required at the end of declarations.



// Return all rows of employees who live in the UK sorted by LastName

var employees =
from emp in Employees
where emp.Country == "UK"
orderby emp.LastName
select emp;

employees.Dump();



Some more complicated ones.



// Find all products that are in stock

var products =
from prod in Products
where prod.UnitsInStock > 0
orderby prod.UnitsInStock
select prod;

products.Dump();

// And the lambda

Products
.Where (prod => ((prod.UnitsInStock) > 0))
.OrderBy (prod => prod.UnitsInStock);

Products.Dump();

// Putting the lambda on one line

Products.Where (prod => ((prod.UnitsInStock) > 0)).OrderBy (prod => prod.UnitsInStock);
Products.Dump();



What about two tables with a foreign key relationship? Let's throw in two "where" statements for fun!



// Find all products that are in stock and that whose category is Beverages

var products =
from prod in Products
from cat in Categories
where prod.UnitsInStock > 0 && prod.CategoryID == cat.CategoryID && cat.CategoryName == "Beverages"
orderby prod.UnitsInStock
select prod;

products.Dump();

// Write the rows out individually line-by-line

foreach(var prod in products)
Console.WriteLine(prod.ProductID + " " + prod.ProductName);

// "products" is an object which we can simply re-sort without another DB look-up

var products2 =
from prod in products
orderby prod.ProductID
select prod;

Console.WriteLine();

foreach(var prod in products2)
Console.WriteLine(prod.ProductID + " " + prod.ProductName);



Enjoy!

Monday, November 03, 2008

Misc : Stackoverflow

Been meaning to blog about this for a while but if you have any programming-related questions, hop on over to stackoverflow.

There's a horde of really knowledgeable people waiting for your call. Now's good!

Ask, answer and get your reputation up.

Enjoy!

Friday, October 31, 2008

Misc : RSS reader

I think online RSS readers like Google Reader suck - I vastly prefer desktop ones. For many years, I used SharpReader. I liked it and it's free - what's to complain about?

But it's no longer maintained. The last update was "Changes in 0.9.7.0 - released August 2 2006" and there were more and more of my blogs that were just throwing feed errors that weren't actually errors.

So reluctantly, I look around for a new one. After a bit of research, I settled on FeedDemon. Took me a while to get used to its quirks but once I got over the initial learning curve, I'm quite happy with it.

And since I could export my subscriptions in opml and import them into FeedDemon, I didn't lose a single one.

Enjoy!

Tuesday, October 28, 2008

Excel : Putting the row number in the cell

Comparing two versions of a spreadsheet (old versus new), I wanted to have a column of the current row number on the old spreadsheet and the corresponding row number on the new one.

For the old spreadsheet, this would just be the row number e.g. column 3 has number 3.

An easy way to do this is to type two numbers in the first two rows (i.e. 1 and 2) and then highlight them as if to drag them which causes the little box to appear in the bottom right hand corner of the cell. Then drag the box down the rest of the column.

When you do this, Excel works out the relationship between the two numbers (i.e. row x = x; row x+1 = x+1) and then applies this to the rest of the column.

Done.

Enjoy!

Excel : Viewing two windows side by side

I have two spreadsheets; one an earlier version and one that reflects the current state after many alterations and updates and I wanted to document all the changes.

So I tried to put them side by side so I could manually compare them row-by-row.

There are two buttons on the task bar (one for each window) but clicking them loads each spreadsheet in exactly the same window. They effectively overlay each other.

After doing some research, I found you need to click Window / Arrange / Vertical and hey presto it's done.

Enjoy!

Thursday, October 16, 2008

C# : T4 - the Text Template Transformation Toolkit

Been looking at code generation templates. Obviously CodeSmith is right up there but then I read an article by Scott Hanselman entitled "T4 (Text Template Transformation Toolkit) Code Generation - Best Kept Visual Studio Secret" which describes a Visual Studio add-in (I'm using VS 2005) which does pretty much the same thing.

There's a link in the article to another "How-To" by Oleg Sych entitled "
How to create a simple T4 template". You need to download DSL Tools and you may also want to install the T4 Editor by Clarius Consulting.

As an example, here's my template (Template.tt):


<#@ template language="C#" #>
<#@ assembly name="System.dll" #>

//
// This code was generated by a tool. Any changes made manually will be lost
// the next time this code is regenerated.
//

using System;

public class <#= this.ClassName #>
{
public static void HelloDude()
{
Console.WriteLine("Hello, Dude");

for (<#= this.Variable #> = <#= this.Start #>; <#= this.Variable #> < <#= this.End #>;
<#= this.Variable#>++)
{
}
}
}
<#+
string ClassName = "MyClass";
string Variable = "i";
string Start = "0";
string End = "10";
#>



and here's the class (TestClass.tt) that uses it:



<#
this.ClassName = "TestClass";
this.Variable = "i";
this.Start = "0";
this.End = "20";
#>

<#@ include file="Path to template\Template.tt" #>



which results in this code:



//
// This code was generated by a tool. Any changes made manually will be lost
// the next time this code is regenerated.
//

using System;

public class TestClass
{
public static void HelloDude()
{
Console.WriteLine("Hello, Dude");

for (i = 0; i < 20;
i++)
{
}
}
}



Enjoy!

Friday, September 05, 2008

openSTA : Logging SOAP faults

The audit log contains the items you log. To log the fact that your web service returned a SOAP fault use something like:



CHARACTER*65535 BODY_RESPONSE
INTEGER FNIDOFFSET

...

LOAD RESPONSE_INFO BODY ON 1 INTO BODY_RESPONSE

SET FNIDOFFSET = ~LOCATE('SOAP:Fault', BODY_RESPONSE), CASE_BLIND
IF (FNIDOFFSET >= 0) THEN
LOG "SOAP:Fault error detected"
ENDIF



Enjoy!

Excel: "Pass" / "Fail" in a column and "Fail" in red

There is a pivot table spreadsheet on the openSTA site which allows you to work with timers. I wanted to compare that with a required result and then place "Pass" or "Fail" in the column depending on the result and then have "Fail" in red to highlight it.

You could of course use VBA but that requires a bit of programming skill.

The fist part is easy - just use the formula:

=IF(H1>K1,"Fail","Pass")

where H1 is the openSTA timer result and K1 is the required result.

The second part is done via selecting the "Pass" / "Fail" column and then right-click / Format / Conditional Formatting.

The condition is "Cell value is equal to "Fail"", then click on the "Format" button and select the red colour.

Enjoy!

openSTA : Creating random variables

I've been involved with openSTA testing lately and will blog on some things I learned.

One thing I needed to do was to run some tests on a web service that creates files. Each file had to be a different name.


INTEGER FILECOUNT, SCRIPT
INTEGER TIMEVAL
CHARACTER*10 C_FC
CHARACTER*10 C_TIMEVAL

...

ACQUIRE TEST-WIDE MUTEX "FC"
SET FILECOUNT = FILECOUNT + 1
CONVERT FILECOUNT TO C_FC
RELEASE TEST-WIDE MUTEX "FC"

ACQUIRE TEST-WIDE MUTEX "DocID"
LOAD TIME into TIMEVAL
!Ensure that the time provides 7 digits
SET TIMEVAL = TIMEVAL + 1000000
CONVERT TIMEVAL TO C_TIMEVAL
RELEASE TEST-WIDE MUTEX "DocID"


and then in the web service, set the title to:

'Doc name '+ C_FC + C_TIMEVAL + '.doc

Enjoy!

Wednesday, August 20, 2008

openSTA : Test hangs

Found an annoying problem with the Test box. When I changed a parameter, the whole application just froze - no error messages, nothing.

I eventually figured out that this was because the test file was R/O! Removing that attribute fixed the problem.

Enjoy!

Monday, July 28, 2008

C# : Reading a binary file

I needed to read a file into a buffer for subsequent encoding.

Made the mistake of usuing TextReader which of course has issues becaise the EOF could be anywhere for a bionary file. Also you don't know how big the file is and you don't want to use Peek and read one character at a time.

Came up with the following:



...

try
{
// Note: This is a binary file so can't use EOF

FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
BinaryReader br = new BinaryReader(fs);

byte[] bArray = new byte[br.BaseStream.Length];
bArray = br.ReadBytes(Convert.ToInt32(br.BaseStream.Length));
return bArray;
}

catch (Exception ex)
{
...
}



Enjoy!

Misc : Error 0X80070052 on USB Flash Drives

I came across this at the weekend.

Trying to copy some files from a DVD to a flash drive and kept getting this error.

Tried everything - reformatted etc. but no joy.

So Mr. Google to the rescue.

One link suggested that the problem was that there is a limit to the number of files you can copy to the root directory of a USB drive.

Sounded suspect but made a folder under root and then copied the files to the folder.

Suddenly everything worked!

Amazing!

Enjoy!

Monday, July 07, 2008

Eclipse : Compiling JVM 1.4 with Eclipse Europa

Eclipse Europa won't work with anything less than JVM 1.5.

But what happens if your client needs a Java 1.4 program? Do you have to download an earlier version of Eclipse?

Actually - no!

You can compile 1.4 programs inside the Europa 1.5 workspace.

Under "Windows - Preferences - Java - Installed JREs", you need to add the path to the 1.4 JVM.

Right click on the 1.4 project.

Under "Properties - Java Build Path - Libraries", ensure that the JRE System Library points to the 1.4 version.

Under "Properties - Java Compiler", click "Enable project specific settings". Set "Compiler compliance level" to 1.4.

That's it.

Enjoy!

Thursday, June 26, 2008

Java : Implementing the C# foreach

This is a common construct:



for (int i = 0; i < "object".length; i++)
{
}



It's messy and in C# you can easily do this far more elegantly with the "foreach" command.

How do you do this in Java?

Use something like e.g. for Reflection fields:



import java.lang.reflect.Field;

Field[] fields = "object".getClass().getDeclaredFields();

for (Field field : fields)
{
...
field[i].getName();
...
}



Enjoy!

Java : Iterating and printing out elements of objects

It's a common problem.

You have an object aa with variables bb, cc and dd and for logging and for debugging purposes, you want to print out the current values of all three so you have:



System.out.println ("value of bb is " + aa.bb);
System.out.println ("value of cc is " + aa.cc);
...



which gets tedious when it's a large object.

Reflection to the rescue!



import java.lang.reflect.Field;

Field[] fields = aa.getClass().getDeclaredFields();

for (int i = 0; i < fields.length; i++)
{
try
{
fields[i].setAccessible(true);
System.out.println (fields[i].getName() + " = " + fields[i].get(xicmd));
}

catch (Exception ex)
{
System.out.println ("Reflection exception = " + ex.getMessage());
}
}



Note that if the variables in the object are declared as "private", this code will throw an exception. Hence the:

setAccessible(true);

method which overrides this.

Enjoy!

Tuesday, June 24, 2008

.NET : Timing averages

Following on from my previous post about TimeSpan, I needed to do some performance testing and calculate the average time each call took.

This was for a Windows Form project.



for (int i = 0; i < iterations; i++)
{

DateTime startTime;
DateTime endTime;

int iterations;
int alteredIterations = 0;

TimeSpan total = new TimeSpan(0);
TimeSpan average = new TimeSpan(0);

alteredIterations = i + 1;

startTime = DateTime.Now;

... some method ...

endTime = DateTime.Now;

TimeSpan duration = endTime - startTime;

txtDuration.Text = duration.ToString();

total = total + duration;

average = TimeSpan.FromMilliseconds(total.TotalMilliseconds / alteredIterations);

txtAverage.Text = average.ToString();

Application.DoEvents();

}



The "Application.DoEvents();" is to allow Windows some time to update the counts on the screen.

Enjoy!

C# : Calculating timing differences

I needed to put some timings in my program. So I need to get a start time, an end time and then calculate the difference between them. TimeSpan to the rescue.



static public DateTime startTime;
static public DateTime endTime;

TimeSpan duration = new TimeSpan();

startTime = DateTime.Now;

...

endTime = DateTime.Now;

duration = endTime - startTime;

"Print" ("Duration is " + duration);



Enjoy!

Eclipse : Starting with a different VM

For various reasons, a PC I use has Java 1.4 installed and the latest Eclipse Europa requires 1.5. I am unable to update the classpath, environment etc. so although 1.5 was installed, Eclipse would not find it.

The solution was to use the command line option:

C:\eclipse\eclipse.exe -vm "C:\Program Files\Java\jdk1.5.0_15\jre\bin\java.exe"

This forces Eclipse to load with the 1.5 VM.

I placed the command above in a shortcut so it's easily accessible from the desktop.

Enjoy!

Thursday, June 05, 2008

XP : Find that missing USB drive

I have a USB drive that simply will not load on one of my PC's.

The PC simply wouldn't recognise the drive no matter what I did.

So Mr. Google to the rescue.

I found the answer here.

The problem was "More than likely the cause is that Windows renamed the drive to a letter that is already in use. This will happen if you have several card readers, thumb drives or external hard drives attached. It will also happen if you are on a network and have mapped drives".

After following the advice, all was well.

Enjoy!

Friday, May 30, 2008

Visual Studio : Snippets

This is for VS 2005.

Type a common keyword e.g.

for

Then hit the Tab key twice.

VS generates:



for (int i = 0; i < length; i++)
{

}



There's a whole lot of others listed here:

Enjoy!

.NET : Carraige return in textbox

I can never remember whether the CR symbol is "/n" or "\n".

Then I discovered that you could say:

txtResult.Text = txtResult.Text + "blah" + Environment.NewLine;

Much easier to remember.

Enjoy!

.Net : Active Directory error 0x80005000

Busy with an Active Directory project in Visual Studio 2005 when I got this error:

System.Runtime.InteropServices.COMException (0x80005000): Unknown error (0x80005000)

Bit of research on Mr. Google and I discovered that my url was something like:

string connectionString = "ldap://blah.local/CN=xxxx Users

when I should have had:

string connectionString = "LDAP://blah.local/CN=xxxx Users

i.e. "LDAP" needs to be in caps.

Enjoy!

Tuesday, May 27, 2008

C# : Simple logging framework

Java of course has Log4J and C# has Log4NET but if you are after a simple, cut down, no bloat framework have a look at:

a .NET logging framework

As the author states:


Why should I use this framework?

How popular is it?
Real companies building real applications use this framework. Most people who evaluate this framework choose to use it.

There is another popular framework with a download of several megabytes(!!), while yours is only about 100K (the DLL itself is only 40K). What gives?
With this framework you get a framework. Out of the box you'll be able to accomplish 99% of your logging tasks. Yet it isn't bloated with unnecessary complexities. For the 1% of other logging tasks you might have, you can easily extend it.


Having used it, yes it works, yes it's simple, yes it did what I wanted!

Enjoy!

Wednesday, May 21, 2008

.NET : Debugging a dll in Visual Studio 2005

So I wrote a .dll in C# (a class library project) which is called by a desktop application.

I set the Visual Studio debugger on the dll code, started the application and ...

nothing happened.

So off to Mr. Google and the answer is that you:

* Right click on the dll project in Solution Explorer.
* Click "Properties"
* Click "Debug"
* Click "Start External Program" and then browse to the application
* Save

Now when you hit F5 (Start Debugging), Visual Studio will start the application and the dll code will breakpoint.

Enjoy!

Thursday, March 20, 2008

Java : Parsing XML

So there I was needing to parse some XML in a Java program and not wanting to refresh my memory of the Java classes to do this.

Had a look around on the Internet and found JFig

This allows you to set up XML files and then read the parameters using a construct like:

JFig.getInstance().getValue(sectionName, key)

So for XML like:


#section name="Section1"*
#entry key="key1" value="val1" /*
#/section*

use:

JFig.getInstance().getValue("Section1", "key1")

Neat - and saves me doing the parsing.

But then I discovered some other neat features e.g. you have have a hierarchy of XML files and some "inherit" others.

Also, you have have substitution values e.g.

#entry key="key2" value="[Section1]{key1}" /*


which allows you to define some "constants" at the beginning of the XML file and then JFig will replicate them through for you.

... where # = "<" and * = ">"

Very neat!

Useful article here. Another solution is to use Jakarta Commons Configuration but JFig is much simpler.

Enjoy!