Jump to content

Wikipedia:WikiProject edit counters/Java Sandbox

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by AySz88 (talk | contribs) at 23:29, 22 February 2006 (Stats.java: 3.51b, prevent loading namespaces needlessly, text formatting). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.


GetContribs class

/**
 * @author AySz88, Titoxd
 * @program Remote source reader for Flcelloguy's Tool
 * @version 4.11; released January 29, 2006
 * @see [[User:Flcelloguy/Tool]]
 * @docRoot http://en.wikipedia.org/wiki/User:Titoxd/Flcelloguy's_Tool
 * @copyright Permission is granted to distribute freely, provided attribution is granted.
 */

import java.io.BufferedInputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Calendar;

public final class GetContribs
{
	private static long killbitCacheTime = 0;
		// 0 = the epoch, a few hours before 1970, so killbit expired by a safe margin :p
	private static boolean cachedKillValue = true;
		// initialize to true to avoid abuse by rolling back time to before 1970

	private static final String KILLURL = "http://en.wikipedia.org/w/index.php?title=Wikipedia:WikiProject_edit_counters/Flcelloguy%27s_Tool/Configs&action=raw";
	private static final long CACHETIME = 1000*60*10; //10 minutes (arbitrary) in milliseconds
		// CACHETIME will bug out if > ~35 yrs (see comment next to killbitCacheTime)
	private static final int PARCELSIZE = 4096; //arbitrary number

	public static void main(String[] args) // testing purposes only
	{
		try
		{
			//String content =
				getSource(new URL(
				"http://en.wikipedia.org/"));
//			getSource(new URL(
//				"http://en.wikipedia.org/w/index.php?title=Special:Contributions&target=AySz88&offset=0&limit=5000"));
//			getSource(new URL(
//				"http://en.wikipedia.org/wiki/Special:Contributions/Essjay"));
//			getSource(new URL(
//				"http://en.wikipedia.org/wiki/Special:Contributions/Titoxd"));
			//System.out.println(content);
		}
		catch (MalformedURLException e)
		{
			e.printStackTrace();
		}
	}
	
	public static String getSource(URL location)
	{
		if (killBit() == false)
				return getSourceDirect(location);
		else
		{
			System.out.println("Killbit active; Scraper is disabled. Try again later.");
			return "Killbit active; scraper is disabled. Try again later.";
		}
	}
	
	public static String getSource(String location) throws MalformedURLException
	{
		return getSource(new URL(location));
	}
	
	private static String getSourceDirect(URL location) //bypasses Killbit
	{
		try
		{
			System.out.println(" Loading -- ");
			
			StringBuilder content = new StringBuilder();
				// faster: String concatination involves StringBuffers anyway
				// 		...and StringBuilders are even faster than StringBuffers
			int bytesTotal = 0;
			
			BufferedInputStream buffer = new BufferedInputStream(location.openStream());
			
			int lengthRead=0;
			byte[] nextParcel;
			
			do
			{
				nextParcel = new byte[PARCELSIZE];
				/* 
				 * Don't try to use buffer.available() instead of PARCELSIZE:
				 * then there's no way to tell when end of stream is reached
				 * without ignoring everything anyway and reading a byte.
				 * 
				 * Also, if nextParcel is full (at PARCELSIZE),
				 * content.append(nextParcel) will add an address at the end
				 * into the content (looks something like "[B&1dfc547")
				 * so avoid using content.append(byte[]) directly.
				 */
				lengthRead = buffer.read(nextParcel);
				bytesTotal += lengthRead;
				
					//if (lengthRead == PARCELSIZE)
						//content.append(nextParcel);  // would have been faster
					//else
				if (lengthRead > 0)
					content.append(new String(nextParcel).substring(0, lengthRead));
					// TODO: any better way to append a subset of a byte[]?

				System.out.print("Bytes loaded: " + bytesTotal + "\n");

			}
			while (lengthRead != -1);
			bytesTotal++; // replace subtracted byte due to lengthRead = -1
			
			System.out.print(" -- DONE! Bytes read: " + bytesTotal + "; String length: " + content.length() + "\n");
			
			return content.toString();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		return null;
	}
	
	public static boolean killBit()
	{
		Calendar now = Calendar.getInstance();
		
		if (killbitCacheTime + CACHETIME > now.getTimeInMillis())
			return cachedKillValue;
		
		String configs = null;
		
		try
		{
			configs = getSourceDirect(new URL(KILLURL));
		}
		catch (Exception e)
		{
			e.printStackTrace();
			cacheKillbit(true, now);
			return true;
			// if killbit cannot be read for any reason, killbit = true
		}
		
		String[] configArray = configs.split("\n");
		for (int i = 0; i < configArray.length; i++)
		{
			String setting = configArray[i];
			System.out.println(configArray[i]);
			if (setting.compareTo("killBit = true;") == 0)
			{
				cacheKillbit(true, now);
				return true;
			}
		}
		cacheKillbit(false, now);
		return false;
	}
	
	public static void clearKillCache()
	{
		killbitCacheTime = 0;
	}
	
	public static void restartSession()
	{
		killbitCacheTime = 0;
	}
	
	private static void cacheKillbit(boolean bool, Calendar time)
	{
		cachedKillValue = bool;
		killbitCacheTime = time.getTimeInMillis();
	}
}

Output

 Loading -- 
Bytes loaded: 33
Bytes loaded: 32
 -- DONE! Bytes read: 33; String length: 33
$globalConfigs:

killBit = false;
 Loading -- 
Bytes loaded: 2366
Bytes loaded: 3794
Bytes loaded: 5222
Bytes loaded: 6650
Bytes loaded: 9506
Bytes loaded: 10934
Bytes loaded: 11898
Bytes loaded: 14754
Bytes loaded: 15994
Bytes loaded: 17422
Bytes loaded: 18850
Bytes loaded: 22946
Bytes loaded: 24186
Bytes loaded: 25614
Bytes loaded: 27042
Bytes loaded: 28470
Bytes loaded: 29898
Bytes loaded: 31326
Bytes loaded: 32378
Bytes loaded: 33806
Bytes loaded: 37902
Bytes loaded: 39330
Bytes loaded: 42269
Bytes loaded: 42268
 -- DONE! Bytes read: 42269; String length: 42269

Namespace class and factory for maps and arrays

/**
 * @author AySz88
 * @program Namespace Loader for Flcelloguy's Tool
 * @version 1.91b; released February 16, 2006
 * @see [[User:Flcelloguy/Tool]]
 * @docRoot http://en.wikipedia.org/wiki/User:Titoxd/Flcelloguy's_Tool
 * @copyright Permission is granted to distribute freely, provided attribution is granted.
 */

import java.net.URL;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.TreeMap;
import java.util.TreeSet;

//For now, the name of the "main" namespace is interpreted as "Main"
	// This may be changed as necessary
//The index of the main namespace must be 0

public class Namespace
{
	// data fields and functions
	private String englishName, localName;
	private int indexNo; // as shown in the Special:Export// page
	private int[][][] countMatrix;
	private TreeSet<Contrib> contribSet;

	public Namespace(String eng, int ind, String loc)
	{
		englishName = eng;
		indexNo = ind;
		localName = loc;
		
		countMatrix = new int[2][2][2]; // autoinitializes to all 0
		
		contribSet = new TreeSet<Contrib>(); //sorts automagically
	}
	
	public void addContrib(Contrib con)
	{
		int minor = 0, auto = 1, manu = 1;
		
		if (con.minorEdit) minor = 1;
		if (con.autoSummary == null) auto = 0;
		if (con.editSummary == null) manu = 0;
		
		countMatrix[minor][auto][manu]++;

		contribSet.add(con);
		
		/*if (contribSet.size() != (getMinorCount() + getMajorCount()))
		{
			int a = 2;
			System.out.println("test" + a);
		}*/
	}
	
	public int getCount() {return contribSet.size();}
	public int getMinorCount()
	{
		return // minor = 1
			countMatrix[1][0][0] +
			countMatrix[1][0][1] +
			countMatrix[1][1][0] +
			countMatrix[1][1][1];
	}
	public int getMajorCount()
	{
		return // minor = 0
			countMatrix[0][0][0] +
			countMatrix[0][0][1] +
			countMatrix[0][1][0] +
			countMatrix[0][1][1];
	}

	public int getSummaryCount()
	{
		return // auto == 1 || manual == 1
			countMatrix[0][0][1] +
			countMatrix[0][1][0] +
			countMatrix[0][1][1] +
			countMatrix[1][0][1] +
			countMatrix[1][1][0] +
			countMatrix[1][1][1];
	}
	public int getManualSummaryCount()
	{
		return // manual == 1
			countMatrix[0][0][1] +
			countMatrix[0][1][1] +
			countMatrix[1][0][1] +
			countMatrix[1][1][1];		
	}
	public int getAutoSummaryCount()
	{
		return // auto == 1
			countMatrix[0][1][0] +
			countMatrix[0][1][1] +
			countMatrix[1][1][0] +
			countMatrix[1][1][1];
	}

	public double getMinorProportion() {return ((double) getMinorCount() / (double) getCount()); }
	public double getSummaryProportion() {return ((double) getSummaryCount() / (double) getCount()); }
	public double getManualSummaryProportion() {return ((double) getManualSummaryCount() / (double) getCount()); }

//	public int newArticleCount() {return newArticleArray.size();}
//	public TreeSet newArticleSet() {return newArticleArray;}
//	public String[] newArticleArray() {return newArticleArray.toArray(new String[1]);}
	
	public String getEnglishName() {return englishName;}
	public String getLocalName() {return localName;}
	public int getMediawikiIndex() {return indexNo;}
	
	static void addContrib(HashMap<String, Namespace> map, Contrib con)
	{
		Namespace ns;
		if (con.namespace.equals("")) ns = map.get(MAINSPACENAME);
		else ns = map.get(con.namespace);
		
		ns.addContrib(con);
	}

	// static fields and functions
	public static final String head = "http://",
						foot = ".wikipedia.org/w/index.php?title=Special:Export//";
	public static final int ENDTAGLENGTH = "</namespace>".length(); // this'll evaluate at compile-time right?
	public static final String MAINSPACENAME = "Main";
	
	public static void main(String[] args)
	{
		//getNamespaces("ko");
		getFullMap("ko");
	}
	
	// TODO: allow any two languages to convert to one another (currently one language must be English)
	public static HashMap<String, Namespace> getFullMap(String local)
	{
		TreeMap<Integer, Namespace> localMap = getNamespaceTreeMap(local);
		Namespace[] engNamespaces = getNamespaces("en");
		
		HashMap<String, Namespace> finishedMap = new HashMap<String, Namespace>();
		
		for (int i = 0; i < engNamespaces.length; i++)
		{
			Namespace ns = localMap.remove(engNamespaces[i].indexNo);
			if (ns == null) System.out.println("The " + local + " wiki does not have the equivalent of English Wikipedia namespace: " + engNamespaces[i].localName);
			else
				finishedMap.put(ns.localName, new Namespace(engNamespaces[i].localName, ns.indexNo, ns.localName));
		}
		
		if (localMap.size() != 0)
		{
			System.out.println("The " + local + " wiki has namespaces not seen in the English Wikipedia");
			Iterator iter = localMap.keySet().iterator();
			while (iter.hasNext())
			{
				Namespace ns = localMap.remove(iter.next());
				finishedMap.put(ns.localName, new Namespace("Translation Unknown - " + ns.indexNo, ns.indexNo, ns.localName));
			}
		}
		return finishedMap;
	}
	
	private static Namespace[] getNamespaces(String local)
	//Does NOT populate the english names
	{
		try
		{
			String[] lineArray = GetContribs.getSource(new URL(head + local + foot)).split("\n");
			
			int i = arrayStepPast(lineArray, "<namespaces>");
			
			ArrayList<Namespace> nsArray = new ArrayList<Namespace>();
			while (!lineArray[i].trim().equals("</namespaces>"))
			{
				String[] parts = lineArray[i].trim().split("\"");
				int number = Integer.parseInt(parts[1]); // 2nd part
				String name = "";
				if (number == 0)
					name = MAINSPACENAME;
				else
					name = parts[2].substring(1, parts[2].length()-ENDTAGLENGTH);
				nsArray.add(new Namespace("", number, name));
				System.out.println(number + " " + name);
				i++;
			}

			return (Namespace[]) nsArray.toArray(new Namespace[1]); // the Namespace[1] is to convey the type of array to return
		}
		catch (Exception e)
		{
			e.printStackTrace();
			return null;
		}
	}
	
	/* Currently unused
	
	private static HashMap<String, Namespace> getNamespaceHashMap(String local)
	//HashMap uses local names of namespaces as the key
	//Does NOT populate the english names
	{
		try
		{
			String[] lineArray = GetContribs.getSource(new URL(head + local + foot)).split("\n");
			
			int i = arrayStepPast(lineArray, "<namespaces>");
			
			HashMap<String, Namespace> nsMap = new HashMap<String, Namespace>();
			while (!lineArray[i].trim().equals("</namespaces>"))
			{
				String[] parts = lineArray[i].trim().split("\"");
				int number = Integer.parseInt(parts[1]); // 2nd part
				String name = "";
				if (number == 0)
					name = MAINSPACENAME;
				else
					name = parts[2].substring(1, parts[2].length()-ENDTAGLENGTH);
				nsMap.put(name, new Namespace("", number, name));
				System.out.println(number + " " + name);
				i++;
			}

			return nsMap;
		}
		catch (Exception e)
		{
			e.printStackTrace();
			return null;
		}
	}*/
	
	private static TreeMap<Integer, Namespace> getNamespaceTreeMap(String local)
	//TreeMap uses index numbers as key
	//Does NOT populate the english names
	//Just in case we can eventually access data using the index numbers
	//Also used to match local names to english names
	{
		try
		{
			String[] lineArray = GetContribs.getSource(new URL(head + local + foot)).split("\n");
			
			int i = arrayStepPast(lineArray, "<namespaces>");
			
			TreeMap<Integer, Namespace> nsMap = new TreeMap<Integer, Namespace>();
			while (!lineArray[i].trim().equals("</namespaces>"))
			{
				String[] parts = lineArray[i].trim().split("\"");
				int number = Integer.parseInt(parts[1]); // 2nd part
				String name = "";
				if (number == 0)
					name = MAINSPACENAME;
				else
					name = parts[2].substring(1, parts[2].length()-ENDTAGLENGTH);
				nsMap.put(number, new Namespace("", number, name));
				System.out.println(number + " " + name);
				i++;
			}

			return nsMap;
		}
		catch (Exception e)
		{
			e.printStackTrace();
			return null;
		}
	}
	
	public static int sumAllCounts(AbstractMap<String, Namespace> map)
	{
		int sum = 0;
		Iterator iter = map.keySet().iterator();
		
		while (iter.hasNext())
			sum += map.get(iter.next()).getCount();

		return sum;
	}
	
	public static int sumAllMinorCounts(AbstractMap<String, Namespace> map)
	{
		int sum = 0;
		Iterator iter = map.keySet().iterator();
		
		while (iter.hasNext())
			sum += map.get(iter.next()).getMinorCount();

		return sum;
	}
	
	public static int sumAllMajorCounts(AbstractMap<String, Namespace> map)
	{
		int sum = 0;
		Iterator iter = map.keySet().iterator();
		
		while (iter.hasNext())
			sum += map.get(iter.next()).getMajorCount();

		return sum;
	}
	
	public static int sumAllSummaryCounts(AbstractMap<String, Namespace> map)
	{
		int sum = 0;
		Iterator iter = map.keySet().iterator();
		
		while (iter.hasNext())
			sum += map.get(iter.next()).getSummaryCount();

		return sum;
	}

	public static int sumAllManualSummaryCounts(AbstractMap<String, Namespace> map)
	{
		int sum = 0;
		Iterator iter = map.keySet().iterator();
		
		while (iter.hasNext())
			sum += map.get(iter.next()).getManualSummaryCount();

		return sum;
	}

	public static int sumAllAutoSummaryCounts(AbstractMap<String, Namespace> map)
	{
		int sum = 0;
		Iterator iter = map.keySet().iterator();
		
		while (iter.hasNext())
			sum += map.get(iter.next()).getAutoSummaryCount();

		return sum;
	}
	
	private static int arrayStepPast(String[] array, String o)
	// was originally going to allow to use with any Comparable object
	// currently only works with Strings due to use of trim()
	{
		int i = 0; // keep the value of i after for loop
		for (; i < array.length && !array[i].trim().equals(o); i++);
		return ++i; // step *past* first, then return
	}
}

Output

 Loading -- 
Bytes loaded: 33
Bytes loaded: 32
 -- DONE! Bytes read: 33; String length: 33
$globalConfigs:

killBit = false;
 Loading -- 
Bytes loaded: 1368
Bytes loaded: 1367
 -- DONE! Bytes read: 1368; String length: 1368
-2 Media
-1 특수기능
0 Main
1 í† ë¡ 
2 사용�
3 사용ìž?í† ë¡ 
4 위키백과
5 ìœ„í‚¤ë°±ê³¼í† ë¡ 
6 그림
7 ê·¸ë¦¼í† ë¡ 
12 �움�
13 ë?„움ë§?í† ë¡ 
14 분류
15 ë¶„ë¥˜í† ë¡ 
8 MediaWiki
9 MediaWiki talk
10 Template
11 Template talk
 Loading -- 
Bytes loaded: 1883
Bytes loaded: 1882
 -- DONE! Bytes read: 1883; String length: 1883
-2 Media
-1 Special
0 Main
1 Talk
2 User
3 User talk
4 Wikipedia
5 Wikipedia talk
6 Image
7 Image talk
8 MediaWiki
9 MediaWiki talk
10 Template
11 Template talk
12 Help
13 Help talk
14 Category
15 Category talk
100 Portal
101 Portal talk
The ko wiki does not have the equivalent of English Wikipedia namespace: Portal
The ko wiki does not have the equivalent of English Wikipedia namespace: Portal talk

Stats.java

/**
 * @author Flcelloguy et al.
 * @program Flcelloguy's Tool (Stats.java)
 * @version 3.51b; released February 22, 2006
 * @see [[User:Flcelloguy/Tool]]
 * @docRoot code from http://en.wikipedia.org/wiki/User:Flcelloguy/Tool
 * @copyright Permission is granted to distribute freely, provided attribution is granted.
 * Capabilities: Count edits, break down by namespace, count minor edits and calculate percentage
 * Please leave this block in. 
 * Note: This new version does not require cut-and-pasting. 
 * Just go to 
 * 	http://en.wikipedia.org/w/index.php?
 * 		title=Special:Contributions&target={{USERNAME}}&offset=0&limit=5000
 * 		where {{USERNAME}} is the name of the user you want to run a check on. 
 */

import javax.swing.JOptionPane;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.TreeMap;

//import java.util.FileReader;

public class Stats
{
	private static final String CURRENTSTATUS =
		"Current status: \n "
		+ "Editcount \n Breakdown by namespace \n Minor edits usage \n Edit summary usage \n"
		+ "Coming soon: \n "
		+ "User friendly version \n First edit date";
	
	private static HashMap<String, Namespace> cleanMap;
	
	private HashMap<String, Namespace> nsMap;
	protected int total, minor, summary, manualSummary;
	protected StringBuilder console;
	
	public Stats()
	{
	}
	
	public void reset()
	{
		if (cleanMap == null)
			cleanMap = Namespace.getFullMap("en");
		
		console = new StringBuilder();
		nsMap = cleanMap;
	}
	
	public static void main(String args[]) throws IOException
	{
		/**
		 * the GUI is too complex to screw up clean, crisp code like this, so
		 * I'm moving it to a separate class. --Titoxd
		 */
		MainGUI.main(null);
	}
	
	public void mainDownloadSingle(String inFile$) throws IOException
	{
		reset();
		
		if (inFile$ == null)
			inFile$ = JOptionPane.showInputDialog("Input file:", inFile$);
		
		JOptionPane
		.showMessageDialog(
				null,CURRENTSTATUS,
				"Information", JOptionPane.INFORMATION_MESSAGE);

		
//		JOptionPane.showMessageDialog(null, "Number of edits: "
		countFromConnection(GetContribs.getSource(
				"http://en.wikipedia.org/wiki/Special:Contributions/"
				+ inFile$.replace(" ", "_")
				+ "?limit=5000"));
//				JOptionPane.INFORMATION_MESSAGE);
	}
	
	public void mainSingle(String inFile$) throws IOException
	{
		reset();
		if (inFile$ == null)
			inFile$ = JOptionPane.showInputDialog("Input file:", inFile$);
		
		JOptionPane
		.showMessageDialog(
				null,CURRENTSTATUS,
				"Information", JOptionPane.INFORMATION_MESSAGE);
		
		editcount(inFile$);
		
//		JOptionPane.showMessageDialog(null, "Number of edits: "
//				+ editcount(inFile$), "Results",
//				JOptionPane.INFORMATION_MESSAGE);
	}
	
	public void mainMulti(String[] args) throws IOException
	{
		reset();
		String outFile$ = null;
		String inFile$ = null;
		
		outFile$ = JOptionPane.showInputDialog(null,
				"Enter the filename of the output file:", outFile$,
				JOptionPane.QUESTION_MESSAGE);
		
		FileWriter writer = new FileWriter(outFile$);
		BufferedWriter out = new BufferedWriter(writer);
		
		out.write("<ul>", 0, "<ul>".length());
		out.newLine();
		
		inFile$ = JOptionPane.showInputDialog(null,
				"Enter the filename of the next contributions file:", inFile$,
				JOptionPane.QUESTION_MESSAGE);
		
		while (inFile$ != null)
		{
			FileReader reader = new FileReader(inFile$);
			BufferedReader in = new BufferedReader(reader);
			
			String inString = "";
			boolean endContribs = false; // marks whether all the
			// contributions have been parsed
			
			inString = in.readLine(); // read from file and discard
			
			do
			{
				if ((inString.trim().compareTo("<ul>") == 0)
						&& (endContribs == false)) // until the <ul> tag is
					// reached,
				{
					do
					{
						inString = in.readLine(); // then start reading and
						// recording
						if ((inString.trim().compareTo("</ul>") != 0))
						{
							out.write(inString.trim(), 0, inString.length());
							out.newLine();
							
							// System.out.println(inString.trim());
						}
						else
						{
							endContribs = true;
						}
					}
					while (endContribs != true);
				}
				inString = in.readLine(); // read from file and discard
			}
			while (inString != null);
			in.close();
			inFile$ = JOptionPane.showInputDialog(null,
					"Enter the filename of the next contributions file:",
					inFile$, JOptionPane.QUESTION_MESSAGE);
		}
		
		out.write("</ul>", 0, "</ul>".length());
		out.newLine();
		out.close();
		mainSingle(outFile$);
	}
	
	public int editcount(String inFile$) throws IOException
	{
		// TODO: inFile --> URL --> edit count using other code
		// need getContribs to return fileReader/bufferedReader-like things
		
		System.out.println("Computing...");
		
		FileReader reader = new FileReader(inFile$);
		BufferedReader in = new BufferedReader(reader);
		
		// FileWriter writer = new FileWriter(outFile$); //for debugging
		// BufferedWriter out = new BufferedWriter(writer); //for debugging
		
		String inString = "";
		Contrib outContrib;
		boolean endContribs = false; // marks whether all the contributions
		// have been parsed
		
		inString = in.readLine(); // read from file and discard
		
		do
		{
			if ((inString.trim().compareTo("<ul>") == 0)
					&& (endContribs == false)) // until the <ul> tag is
				// reached,
			{
				do
				{
					inString = in.readLine(); // then start reading and
					// recording
					if ((inString.trim().compareTo("</ul>") != 0))
					{
						// System.out.println(inString.trim());
						outContrib = PurgeContribs.Parse(inString.trim());
						// System.out.println(outString.trim());
						
						addContrib(outContrib);
						// out.newLine();
					}
					else
					{
						endContribs = true;
					}
				}
				while (endContribs != true);
			}
			inString = in.readLine(); // read from file and discard
		}
		while (inString != null);
		in.close();
		// out.close();
		
		// Prints out statistics
		int total = buildConsole(nsMap);
		//MainGUI.createTextFrame(console.toString());
		System.out.print(console);
		return total;
	}
	
	public int countFromConnection(String source) throws IOException
	{
		String linkString = null;    
		System.out.println("Computing...");
		
		String[] array = source.split("\n");
		Contrib outContrib;
		
		int i = 1;
		
		for (; i < array.length && ! array[i].trim().equals("<ul>"); i++) 
		{
			if (array[i].startsWith("<p>")) 
				linkString = PurgeContribs.getNextDiffs(array[i]);
		}
		
		if (linkString != null) linkString = "http://en.wikipedia.org" + linkString;
			//complete URL here
		
		i++; // increment past
		
		while (i < array.length && !array[i].trim().equals("</ul>"))
		{
			// then start reading and recording
			outContrib = PurgeContribs.Parse(array[i].trim());
			addContrib(outContrib);
			
			i++;
		}
		
		// Prints out statistics
		int total = buildConsole(nsMap);
		//MainGUI.createTextFrame(console.toString());
		System.out.print(console);
		return total;
	}
	
	private void addContrib(Contrib con)
	{
		con.checkCorrectNamespace(nsMap);
		nsMap.get(con.namespace).addContrib(con);
	}
	
	public int buildConsole(HashMap<String, Namespace> nsMap)
	{
		total = Namespace.sumAllCounts(nsMap);
		minor = Namespace.sumAllMinorCounts(nsMap);
		summary = Namespace.sumAllSummaryCounts(nsMap);
		manualSummary = Namespace.sumAllManualSummaryCounts(nsMap);
		
		console.append("- Total: " + total + " -\n");
		Namespace ns; // reusable memory location
		
		Iterator<String> iter1 = nsMap.keySet().iterator();
		
		// Convert arbitrary Name->Namespace HashMapping to
		//   sorted index->Namespace TreeMapping
		TreeMap<Integer, Namespace> indexNamespaceMap = new TreeMap <Integer, Namespace>();
		while (iter1.hasNext())
		{
			ns = nsMap.get(iter1.next());
			
			indexNamespaceMap.put(ns.getMediawikiIndex(), ns);
		}
		
		Iterator<Integer> iter2 = indexNamespaceMap.keySet().iterator();
		
		int next = iter2.next();
		
		for (; next < 0 && iter2.hasNext(); next = iter2.next());
		// next has the value 0 or larger than 0 here, or hasNext() false
		
		while (iter2.hasNext())
		{
			ns = indexNamespaceMap.get(next);
			
			int count = ns.getCount();
			if (count > 0) 
				console.append(ns.getEnglishName() + ": " + count + "\n");
			next = iter2.next();
		}
		
		
		console.append("-------------------" + "\n" + "Overall statistics:"
				+ "\n" + "Total edits: " + total + "\n");
		console.append("Minor edits: " + minor + "\n");
		console.append("Edits with edit summary: " + summary + "\n");
		console.append("Edits with manual edit summary: " + manualSummary + "\n");
		console.append("Percent minor edits: "
				+ ((minor * 100) / (total)) + "%  *\n");
		console.append("Percent edit summary use: "
				+ ((summary * 100) / (total)) + "%  *\n");
		console.append("Percent manual edit summary use: "
				+ ((manualSummary * 100) / (total)) + "%  *\n");
		
		console.append("-------------------\n");
		console.append("* - percentages are rounded down.\n");
		console.append("-------------------\n");
		
		return total;
	}
}

Sample on-screen output

Jimbo Wales at 23:29, 22 February 2006 (UTC)

- Total: 1966 -
Main: 429
Talk: 230
User: 127
User talk: 836
Wikipedia: 211
Wikipedia talk: 68
Image: 18
Image talk: 2
MediaWiki: 12
MediaWiki talk: 7
Template: 3
Template talk: 1
Category: 4
Category talk: 18
-------------------
Overall statistics:
Total edits: 1966
Minor edits: 195
Edits with edit summary: 1222
Edits with manual edit summary: 600
Percent minor edits: 9%  *
Percent edit summary use: 62%  *
Percent manual edit summary use: 30%  *
-------------------
* - percentages are rounded down.
-------------------

QueryFrame

/**
 * @author Titoxd
 * @program Query Graphical User Interface for Flcelloguy's Tool
 * @version 3.56b; released February 22, 2006
 * @see [[User:Flcelloguy/Tool]]
 * @docRoot http://en.wikipedia.org/wiki/User:Titoxd/Flcelloguy's_Tool
 * @copyright Permission is granted to distribute freely, provided attribution is granted.
 */

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComboBox;
//import javax.swing.JFrame;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JFileChooser;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SpringLayout;

import java.awt.event.*;
import java.awt.*;
import java.io.IOException;

/* Used by MainGUI.java. */
public class QueryFrame extends JInternalFrame implements ActionListener
{
	static int openFrameCount = 0;
	static final int xOffset = 10, yOffset = 10;
	
	static final Dimension MINIMUM_TEXTPANEL_SIZE = new Dimension(250, 250);
	
	private JLabel
	topLabel        = new JLabel("Online help"),
	label           = new JLabel("Flcelloguy's Tool: Statistics for editcounters.");
	
	private JTextField helpURL = new JTextField("http://en.wikipedia.org/wiki/User:Flcelloguy/Tool/Help"),
	nameInput = new JTextField();
	
	private static final String NA_TEXT = "No data available.";
	
	private String[] phases = {
			"Single download (\u2264 5,000 edits)",	//"Single download (≤5,000 edits)"
			"Single local (\u2264 5,000 edits)",		//"Single local (≤5,000 edits)"
			"Multiple local (\u2265 5,000 edits)"			//"Multiple local (≥5,000 edits)"
	};
	
	private JComboBox methodBox = new JComboBox(phases);
	private JButton button = new JButton("Proceed");
	
	private CardLayout locationOption = new CardLayout();
	private JPanel row3 = new JPanel(locationOption);
	private JLabel fileChooseDesc = new JLabel();
	private JTextField filePathField = new JTextField();
	private JFileChooser fc = new JFileChooser();
	private JButton browseButton = new JButton("Browse...");
	
	private JTabbedPane outputTabs = new JTabbedPane();
	private JPanel textOutputPanel = new JPanel();
	private JTextArea textOutput = new JTextArea(20, 20);
		private JScrollPane areaScrollPane = new JScrollPane(textOutput);
	private JPanel graphOutputPanel = new JPanel();
	private JPanel treeOutputPanel = new JPanel();
	
	private Stats statParcel = new Stats();
	
	public QueryFrame()
	{
		super("New Query " + (++openFrameCount), true, // resizable
				true, // closable
				true, // maximizable
				true);// iconifiable
		
		// ...Create the GUI and put it in the window...
		
		JPanel panel = (JPanel) createComponents();
		panel.setBorder(BorderFactory.createEmptyBorder(20, // top
				30, // left
				10, // bottom
				30) // right
		);
		getContentPane().add(panel);
		
		// ...Then set the window size or call pack...
		pack();
		
		// Set the window's location.
		setLocation(xOffset * openFrameCount, yOffset * openFrameCount);
	}
	
	public Component createComponents()
	{
		GridBagConstraints optionC = new GridBagConstraints();
		GridBagConstraints c = new GridBagConstraints();
		
		//label.setLabelFor(button);
		button.setMnemonic('i');
		button.addActionListener(this);
		
		methodBox.setSelectedIndex(0);
		methodBox.addActionListener(this);
		
		JPanel mainPanel = new JPanel(new BorderLayout());
		
		JPanel optionPanel = new JPanel();
		optionPanel.setLayout(new GridBagLayout());
		
		optionC.gridx = 0; optionC.gridy = 0;
		optionC.weightx = 1; optionC.weighty = .5;
		optionC.anchor = GridBagConstraints.WEST;
		optionC.fill = GridBagConstraints.HORIZONTAL;
		
		
		{
			JPanel row1 = new JPanel(new GridBagLayout());
			GridBagConstraints tempC = new GridBagConstraints();
			
			tempC.gridx = 0; tempC.gridy = 0;
			tempC.weightx = 0;
			row1.add(topLabel, tempC);
			
			tempC.gridx++;
			tempC.weightx = 1;
			tempC.fill = GridBagConstraints.BOTH;
			row1.add(helpURL, tempC);
			helpURL.setEditable(false);
			
			optionPanel.add(row1, optionC);
		}
		
		{
			/*
			 JPanel row2 = new JPanel(new GridBagLayout());
			 GridBagConstraints tempC = new GridBagConstraints();
			 tempC.gridx = 0; tempC.gridy = 0;
			 tempC.weightx = 0;
			 row2.add(new JLabel("Statistics for:"), tempC);
			 tempC.gridx++;
			 tempC.weightx = 1;
			 tempC.fill = GridBagConstraints.BOTH;
			 row2.add(nameInput, tempC);
			 tempC.fill = GridBagConstraints.NONE;
			 tempC.weightx = 0;
			 tempC.gridx++;
			 JLabel via = new JLabel("via");
			 via.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 2));
			 row2.add(via, tempC);
			 tempC.gridx++;
			 row2.add(methodBox, tempC);
			 tempC.gridx++;
			 row2.add(button, tempC);
			 */
			
			/*
			 JPanel row2 = new JPanel();
			 row2.setLayout(new BoxLayout(row2,BoxLayout.X_AXIS));
			 row2.add(new JLabel("Statistics for:"));
			 row2.add(nameInput);
			 JLabel via = new JLabel("via");
			 via.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 2));
			 row2.add(via);
			 row2.add(methodBox);
			 row2.add(button);
			 */
			
			
			SpringLayout layout = new SpringLayout();
			JPanel row2 = new JPanel(layout);
			JLabel lineStart = new JLabel("Statistics for:");
			JLabel via = new JLabel("via");
			
			nameInput.setMinimumSize(new Dimension(
					methodBox.getPreferredSize().width + via.getMaximumSize().width + 4,
					nameInput.getMinimumSize().height));
			nameInput.setPreferredSize(nameInput.getMinimumSize());
			
			row2.add(lineStart);
			row2.add(nameInput);
			row2.add(via);
			row2.add(methodBox);
			row2.add(button);
			row2.add(label);
			
			layout.putConstraint(
					SpringLayout.WEST, lineStart,
					0,
					SpringLayout.WEST, row2);
			layout.putConstraint(
					SpringLayout.WEST, nameInput,
					0,
					SpringLayout.EAST, lineStart); 
			layout.putConstraint(
					SpringLayout.WEST, via,
					0,
					SpringLayout.EAST, lineStart); 
			layout.putConstraint(
					SpringLayout.WEST, methodBox,
					2,
					SpringLayout.EAST, via);
			layout.putConstraint(
					SpringLayout.WEST, button,
					5,
					SpringLayout.EAST, nameInput);
			layout.putConstraint(
					SpringLayout.EAST, row2,
					2,
					SpringLayout.EAST, button);
			layout.putConstraint(
					SpringLayout.EAST, label,
					0,
					SpringLayout.EAST, row2);
			
			layout.putConstraint(
					SpringLayout.NORTH, lineStart,
					5,
					SpringLayout.NORTH, row2);
			layout.putConstraint(
					SpringLayout.NORTH, nameInput,
					2,
					SpringLayout.NORTH, row2);
			layout.putConstraint(
					SpringLayout.NORTH, via,
					7,
					SpringLayout.SOUTH, nameInput);
			layout.putConstraint(
					SpringLayout.NORTH, methodBox,
					2,
					SpringLayout.SOUTH, nameInput);
			layout.putConstraint(
					SpringLayout.NORTH, button,
					10,
					SpringLayout.NORTH, row2);
			layout.putConstraint(
					SpringLayout.NORTH, label,
					0,
					SpringLayout.SOUTH, methodBox);
			layout.putConstraint(
					SpringLayout.SOUTH, row2,
					0,
					SpringLayout.SOUTH, label);
			
			optionC.gridy++;
			//optionC.gridwidth = GridBagConstraints.REMAINDER;
			
			optionC.fill = GridBagConstraints.HORIZONTAL;
			optionPanel.add(row2, optionC);
			optionC.fill = GridBagConstraints.NONE;
		}
		
		/*
		{
			JPanel row2a = new JPanel();
			row2a.setLayout(new BoxLayout(row2a, BoxLayout.X_AXIS));
			row2a.add(Box.createHorizontalGlue(), c);
			row2a.add(label, c);
			
			optionC.gridx = 0; optionC.gridy++;
			optionC.fill = GridBagConstraints.HORIZONTAL;
			optionPanel.add(row2a, optionC);
			optionC.fill = GridBagConstraints.NONE;
		}
		*/
		
		// row3 was already declared
		JPanel filePanel = new JPanel(new GridBagLayout());
		c.gridx = 0; c.gridy = 0;
		filePanel.add(fileChooseDesc);
		c.gridx++;
		
		c.fill = GridBagConstraints.HORIZONTAL;
		filePanel.add(filePathField);
		c.fill = GridBagConstraints.NONE;
		
		c.gridx++;
		browseButton.addActionListener(this);
		filePanel.add(browseButton);
		
		row3.add(new JLabel("[en.wikipedia.com, es, Wikimedia sites in en, etc.] [New Set]"), phases[0]);
		row3.add(filePanel, phases[1]);
		row3.add(filePanel, phases[2]);
		locationOption.show(row3, phases[0]);
		optionC.gridy++;
		optionC.fill = GridBagConstraints.HORIZONTAL;
		optionPanel.add(row3, optionC);
		optionC.fill = GridBagConstraints.NONE;
		
		{
			JPanel row4 = new JPanel(new GridBagLayout());
			row4.setBorder(BorderFactory
					.createTitledBorder("Filter by date"));
			c.gridx = 0; c.gridy = 0;
			c.gridheight = 1;
			row4.add(new JLabel("From:"), c);
			c.gridy++;
			row4.add(new JLabel("To:"), c);
			c.gridy = 0; c.gridx++;
			row4.add(new JLabel("[mm/dd/yyyy]"), c);
			c.gridy++;
			row4.add(new JLabel("[mm/dd/yyyy]"), c);
			c.gridy = 0; c.gridx++;
			c.gridheight = 2;
			c.weightx = 1;
			row4.add(Box.createHorizontalGlue());
			c.weightx = 0;
			c.gridx++;
			row4.add(new JLabel("or"), c);
			c.gridx++;
			c.weightx = 1;
			row4.add(Box.createHorizontalGlue());
			c.weightx = 0;
			c.gridx++; //c.gridy = 0;
			c.gridheight = 1;
			row4.add(new JLabel("[n] [days/months/edits]"), c);
			c.gridy++;
			row4.add(new JLabel("[before/after] [mm/dd/yyyy]"), c);
			optionC.gridy++;
			optionPanel.add(row4, optionC);
		}
		
		{
			JPanel row5 = new JPanel();
			row5.setLayout(new BoxLayout(row5, BoxLayout.X_AXIS));
			
			JPanel graphTypes = new JPanel(new GridBagLayout());
			graphTypes.setBorder(BorderFactory
							.createTitledBorder("Graph Type"));
			c.anchor = GridBagConstraints.WEST;
			c.gridx = 0; c.gridy = 0;
			graphTypes.add(new JLabel("O Stacked"), c);
			c.gridy++;
			graphTypes.add(new JLabel("O Unstacked"), c);
			c.gridy++;
			graphTypes.add(new JLabel("O Proportion"), c);
			c.gridy++;
			c.gridwidth = 2;
			c.anchor = GridBagConstraints.CENTER;
			graphTypes.add(new JLabel("O Pie"), c);
			c.anchor = GridBagConstraints.WEST;
			c.gridwidth = 1;
			c.gridx++; c.gridy = 0;
			graphTypes.add(new JLabel("O Line"), c);
			c.gridy++;
			graphTypes.add(new JLabel("O Area"), c);
			c.gridy++;
			graphTypes.add(new JLabel("O Histogram"), c);
			row5.add(graphTypes);
			row5.add(Box.createHorizontalGlue());
			
			JPanel graphAnalyses = new JPanel(new GridBagLayout());
			graphAnalyses.setBorder(BorderFactory
							.createTitledBorder("Time Axis"));
			c.anchor = GridBagConstraints.WEST;
			c.gridx = 0; c.gridy = 0;
			graphAnalyses.add(new JLabel("O Continuous"), c);
			c.gridy++;
			graphAnalyses.add(new JLabel("O Sum over week"), c);
			c.gridy++;
			graphAnalyses.add(new JLabel("O Sum over day"), c);
			c.gridy++;
			graphAnalyses.add(new JLabel("Resolution: [n] [hours/days/edits]"), c);
			c.gridx = 1;
			row5.add(graphAnalyses);
			
			optionC.gridy++;
			optionPanel.add(row5, optionC);
		}
		
		{
			JPanel row6 = new JPanel(new GridBagLayout());
			row6.setBorder(BorderFactory
					.createTitledBorder("Filters and splits"));
			c.gridx = 0; c.gridy = 0;
			row6.add(new JLabel("O Major/minor split"), c);
			c.gridy++;
			row6.add(new JLabel("O Namespaces [groups, exceptions, and colors]"), c);
			c.gridy++;
			row6.add(new JLabel("X Top [n] [% or articles] edited"), c);
			c.gridy++;
			row6.add(new JLabel("X Exclude articles with less than [n] edits"), c);
			c.gridy++;
			row6.add(new JLabel("O Edit summary split"), c);
			optionC.gridy++;
			optionPanel.add(row6, optionC);
		}
		
		/*
		 {
		 JPanel row7 = new JPanel();
		 row7.add(new JLabel("O Text")); // use tabs instead?
		 row7.add(new JLabel("O Graph"));
		 row7.add(new JLabel("O Tree"));
		 optionC.gridy++;
		 optionPanel.add(row7, optionC);
		 }
		 */
		
		// TODO: use JSplitPane here
		textOutput.setFont(new Font("Ariel", Font.PLAIN, 12));
		textOutput.setLineWrap(true);
		textOutput.setWrapStyleWord(true);
		textOutput.setText(NA_TEXT);
		//textOutput.setPreferredSize(new Dimension(
		//		
		//		textOutput.getPreferredSize().height));
		areaScrollPane
		.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
		areaScrollPane.setMinimumSize(MINIMUM_TEXTPANEL_SIZE);
		
		textOutputPanel.setLayout(new BorderLayout());
		textOutputPanel.add(areaScrollPane, BorderLayout.CENTER);
		
		outputTabs.addTab("Text", textOutputPanel);
		outputTabs.addTab("Graph", graphOutputPanel);
		outputTabs.addTab("Tree", treeOutputPanel);
		optionC.gridx = 1;
		optionC.gridheight = GridBagConstraints.REMAINDER;
		optionC.gridy = 0;
		//optionC.gridwidth = GridBagConstraints.REMAINDER;
		optionC.weightx = 1; optionC.weighty = 1;
		optionC.fill = GridBagConstraints.BOTH;
		//optionPanel.add(outputTabs, optionC);

		JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
                optionPanel, outputTabs);
		
		mainPanel.add(splitPane, BorderLayout.CENTER);
		
		return mainPanel;
	}
	
	public void actionPerformed(ActionEvent event)
	{
		if (event.getSource() == methodBox && "comboBoxChanged".equals(event.getActionCommand()))
		{
			if (methodBox.getSelectedItem().equals(phases[0]))
			{
				locationOption.show(row3, phases[0]);
				label.setText("Please put the username in the upper-left and the site below");
			}
			else if (methodBox.getSelectedItem().equals(phases[1]))
			{
				fileChooseDesc.setText("File path:");
				locationOption.show(row3, phases[1]);
				label.setText("Please indicate the file to load below");
			}
			else if (methodBox.getSelectedItem().equals(phases[2]))
			{
				fileChooseDesc.setText("First file path:");
				locationOption.show(row3, phases[2]);
				label.setText("Please indicate the first file to load below");
			}
		}
		else if ("Proceed".equals(event.getActionCommand()))
		{
			try
			{
				setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
				statParcel.reset();
				if (methodBox.getSelectedItem().equals(phases[0])) 
				{
					statParcel.mainDownloadSingle(nameInput.getText());
				}
				else if (methodBox.getSelectedItem().equals(phases[1]))
				{
					statParcel.mainSingle(filePathField.getText());
				}
				else if (methodBox.getSelectedItem().equals(phases[2]))
				{
					statParcel.mainMulti(null);
				}
				setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
				textOutput.setText(statParcel.console.toString());
				textOutput.setCaretPosition(0);
				
				//The following didn't work:
				//JScrollBar scroll = areaScrollPane.getVerticalScrollBar();
				//scroll.setValue(scroll.getMinimum());
			}
			catch (IOException e)
			{
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			//dispose();
		}
		else if (event.getSource() == browseButton)
		{
			if (fc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION)
			{
				filePathField.setText(fc.getSelectedFile().toString());
			}
		}
	}
}

Output

Incomplete!

Contrib.java

/**
 * @author Titoxd
 * @program Contribution class for Flcelloguy's Tool
 * @version 4.03c; released February 20, 2006
 * @see [[User:Flcelloguy/Tool]]
 * @docRoot http://en.wikipedia.org/wiki/User:Titoxd/Flcelloguy's_Tool
 * @copyright Permission is granted to distribute freely, provided attribution is granted.
 */

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.StringTokenizer;

public class Contrib implements Comparable
{
	protected String timeStamp;
	protected String pageName, namespace, shortName;
	protected boolean minorEdit;
	protected String editSummary, autoSummary;
		// editSummary is only the manual part
	protected boolean topEdit;
	protected long editID;
	protected Calendar date = new GregorianCalendar();
	
	public Contrib(String inStamp, String inName, boolean inMin, String inSummary, String inAuto, boolean inTop, long inEditID)
	{
		timeStamp = inStamp;
		pageName = inName;
		
		String[] nameArray=pageName.split(":",2);
		if (nameArray.length == 1)
		{
			namespace = Namespace.MAINSPACENAME;
			shortName = pageName;
		}
		else
		{
			namespace = nameArray[0];
			shortName = nameArray[1];
		}
		
		minorEdit = inMin;
		editSummary = inSummary;
		autoSummary = inAuto;
		topEdit = inTop;
		editID = inEditID;
		setDate(timeStamp);
	}
	
	/**
	 *  Constructor for the internal Calendar object
	 */
	private void setDate(String dateString)
	{
		StringTokenizer stamp = new StringTokenizer(dateString," :,");
		int hour = Integer.parseInt(stamp.nextToken());
		int minute = Integer.parseInt(stamp.nextToken());
		int day = Integer.parseInt(stamp.nextToken());
		String month = stamp.nextToken();
		int year = Integer.parseInt(stamp.nextToken());
		int monthNo = 0;
		{
			if (month.equalsIgnoreCase("January")) monthNo = Calendar.JANUARY;
			else if (month.equalsIgnoreCase("February")) monthNo= Calendar.FEBRUARY;
			else if (month.equalsIgnoreCase("March")) monthNo = Calendar.MARCH;
			else if (month.equalsIgnoreCase("April")) monthNo = Calendar.APRIL;
			else if (month.equalsIgnoreCase("May")) monthNo = Calendar.MAY;
			else if (month.equalsIgnoreCase("June")) monthNo = Calendar.JUNE;
			else if (month.equalsIgnoreCase("July")) monthNo = Calendar.JULY;
			else if (month.equalsIgnoreCase("August")) monthNo = Calendar.AUGUST;
			else if (month.equalsIgnoreCase("September")) monthNo = Calendar.SEPTEMBER;
			else if (month.equalsIgnoreCase("October")) monthNo = Calendar.OCTOBER;
			else if (month.equalsIgnoreCase("November")) monthNo = Calendar.NOVEMBER;
			else if (month.equalsIgnoreCase("December")) monthNo = Calendar.DECEMBER;
		}
		
		date.set(year, monthNo, day, hour, minute);
		date.set(Calendar.SECOND,0);            //these fields shouldn't be used, so they're zeroed out
		date.set(Calendar.MILLISECOND,0);
	}
	
	protected void checkCorrectNamespace(HashMap<String, Namespace> nsMap)
	{
		if (!nsMap.containsKey(namespace))
		{
			System.out.println("Page: "+ namespace+":"+shortName+" set to main namespace."); //for debug purposes
			namespace = Namespace.MAINSPACENAME;   //set to main namespace by default
		}
	}
	
	public int compareTo(Object o) //may throw ClassCastException
		//sorts by editID (same as sorting by date)
	{
		Contrib con = (Contrib) o;
		
		if (editID > con.editID) return 1;
		if (editID == con.editID) return 0;
		return -1;
	}
	
	public String toString()
	{
		String returnString = "Time: " + timeStamp + "\r" + 
		"Page: " + pageName + " (Namespace: " + namespace + "; Article: " + shortName + ")\r" + 
		"Minor edit: " + minorEdit + "\r" + 
		"Edit Summary: " + editSummary + "\r" +
		"Most recent edit: " + topEdit;
		return returnString;
	}
}

PurgeContribs.java

/**
 * @author Titoxd
 * @program HTML -> ContribFile converter for Flcelloguy's Tool
 * @version 3.52d; released February 16, 2006
 * @see [[User:Flcelloguy/Tool]]
 * @docRoot http://en.wikipedia.org/wiki/User:Titoxd/Flcelloguy's_Tool
 * @copyright Permission is granted to distribute freely, provided attribution is granted.
 */

import java.io.IOException;
import java.util.StringTokenizer;

public class PurgeContribs
{
        /**
         * @param purgedLine
         *            (input line in raw HTML, leading and trailing whitespace
         *            removed)
         * @return Contrib class object: for analysis
         * @throws IOException
         */
        public static Contrib Parse(String purgedLine) throws IOException
        {
                /**** Take out the <li> tags ****/
                String midString1;
                String timeStamp;
                String editSummary = null;
                String autoSummary = null;
                boolean minorEdit = false;
                boolean endLoop = false;
                boolean newestEdit = false;
                //boolean sectionParsed = false;
                midString1 = purgedLine.substring(4, purgedLine.length() - 5);
                
                /**** Process the time stamp ****/
                StringTokenizer token;
                token = new StringTokenizer(midString1.trim());
                {
                        String time = token.nextToken();
                        String day = token.nextToken();
                        String month = token.nextToken();
                        String year = token.nextToken();
                        timeStamp = time + " " + day + " " + month + " " + year;
                }
                
                /**** Process the page name ****/
                
                String dummy = token.nextToken(); // get rid of (<a
                /*String URL =*/ token.nextToken();
                StringBuilder titleBuilder = new StringBuilder();
                //String pageName = URL.substring(25, URL.length() - 20);
                
                ///**** Get rid of a few extra tokens ****/
                 // start with the "title" piece
                
                do
                {
                        dummy = token.nextToken();
                        titleBuilder.append(dummy); titleBuilder.append(" ");
                        endLoop = false;
                        if (dummy.lastIndexOf('<') != -1)
                        {
                                if (dummy.substring(dummy.lastIndexOf('<'),
                                                dummy.lastIndexOf('<') + 3).compareTo("</a>") != 0)
                                        endLoop = true;
                        }
                }
                while (endLoop == false);
                
                String title = titleBuilder.toString();
                String pageName = title.substring(7, title.length() - 11);
                
                /**** Do the same with the diff link ****/
                dummy = token.nextToken(); // get rid of (<a
                String diffURL = token.nextToken(); // this URL is not needed, so it is dummied out
                String diffIDString = diffURL.substring(diffURL.lastIndexOf("=")+1, diffURL.length()-1); // ditto
                long diffID = Long.parseLong(diffIDString);
                
                do
                {
                        endLoop = false;
                        dummy = token.nextToken();
                        if (dummy.lastIndexOf('<') != -1)
                        {
                                if (dummy.substring(dummy.lastIndexOf('<'),
                                                dummy.lastIndexOf('<') + 3).compareTo("</a>") != 0)
                                        endLoop = true;
                        }
                }
                while (endLoop == false);
                
                String dummyPageName;
                
                /**** Determine if edit is minor or not ****/
                dummy = token.nextToken(); // get rid of (<span
                dummy = token.nextToken(); // read the next token; it should be class="minor">m</span> if a minor edit
                if (dummy.compareTo("class=\"minor\">m</span>") == 0)
                {
                        minorEdit = true;
                        dummyPageName = null;
                }
                else
                {
                        minorEdit = false;
                        dummyPageName = dummy;
                }
                
                if (dummyPageName == null) // if it was a minor edit, advance token
                        // cursor to match non-minor edits
                {
                        dummy = token.nextToken(); // get rid of <a
                        dummyPageName = token.nextToken();
                }
                
                do
                {
                        endLoop = false;
                        dummy = token.nextToken();
                        if (dummy.lastIndexOf('<') != -1)
                        {
                                if (dummy.substring(dummy.lastIndexOf('<'),
                                                dummy.lastIndexOf('<') + 3).compareTo("</a>") != 0)
                                        endLoop = true;
                        }
                }
                while (endLoop == false);
                
                /**** flush the StringTokenizer ****/
                
                StringBuilder tokenDump = new StringBuilder();
                String dump;
                if (token.hasMoreTokens() == true) // 
                {
                        do
                        {
                                tokenDump.append(token.nextToken());
                                tokenDump.append(' ');
                        } while (token.hasMoreTokens()==true);
                        tokenDump.trimToSize();
                        dump = tokenDump.toString();
                }
                else    //not top edit, no edit summary
                {
                        dump = null;    
                }
                
                /**** Top edit? ****/
                if (dump != null && dump.contains("<strong> (top)</strong>") == true)
                {
                        newestEdit = true;
                        dump = dump.substring(0,dump.indexOf("<strong> (top)</strong>")); //truncate to remove rollback links and other garbage 
                        dump = dump.trim();             
                }
                else newestEdit = false;
                
                /**** Process edit summary ****/
                String[] summaries = ParseSummary(dump);
                autoSummary = summaries[0];
                editSummary = summaries[1];
                
                Contrib contrib = new Contrib(timeStamp, pageName, minorEdit,
                                editSummary, autoSummary, newestEdit, diffID);
                return contrib;
        }
        
        /**
         * @param dump
         * @return String[2] array, String[0] is the auto summary, String[1] is the manual summary  
         */
        private static String[] ParseSummary(String dump)
        {
                // TODO: clean this up
                
                /****Check that there is an edit summary to begin with ****/
                if (dump == null || dump.equals("")) return new String[] {null, null};
                
                String[] summaryArray = new String[2];
                
                if (dump.indexOf("<span class=\"autocomment\">") != -1)  //autocomment present
                {
                        String autoSummary = // everything within the <span class="autocomment">
                                dump.substring(
                                                dump.indexOf("<span class=\"autocomment\">"),
                                                dump.indexOf("</span>")+7);

                        summaryArray[0] = autoSummary.substring(autoSummary.indexOf("<a href="));

                        summaryArray[1] = dump.substring(0,dump.indexOf(autoSummary)) + dump.substring(dump.indexOf(autoSummary)+ autoSummary.length()).trim();
                        summaryArray[0] = summaryArray[0].substring(0,summaryArray[0].lastIndexOf("<"));
                        summaryArray[0] = summaryArray[0].substring(summaryArray[0].lastIndexOf(">")+1);
                        if (summaryArray[0].endsWith(" -")== true)
                        {
                                summaryArray[0] = summaryArray[0].substring(0,summaryArray[0].length()-1);
                        }
                }
                else
                {
                        if (dump != "") summaryArray[1] = dump;
                }
                
                if (summaryArray[1] != null && summaryArray[1].length() != 0)
                {
                        summaryArray[1] = summaryArray[1].substring(summaryArray[1].indexOf(">")+1,summaryArray[1].lastIndexOf("<"));
                        summaryArray[1] = summaryArray[1].trim();
                        if (summaryArray[1].equals("()")) summaryArray[1]=null;
                }
                
                if (summaryArray[0]=="") summaryArray[0]=null;
                if (summaryArray[1]=="") summaryArray[1]=null; //so the edge cases don't trigger exceptions
                
                if (summaryArray[0] != null) summaryArray[0] = summaryArray[0].trim();

                return summaryArray;
        }

		/**
		 * "Next 5000" contributions link parser
		 * @param inLine (<code>String</code> object that contains the raw HTML for the Contributions line 
		 * @return String with the relative URL of the link if the link is available, null if it is not
		 */
		public static String getNextDiffs(String inLine)
		{
			StringTokenizer token = new StringTokenizer(inLine,"<>");
			String tag = token.nextToken();
			String link = null; 
			boolean diffObtained = false;
			//FIXME: Internationalize this function
			do
			{
				if (tag.contains("href=\"/w/index.php?title=Special:Contributions&") == true)
				{
					if (tag.contains("limit=5000\"") == true)
					{
						{
							if (token.nextToken().contains("next") == true)
							link = tag.split("\"")[1];
							diffObtained = true;
						}
					}
				}
				if (token.hasMoreTokens() == true)
				{
					tag = token.nextToken();
				}
				else
				{
					diffObtained = true;
				}
			} while (diffObtained == false);
			
			return link;
		}
}

Output

N/A