public class ApplicationSettings {

	private static ResourceBundle CONFIG_BUNDLE;
	
	private static boolean debug;
	
	public static void setDebug(boolean debug2) {
		debug = debug2;
	}
	
	public static boolean isDebug(){
		return debug || isEqual(getConfigBundle().getString("debug"), true, true);
	}
	
	public static boolean isEncouragementEnabled(){
		return isEqual(getConfigBundle().getString("enable_encouragement"), true, true);
	}

	public static boolean isExpectationResultVisible(){
		return isEqual(getConfigBundle().getString("enable_expectation_result"), true, true);
	}
	
	public static boolean isInteractive(){
        return isEqual(getConfigBundle().getString("interactive"), true, true);
    }
	
	public static char getExitChar(){
		return getConfigBundle().getString("exit_character").charAt(0);
	}
	
	public static String getPathXmlFileName(){
		return getConfigBundle().getString("path_xml_filename");
	}
	
	public static long getFileCompilationTimeoutInMs(){
		return Long.valueOf(getConfigBundle().getString("compile_timeout_in_ms"));
	}

	public static String getMonitorIgnorePattern(){
		return getConfigBundle().getString("ignore_from_monitoring");
	}
	
	private static boolean isEqual(String value, Object e2, boolean ignoreCase){
		if(value == e2){
			return true;
		}
		if(value != null){
			String stringValue = String.valueOf(e2);
			return ignoreCase ? value.equalsIgnoreCase(stringValue) : value.equals(stringValue);
		}
		return false;
	}
	
	private static ResourceBundle getConfigBundle(){
		if(CONFIG_BUNDLE == null){
			try {
				CONFIG_BUNDLE = new PropertyResourceBundle(new FileInputStream(
						DirectoryManager.injectFileSystemSeparators(
							DirectoryManager.getConfigDir(), "config.properties")));
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
		return CONFIG_BUNDLE;	
	}
	
} public class KoanClassLoader extends DynamicClassLoader {

	private static KoanClassLoader instance;
	private final FileMonitor fileMonitor;
	
	private KoanClassLoader(){
		this(DirectoryManager.getBinDir(),
				DirectoryManager.getSourceDir(), 
				buildClassPath(),
				DynamicClassLoader.class.getClassLoader(),
				ApplicationSettings.getFileCompilationTimeoutInMs(),
				getFileMonitor());
	}

	private static FileMonitor getFileMonitor() {
		try{
			return FileMonitorFactory.getInstance(new File(DirectoryManager.getMainDir()), new File(DirectoryManager.getDataFile()));
		}catch(RuntimeException x){
			if(x.getCause() instanceof UTFDataFormatException){
				ApplicationUtils.getPresenter().displayError("Issue loading file system hashes, please rerun run.bat or run.sh with the -clear switch to reset.");
				System.exit(4);
			}
			throw x;
		}
	}

	private KoanClassLoader(String binDir, String sourceDir,
			String[] classPath, ClassLoader parent,
			long timeout, FileMonitor fileMonitor) {
		super(binDir, sourceDir, classPath, parent, timeout);
		this.fileMonitor = fileMonitor;
	}
	
	@Override
	public boolean isFileModifiedSinceLastPoll(String sourcePath, long lastModified) {
		return fileMonitor.isFileModifiedSinceLastPoll(sourcePath, lastModified);
	}

	@Override
	public void updateFileSavedTime(File sourceFile) {
		fileMonitor.updateFileSaveTime(sourceFile);
	}

	private static String[] buildClassPath() {
		File[] jars = new File(DirectoryManager.getProjectLibraryDir()).listFiles();
		String[] classPath = new String[jars.length];
		for (int i = 0; i < jars.length; i++) {
			if(jars[i].getAbsolutePath().toLowerCase().endsWith(".jar")){
				classPath[i] = jars[i].getAbsolutePath();
			}
		}
		return classPath;
	}
	
	synchronized public static DynamicClassLoader getInstance(){
		if(instance == null){
			instance = new KoanClassLoader();
		}
		return instance.clone();
	}
	
	@Override
	public KoanClassLoader clone(){
		return new KoanClassLoader(getBinDir(), getSourceDir(), getClassPath(), getClass().getClassLoader(), getTimeout(), fileMonitor);
	}
	
} public class KoanMethod {
	
	private final transient Method method;
	private final String lesson;
	private final boolean displayIncompleteException;
	private static final KoanSuiteCompilationListener listener = new KoanSuiteCompilationListener();
	
	private KoanMethod(KoanElementAttributes koanAttributes) throws SecurityException, NoSuchMethodException{
		this(null, koanAttributes);
	}
	
	public static KoanMethod getInstance(KoanElementAttributes koanAttributes){
		try {
			return new KoanMethod(koanAttributes);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	public static KoanMethod getInstance(Method method){
		return new KoanMethod(null, method, true);
	}
	
	public static KoanMethod getInstance(String lesson, Method method){
		return new KoanMethod(lesson, method, true);
	}
	
	private KoanMethod(String lesson, Method method, boolean displayIncompleteException){
		if(method == null){
			throw new IllegalArgumentException("method may not be null");
		}
		this.method = method;
		this.lesson = new RbVariableInjector(lesson, method).injectLessonVariables();
		this.displayIncompleteException = displayIncompleteException;
	}

	public KoanMethod(String lesson, KoanElementAttributes koanAttributes) throws SecurityException, NoSuchMethodException {
		this(	lesson, 
				KoanClassLoader.getInstance().loadClass(koanAttributes.className, listener)
					.getMethod(koanAttributes.name),
				!"false".equalsIgnoreCase(koanAttributes.displayIncompleteKoanException));
	}

	public String getLesson() {
		return lesson;
	}

	public Method getMethod() {
		return method;
	}
	
	public boolean displayIncompleteException() {
		return displayIncompleteException;
	}
	
	public KoanMethod clone(Method method){
		return new KoanMethod(lesson, method, displayIncompleteException);
	}
	
	@Override public String toString(){
		return "{"+getMethod().getName()
				+" : "+ (lesson.length() > 20 ? lesson.substring(0, 20) + "..." : lesson)+"}";
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + (displayIncompleteException ? 1231 : 1237);
		result = prime * result + ((lesson == null) ? 0 : lesson.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		KoanMethod other = (KoanMethod) obj;
		if (displayIncompleteException != other.displayIncompleteException)
			return false;
		if (lesson == null) {
			if (other.lesson != null)
				return false;
		} else if (!lesson.equals(other.lesson))
			return false;
		return true;
	}
	
	
} public abstract class AbstractArgumentBehavior implements ArgumentBehavior{
	
	public String getErrorMessage() {
		return Strings.getMessage(getClass(), "error");
	}
	
	public String getSuccessMessage() {
		return Strings.getMessage(getClass(), "success");
	}
	
} public abstract class KoanConstants {

	private KoanConstants(){}
	
	public static final String  __					= Strings.getMessage("__");
	
	public static final String  EOL					= System.getProperty("line.separator");
	public static final String  EOLS				= "[\n\r"+EOL+"]";
	
	public static final String  PERIOD 				= ".";
	public static final String  EXPECTATION_LEFT_ARG= "has expectation as wrong argument!";
	public static final String  EXPECTED_LEFT 		=  ":<";
	public static final String  EXPECTED_RIGHT 		= ">";
	public static final String  LINE_NO_START		= ".java:";
	public static final String  LINE_NO_END 		= ")";
	
	public static final String 	COMPLETE_CHAR 		= "X";
	public static final String 	INCOMPLETE_CHAR		= "-";
	
	public static final int		PROGRESS_BAR_WIDTH	= 50;
	public static final String 	PROGRESS_BAR_START	= "[";
	public static final String 	PROGRESS_BAR_END	= "]";
	
	public static final String  XML_PARAMETER_START = "${";
	public static final String  XML_PARAMETER_END 	= "}";
	
} public class KoanElementAttributes{
	
	public String name, displayIncompleteKoanException, className;
	
	public KoanElementAttributes(String name, String displayIncompleteKoanException, String className){
		this.name = name;
		this.displayIncompleteKoanException = displayIncompleteKoanException;
		this.className = className;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((className == null) ? 0 : className.hashCode());
		result = prime
				* result
				+ ((displayIncompleteKoanException == null) ? 0
						: displayIncompleteKoanException.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		KoanElementAttributes other = (KoanElementAttributes) obj;
		if (className == null) {
			if (other.className != null)
				return false;
		} else if (!className.equals(other.className))
			return false;
		if (displayIncompleteKoanException == null) {
			if (other.displayIncompleteKoanException != null)
				return false;
		} else if (!displayIncompleteKoanException
				.equals(other.displayIncompleteKoanException))
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}

	@Override
	public String toString() {
		return "KoanElementAttributes [name=" + name
				+ ", displayIncompleteKoanException="
				+ displayIncompleteKoanException + ", className=" + className
				+ "]";
	}
	
} public class KoanMethodResult {

	public final static KoanMethodResult PASSED = new KoanMethodResult(null,"PASSED","PASSED");
	
	private final String message;
	private final String lineNumber;
	private final KoanMethod failingMethod;	
	
	public KoanMethodResult(KoanMethod failingMethod, String message, String lineNumber){
		this.message = message;
		this.failingMethod = failingMethod;
		this.lineNumber = lineNumber;
	}

	public static KoanMethodResult getPassing() {
		return PASSED;
	}

	public String getMessage() {
		return message;
	}

	public String getLineNumber() {
		return lineNumber;
	}

	public KoanMethod getFailingMethod() {
		return failingMethod;
	}

	public boolean isPassed(){
		return this == PASSED;
	}
	
	@Override
	public String toString() {
		return "KoanMethodResult [message=" + message + ", lineNumber="
				+ lineNumber + ", failingMethod=" + failingMethod + "]";
	}
	
} public class ApplicationUtils {

	private static SuitePresenterFactory suitePresenterFactory = new SuitePresenterFactory();
	
	static public boolean isFirstTimeAppHasBeenRun() {
		File dataDirectory = new File(DirectoryManager.getDataDir());
		return !dataDirectory.exists();
	}
	
	static public boolean isWindows(){
		return System.getProperty("os.name").toLowerCase().contains("win");
	}

	static public SuitePresenter getPresenter(){
		return suitePresenterFactory.create();
	}
	
	public static class SuitePresenterFactory {
		protected SuitePresenter create(){
			return new ConsolePresenter();
		}
	}
} public class KoanSuiteCompilationListener implements CompilationListener {

	private boolean lastCompilationAttemptFailed = false;
	private String lastMessageShown = null;
	
	public void compilationFailed(File src, String[] command, int exitCode, String errorMessage, Throwable x) {
		if(lastMessageShown == null || !errorMessage.trim().equals(lastMessageShown.trim())){
			FileCompilerAction.LOGGING_HANDLER.compilationFailed(src, command, exitCode, errorMessage, x);
		}
		lastMessageShown = errorMessage;
		lastCompilationAttemptFailed = true;
	}
	
	public void compilationSucceeded(File src, String[] command, String stdIo, Throwable x) {
		lastMessageShown = null; // reset last failed compilation message
		lastCompilationAttemptFailed = false;
	}
	
	public boolean isLastCompilationAttemptFailure(){
		return lastCompilationAttemptFailed;
	}
} public abstract class DirectoryManager {
	
	private DirectoryManager(){}
	
	private static DirectorySet production = new ProductionDirectories();
	private static DirectorySet instance = production;
	
	public static final String FILESYSTEM_SEPARATOR	= System.getProperty("file.separator");
	
	public static void setDirectorySet(DirectorySet lInstance){
		instance = lInstance;
	}
	
	public static String getMainDir(){
		return constructMainDir(instance);
	}
	
	public static String getProdMainDir() {
		return constructMainDir(production);
	}
	
	private static String constructMainDir(DirectorySet directories){
		return injectFileSystemSeparators(	directories.getBaseDir(),
											directories.getProjectDir());
	}
	
	public static String getConfigDir(){
		return injectFileSystemSeparators(	instance.getBaseDir(),
											production.getProjectDir(),
											instance.getAppDir(),
											instance.getConfigDir());
	}
	
	public static String getSourceDir(){
		return constructProjectDir(instance, instance.getSourceDir());
	}
	
	public static String getProdSourceDir(){
		return constructProjectDir(production, production.getSourceDir());
	}
	
	public static String getBinDir(){
		return injectFileSystemSeparators(  instance.getBaseDir(),
											production.getProjectDir(),
											instance.getAppDir(),
											instance.getBinaryDir());
	}
	
	public static String getDataDir() {
		return injectFileSystemSeparators(	instance.getBaseDir(), 
											production.getProjectDir(),
											instance.getAppDir(), 
											instance.getDataDir());
	}
	
	private static String constructProjectDir(DirectorySet directories, String childDir){
		return injectFileSystemSeparators(	directories.getBaseDir(), 
											directories.getProjectDir(), 
											childDir);
	}
	
	public static String getProjectLibraryDir(){
		return injectFileSystemSeparators(	instance.getBaseDir(), 
											production.getProjectDir(),
											instance.getAppDir(),
											instance.getLibrariesDir());
	}
	
	public static String getProjectI18nDir(){
		return injectFileSystemSeparators(	instance.getBaseDir(),
											production.getProjectDir(),
											instance.getAppDir(),
											instance.getConfigDir(), 
											instance.getI18nDir());
	}
	
	public static String getProjectDataSourceDir() {
		return injectFileSystemSeparators(getDataDir(), instance.getSourceDir());
	}
	
	public static String getDataFile() {
		return injectFileSystemSeparators(getConfigDir(), "file_hashes.dat");
	}
	
	/**
	 * Will take "home", "wilford", "liberty" and produce: "/home/wilford/liberty"
	 * 
	 * @param folders
	 * @return concatenated folders w/ file separators
	 */
	public static String injectFileSystemSeparators(String...folders){
		StringBuilder builder = new StringBuilder();
		for(String folder : folders){
			if(FILESYSTEM_SEPARATOR.equals(folder)){
				continue;
			}
			builder.append(FILESYSTEM_SEPARATOR);
			while(folder.startsWith(FILESYSTEM_SEPARATOR)){
				folder = folder.substring(1);
			}
			while(folder.endsWith(FILESYSTEM_SEPARATOR)){
				folder = folder.substring(0, folder.lastIndexOf(FILESYSTEM_SEPARATOR));
			}
			builder.append(folder);
		}
		String constructedFolder = builder.toString();
		if(ApplicationUtils.isWindows()){
			if(constructedFolder.startsWith(FILESYSTEM_SEPARATOR)){
				constructedFolder = constructedFolder.substring(1);
			}
			constructedFolder = constructedFolder.replace("%20", " ");
		}
		return constructedFolder;
	}
	
}
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.sandwich.koan.ApplicationSettings;
import com.sandwich.koan.Koan;
import com.sandwich.koan.KoanClassLoader;
import com.sandwich.koan.KoanMethod;
import com.sandwich.koan.cmdline.behavior.AbstractArgumentBehavior;
import com.sandwich.koan.constant.KoanConstants;
import com.sandwich.koan.path.PathToEnlightenment;
import com.sandwich.koan.path.PathToEnlightenment.Path;
import com.sandwich.koan.path.xmltransformation.KoanElementAttributes;
import com.sandwich.koan.result.KoanMethodResult;
import com.sandwich.koan.result.KoanSuiteResult;
import com.sandwich.koan.result.KoanSuiteResult.KoanResultBuilder;
import com.sandwich.koan.util.ApplicationUtils;
import com.sandwich.util.ExceptionUtils;
import com.sandwich.util.KoanComparator;
import com.sandwich.util.io.KoanSuiteCompilationListener;
import com.sandwich.util.io.classloader.DynamicClassLoader;
import com.sandwich.util.io.directories.DirectoryManager;
import com.sandwich.util.io.filecompiler.CompilationListener;
import com.sandwich.util.io.filecompiler.FileCompiler;
                AppLauncher.exit(Math.min(255, result.getTotalNumberOfKoans() - result.getNumberPassing()));
            }
        }

	KoanSuiteResult runKoans() {
		List<String> passingSuites = new ArrayList<String>();
		List<String> failingSuites = new ArrayList<String>();
		String level = null;
		KoanMethodResult failure = null;
		DynamicClassLoader loader = KoanClassLoader.getInstance();
		Path pathToEnlightenment = getPathToEnlightenment();
		KoanSuiteCompilationListener compilationListener = new KoanSuiteCompilationListener();
		int successfull = 0;
		for (Entry<String, Map<String, Map<String, KoanElementAttributes>>> packages : pathToEnlightenment) {
			for (Entry<String, Map<String, KoanElementAttributes>> e : packages.getValue().entrySet()) {
				String name = e.getKey().substring(e.getKey().lastIndexOf('.')+1);
				if(failure == null){
					Object suite = safelyConstructSuite(loader, compilationListener, e);
					final List<KoanElementAttributes> attributes = new ArrayList<KoanElementAttributes>(e.getValue().values());
					final List<KoanMethod> methods = mergeJavaFilesMethodsAndThoseInXml(suite, attributes, pathToEnlightenment.getOnlyMethodNameToRun());
					Collections.sort(methods, new KoanComparator());
					for (final KoanMethod koan : methods) {
						KoanMethodResult result = KoanMethodRunner.run(suite, koan);
						if(KoanMethodResult.PASSED != result){
							// Test failed!
							failure = result;
							failingSuites.add(name);
							break;
						}else{
							successfull++;
						}
					}
					if (failure == null) {
						passingSuites.add(name);
					}
					level = packages.getKey();
				}else{
					failingSuites.add(name);
				}
			}
		}
		return new KoanResultBuilder()
				.level(level)
				.numberPassing(successfull)
				.totalNumberOfKoanMethods(pathToEnlightenment.getTotalNumberOfKoans())
				.methodResult(failure).passingCases(passingSuites)
				.remainingCases(failingSuites).build();
	}

	private Object safelyConstructSuite(DynamicClassLoader loader,
			KoanSuiteCompilationListener compilationListener,
			Entry<String, Map<String, KoanElementAttributes>> e) {
		// this is written strangely so stack trace will always be the same when constructSuite fails
		// this permits the app to only show the compilation failure once, despite the fact this
		// is getting hit every second.
		Object suite = null;
		while(suite == null || compilationListener.isLastCompilationAttemptFailure()) {
			suite = constructSuite(loader, e.getKey(), compilationListener);
			if(compilationListener.isLastCompilationAttemptFailure()){
			    if(!ApplicationSettings.isInteractive()){
			        AppLauncher.exit(255);
			    }
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e1) {}
			}
		}
		return suite;
	}

	private List<KoanMethod> mergeJavaFilesMethodsAndThoseInXml(Object suite,
			final List<KoanElementAttributes> attributes, String onlyMethodNameToRun) {
		final List<KoanMethod> methods = new ArrayList<KoanMethod>();
		final List<String> methodNames = new ArrayList<String>();
		for(KoanElementAttributes attributeSet : attributes){
			if(isMethodEligibleForRunning(onlyMethodNameToRun, attributeSet.name)){
				KoanMethod method = KoanMethod.getInstance(attributeSet);
				methods.add(method);
				methodNames.add(method.getMethod().getName());
			}
		}
		for(Method method : suite.getClass().getMethods()){
			if(!methodNames.contains(method.getName()) &&
					isMethodEligibleForRunning(onlyMethodNameToRun, method.getName()) &&
					method.getAnnotation(Koan.class) != null){
				methods.add(KoanMethod.getInstance(method));
			}
		}
		return methods;
	}

	private boolean isMethodEligibleForRunning(String onlyMethodNameToRun, String name) {
		return onlyMethodNameToRun == null || onlyMethodNameToRun.equals(name);
	}

	private Object constructSuite(DynamicClassLoader loader, String className, CompilationListener listener) {
		Object suite;
		try {
			Class<?> clazz = loader.loadClass(className, listener);
			if(!clazz.isAnonymousClass()){
				suite = clazz.newInstance();
			}else{
				Class<?> enclosingClass = clazz.getEnclosingClass();
				Constructor<?> cons = null;
				for(Constructor<?> c : clazz.getDeclaredConstructors()){
					for(Class<?> p : c.getParameterTypes()){
						if(p == enclosingClass){
							cons = c;
							break;
						}
					}
				}
				cons.setAccessible(true);
				suite = cons.newInstance(enclosingClass.newInstance());
			}
		} catch (Exception e1) {
			throw new RuntimeException(e1);
		} catch (Error e1) {
			if(e1.getClass() == Error.class && e1.getMessage().contains("Unresolved compilation problem")) {
				File sourceFile = FileCompiler.getSourceFileFromClass(
						DirectoryManager.getSourceDir(), className);