/*
 * DataFileCreator.java
 *
 * Ace Kanji Workout
 * Copyright (C) 2006 Alex Quinn <aquinn@cs.washington.edu>
 * Licensed under GPL v2, http://www.gnu.org/licenses/gpl.html
 *
 * Script to extract and consolidate information from the
 * kanjidic, edict, and jlpt kanji indexes to create the data file
 * that will be used by Ace Kanji Workout.
 *
 * Created: May 31, 2006
 * Author:  Alex Quinn
 */

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.text.ParseException;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.ListIterator;

class DataFileCreator {
	protected static final String ORIGINAL_DATA_DIRECTORY = "original-data/";
	protected static final String KANJIDIC_FILENAME = ORIGINAL_DATA_DIRECTORY+"kanjidic.txt";
	protected static final String KANJIDIC_ENCODING = "EUC-JP";
	protected static final String EDICT_FILENAME = ORIGINAL_DATA_DIRECTORY+"edict.txt";
	protected static final String EDICT_ENCODING = "EUC-JP";
	protected static final boolean KEEP_DATA_FILES_SHORT = true;
	protected static final String JLPT_KANJI_FILENAME = ORIGINAL_DATA_DIRECTORY+"jlpt_kanji_level_%d_base.txt";
	protected static final String JLPT_KANJI_ENCODING = "UTF-8";
	protected static final String JLPT_VOCAB_FILENAME = ORIGINAL_DATA_DIRECTORY+"jlpt-voc-%d.txt";
	protected static final String JLPT_VOCAB_ENCODING = "UTF-8";
	protected static final String OKURIGANA_DIVIDER = ".";
	protected static void reportStatus(String s) {
		System.err.print(s);
		System.err.flush();
	}

	public static void main(String[] args) {
					reportStatus("--------------------------------------------------\n");
					reportStatus("Generating data file:\n");
					reportStatus("--------------------------------------------------\n");
					reportStatus("Reading original data files:\n - JLPT Kanji lists\n");
		Hashtable<String,Integer> jlptKanjiHash = readJlptKanjiLists();
					reportStatus(" - JLPT vocab lists\n");
		Hashtable<String,Integer> jlptVocabHash = readJlptVocabLists();
					reportStatus(" - Kanjidic\n");
		Hashtable<String,Kanji> kanjiDicHash = readKanjidic(jlptKanjiHash);
					reportStatus(" - Edict\n");
		Hashtable<String,Example> exampleHash = readEdict(jlptVocabHash);
					reportStatus("Writing informal information files\n");
					reportStatus(" - Examples\n");
		dumpExampleHashToFile(exampleHash,"examples1.txt"); // for debugging
					reportStatus(" - Kanji\n");
		dumpKanjiHashToFile(kanjiDicHash,"kanji1.txt");     // for debugging
					reportStatus("Making example splits.\n");
		makeAllExampleSplits(exampleHash,kanjiDicHash);
					reportStatus("Trimming the kanji set.\n");
		filterKanjiReadingsAndAddExamples(exampleHash, kanjiDicHash);
					reportStatus("Writing informal information files\n");
					reportStatus(" - Examples\n");
		dumpExampleHashToFile(exampleHash,"examples.txt"); // for debugging
					reportStatus(" - Kanji\n");
		dumpKanjiHashToFile(kanjiDicHash,"kanji.txt");     // for debugging
					reportStatus("Writing data files\n");
					reportStatus(" - Examples\n");
		saveExampleFile(exampleHash,"data-examples.txt");
					reportStatus(" - Kanji\n");
		saveKanjiFile(kanjiDicHash,"data-kanji.txt");
					reportStatus("DONE.\n");
	}
	protected static final String DATA_FILE_ENCODING = "UTF-8";

	static void saveKanjiFile(Hashtable<String,Kanji> kanjiHash, String filename) {
		Enumeration keys = kanjiHash.keys();
		Kanji kanji;
		OutFile outfile = new OutFile(filename, DATA_FILE_ENCODING, false);
		while( keys.hasMoreElements() ) {
			kanji = kanjiHash.get(keys.nextElement());
			outfile.writeLine(kanji.toString());
		}
		outfile.close();
	}
	static void saveExampleFile(Hashtable<String,Example> exampleHash, String filename) {
		Enumeration keys = exampleHash.keys();
		Example example;
		OutFile outfile = new OutFile(filename, DATA_FILE_ENCODING, false);
		while( keys.hasMoreElements() ) {
			example = exampleHash.get(keys.nextElement());
			outfile.writeLine(example.toString());
		}
		outfile.close();
	}

	static void filterKanjiReadingsAndAddExamples(
					Hashtable<String,Example> exampleHash,
					Hashtable<String,Kanji> kanjiHash) {
		String kanjiChar;
		Example example;
		Kanji kanji;
		String[] partsKana, partsKanji;
		int i;
		Hashtable<String,LinkedList<Example>> kanjiToExamples = 
					new Hashtable<String,LinkedList<Example>>();
		Enumeration exampleKeys = exampleHash.keys();
		LinkedList<Example> exampleList;

		// Create a mapping from kanji character to the pair of a reference to
		// an example object and the specific way that example is read in that
		// particular kanji.
		while( exampleKeys.hasMoreElements() ) {
			example = exampleHash.get(exampleKeys.nextElement());
			partsKanji = example.kanjiSplit.split(Example.SPLIT_SEPARATOR);
			if( example.kanaSplit.length()>0 ) {
				for( i=0; i<partsKanji.length; i++ ) {
					if( Japanese.charIsKanji(partsKanji[i].charAt(0)) ) { 
						kanjiChar = partsKanji[i];
						assert kanjiChar.length()==1;
						assert Japanese.charIsKanji(kanjiChar.charAt(0));
						if( kanjiToExamples.containsKey(kanjiChar) ) {
							exampleList = kanjiToExamples.get(kanjiChar);
						}
						else {
							exampleList = new LinkedList<Example>();
							kanjiToExamples.put(kanjiChar,exampleList);
						}
						exampleList.add(example);
					}
				}
			}
			else {
				for( i=0; i<example.inKanji.length(); i++ ) {
					kanjiChar = example.inKanji.substring(i,i+1);
					assert kanjiChar.length()>0;
					if( Japanese.charIsKanji(kanjiChar.charAt(0)) && kanjiHash.containsKey(kanjiChar) ) {
						assert kanjiChar.length()==1;
						if( kanjiToExamples.containsKey(kanjiChar) ) {
							exampleList = kanjiToExamples.get(kanjiChar);
						}
						else {
							exampleList = new LinkedList<Example>();
							kanjiToExamples.put(kanjiChar,exampleList);
						}
						exampleList.add(example);
					}
				}
			}
		}

		// At this point, kanjiToExamples maps most/all kanji characters to a linked list
		// of Example objects.

		// Run through every kanji and add examples and filter its readings.
		Enumeration kanjiKeys = kanjiToExamples.keys();
		while( kanjiKeys.hasMoreElements() ) {
			kanji = kanjiHash.get(kanjiKeys.nextElement());
			kanjiChar = kanji.kanjiChar; //private member access
			exampleList = kanjiToExamples.get(kanji.kanjiChar);
			HashSet<String> newExamplesSet = new HashSet<String>();
			HashSet<String> allReadingsFromExamplesSet = new HashSet<String>();
			HashSet<String> newOnReadings = new HashSet<String>();
			HashSet<String> newKunReadings = new HashSet<String>();
			ListIterator<Example> exampleIterator = exampleList.listIterator(0);

			// Add the examples to the kanji.  As of 6/21/06, I'm planning to add all
			// examples to the kanji objects.  I'll see how this affects the file size.
			// it might be useful later.  Then, I'll have a method in the Example class
			// which selects the most popular example for each reading.
			while( exampleIterator.hasNext() ) {
				example = exampleIterator.next();
				newExamplesSet.add(example.inKanji); //private member access
				partsKanji = example.kanjiSplit.split(Example.SPLIT_SEPARATOR); //private member access
				partsKana = example.kanaSplit.split(Example.SPLIT_SEPARATOR); //private member access
				for( i=0; i<partsKanji.length; i++ ) {
					if( example.kanaSplit.length()>0 && partsKanji[i].equals(kanjiChar) ) {
						allReadingsFromExamplesSet.add(partsKana[i]);
					}
				}
			}

			// Add the examples to the Kanji object.
			kanji.examples = newExamplesSet.toArray(new String[newExamplesSet.size()]);

			// Make a new list of kanji's ON-readings and then replace the old list with the new one.
			for( String oldOnReading : kanji.onReadings ) { //private member access
				if( allReadingsFromExamplesSet.contains( Japanese.toHiragana(oldOnReading) ) ||
						allReadingsFromExamplesSet.contains( Japanese.toKatakana(oldOnReading) ) ) {
					newOnReadings.add(oldOnReading);
				}
			}

			// Make a new list of kanji's KUN-readings and then replace the old list with the new one.
			for( String oldKunReading : kanji.kunReadings ) { //private member access
				String trimmedKunReading;

				// We're going to compare a trimmed down version that includes no okurigana.
				// Then, we'll add the original version that does include the okurigana.
				if( oldKunReading.contains(OKURIGANA_DIVIDER) )
					trimmedKunReading = oldKunReading.substring(0,oldKunReading.indexOf(OKURIGANA_DIVIDER));
				else
					trimmedKunReading = new String(oldKunReading);

				if( allReadingsFromExamplesSet.contains( Japanese.toHiragana(trimmedKunReading) ) ||
						allReadingsFromExamplesSet.contains( Japanese.toKatakana(trimmedKunReading) ) ) {
					newKunReadings.add(oldKunReading);
				}
			}

			// Replace the old on and kun readings with the new, filtered ones, but
			// only if there's at least one reading in the new set.  Otherwise, just
			// leave all the original readings and let the user decide which is
			// important.
			if( newOnReadings.size()>0 || newKunReadings.size()>0 ) {
				kanji.onReadings = newOnReadings.toArray(new String[newOnReadings.size()]); //private member access
				kanji.kunReadings = newKunReadings.toArray(new String[newKunReadings.size()]); //private member access
			}
		}
	}

	static void makeAllExampleSplits(Hashtable<String,Example> exampleHash, Hashtable<String,Kanji> kanjiHash) {
	// Go through every example in the hashtable and call the makeSplits function
	// to fill up its kanaSplit and kanjiSplit fields.  In the end, there should
	// be a symbol between any two kanji and between any kana and kanji in the
	// kanjiSplit field.  The kanaSplit field should have corresponding symbols,
	// if possible.
	//
	// As of 6/20/06, we are throwing out any examples which could not be
	// perfectly split in this way.
		Enumeration<String> keys = exampleHash.keys();
		String inKanji;
		LinkedList<String> badExamplesList = new LinkedList<String>();
		int counter=0;
		System.err.println("_____________________________________________________________________________");
		while(keys.hasMoreElements()) {
			inKanji = keys.nextElement();
			try {
				exampleHash.get(inKanji).makeSplits(kanjiHash);
				if( counter % 215==0 ) {
					System.err.print("."); System.err.flush();
				}
				counter++;
			}
			catch(KanjiTrainerBadExampleException e) {
				if( e.exampleIsUsable()==false )
					badExamplesList.add(e.inKanji);
			}
		}
		System.err.println("");
		System.err.flush();
		for( String badExample : badExamplesList ) {
			exampleHash.remove(badExample);
		}

		// Try to fill some of the holes using a hand-edited file which contains
		// tab-delimited triplets of inKanji, inKana, kanaSplit.
		JarInFile infile = new JarInFile(ORIGINAL_DATA_DIRECTORY+"kana-splits-edits.txt","UTF-8"); // FIXME, move this string
		String[] parts;
		String editKanji, editKana, editKanaSplit;
		Example example;
		String line = infile.readLine();
		int lineNumber = 1;
		while( line!=null ) {
			parts = line.split("\t");
			editKanji = parts[0];
			editKana = parts[1];
			if( parts.length>=3 )
				editKanaSplit = parts[2];
			else
				editKanaSplit = "";

			if( exampleHash.containsKey(editKanji) ) {
				example = exampleHash.get(editKanji);

				// Make sure there's at least the same number of colons in the machine-calculated
				// kanjiSplit and the hand-edited kana split.  This little section is just for
				// verification and nothing else.
				if( editKanaSplit.length()>0 ) { // don't bother if there's no split (irregular)
					int colonCounter = 0;
					for( int i=0; i<example.kanjiSplit.length(); i++ )
						if( example.kanjiSplit.substring(i,i+1).equals(Example.SPLIT_SEPARATOR) )
							colonCounter++;
					for( int i=0; i<editKanaSplit.length(); i++ )
						if( editKanaSplit.substring(i,i+1).equals(Example.SPLIT_SEPARATOR) )
							colonCounter--;
					assert colonCounter==0 : String.format("Error (wrong number of colons) on line %d",lineNumber);
				}
				// Okay, enough verification.

				if( example.inKanji.equals(editKanji) && example.inKana.equals(editKana) ) {
					example.kanaSplit = editKanaSplit; // FIXME, this really should be done in Example.
					//System.err.format("Added the split (%d chars) to %s.\n",editKanaSplit.length(),example.meanings[0]);
				}
				else
					System.err.format("Something doesn't match on line %d.\n",lineNumber);
			}
			else {
				System.err.println("Didn't find one of the kanji in the edit file.");
			}
			line = infile.readLine();
			lineNumber++;
		}
	}

	static void dumpExampleHashToFile(Hashtable<String,Example> exampleHash, String filename) { // for debugging only
		OutFile outfile = new OutFile(filename,"UTF-8",true);
		Enumeration keys = exampleHash.keys();
		String key;
		Example example;
		String[] meanings;
		int i = 0;
		while( keys.hasMoreElements() ) {
			i++;
			key = (String)keys.nextElement();
			example = exampleHash.get(key);
			meanings = example.meanings;
			if( meanings==null || meanings.length==0 )
				outfile.writeLine("no meanings with "+key+" at line "+String.valueOf(i));
			else
				if( example.goodForStudying() ) { 
					outfile.writeLine(exampleHash.get(key).debugString());
				}
		}
		outfile.close();
	}

	static void dumpKanjiHashToFile(Hashtable<String,Kanji> kanjiHash, String filename) { // for debugging only
		OutFile outfile = new OutFile(filename,"UTF-8",true);
		Enumeration keys = kanjiHash.keys();
		String key;
		Kanji kanji;
		Example example;
		String[] meanings;
		int i = 0;
		while( keys.hasMoreElements() ) {
			i++;
			key = (String)keys.nextElement();
			kanji = kanjiHash.get(key);
			meanings = kanji.getMeanings();
			if( meanings==null || meanings.length==0 )
				outfile.writeLine("no meanings with "+key+" at line "+String.valueOf(i));
			else
				outfile.writeLine(kanjiHash.get(key).debugString());
		}
		outfile.close();
	}

	static Hashtable<String,Example> readEdict(Hashtable<String,Integer> jlptVocabHash) {
	// Read in the Edict file and create a hashtable that maps the kanji representation
	// of an example with an Example object.
	//
	// As of 6/20/06, we throw out any examples which contain no kanji.  They are not
	// useful for this kanji study program.
		Hashtable<String,Example> edictHash = null;
		try {
			Reader in = null;
			try {
				in = new InputStreamReader(new FileInputStream(EDICT_FILENAME), EDICT_ENCODING);
			}
			catch( FileNotFoundException e ) {
				System.err.println("Couldn't find the Edict file, \""+EDICT_FILENAME+"\".");
				System.exit(-1);
			}
			edictHash = new Hashtable<String,Example>();
			BufferedReader br = new BufferedReader(in);
			int lineNumber = 1;
			String s = br.readLine();
			Example ex;
			while (s!=null) {
				lineNumber++;
				s = br.readLine();

				// s will be null after the file has been fully read.
				// s will start with # for a comment.
				if( s!=null && s!="" && s.charAt(0)!='#' ) {
					try {
						ex = parseEdictLine(s);
						if( Japanese.countKanji(ex.inKanji)>0 ) {
							if( jlptVocabHash.containsKey(ex.inKanji) )
								ex.setJlptLevel(jlptVocabHash.get(ex.inKanji));
							if( ex.goodForStudying() || KEEP_DATA_FILES_SHORT==false )
								edictHash.put(ex.inKanji, ex);
						}
					}
					catch( ParseException e ) {
						System.err.println("Error in "+EDICT_FILENAME+" on line "+String.valueOf(lineNumber)+
								" in character "+String.valueOf(e.getErrorOffset())+".");
					}
				}
			}
			br.close();
		}
		catch(IOException e) {
			System.err.println("Error: " + e.getMessage());
			e.printStackTrace();
		}
		return edictHash;
	}

	static Hashtable<String,Kanji> readKanjidic(Hashtable<String,Integer> jlptKanjiHash) {
		Hashtable<String,Kanji> kanjiDicHash = new Hashtable<String,Kanji>();
		try {
			Reader in = new InputStreamReader(new FileInputStream(KANJIDIC_FILENAME), KANJIDIC_ENCODING);
			BufferedReader br = new BufferedReader(in);
			int lineNumber = 1;
			String s = br.readLine();
			Kanji k;
			while (s!=null) {
				lineNumber++;
				s = br.readLine();

				// s will be null after the file has been fully read.
				// s will start with # for a comment.
				if( s!=null && s!="" && s.charAt(0)!='#' ) {
					k = parseKanjidicLine(s);
					if( jlptKanjiHash.containsKey(k.getKanjiChar()) )
						k.setJlptLevel(jlptKanjiHash.get(k.getKanjiChar()));
					if( k.isJlptOrJoyo()==true || KEEP_DATA_FILES_SHORT==false )
						kanjiDicHash.put(k.getKanjiChar(),k);
				}
			}
			br.close();
		}
		catch(IOException e) {
			System.err.println("Error: " + e.getMessage());
			e.printStackTrace();
		}
		return kanjiDicHash;
	}

	static Example parseEdictLine(String line) throws ParseException {
		String inKanji="";
		String inKana="";
		String[] meaningsArray = new String[0];
		LinkedList<String> meaningsList = new LinkedList<String>();
		String meaning="";
		boolean isPopular = false;
		final int KANJI=0, KANA=1, SPACE_AFTER_KANA=2, BRACKET_BEFORE_KANA=3;
		final int BRACKET_AFTER_KANA=4, MEANINGS=5, SPACE_AFTER_KANJI=6;
		int expected = KANJI;
		char ch;
		for( int indexInString=0; indexInString<line.length(); indexInString++ ) {
			ch = line.charAt(indexInString);
			if( expected==KANJI && ch!=' '&&ch!='['&&ch!=']'&&ch!='/' ) {
				inKanji = inKanji + String.valueOf(ch);
			}
			else if( expected==KANJI && ch==' ' ) {
				expected = BRACKET_BEFORE_KANA;
			}
			else if( expected==BRACKET_BEFORE_KANA && ch=='[' ) {
				expected = KANA;
			}
			else if( expected==BRACKET_BEFORE_KANA && ch=='/' ) {
				expected = MEANINGS;
			}
			else if( expected==KANA && Japanese.charIsKana(ch) ) {
				inKana += String.valueOf(ch);
			}
			else if( expected==KANA && ch==']' ) {
				expected = SPACE_AFTER_KANA;
			}
			else if( expected==SPACE_AFTER_KANA && ch==' ' ) {
				expected = MEANINGS;
			}
			else if( expected==MEANINGS && ch=='/' ) {
				if( meaning.equals("(P)")==true ) {
					isPopular = true;
				}
				else if( meaning!="" ) {
					meaningsList.add(meaning);
				}
				meaning = "";
			}
			else if( expected==MEANINGS && ch!='/' ) {
				meaning += String.valueOf(ch);
			}
			else {
				throw new ParseException(line,indexInString*1000+expected);
			}
		}
		meaningsArray = meaningsList.toArray(meaningsArray);
		return new Example(inKanji,inKana,isPopular,meaningsArray);
	}

	static Kanji parseKanjidicLine(String line) {
		assert line!=null : "line=="+line;
		String[] pieces;
		pieces = line.split("\\s");
		Kanji kanji;
		int i = 0;
		String piece = "";
		boolean inBrackets = false;

		// Fields needed for the Kanji object
		String kanjiChar;
		int unicode = 0;
		int frequency = 0;
		String[] onReadings = null;
		String[] kunReadings = null;
		String[] meanings = null;
		int heisig = 0;
		int halpern = 0;
		int henshall = 0;
		int jlptLevel = 0;
		int joyo = 0;
		LinkedList<String> meaningsList = new LinkedList<String>();
		LinkedList<String> kunList = new LinkedList<String>();
		LinkedList<String> onList = new LinkedList<String>();
		kanjiChar = pieces[0];
		assert kanjiChar.length()==1 : "kanjiChar should only be 1 character.";
		for(i=1; i<pieces.length; i++) {
			piece = pieces[i];

			// Deal with definitions wrapped in brackets that contain spaces
			if( piece.startsWith("{") ) {
				while( piece.endsWith("}")==false ) {
					assert piece.substring(1).contains("{")==false;
					i++;
					piece += " " + pieces[i];
				}
			}

			// Parse the fields.
			if( piece.matches("F\\d+") )
				frequency = Integer.parseInt(piece.substring(1));
			if( piece.matches("G\\d+") )
				joyo = Integer.parseInt(piece.substring(1));
			if( piece.matches("DK\\d+") )
				halpern = Integer.parseInt(piece.substring(2));
			if( piece.matches("U[\\da-fA-F]+") )
				unicode = Integer.parseInt(piece.substring(1),16);
			if( piece.matches("E\\d+") )
				henshall = Integer.parseInt(piece.substring(1));
			if( piece.matches("L\\d+") )
				heisig = Integer.parseInt(piece.substring(1));
			if( piece.matches("\\{.*\\}") )
				meaningsList.add(piece.substring(1,piece.length()-1));
			if( Japanese.charIsHiragana(piece.charAt(0)) )
				kunList.add(piece);
			if( Japanese.charIsKatakana(piece.charAt(0)) )
				onList.add(piece);
		}

		// Create the Kanji object.
		meanings = meaningsList.toArray(new String[meaningsList.size()]);
		onReadings = onList.toArray(new String[onList.size()]);
		kunReadings = kunList.toArray(new String[kunList.size()]);
		kanji = new Kanji(kanjiChar, unicode, frequency, onReadings, kunReadings, meanings, 
					heisig, halpern, henshall, joyo);
		return kanji;
	}

	static Hashtable<String,Integer> readJlptKanjiLists() {
		Hashtable<String,Integer> dict = new Hashtable<String,Integer>();
		for( int jlptLevel=1; jlptLevel<=4; jlptLevel++ ) {
			try {
				BufferedReader in = new BufferedReader(new InputStreamReader(
							new FileInputStream(String.format(JLPT_KANJI_FILENAME,jlptLevel)),JLPT_KANJI_ENCODING));
				String line = in.readLine();
				while( line!=null ) {
					line = line.trim();
					if( !line.startsWith("#") && line.length()>1 ) {
						if( Japanese.charIsKanji(line.charAt(0)) ) {
							dict.put(line.substring(0,1),new Integer(jlptLevel));
						}
					}
					line = in.readLine();
				}
				in.close();
			}
			catch( IOException e ) {
				e.printStackTrace();
				System.exit(0);
			}
		}
		return dict;
	}

	static Hashtable<String,Integer> readJlptVocabLists() {
	// Reads a set of vocabulary files collected from the web site,
	// http://www.thbz.org/kanjimots/jlpt.php3
	// and builds a hashtable which maps a word to its JLPT level.
	// Files are named jlpt-voc-X.txt, where X is the JLPT level.
	// They are in UTF-8 encoding.
		Hashtable<String,Integer> dict = new Hashtable<String,Integer>();
		String kanjiChar;
		for( int jlptLevel=1; jlptLevel<=4; jlptLevel++ ) {
			try {
				BufferedReader in = new BufferedReader(new InputStreamReader(
							new FileInputStream(String.format(JLPT_VOCAB_FILENAME,jlptLevel)),JLPT_VOCAB_ENCODING));
				String line = in.readLine();
				while( line!=null ) {
					line = line.trim();
					if( !line.startsWith("#") && line.contains(" ") ) {
					// Some lines contain words that are only hiragana.  We skip those,
					// since we're only interested in words that can be used to study
					// kanji.
						kanjiChar = line.split("\\s")[0];
						dict.put(kanjiChar, new Integer(jlptLevel));
					}
					line = in.readLine();
				}
				in.close();
			}
			catch( IOException e ) {
				e.printStackTrace();
				System.exit(0);
			}
		}
		return dict;
	}
}

class KanjiTrainerBadExampleException extends Exception {
	protected String inKanji;
	protected String message;
	protected boolean exampleUsable;
	public String getInKanji() { return inKanji; }
	public boolean exampleIsUsable() { return exampleUsable; }
	public KanjiTrainerBadExampleException(String inKanji, String message, boolean exampleUsable) {
		super(message);
		this.inKanji = inKanji;
		this.message = message;
		this.exampleUsable = exampleUsable;
	}
}
