/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */

/*
Part of the Processing project - http://processing.org
Copyright (c) 2012-15 The Processing Foundation

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package processing.mode.java.pdex;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import javax.swing.DefaultListModel;

import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ArrayAccess;
import org.eclipse.jdt.core.dom.ArrayType;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.PrimitiveType;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationExpression;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;

import processing.app.Messages;
import processing.mode.java.JavaMode;

import com.google.classpath.ClassPath;
import com.google.classpath.RegExpResourceFilter;

@SuppressWarnings({ "unchecked" })
public class CompletionGenerator {

	public CompletionGenerator() {
		//addCompletionPopupListner();
		//loadJavaDoc();
	}


	public static CompletionCandidate[] checkForTypes(ASTNode node) {

		List<VariableDeclarationFragment> vdfs = null;
		switch (node.getNodeType()) {
		case ASTNode.TYPE_DECLARATION:
			return new CompletionCandidate[]{new CompletionCandidate((TypeDeclaration) node)};

		case ASTNode.METHOD_DECLARATION:
			MethodDeclaration md = (MethodDeclaration) node;
			log(getNodeAsString(md));
			List<ASTNode> params = (List<ASTNode>) md
					.getStructuralProperty(MethodDeclaration.PARAMETERS_PROPERTY);
			CompletionCandidate[] cand = new CompletionCandidate[params.size() + 1];
			cand[0] = new CompletionCandidate(md);
			for (int i = 0; i < params.size(); i++) {
//				cand[i + 1] = new CompletionCandidate(params.get(i).toString(), "", "",
//																							CompletionCandidate.LOCAL_VAR);
				cand[i + 1] = new CompletionCandidate((SingleVariableDeclaration) params.get(i));
			}
			return cand;

		case ASTNode.SINGLE_VARIABLE_DECLARATION:
			return new CompletionCandidate[]{new CompletionCandidate((SingleVariableDeclaration) node)};

		case ASTNode.FIELD_DECLARATION:
			vdfs = ((FieldDeclaration) node).fragments();
			break;
		case ASTNode.VARIABLE_DECLARATION_STATEMENT:
			vdfs = ((VariableDeclarationStatement) node).fragments();
			break;
		case ASTNode.VARIABLE_DECLARATION_EXPRESSION:
			vdfs = ((VariableDeclarationExpression) node).fragments();
			break;
		default:
			break;
		}

		if (vdfs != null) {
			CompletionCandidate ret[] = new CompletionCandidate[vdfs.size()];
			int i = 0;
			for (VariableDeclarationFragment vdf : vdfs) {
//				ret[i++] = new CompletionCandidate(getNodeAsString2(vdf), "", "",
//																					 CompletionCandidate.LOCAL_VAR);
				ret[i++] = new CompletionCandidate(vdf);
			}
			return ret;
		}

		return null;
	}

	/**
	 * Find the parent of the expression in a().b, this would give me the return
	 * type of a(), so that we can find all children of a() begininng with b
	 *
	 * @param nearestNode
	 * @param expression
	 * @return
	 */
	public static ASTNode resolveExpression(ASTNode nearestNode,
																					ASTNode expression, boolean noCompare) {
		log("Resolving " + getNodeAsString(expression) + " noComp "
				+ noCompare);
		if (expression instanceof SimpleName) {
			return findDeclaration2(((SimpleName) expression), nearestNode);
		} else if (expression instanceof MethodInvocation) {
			log("3. Method Invo "
					+ ((MethodInvocation) expression).getName());
			return findDeclaration2(((MethodInvocation) expression).getName(),
															nearestNode);
		} else if (expression instanceof FieldAccess) {
			log("2. Field access "
					+ getNodeAsString(((FieldAccess) expression).getExpression()) + "|||"
					+ getNodeAsString(((FieldAccess) expression).getName()));
			if (noCompare) {
				/*
				 * ASTNode ret = findDeclaration2(((FieldAccess) expression).getName(),
				 * nearestNode); log("Found as ->"+getNodeAsString(ret));
				 * return ret;
				 */
				return findDeclaration2(((FieldAccess) expression).getName(),
																nearestNode);
			} else {

				/*
				 * Note how for the next recursion, noCompare is reversed. Let's say
				 * I've typed getABC().quark.nin where nin is incomplete(ninja being the
				 * field), when execution first enters here, it calls resolveExpr again
				 * for "getABC().quark" where we know that quark field must be complete,
				 * so we toggle noCompare. And kaboom.
				 */
				return resolveExpression(nearestNode,
																 ((FieldAccess) expression).getExpression(),
																 true);
			}
			//return findDeclaration2(((FieldAccess) expression).getExpression(), nearestNode);
		} else if (expression instanceof QualifiedName) {
			log("1. Resolving "
					+ ((QualifiedName) expression).getQualifier() + " ||| "
					+ ((QualifiedName) expression).getName());
			if (noCompare) { // no compare, as in "abc.hello." need to resolve hello here
				return findDeclaration2(((QualifiedName) expression).getName(),
																nearestNode);
			} else {
				//User typed "abc.hello.by" (bye being complete), so need to resolve "abc.hello." only
				return findDeclaration2(((QualifiedName) expression).getQualifier(),
													nearestNode);
			}
		}

		return null;
	}

	/**
	 * Finds the type of the expression in foo.bar().a().b, this would give me the
	 * type of b if it exists in return type of a(). If noCompare is true,
	 * it'll return type of a()
	 * @param nearestNode
	 * @param astNode
	 * @return
	 */
	public static ClassMember resolveExpression3rdParty(PreprocessedSketch ps, ASTNode nearestNode,
																											ASTNode astNode, boolean noCompare) {
		log("Resolve 3rdParty expr-- " + getNodeAsString(astNode)
				+ " nearest node " + getNodeAsString(nearestNode));
		if(astNode == null) return null;
		ClassMember scopeParent;
		SimpleType stp;
		if(astNode instanceof SimpleName){
			ASTNode decl = findDeclaration2(((SimpleName)astNode),nearestNode);
			if(decl != null){
				// see if locally defined
				log(getNodeAsString(astNode)+" found decl -> " + getNodeAsString(decl));

				{
					if (decl.getNodeType() == ASTNode.TYPE_DECLARATION) {
						TypeDeclaration td = (TypeDeclaration) decl;
						return new ClassMember(ps, td);
					}
				}

				{ // Handle "array." x "array[1]."
					Type type = extracTypeInfo2(decl);
					if (type != null && type.isArrayType() &&
							astNode.getParent().getNodeType() != ASTNode.ARRAY_ACCESS) {
						// No array access, we want members of the array itself
						Type elementType = ((ArrayType) type).getElementType();

						// Get name of the element class
						String name = "";
						if (elementType.isSimpleType()) {
							Class<?> c = findClassIfExists(ps, elementType.toString());
							if (c != null) name = c.getName();
						} else if (elementType.isPrimitiveType()) {
							name = ((PrimitiveType) elementType).getPrimitiveTypeCode().toString();
						}

						// Convert element class to array class
						Class<?> arrayClass = getArrayClass(name, ps.classLoader);

						return arrayClass == null ? null : new ClassMember(arrayClass);
					}
				}

				return new ClassMember(ps, extracTypeInfo(decl));
			}
			else {
				// or in a predefined class?
				Class<?> tehClass = findClassIfExists(ps, astNode.toString());
				if (tehClass != null) {
					return new ClassMember(tehClass);
				}
			}
			astNode = astNode.getParent();
		}
		switch (astNode.getNodeType()) {
		//TODO: Notice the redundancy in the 3 cases, you can simplify things even more.
		case ASTNode.FIELD_ACCESS:
			FieldAccess fa = (FieldAccess) astNode;
			if (fa.getExpression() == null) {

				// TODO: Check for existence of 'new' keyword. Could be a ClassInstanceCreation

				// Local code or belongs to super class
				log("FA,Not implemented.");
				return null;
			} else {
				if (fa.getExpression() instanceof SimpleName) {
					stp = extracTypeInfo(findDeclaration2((SimpleName) fa.getExpression(),
																								nearestNode));
					if(stp == null){
						/*The type wasn't found in local code, so it might be something like
						 * log(), or maybe belonging to super class, etc.
						 */
						Class<?> tehClass = findClassIfExists(ps, fa.getExpression().toString());
						if (tehClass != null) {
							// Method Expression is a simple name and wasn't located locally, but found in a class
							// so look for method in this class.
							return definedIn3rdPartyClass(ps, new ClassMember(tehClass), fa
									.getName().toString());
						}
						log("FA resolve 3rd par, Can't resolve " + fa.getExpression());

						return null;
					}
					log("FA, SN Type " + getNodeAsString(stp));
					scopeParent = definedIn3rdPartyClass(ps, stp.getName().toString(), "THIS");

				} else {
					scopeParent = resolveExpression3rdParty(ps, nearestNode,
																									fa.getExpression(), noCompare);
				}
				log("FA, ScopeParent " + scopeParent);
				return definedIn3rdPartyClass(ps, scopeParent, fa.getName().toString());
			}
		case ASTNode.METHOD_INVOCATION:
			MethodInvocation mi = (MethodInvocation) astNode;
			ASTNode temp = findDeclaration2(mi.getName(), nearestNode);
			if(temp instanceof MethodDeclaration){
				// method is locally defined
				log(mi.getName() + " was found locally," + getNodeAsString(extracTypeInfo(temp)));

				{ // Handle "array." x "array[1]."
					Type type = extracTypeInfo2(temp);
					if (type != null && type.isArrayType() &&
							astNode.getParent().getNodeType() != ASTNode.ARRAY_ACCESS) {
						// No array access, we want members of the array itself
						Type elementType = ((ArrayType) type).getElementType();

						// Get name of the element class
						String name = "";
						if (elementType.isSimpleType()) {
							Class<?> c = findClassIfExists(ps, elementType.toString());
							if (c != null) name = c.getName();
						} else if (elementType.isPrimitiveType()) {
							name = ((PrimitiveType) elementType).getPrimitiveTypeCode().toString();
						}

						// Convert element class to array class
						Class<?> arrayClass = getArrayClass(name, ps.classLoader);

						return arrayClass == null ? null : new ClassMember(arrayClass);
					}
				}

				return new ClassMember(ps, extracTypeInfo(temp));
			}
			if (mi.getExpression() == null) {
//				if()
				//Local code or belongs to super class
				log("MI,Not implemented.");
				return null;
			} else {
				if (mi.getExpression() instanceof SimpleName) {
					ASTNode decl = findDeclaration2((SimpleName) mi.getExpression(),
																					nearestNode);
					if (decl != null) {
						if (decl.getNodeType() == ASTNode.TYPE_DECLARATION) {
							TypeDeclaration td = (TypeDeclaration) decl;
							return new ClassMember(ps, td);
						}

						stp = extracTypeInfo(decl);
						if(stp == null){
						/*The type wasn't found in local code, so it might be something like
						 * System.console()., or maybe belonging to super class, etc.
						 */
							Class<?> tehClass = findClassIfExists(ps, mi.getExpression().toString());
							if (tehClass != null) {
								// Method Expression is a simple name and wasn't located locally, but found in a class
								// so look for method in this class.
								return definedIn3rdPartyClass(ps, new ClassMember(tehClass), mi
										.getName().toString());
							}
							log("MI resolve 3rd par, Can't resolve " + mi.getExpression());
							return null;
						}
						log("MI, SN Type " + getNodeAsString(stp));
						ASTNode typeDec = findDeclaration2(stp.getName(),nearestNode);
						if(typeDec == null){
							log(stp.getName() + " couldn't be found locally..");
							Class<?> tehClass = findClassIfExists(ps, stp.getName().toString());
							if (tehClass != null) {
								// Method Expression is a simple name and wasn't located locally, but found in a class
								// so look for method in this class.
								return definedIn3rdPartyClass(ps, new ClassMember(tehClass), mi
										.getName().toString());
							}
							//return new ClassMember(findClassIfExists(stp.getName().toString()));
						}
						//scopeParent = definedIn3rdPartyClass(stp.getName().toString(), "THIS");
						return definedIn3rdPartyClass(ps, new ClassMember(ps, typeDec), mi
								.getName().toString());
					}
				} else {
					log("MI EXP.."+getNodeAsString(mi.getExpression()));
//					return null;
					scopeParent = resolveExpression3rdParty(ps, nearestNode,
																									mi.getExpression(), noCompare);
					log("MI, ScopeParent " + scopeParent);
					return definedIn3rdPartyClass(ps, scopeParent, mi.getName().toString());
				}

			}
			break;
		case ASTNode.QUALIFIED_NAME:
			QualifiedName qn = (QualifiedName) astNode;
			ASTNode temp2 = findDeclaration2(qn.getName(), nearestNode);
			if(temp2 instanceof FieldDeclaration){
				// field is locally defined
				log(qn.getName() + " was found locally," + getNodeAsString(extracTypeInfo(temp2)));
				return new ClassMember(ps, extracTypeInfo(temp2));
			}
			if (qn.getQualifier() == null) {
				log("QN,Not implemented.");
				return null;
			} else	{

				if (qn.getQualifier() instanceof SimpleName) {
					stp = extracTypeInfo(findDeclaration2(qn.getQualifier(), nearestNode));
					if(stp == null){
						/*The type wasn't found in local code, so it might be something like
						 * log(), or maybe belonging to super class, etc.
						 */
						Class<?> tehClass = findClassIfExists(ps, qn.getQualifier().toString());
						if (tehClass != null) {
							// note how similar thing is called on line 690. Check check.
							return definedIn3rdPartyClass(ps, new ClassMember(tehClass), qn
									.getName().toString());
						}
						log("QN resolve 3rd par, Can't resolve " + qn.getQualifier());
						return null;
					}
					log("QN, SN Local Type " + getNodeAsString(stp));
					//scopeParent = definedIn3rdPartyClass(stp.getName().toString(), "THIS");
					ASTNode typeDec = findDeclaration2(stp.getName(),nearestNode);
					if(typeDec == null){
						log(stp.getName() + " couldn't be found locally..");

						Class<?> tehClass = findClassIfExists(ps, stp.getName().toString());
						if (tehClass != null) {
							// note how similar thing is called on line 690. Check check.
							return definedIn3rdPartyClass(ps, new ClassMember(tehClass), qn
									.getName().toString());
						}
						log("QN resolve 3rd par, Can't resolve " + qn.getQualifier());
						return null;
					}
					return definedIn3rdPartyClass(ps, new ClassMember(ps, typeDec), qn
																				.getName().toString());
				} else {
					scopeParent = resolveExpression3rdParty(ps, nearestNode,
																									qn.getQualifier(), noCompare);
					log("QN, ScopeParent " + scopeParent);
					return definedIn3rdPartyClass(ps, scopeParent, qn.getName().toString());
				}

			}
		case ASTNode.ARRAY_ACCESS:
			ArrayAccess arac = (ArrayAccess)astNode;
			return resolveExpression3rdParty(ps, nearestNode, arac.getArray(), noCompare);
		default:
			log("Unaccounted type " + getNodeAsString(astNode));
			break;
		}

		return null;
	}


	public static Class<?> getArrayClass(String elementClass, ClassLoader classLoader) {
		String name;
		if (elementClass.startsWith("[")) {
			// just add a leading "["
			name = "[" + elementClass;
		} else if (elementClass.equals("boolean")) {
			name = "[Z";
		} else if (elementClass.equals("byte")) {
			name = "[B";
		} else if (elementClass.equals("char")) {
			name = "[C";
		} else if (elementClass.equals("double")) {
			name = "[D";
		} else if (elementClass.equals("float")) {
			name = "[F";
		} else if (elementClass.equals("int")) {
			name = "[I";
		} else if (elementClass.equals("long")) {
			name = "[J";
		} else if (elementClass.equals("short")) {
			name = "[S";
		} else {
			// must be an object non-array class
			name = "[L" + elementClass + ";";
		}
		return loadClass(name, classLoader);
	}


	/**
	 * For a().abc.a123 this would return a123
	 *
	 * @param expression
	 * @return
	 */
	public static ASTNode getChildExpression(ASTNode expression) {
//		ASTNode anode = null;
		if (expression instanceof SimpleName) {
			return expression;
		} else if (expression instanceof FieldAccess) {
			return ((FieldAccess) expression).getName();
		} else if (expression instanceof QualifiedName) {
			return ((QualifiedName) expression).getName();
		}else if (expression instanceof MethodInvocation) {
			return ((MethodInvocation) expression).getName();
		}else if(expression instanceof ArrayAccess){
			return ((ArrayAccess)expression).getArray();
		}
		log(" getChildExpression returning NULL for "
				+ getNodeAsString(expression));
		return null;
	}

	public static ASTNode getParentExpression(ASTNode expression) {
//	ASTNode anode = null;
		if (expression instanceof SimpleName) {
			return expression;
		} else if (expression instanceof FieldAccess) {
			return ((FieldAccess) expression).getExpression();
		} else if (expression instanceof QualifiedName) {
			return ((QualifiedName) expression).getQualifier();
		} else if (expression instanceof MethodInvocation) {
			return ((MethodInvocation) expression).getExpression();
		} else if (expression instanceof ArrayAccess) {
			return ((ArrayAccess) expression).getArray();
		}
		log("getParentExpression returning NULL for "
				+ getNodeAsString(expression));
		return null;
	}


	/**
	 * Loads classes from .jar files in sketch classpath
	 *
	 * @param typeName
	 * @param child
	 * @param noCompare
	 * @return
	 */
	public static ArrayList<CompletionCandidate> getMembersForType(PreprocessedSketch ps,
																																 String typeName,
																																 String child,
																																 boolean noCompare,
																																 boolean staticOnly) {
		ArrayList<CompletionCandidate> candidates = new ArrayList<>();
		log("In GMFT(), Looking for match " + child
				+ " in class " + typeName + " noCompare " + noCompare + " staticOnly "
				+ staticOnly);
		Class<?> probableClass = findClassIfExists(ps, typeName);
		if(probableClass == null){
			log("In GMFT(), class not found.");
			return candidates;
		}
	 return getMembersForType(ps, new ClassMember(probableClass), child, noCompare, staticOnly);

	}

	public static ArrayList<CompletionCandidate> getMembersForType(PreprocessedSketch ps,
																																 ClassMember tehClass,
																																 String childToLookFor,
																																 boolean noCompare,
																																 boolean staticOnly) {
		String child = childToLookFor.toLowerCase();
		ArrayList<CompletionCandidate> candidates = new ArrayList<>();
		log("getMemFoType-> Looking for match " + child
				+ " inside " + tehClass + " noCompare " + noCompare + " staticOnly "
				+ staticOnly);
		if(tehClass == null){
			return candidates;
		}
		// tehClass will either be a TypeDecl defined locally
		if(tehClass.getDeclaringNode() instanceof TypeDeclaration){
			TypeDeclaration td = (TypeDeclaration) tehClass.getDeclaringNode();
			{
				FieldDeclaration[] fields = td.getFields();
				for (FieldDeclaration field : fields) {
					if (staticOnly && !isStatic(field.modifiers())) {
						continue;
					}
					List<VariableDeclarationFragment> vdfs = field.fragments();
					for (VariableDeclarationFragment vdf : vdfs) {
						if (noCompare) {
							candidates.add(new CompletionCandidate(vdf));
						} else if (vdf.getName().toString().toLowerCase().startsWith(child))
							candidates.add(new CompletionCandidate(vdf));
					}
				}
			}
			{
				MethodDeclaration[] methods = td.getMethods();
				for (MethodDeclaration method : methods) {
					if (staticOnly && !isStatic(method.modifiers())) {
						continue;
					}
					if (noCompare) {
						candidates.add(new CompletionCandidate(method));
					} else if (method.getName().toString().toLowerCase()
							.startsWith(child))
						candidates.add(new CompletionCandidate(method));
				}
			}

			ArrayList<CompletionCandidate> superClassCandidates;
			if(td.getSuperclassType() != null){
				log(getNodeAsString(td.getSuperclassType()) + " <-Looking into superclass of " + tehClass);
				superClassCandidates = getMembersForType(ps, new ClassMember(ps, td
																										 .getSuperclassType()),
																								 childToLookFor, noCompare, staticOnly);
			}
			else
			{
				superClassCandidates = getMembersForType(ps, new ClassMember(Object.class),
																								 childToLookFor, noCompare, staticOnly);
			}
			for (CompletionCandidate cc : superClassCandidates) {
				candidates.add(cc);
			}
			return candidates;
		}

		// Or tehClass will be a predefined class

		Class<?> probableClass;
		if (tehClass.getClass_() != null) {
			probableClass = tehClass.getClass_();
		} else {
			probableClass = findClassIfExists(ps, tehClass.getTypeAsString());
			if (probableClass == null) {
				log("Couldn't find class " + tehClass.getTypeAsString());
				return candidates;
			}
			log("Loaded " + probableClass.toString());
		}
		for (Method method : probableClass.getMethods()) {
			if (!Modifier.isStatic(method.getModifiers()) && staticOnly) {
				continue;
			}

			StringBuilder label = new StringBuilder(method.getName() + "(");
			for (int i = 0; i < method.getParameterTypes().length; i++) {
				label.append(method.getParameterTypes()[i].getSimpleName());
				if (i < method.getParameterTypes().length - 1)
					label.append(",");
			}
			label.append(")");
			if (noCompare) {
				candidates.add(new CompletionCandidate(method));
			} else if (label.toString().toLowerCase().startsWith(child)) {
				candidates.add(new CompletionCandidate(method));
			}
		}
		for (Field field : probableClass.getFields()) {
			if (!Modifier.isStatic(field.getModifiers()) && staticOnly) {
				continue;
			}
			if (noCompare) {
				candidates.add(new CompletionCandidate(field));
			} else if (field.getName().toLowerCase().startsWith(child)) {
				candidates.add(new CompletionCandidate(field));
			}
		}
		if (probableClass.isArray() && !staticOnly) {
			// add array members manually, they can't be fetched through code

			String className = probableClass.getSimpleName();

			if (noCompare || "clone()".startsWith(child)) {
				String methodLabel = "<html>clone() : " + className +
						" - <font color=#777777>" + className + "</font></html>";
				candidates.add(new CompletionCandidate("clone()", methodLabel, "clone()",
																							 CompletionCandidate.PREDEF_METHOD));
			}

			if ("length".startsWith(child)) {
				String fieldLabel = "<html>length : int - <font color=#777777>" +
						className + "</font></html>";
				candidates.add(new CompletionCandidate("length", fieldLabel, "length",
																							 CompletionCandidate.PREDEF_FIELD));
			}
		}
		return candidates;
	}

	private static boolean isStatic(List<org.eclipse.jdt.core.dom.Modifier> modifiers) {
		for (org.eclipse.jdt.core.dom.Modifier m : modifiers) {
			if (m.isStatic()) return true;
		}
		return false;
	}


	/**
	 * Searches for the particular class in the default list of imports as well as
	 * the Sketch classpath
	 * @param className
	 * @return
	 */
	protected static Class<?> findClassIfExists(PreprocessedSketch ps, String className){
		if (className == null){
			return null;
		}

		if (className.indexOf('.') >= 0) {
			// Figure out what is package and what is class
			String[] parts = className.split("\\.");
			String newClassName = parts[0];
			int i = 1;
			while (i < parts.length &&
					ps.classPath.isPackage(newClassName)) {
				newClassName = newClassName + "/" + parts[i++];
			}
			while (i < parts.length) {
				newClassName = newClassName + "$" + parts[i++];
			}
			className = newClassName.replace('/', '.');
		}

		// First, see if the classname is a fully qualified name and loads straightaway
		Class<?> tehClass = loadClass(className, ps.classLoader);

		if (tehClass != null) {
			//log(tehClass.getName() + " located straightaway");
			return tehClass;
		}

		// This name is qualified and it already had its chance
		if (className.indexOf('.') >= 0) {
			return null;
		}

		log("Looking in the classloader for " + className);
		// Using ClassPath and RegExResourceFilter to find a matching class
		// and then loading the thing might be simpler and faster

		// These can be preprocessed during error check for performance
		// (collect, split into starred and not starred)
		List<ImportStatement> programImports = ps.programImports;
		List<ImportStatement> codeFolderImports = ps.codeFolderImports;
		List<ImportStatement> coreAndDefaultImports = ps.coreAndDefaultImports;

		ImportStatement javaLang = ImportStatement.wholePackage("java.lang");

		Stream<List<ImportStatement>> importListStream =
				Stream.of(Collections.singletonList(javaLang), coreAndDefaultImports,
									programImports, codeFolderImports);

		final String finalClassName = className;

		// These streams can be made unordered parallel if it helps performance
		return importListStream
				.map(list -> list.stream()
						.map(is -> {
							if (is.getClassName().equals(finalClassName)) {
								return is.getFullClassName();
							} else if (is.isStarredImport()) {
								return is.getPackageName() + "." + finalClassName;
							}
							return null;
						})
						.filter(name -> name != null)
						.map(name -> loadClass(name, ps.classLoader))
						.filter(cls -> cls != null)
						.findAny())
				.filter(Optional::isPresent)
				.map(Optional::get)
				.findAny()
				.orElse(null);
	}

	protected static Class<?> loadClass(String className, ClassLoader classLoader){
		Class<?> tehClass = null;
		if (className != null) {
			try {
				tehClass = Class.forName(className, false, classLoader);
			} catch (ClassNotFoundException e) {
				//log("Doesn't exist in package: ");
			}
		}
		return tehClass;
	}

	public static ClassMember definedIn3rdPartyClass(PreprocessedSketch ps, String className,String memberName){
		Class<?> probableClass = findClassIfExists(ps, className);
		if (probableClass == null) {
			log("Couldn't load " + className);
			return null;
		}
		if (memberName.equals("THIS")) {
			return new ClassMember(probableClass);
		} else {
			return definedIn3rdPartyClass(ps, new ClassMember(probableClass), memberName);
		}
	}

	public static ClassMember definedIn3rdPartyClass(PreprocessedSketch ps, ClassMember tehClass,String memberName){
		if(tehClass == null)
			return null;
		log("definedIn3rdPartyClass-> Looking for " + memberName
				+ " in " + tehClass);
		String memberNameL = memberName.toLowerCase();
		if (tehClass.getDeclaringNode() instanceof TypeDeclaration) {

			TypeDeclaration td = (TypeDeclaration) tehClass.getDeclaringNode();
			for (int i = 0; i < td.getFields().length; i++) {
				List<VariableDeclarationFragment> vdfs =
					td.getFields()[i].fragments();
				for (VariableDeclarationFragment vdf : vdfs) {
					if (vdf.getName().toString().toLowerCase()
							.startsWith(memberNameL))
						return new ClassMember(ps, vdf);
				}

			}
			for (int i = 0; i < td.getMethods().length; i++) {
			 if (td.getMethods()[i].getName().toString().toLowerCase()
						.startsWith(memberNameL))
				 return new ClassMember(ps, td.getMethods()[i]);
			}
			if (td.getSuperclassType() != null) {
				log(getNodeAsString(td.getSuperclassType()) + " <-Looking into superclass of " + tehClass);
				return definedIn3rdPartyClass(ps, new ClassMember(ps, td
																										 .getSuperclassType()),memberName);
			} else {
				return definedIn3rdPartyClass(ps, new ClassMember(Object.class),memberName);
			}
		}

		Class<?> probableClass;
		if (tehClass.getClass_() != null) {
			probableClass = tehClass.getClass_();
		} else {
			probableClass = findClassIfExists(ps, tehClass.getTypeAsString());
			log("Loaded " + probableClass.toString());
		}
		for (Method method : probableClass.getMethods()) {
			if (method.getName().equalsIgnoreCase(memberName)) {
				return new ClassMember(method);
			}
		}
		for (Field field : probableClass.getFields()) {
			if (field.getName().equalsIgnoreCase(memberName)) {
				return new ClassMember(field);
			}
		}
		return null;
	}


	protected static ASTNode findClosestParentNode(int lineNumber, ASTNode node) {
		// Base.loge("Props of " + node.getClass().getName());
		for (StructuralPropertyDescriptor prop : (Iterable<StructuralPropertyDescriptor>) node
				.structuralPropertiesForType()) {
			if (prop.isChildProperty() || prop.isSimpleProperty()) {
				if (node.getStructuralProperty(prop) != null) {
//					System.out
//							.println(node.getStructuralProperty(prop) + " -> " + (prop));
					if (node.getStructuralProperty(prop) instanceof ASTNode) {
						ASTNode cnode = (ASTNode) node.getStructuralProperty(prop);
//						log("Looking at " + getNodeAsString(cnode)+ " for line num " + lineNumber);
						int cLineNum = ((CompilationUnit) cnode.getRoot())
								.getLineNumber(cnode.getStartPosition() + cnode.getLength());
						if (getLineNumber(cnode) <= lineNumber && lineNumber <= cLineNum) {
							return findClosestParentNode(lineNumber, cnode);
						}
					}
				}
			} else if (prop.isChildListProperty()) {
				List<ASTNode> nodelist = (List<ASTNode>) node
						.getStructuralProperty(prop);
				for (ASTNode cnode : nodelist) {
					int cLineNum = ((CompilationUnit) cnode.getRoot())
							.getLineNumber(cnode.getStartPosition() + cnode.getLength());
//					log("Looking at " + getNodeAsString(cnode)+ " for line num " + lineNumber);
					if (getLineNumber(cnode) <= lineNumber && lineNumber <= cLineNum) {
						return findClosestParentNode(lineNumber, cnode);
					}
				}
			}
		}
		return node;
	}

	protected static ASTNode findClosestNode(int lineNumber, ASTNode node) {
		log("findClosestNode to line " + lineNumber);
		ASTNode parent = findClosestParentNode(lineNumber, node);
		log("findClosestParentNode returned " + getNodeAsString(parent));
		if (parent == null)
			return null;
		if (getLineNumber(parent) == lineNumber){
			log(parent + "|PNode " + getLineNumber(parent) + ", lfor " + lineNumber );
			return parent;
		}
		List<ASTNode> nodes;
		if (parent instanceof TypeDeclaration) {
			nodes = ((TypeDeclaration) parent).bodyDeclarations();
		} else if (parent instanceof Block) {
			nodes = ((Block) parent).statements();
		} else {
			log("findClosestNode() found " + getNodeAsString(parent));
			return null;
		}

		if (nodes.size() > 0) {
			ASTNode retNode = parent;
			for (ASTNode cNode : nodes) {
				log(cNode + "|cNode " + getLineNumber(cNode) + ", lfor " + lineNumber);
				if (getLineNumber(cNode) <= lineNumber)
					retNode = cNode;
			}

			return retNode;
		}
		return parent;
	}


	/**
	 * Fetches line number of the node in its CompilationUnit.
	 * @param node
	 * @return
	 */
	public static int getLineNumber(ASTNode node) {
		return ((CompilationUnit) node.getRoot()).getLineNumber(node
				.getStartPosition());
	}


	/*
	protected SketchOutline sketchOutline;

	public void showSketchOutline() {
		if (editor.hasJavaTabs()) return;

		sketchOutline = new SketchOutline(editor, codeTree);
		sketchOutline.show();
	}


	public void showTabOutline() {
		new TabOutline(editor).show();
	}
	*/


	/**
	 * Give this thing a {@link Name} instance - a {@link SimpleName} from the
	 * ASTNode for ex, and it tries its level best to locate its declaration in
	 * the AST. It really does.
	 *
	 * @param findMe
	 * @return
	 */
	protected static ASTNode findDeclaration(Name findMe) {

		// WARNING: You're entering the Rube Goldberg territory of Experimental Mode.
		// To debug this code, thou must take the Recursive Leap of Faith.

		// log("entering --findDeclaration1 -- " + findMe.toString());
		ASTNode declaringClass;
		ASTNode parent = findMe.getParent();
		ASTNode ret;
		ArrayList<Integer> constrains = new ArrayList<>();
		if (parent.getNodeType() == ASTNode.METHOD_INVOCATION) {
			Expression exp = (Expression) parent.getStructuralProperty(MethodInvocation.EXPRESSION_PROPERTY);
			//TODO: Note the imbalance of constrains.add(ASTNode.METHOD_DECLARATION);
			// Possibly a bug here. Investigate later.
			if (((MethodInvocation) parent).getName().toString()
					.equals(findMe.toString())) {
				constrains.add(ASTNode.METHOD_DECLARATION);

				if (exp != null) {
					constrains.add(ASTNode.TYPE_DECLARATION);
//					log("MI EXP: " + exp.toString() + " of type "
//							+ exp.getClass().getName() + " parent: " + exp.getParent());
					if (exp instanceof MethodInvocation) {
						SimpleType stp = extracTypeInfo(findDeclaration(((MethodInvocation) exp)
								.getName()));
						if (stp == null)
							return null;
						declaringClass = findDeclaration(stp.getName());
						return definedIn(declaringClass, ((MethodInvocation) parent)
								.getName().toString(), constrains);
					} else if (exp instanceof FieldAccess) {
						SimpleType stp = extracTypeInfo(findDeclaration(((FieldAccess) exp)
								.getName()));
						if (stp == null)
							return null;
						declaringClass = findDeclaration((stp.getName()));
						return definedIn(declaringClass, ((MethodInvocation) parent)
								.getName().toString(), constrains);
					}
					if (exp instanceof SimpleName) {
						SimpleType stp = extracTypeInfo(findDeclaration(((SimpleName) exp)));
						if (stp == null)
							return null;
						declaringClass = findDeclaration(stp.getName());
//						log("MI.SN " + getNodeAsString(declaringClass));
						constrains.add(ASTNode.METHOD_DECLARATION);
						return definedIn(declaringClass, ((MethodInvocation) parent)
								.getName().toString(), constrains);
					}

				}
			} else {
				parent = parent.getParent(); // Move one up the ast. V V IMP!!
			}
		} else if (parent.getNodeType() == ASTNode.FIELD_ACCESS) {
			FieldAccess fa = (FieldAccess) parent;
			Expression exp = fa.getExpression();
			if (fa.getName().toString().equals(findMe.toString())) {
				constrains.add(ASTNode.FIELD_DECLARATION);

				if (exp != null) {
					constrains.add(ASTNode.TYPE_DECLARATION);
//					log("FA EXP: " + exp.toString() + " of type "
//							+ exp.getClass().getName() + " parent: " + exp.getParent());
					if (exp instanceof MethodInvocation) {
						SimpleType stp = extracTypeInfo(findDeclaration(((MethodInvocation) exp)
								.getName()));
						if (stp == null)
							return null;
						declaringClass = findDeclaration(stp.getName());
						return definedIn(declaringClass, fa.getName().toString(),
														 constrains);
					} else if (exp instanceof FieldAccess) {
						SimpleType stp = extracTypeInfo(findDeclaration(((FieldAccess) exp)
								.getName()));
						if (stp == null)
							return null;
						declaringClass = findDeclaration((stp.getName()));
						constrains.add(ASTNode.TYPE_DECLARATION);
						return definedIn(declaringClass, fa.getName().toString(),
														 constrains);
					}
					if (exp instanceof SimpleName) {
						SimpleType stp = extracTypeInfo(findDeclaration(((SimpleName) exp)));
						if (stp == null)
							return null;
						declaringClass = findDeclaration(stp.getName());
//						log("FA.SN " + getNodeAsString(declaringClass));
						constrains.add(ASTNode.METHOD_DECLARATION);
						return definedIn(declaringClass, fa.getName().toString(),
														 constrains);
					}
				}

			} else {
				parent = parent.getParent(); // Move one up the ast. V V IMP!!
			}
		} else if (parent.getNodeType() == ASTNode.QUALIFIED_NAME) {

			QualifiedName qn = (QualifiedName) parent;
			if (!findMe.toString().equals(qn.getQualifier().toString())) {

				SimpleType stp = extracTypeInfo(findDeclaration((qn.getQualifier())));
//				log(qn.getQualifier() + "->" + qn.getName());
				if (stp == null) {
					return null;
				}

				declaringClass = findDeclaration(stp.getName());

//				log("QN decl class: " + getNodeAsString(declaringClass));
				constrains.clear();
				constrains.add(ASTNode.TYPE_DECLARATION);
				constrains.add(ASTNode.FIELD_DECLARATION);
				return definedIn(declaringClass, qn.getName().toString(), constrains);
			}
			else{
				if(findMe instanceof QualifiedName){
					QualifiedName qnn = (QualifiedName) findMe;
//					log("findMe is a QN, "
//							+ (qnn.getQualifier().toString() + " other " + qnn.getName()
//									.toString()));

					SimpleType stp = extracTypeInfo(findDeclaration((qnn.getQualifier())));
					if (stp == null) {
						return null;
					}
					declaringClass = findDeclaration(stp.getName());
					constrains.clear();
					constrains.add(ASTNode.TYPE_DECLARATION);
					constrains.add(ASTNode.FIELD_DECLARATION);
					return definedIn(declaringClass, qnn.getName().toString(),
													 constrains);
				}
			}
		} else if (parent.getNodeType() == ASTNode.SIMPLE_TYPE) {
			constrains.add(ASTNode.TYPE_DECLARATION);
			if (parent.getParent().getNodeType() == ASTNode.CLASS_INSTANCE_CREATION) {
				constrains.add(ASTNode.CLASS_INSTANCE_CREATION);
			}
		} else if (parent.getNodeType() == ASTNode.TYPE_DECLARATION) {
			// The condition where we look up the name of a class decl
			TypeDeclaration td = (TypeDeclaration) parent;
			if (findMe.equals(td.getName())) {
				return parent;
			}

		} else if (parent instanceof Expression) {
//			constrains.add(ASTNode.TYPE_DECLARATION);
//			constrains.add(ASTNode.METHOD_DECLARATION);
//			constrains.add(ASTNode.FIELD_DECLARATION);
		}
//		else if(findMe instanceof QualifiedName){
//			QualifiedName qn = (QualifiedName) findMe;
//			System.out
//					.println("findMe is a QN, "
//							+ (qn.getQualifier().toString() + " other " + qn.getName()
//									.toString()));
//		}
		while (parent != null) {
//			log("findDeclaration1 -> " + getNodeAsString(parent));
			for (Object oprop : parent.structuralPropertiesForType()) {
				StructuralPropertyDescriptor prop = (StructuralPropertyDescriptor) oprop;
				if (prop.isChildProperty() || prop.isSimpleProperty()) {
					if (parent.getStructuralProperty(prop) instanceof ASTNode) {
//						log(prop + " C/S Prop of -> "
//								+ getNodeAsString(parent));
						ret = definedIn((ASTNode) parent.getStructuralProperty(prop),
														findMe.toString(), constrains);
						if (ret != null)
							return ret;
					}
				} else if (prop.isChildListProperty()) {
//					log((prop) + " ChildList props of "
//							+ getNodeAsString(parent));
					List<ASTNode> nodelist = (List<ASTNode>) parent
							.getStructuralProperty(prop);
					for (ASTNode retNode : nodelist) {
						ret = definedIn(retNode, findMe.toString(), constrains);
						if (ret != null)
							return ret;
					}
				}
			}
			parent = parent.getParent();
		}
		return null;
	}

	/**
	 * A variation of findDeclaration() but accepts an alternate parent ASTNode
	 * @param findMe
	 * @param alternateParent
	 * @return
	 */
	protected static ASTNode findDeclaration2(Name findMe, ASTNode alternateParent) {
		ASTNode declaringClass;
		ASTNode parent = findMe.getParent();
		ASTNode ret;
		ArrayList<Integer> constrains = new ArrayList<>();
		if (parent.getNodeType() == ASTNode.METHOD_INVOCATION) {
			Expression exp = (Expression) parent.getStructuralProperty(MethodInvocation.EXPRESSION_PROPERTY);
			//TODO: Note the imbalance of constrains.add(ASTNode.METHOD_DECLARATION);
			// Possibly a bug here. Investigate later.
			if (((MethodInvocation) parent).getName().toString()
					.equals(findMe.toString())) {
				constrains.add(ASTNode.METHOD_DECLARATION);

				if (exp != null) {
					constrains.add(ASTNode.TYPE_DECLARATION);
//					log("MI EXP: " + exp.toString() + " of type "
//							+ exp.getClass().getName() + " parent: " + exp.getParent());
					if (exp instanceof MethodInvocation) {
						SimpleType stp = extracTypeInfo(findDeclaration2(((MethodInvocation) exp)
																																 .getName(),
																														 alternateParent));
						if (stp == null)
							return null;
						declaringClass = findDeclaration2(stp.getName(), alternateParent);
						return definedIn(declaringClass, ((MethodInvocation) parent)
								.getName().toString(), constrains);
					} else if (exp instanceof FieldAccess) {
						SimpleType stp = extracTypeInfo(findDeclaration2(((FieldAccess) exp)
																																 .getName(),
																														 alternateParent));
						if (stp == null)
							return null;
						declaringClass = findDeclaration2((stp.getName()), alternateParent);
						return definedIn(declaringClass, ((MethodInvocation) parent)
								.getName().toString(), constrains);
					}
					if (exp instanceof SimpleName) {
						SimpleType stp = extracTypeInfo(findDeclaration2(((SimpleName) exp),
																														 alternateParent));
						if (stp == null)
							return null;
						declaringClass = findDeclaration2(stp.getName(), alternateParent);
//						log("MI.SN " + getNodeAsString(declaringClass));
						constrains.add(ASTNode.METHOD_DECLARATION);
						return definedIn(declaringClass, ((MethodInvocation) parent)
								.getName().toString(), constrains);
					}

				}
			} else {
				parent = parent.getParent(); // Move one up the ast. V V IMP!!
				alternateParent = alternateParent.getParent();
			}
		} else if (parent.getNodeType() == ASTNode.FIELD_ACCESS) {
			FieldAccess fa = (FieldAccess) parent;
			Expression exp = fa.getExpression();
			if (fa.getName().toString().equals(findMe.toString())) {
				constrains.add(ASTNode.FIELD_DECLARATION);

				if (exp != null) {
					constrains.add(ASTNode.TYPE_DECLARATION);
//					log("FA EXP: " + exp.toString() + " of type "
//							+ exp.getClass().getName() + " parent: " + exp.getParent());
					if (exp instanceof MethodInvocation) {
						SimpleType stp = extracTypeInfo(findDeclaration2(((MethodInvocation) exp)
																																 .getName(),
																														 alternateParent));
						if (stp == null)
							return null;
						declaringClass = findDeclaration2(stp.getName(), alternateParent);
						return definedIn(declaringClass, fa.getName().toString(),
														 constrains);
					} else if (exp instanceof FieldAccess) {
						SimpleType stp = extracTypeInfo(findDeclaration2(((FieldAccess) exp)
																																 .getName(),
																														 alternateParent));
						if (stp == null)
							return null;
						declaringClass = findDeclaration2((stp.getName()), alternateParent);
						constrains.add(ASTNode.TYPE_DECLARATION);
						return definedIn(declaringClass, fa.getName().toString(),
														 constrains);
					}
					if (exp instanceof SimpleName) {
						SimpleType stp = extracTypeInfo(findDeclaration2(((SimpleName) exp),
																														 alternateParent));
						if (stp == null)
							return null;
						declaringClass = findDeclaration2(stp.getName(), alternateParent);
//						log("FA.SN " + getNodeAsString(declaringClass));
						constrains.add(ASTNode.METHOD_DECLARATION);
						return definedIn(declaringClass, fa.getName().toString(),
														 constrains);
					}
				}

			} else {
				parent = parent.getParent(); // Move one up the ast. V V IMP!!
				alternateParent = alternateParent.getParent();
			}
		} else if (parent.getNodeType() == ASTNode.QUALIFIED_NAME) {

			QualifiedName qn = (QualifiedName) parent;
			if (!findMe.toString().equals(qn.getQualifier().toString())) {

				SimpleType stp = extracTypeInfo(findDeclaration2((qn.getQualifier()),
																												 alternateParent));
				if(stp == null)
					return null;
				declaringClass = findDeclaration2(stp.getName(), alternateParent);
//				log(qn.getQualifier() + "->" + qn.getName());
//				log("QN decl class: " + getNodeAsString(declaringClass));
				constrains.clear();
				constrains.add(ASTNode.TYPE_DECLARATION);
				constrains.add(ASTNode.FIELD_DECLARATION);
				return definedIn(declaringClass, qn.getName().toString(), constrains);
			}
			else{
				if(findMe instanceof QualifiedName){
					QualifiedName qnn = (QualifiedName) findMe;
//					log("findMe is a QN, "
//							+ (qnn.getQualifier().toString() + " other " + qnn.getName()
//									.toString()));

					SimpleType stp = extracTypeInfo(findDeclaration2((qnn.getQualifier()), alternateParent));

					if (stp == null) {
						return null;
					}

//					log(qnn.getQualifier() + "->" + qnn.getName());
					declaringClass = findDeclaration2(stp.getName(), alternateParent);

//					log("QN decl class: "
//							+ getNodeAsString(declaringClass));
					constrains.clear();
					constrains.add(ASTNode.TYPE_DECLARATION);
					constrains.add(ASTNode.FIELD_DECLARATION);
					return definedIn(declaringClass, qnn.getName().toString(), constrains);
				}
			}
		} else if (parent.getNodeType() == ASTNode.SIMPLE_TYPE) {
			constrains.add(ASTNode.TYPE_DECLARATION);
			if (parent.getParent().getNodeType() == ASTNode.CLASS_INSTANCE_CREATION)
				constrains.add(ASTNode.CLASS_INSTANCE_CREATION);
		} else if (parent instanceof Expression) {
//			constrains.add(ASTNode.TYPE_DECLARATION);
//			constrains.add(ASTNode.METHOD_DECLARATION);
//			constrains.add(ASTNode.FIELD_DECLARATION);
		} // TODO: in findDec, we also have a case where parent of type TD is handled.
			// Figure out if needed here as well.
//		log("Alternate parent: " + getNodeAsString(alternateParent));
		while (alternateParent != null) {
//			log("findDeclaration2 -> "
//					+ getNodeAsString(alternateParent));
			for (Object oprop : alternateParent.structuralPropertiesForType()) {
				StructuralPropertyDescriptor prop = (StructuralPropertyDescriptor) oprop;
				if (prop.isChildProperty() || prop.isSimpleProperty()) {
					if (alternateParent.getStructuralProperty(prop) instanceof ASTNode) {
//						log(prop + " C/S Prop of -> "
//								+ getNodeAsString(alternateParent));
						ret = definedIn((ASTNode) alternateParent
																.getStructuralProperty(prop),
														findMe.toString(), constrains);
						if (ret != null)
							return ret;
					}
				} else if (prop.isChildListProperty()) {
//					log((prop) + " ChildList props of "
//							+ getNodeAsString(alternateParent));
					List<ASTNode> nodelist = (List<ASTNode>) alternateParent
							.getStructuralProperty(prop);
					for (ASTNode retNode : nodelist) {
						ret = definedIn(retNode, findMe.toString(), constrains);
						if (ret != null)
							return ret;
					}
				}
			}
			alternateParent = alternateParent.getParent();
		}
		return null;
	}


	protected static boolean ignorableSuggestionImport(PreprocessedSketch ps, String impName) {

		String impNameLc = impName.toLowerCase();

		List<ImportStatement> programImports = ps.programImports;
		List<ImportStatement> codeFolderImports = ps.codeFolderImports;

		boolean isImported = Stream
				.concat(programImports.stream(), codeFolderImports.stream())
				.anyMatch(impS -> {
					String packageNameLc = impS.getPackageName().toLowerCase();
					return impNameLc.startsWith(packageNameLc);
				});

		if (isImported) return false;

		final String include = "include";
		final String exclude = "exclude";

		if (impName.startsWith("processing")) {
			if (JavaMode.suggestionsMap.containsKey(include) && JavaMode.suggestionsMap.get(include).contains(impName)) {
				return false;
			} else if (JavaMode.suggestionsMap.containsKey(exclude) && JavaMode.suggestionsMap.get(exclude).contains(impName)) {
				return true;
			}
		} else if (impName.startsWith("java")) {
			if (JavaMode.suggestionsMap.containsKey(include) && JavaMode.suggestionsMap.get(include).contains(impName)) {
				return false;
			}
		}

		return true;
	}


	/**
	 * A wrapper for java.lang.reflect types.
	 * Will have to see if the usage turns out to be internal only here or not
	 * and then accordingly decide where to place this class.
	 * @author quarkninja
	 *
	 */
	public static class ClassMember {
		private Field field;

		private Method method;

		private Constructor<?> cons;

		private Class<?> thisclass;

		private String stringVal;

		private String classType;

		private ASTNode astNode;

		private ASTNode declaringNode;

		public ClassMember(Class<?> m) {
			thisclass = m;
			stringVal = "Predefined Class " + m.getName();
			classType = m.getName();
		}

		public ClassMember(Method m) {
			method = m;
			stringVal = "Method " + m.getReturnType().getName() + " | " + m.getName()
					+ " defined in " + m.getDeclaringClass().getName();
			classType = m.getReturnType().getName();
		}

		public ClassMember(Field m) {
			field = m;
			stringVal = "Field " + m.getType().getName() + " | " + m.getName()
					+ " defined in " + m.getDeclaringClass().getName();
			classType = m.getType().getName();
		}

		public ClassMember(Constructor<?> m) {
			cons = m;
			stringVal = "Cons " + " " + m.getName() + " defined in "
					+ m.getDeclaringClass().getName();
		}

		public ClassMember(PreprocessedSketch ps, ASTNode node){
			astNode = node;
			stringVal = getNodeAsString(node);
			if(node instanceof TypeDeclaration){
				declaringNode = node;
			}
			if(node instanceof SimpleType){
				classType = ((SimpleType)node).getName().toString();
			}
			SimpleType stp = (node instanceof SimpleType) ? (SimpleType) node
					: extracTypeInfo(node);
			if(stp != null){
				ASTNode decl =findDeclaration(stp.getName());
				// Czech out teh mutation
				if(decl == null){
					// a predefined type
					classType = stp.getName().toString();
					thisclass = findClassIfExists(ps, classType);
				}
				else{
					// a local type
					declaringNode = decl;
				}
			}
		}

		public Class<?> getClass_() {
			return thisclass;
		}

		public ASTNode getDeclaringNode(){
			return declaringNode;
		}

		public Field getField() {
			return field;
		}

		public Method getMethod() {
			return method;
		}

		public Constructor<?> getCons() {
			return cons;
		}

		public ASTNode getASTNode(){
			return astNode;
		}

		public String toString() {
			return stringVal;
		}

		public String getTypeAsString(){
			return classType;
		}
	}


	/**
	 * Find the SimpleType from FD, SVD, VDS, etc
	 *
	 * @param node
	 * @return
	 */
	public static SimpleType extracTypeInfo(ASTNode node) {
		if (node == null) {
			return null;
		}
		Type t = extracTypeInfo2(node);
		if (t instanceof PrimitiveType) {
			return null;
		} else if (t instanceof ArrayType) {
			ArrayType at = (ArrayType) t;
			log("ele type "
							+ at.getElementType() + ", "
							+ at.getElementType().getClass().getName());
			if (at.getElementType() instanceof PrimitiveType) {
				return null;
			} else if (at.getElementType() instanceof SimpleType) {
				return (SimpleType) at.getElementType();
			} else
				return null;
		} else if (t instanceof ParameterizedType) {
			ParameterizedType pmt = (ParameterizedType) t;
			log(pmt.getType() + ", " + pmt.getType().getClass());
			if (pmt.getType() instanceof SimpleType) {
				return (SimpleType) pmt.getType();
			} else
				return null;
		}
		return (SimpleType) t;
	}


	static public Type extracTypeInfo2(ASTNode node) {
		Messages.log("* extracTypeInfo2");
		if (node == null)
			return null;
		switch (node.getNodeType()) {
		case ASTNode.METHOD_DECLARATION:
			return ((MethodDeclaration) node).getReturnType2();
		case ASTNode.FIELD_DECLARATION:
			return ((FieldDeclaration) node).getType();
		case ASTNode.VARIABLE_DECLARATION_EXPRESSION:
			return	((VariableDeclarationExpression) node).getType();
		case ASTNode.VARIABLE_DECLARATION_STATEMENT:
			return	((VariableDeclarationStatement) node).getType();
		case ASTNode.SINGLE_VARIABLE_DECLARATION:
			return	((SingleVariableDeclaration) node).getType();
		case ASTNode.VARIABLE_DECLARATION_FRAGMENT:
			return extracTypeInfo2(node.getParent());
		}
		log("Unknown type info request " + getNodeAsString(node));
		return null;
	}


	static protected ASTNode definedIn(ASTNode node, String name,
																	 ArrayList<Integer> constrains) {
		if (node == null)
			return null;
		if (constrains != null) {
//			log("Looking at " + getNodeAsString(node) + " for " + name
//					+ " in definedIn");
			if (!constrains.contains(node.getNodeType()) && constrains.size() > 0) {
//				System.err.print("definedIn -1 " + " But constrain was ");
//				for (Integer integer : constrains) {
//					System.out.print(ASTNode.nodeClassForType(integer) + ",");
//				}
//				log();
				return null;
			}
		}

		List<VariableDeclarationFragment> vdfList = null;
		switch (node.getNodeType()) {

		case ASTNode.TYPE_DECLARATION:
			//Base.loge(getNodeAsString(node));
			TypeDeclaration td = (TypeDeclaration) node;
			if (td.getName().toString().equals(name)) {
				if (constrains.contains(ASTNode.CLASS_INSTANCE_CREATION)) {
					// look for constructor;
					MethodDeclaration[] methods = td.getMethods();
					for (MethodDeclaration md : methods) {
						if (md.getName().toString().equalsIgnoreCase(name)) {
							log("Found a constructor.");
							return md;
						}
					}
				} else {
					// it's just the TD we're lookin for
					return node;
				}
			} else {
				if (constrains.contains(ASTNode.FIELD_DECLARATION)) {
					// look for fields
					FieldDeclaration[] fields = td.getFields();
					for (FieldDeclaration fd : fields) {
						List<VariableDeclarationFragment> fragments = fd.fragments();
						for (VariableDeclarationFragment vdf : fragments) {
							if (vdf.getName().toString().equalsIgnoreCase(name))
								return fd;
						}
					}
				} else if (constrains.contains(ASTNode.METHOD_DECLARATION)) {
					// look for methods
					MethodDeclaration[] methods = td.getMethods();
					for (MethodDeclaration md : methods) {
						if (md.getName().toString().equalsIgnoreCase(name)) {
							return md;
						}
					}
				}
			}
			break;
		case ASTNode.METHOD_DECLARATION:
			//Base.loge(getNodeAsString(node));
			if (((MethodDeclaration) node).getName().toString().equalsIgnoreCase(name))
				return node;
			break;
		case ASTNode.SINGLE_VARIABLE_DECLARATION:
			//Base.loge(getNodeAsString(node));
			if (((SingleVariableDeclaration) node).getName().toString().equalsIgnoreCase(name))
				return node;
			break;
		case ASTNode.FIELD_DECLARATION:
			//Base.loge("FD" + node);
			vdfList = ((FieldDeclaration) node).fragments();
			break;
		case ASTNode.VARIABLE_DECLARATION_EXPRESSION:
			//Base.loge("VDE" + node);
			vdfList = ((VariableDeclarationExpression) node).fragments();
			break;
		case ASTNode.VARIABLE_DECLARATION_STATEMENT:
			//Base.loge("VDS" + node);
			vdfList = ((VariableDeclarationStatement) node).fragments();
			break;

		default:

		}
		if (vdfList != null) {
			for (VariableDeclarationFragment vdf : vdfList) {
				if (vdf.getName().toString().equalsIgnoreCase(name))
					return node;
			}
		}
		return null;
	}


	static protected String getNodeAsString(ASTNode node) {
		if (node == null)
			return "NULL";
		String className = node.getClass().getName();
		int index = className.lastIndexOf(".");
		if (index > 0)
			className = className.substring(index + 1);

		// if(node instanceof BodyDeclaration)
		// return className;

		String value = className;

		if (node instanceof TypeDeclaration)
			value = ((TypeDeclaration) node).getName().toString() + " | " + className;
		else if (node instanceof MethodDeclaration)
			value = ((MethodDeclaration) node).getName().toString() + " | "
					+ className;
		else if (node instanceof MethodInvocation)
			value = ((MethodInvocation) node).getName().toString() + " | "
					+ className;
		else if (node instanceof FieldDeclaration)
			value = node.toString() + " FldDecl | ";
		else if (node instanceof SingleVariableDeclaration)
			value = ((SingleVariableDeclaration) node).getName() + " - "
					+ ((SingleVariableDeclaration) node).getType() + " | SVD ";
		else if (node instanceof ExpressionStatement)
			value = node.toString() + className;
		else if (node instanceof SimpleName)
			value = ((SimpleName) node).getFullyQualifiedName() + " | " + className;
		else if (node instanceof QualifiedName)
			value = node.toString() + " | " + className;
		else if(node instanceof FieldAccess)
			value = node.toString() + " | ";
		else if (className.startsWith("Variable"))
			value = node.toString() + " | " + className;
		else if (className.endsWith("Type"))
			value = node.toString() + " | " + className;
		value += " [" + node.getStartPosition() + ","
				+ (node.getStartPosition() + node.getLength()) + "]";
		value += " Line: "
				+ ((CompilationUnit) node.getRoot()).getLineNumber(node
						.getStartPosition());
		return value;
	}


//	public void jdocWindowVisible(boolean visible) {
//	 // frmJavaDoc.setVisible(visible);
//	}

//	public static String readFile2(String path) {
//		BufferedReader reader = null;
//		try {
//			reader = new BufferedReader(
//																	new InputStreamReader(
//																												new FileInputStream(
//																																						new File(
//																																										 path))));
//		} catch (FileNotFoundException e) {
//			e.printStackTrace();
//		}
//		try {
//			StringBuilder ret = new StringBuilder();
//			// ret.append("package " + className + ";\n");
//			String line;
//			while ((line = reader.readLine()) != null) {
//				ret.append(line);
//				ret.append("\n");
//			}
//			return ret.toString();
//		} catch (IOException e) {
//			e.printStackTrace();
//		} finally {
//			try {
//				reader.close();
//			} catch (IOException e) {
//				e.printStackTrace();
//			}
//		}
//		return null;
//	}


	static private void log(Object object) {
		Messages.log(object == null ? "null" : object.toString());
	}


	/// Predictions --------------------------------------------------------------


	protected static List<CompletionCandidate> trimCandidates(String newWord, List<CompletionCandidate> candidates) {
		ArrayList<CompletionCandidate> newCandidate = new ArrayList<>();
		newWord = newWord.toLowerCase();
		for (CompletionCandidate comp : candidates) {
			//if (comp.getNoHtmlLabel().toLowerCase().startsWith(newWord)) {
			if (comp.startsWith(newWord)) {
				newCandidate.add(comp);
			}
		}
		return newCandidate;
	}

	protected List<CompletionCandidate> candidates;
	protected String lastPredictedPhrase = " ";

	/**
	 * The main function that calculates possible code completion candidates
	 *
	 * @param pdePhrase
	 * @param line
	 * @param lineStartNonWSOffset
	 */
	public List<CompletionCandidate> preparePredictions(final PreprocessedSketch ps,
																											final String pdePhrase,
																											final int lineNumber) {
		Messages.log("* preparePredictions");

		ASTNode astRootNode = (ASTNode) ps.compilationUnit.types().get(0);

		// If the parsed code contains pde enhancements, take 'em out.
		// TODO: test this
		TextTransform transform = new TextTransform(pdePhrase);
		transform.addAll(SourceUtils.replaceTypeConstructors(pdePhrase));
		transform.addAll(SourceUtils.replaceHexLiterals(pdePhrase));
		transform.addAll(SourceUtils.replaceColorRegex(pdePhrase));
		transform.addAll(SourceUtils.fixFloatsRegex(pdePhrase));
		String phrase = transform.apply();

		//After typing 'arg.' all members of arg type are to be listed. This one is a flag for it
		boolean noCompare = phrase.endsWith(".");

		if (noCompare) {
			phrase = phrase.substring(0, phrase.length() - 1);
		}

		boolean incremental = !noCompare &&
				phrase.length() > lastPredictedPhrase.length() &&
				phrase.startsWith(lastPredictedPhrase);


		if (incremental) {
			log(pdePhrase + " starts with " + lastPredictedPhrase);
			log("Don't recalc");

			if (phrase.contains(".")) {
				int x = phrase.lastIndexOf('.');
				candidates = trimCandidates(phrase.substring(x + 1), candidates);
			} else {
				candidates = trimCandidates(phrase, candidates);
			}
			lastPredictedPhrase = phrase;
			return candidates;
		}

		// Ensure that we're not inside a comment. TODO: Binary search

		/*for (Comment comm : getCodeComments()) {
			int commLineNo = PdeToJavaLineNumber(compilationUnit
					.getLineNumber(comm.getStartPosition()));
			if(commLineNo == lineNumber){
				log("Found a comment line " + comm);
				log("Comment LSO "
						+ javaCodeOffsetToLineStartOffset(compilationUnit
					.getLineNumber(comm.getStartPosition()),
																							comm.getStartPosition()));
				break;
			}
		}*/

		// Now parse the expression into an ASTNode object
		ASTNode nearestNode;
		ASTParser parser = ASTParser.newParser(AST.JLS8);
		parser.setKind(ASTParser.K_EXPRESSION);
		parser.setSource(phrase.toCharArray());
		ASTNode testnode = parser.createAST(null);
		//Base.loge("PREDICTION PARSER PROBLEMS: " + parser);
		// Find closest ASTNode of the document to this word
		Messages.loge("Typed: " + phrase + "|" + " temp Node type: " + testnode.getClass().getSimpleName());
		if(testnode instanceof MethodInvocation){
			MethodInvocation mi = (MethodInvocation)testnode;
			log(mi.getName() + "," + mi.getExpression() + "," + mi.typeArguments().size());
		}

		// find nearest ASTNode
		nearestNode = findClosestNode(lineNumber, astRootNode);
		if (nearestNode == null) {
			// Make sure nearestNode is not NULL if couldn't find a closest node
			nearestNode = astRootNode;
		}
		Messages.loge(lineNumber + " Nearest ASTNode to PRED "
											+ getNodeAsString(nearestNode));

		candidates = new ArrayList<>();
		lastPredictedPhrase = phrase;
		// Determine the expression typed

		if (testnode instanceof SimpleName && !noCompare) {
			Messages.loge("One word expression " + getNodeAsString(testnode));
			//==> Simple one word exprssion - so is just an identifier

			// Bottom up traversal of the AST to look for possible definitions at
			// higher levels.
			//nearestNode = nearestNode.getParent();
			while (nearestNode != null) {
				// If the current class has a super class, look inside it for
				// definitions.
				if (nearestNode instanceof TypeDeclaration) {
					TypeDeclaration td = (TypeDeclaration) nearestNode;
					if (td.getStructuralProperty(TypeDeclaration.SUPERCLASS_TYPE_PROPERTY) != null) {
						SimpleType st = (SimpleType) td.getStructuralProperty(TypeDeclaration.SUPERCLASS_TYPE_PROPERTY);
						log("Superclass " + st.getName());
						ArrayList<CompletionCandidate> tempCandidates =
								getMembersForType(ps, st.getName().toString(), phrase, false, false);
						for (CompletionCandidate can : tempCandidates) {
							candidates.add(can);
						}
						//findDeclaration(st.getName())
					}
				}
				List<StructuralPropertyDescriptor> sprops =
						nearestNode.structuralPropertiesForType();
				for (StructuralPropertyDescriptor sprop : sprops) {
					ASTNode cnode;
					if (!sprop.isChildListProperty()) {
						if (nearestNode.getStructuralProperty(sprop) instanceof ASTNode) {
							cnode = (ASTNode) nearestNode.getStructuralProperty(sprop);
							CompletionCandidate[] types = checkForTypes(cnode);
							if (types != null) {
								for (CompletionCandidate type : types) {
									if (type.getElementName().toLowerCase().startsWith(phrase.toLowerCase()))
										candidates.add(type);
								}
							}
						}
					} else {
						// Childlist prop
						List<ASTNode> nodelist =
								(List<ASTNode>) nearestNode.getStructuralProperty(sprop);
						for (ASTNode clnode : nodelist) {
							CompletionCandidate[] types = checkForTypes(clnode);
							if (types != null) {
								for (CompletionCandidate type : types) {
									if (type.getElementName().toLowerCase().startsWith(phrase.toLowerCase()))
										candidates.add(type);
								}
							}
						}
					}
				}
				nearestNode = nearestNode.getParent();
			}
			// We're seeing a simple name that's not defined locally or in
			// the parent class. So most probably a pre-defined type.
			log("Empty can. " + phrase);
			ClassPath classPath = ps.classPath;
			if (classPath != null) {
				RegExpResourceFilter regExpResourceFilter =
						new RegExpResourceFilter(Pattern.compile(".*"),
																		 Pattern.compile(phrase + "[a-zA-Z_0-9]*.class",
																										 Pattern.CASE_INSENSITIVE));
				String[] resources = classPath.findResources("", regExpResourceFilter);

				for (String matchedClass2 : resources) {
					matchedClass2 = matchedClass2.replace('/', '.'); //package name
					String matchedClass = matchedClass2.substring(0, matchedClass2.length() - 6);
					int d = matchedClass.lastIndexOf('.');
					if (!ignorableSuggestionImport(ps, matchedClass)) {
						matchedClass = matchedClass.substring(d + 1); //class name
						// display package name in grey
						String html = "<html>" + matchedClass + " : <font color=#777777>" +
								matchedClass2.substring(0, d) + "</font></html>";
						candidates.add(new CompletionCandidate(matchedClass, html,
																									 matchedClass,
																									 CompletionCandidate.PREDEF_CLASS));
					}
				}
			}
		} else {
			// ==> Complex expression of type blah.blah2().doIt,etc
			// Have to resolve it by carefully traversing AST of testNode
			Messages.loge("Complex expression " + getNodeAsString(testnode));
			log("candidates empty");
			ASTNode childExpr = getChildExpression(testnode);
			log("Parent expression : " + getParentExpression(testnode));
			log("Child expression : " + childExpr);
			if (!noCompare) {
				log("Original testnode " + getNodeAsString(testnode));
				testnode = getParentExpression(testnode);
				log("Corrected testnode " + getNodeAsString(testnode));
			}
			ClassMember expr =
					resolveExpression3rdParty(ps, nearestNode, testnode, noCompare);
			if (expr == null) {
				log("Expr is null");
			} else {
				boolean isArray = expr.thisclass != null && expr.thisclass.isArray();
				boolean isSimpleType = (expr.astNode != null) &&
						expr.astNode.getNodeType() == ASTNode.SIMPLE_TYPE;
				boolean isMethod = expr.method != null;
				boolean staticOnly = !isMethod && !isArray && !isSimpleType;
				log("Expr is " + expr.toString());
				String lookFor = (noCompare || (childExpr == null)) ?
						"" : childExpr.toString();
				candidates = getMembersForType(ps, expr, lookFor, noCompare, staticOnly);
			}
		}
		return candidates;
	}


	protected static DefaultListModel<CompletionCandidate> filterPredictions(List<CompletionCandidate> candidates) {
		Messages.log("* filterPredictions");
		DefaultListModel<CompletionCandidate> defListModel = new DefaultListModel<>();
		if (candidates.isEmpty())
			return defListModel;
		// check if first & last CompCandidate are the same methods, only then show all overloaded methods
		if (candidates.get(0).getElementName()
				.equals(candidates.get(candidates.size() - 1).getElementName())) {
			log("All CC are methods only: " + candidates.get(0).getElementName());
			for (int i = 0; i < candidates.size(); i++) {
				CompletionCandidate cc = candidates.get(i).withRegeneratedCompString();
				candidates.set(i, cc);
				defListModel.addElement(cc);
			}
		}
		else {
			boolean ignoredSome = false;
			for (int i = 0; i < candidates.size(); i++) {
				if(i > 0 && (candidates.get(i).getElementName()
						.equals(candidates.get(i - 1).getElementName()))){
					if (candidates.get(i).getType() == CompletionCandidate.LOCAL_METHOD
							|| candidates.get(i).getType() == CompletionCandidate.PREDEF_METHOD) {
						CompletionCandidate cc = candidates.get(i - 1);
						String label = cc.getLabel();
						int x = label.lastIndexOf(')');
						String newLabel;
						if (candidates.get(i).getType() == CompletionCandidate.PREDEF_METHOD) {
							newLabel = (cc.getLabel().contains("<html>") ? "<html>" : "")
									+ cc.getElementName() + "(...)" + label.substring(x + 1);
						} else {
							newLabel = cc.getElementName() + "(...)" + label.substring(x + 1);
						}
						String newCompString = cc.getElementName() + "(";
						candidates.set(i - 1, cc.withLabelAndCompString(newLabel, newCompString));
						ignoredSome = true;
						continue;
					}
				}
				defListModel.addElement(candidates.get(i));
			}
			if (ignoredSome) {
				log("Some suggestions hidden");
			}
		}
		return defListModel;
	}



	/// JavaDocs -----------------------------------------------------------------

	//TODO: Work on this later.
/*
	protected TreeMap<String, String> jdocMap;


	protected void loadJavaDoc() {
		jdocMap = new TreeMap<>();

		// presently loading only p5 reference for PApplet
		// TODO: use something like ExecutorService here [jv]
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					loadJavaDoc(jdocMap, editor.getMode().getReferenceFolder());
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}).start();
	}


	static void loadJavaDoc(TreeMap<String, String> jdocMap,
													File referenceFolder) throws IOException {
		Document doc;

		FileFilter fileFilter = new FileFilter() {
			@Override
			public boolean accept(File file) {
				if(!file.getName().endsWith("_.html"))
					return false;
				int k = 0;
				for (int i = 0; i < file.getName().length(); i++) {
					if(file.getName().charAt(i)== '_')
						k++;
					if(k > 1)
						return false;
				}
				return true;
			}
		};

		for (File docFile : referenceFolder.listFiles(fileFilter)) {
			doc = Jsoup.parse(docFile, null);
			Elements elm = doc.getElementsByClass("ref-item");
			String msg = "";
			String methodName = docFile.getName().substring(0, docFile.getName().indexOf('_'));
			//System.out.println(methodName);
			for (org.jsoup.nodes.Element ele : elm) {
				msg = "<html><body> <strong><div style=\"width: 300px; text-justification: justify;\"></strong><table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"ref-item\">"
						+ ele.html() + "</table></div></html></body></html>";
				//mat.replaceAll("");
				msg = msg.replaceAll("img src=\"", "img src=\""
						+ referenceFolder.toURI().toURL().toString() + "/");
				//System.out.println(ele.text());
			}
			jdocMap.put(methodName, msg);
		}
		//System.out.println("JDoc loaded " + jdocMap.size());
	}


	public void updateJavaDoc(final CompletionCandidate candidate) {
		String methodmatch = candidate.toString();
		if (methodmatch.indexOf('(') != -1) {
			methodmatch = methodmatch.substring(0, methodmatch.indexOf('('));
		}

		//log("jdoc match " + methodmatch);
		String temp = "<html> </html>";
		for (final String key : jdocMap.keySet()) {
			if (key.startsWith(methodmatch) && key.length() > 3) {
				log("Matched jdoc " + key);
				if (candidate.getWrappedObject() != null) {
					String definingClass = "";
					if (candidate.getWrappedObject() instanceof Field)
						definingClass = ((Field) candidate.getWrappedObject())
								.getDeclaringClass().getName();
					else if (candidate.getWrappedObject() instanceof Method)
						definingClass = ((Method) candidate.getWrappedObject())
								.getDeclaringClass().getName();
					if (definingClass.equals("processing.core.PApplet")) {
						temp = (jdocMap.get(key));
						break;
					}
				}
			}
		}

		final String jdocString = temp;
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				javadocPane.setText(jdocString);
				scrollPane.getVerticalScrollBar().setValue(0);
				//frmJavaDoc.setVisible(!jdocString.equals("<html> </html>"));
				editor.toFront();
				editor.ta.requestFocus();
			}
		});
	}
*/

}
