package proof;

import sdsi.*;
import sdsi.sexp.*;
import servlet.*;
import jp.*;
import Tools.*;

import java.io.*;
import java.security.*;
import java.util.*;

/**
 * A first cut (first at least in this third incarnation of Snowflake
 * sharing/security) at the client's prover tool.
 * 
 * @deprecated See {@link Prover2}.
 */
public class Prover {
	MultiMap proofSteps;
	MultiMap nameSteps;
	MultiMap proofsByIssuer;
	HashMap objectToMnemonic;
	HashMap objectToSecureName;		// names according to Def certificates
	HashMap privateToPublic;
	HashMap publicToPrivate;
	HashSet objectList = new HashSet();
	String dirname;
	File cacheDir;
	long lastCheck = -1L;

	public IndentWriter iw;	// used for debugging proof lookup
	public PrintWriter pw;	// used for debugging proof lookup
	public DebugTool dt;

	public HashSet getObjectList() {
		return new HashSet(objectList);
	}

	public HashSet getObjectsByType(Class c) {
		HashSet hs = new HashSet();
		Iterator iter = objectList.iterator();
		while (iter.hasNext()) {
			Object o = iter.next();
			if (c.isInstance(o)) {
				hs.add(o);
			}
		}
		return hs;
	}

	public String getName(Object obj) {
		String s;

		s = getSecureName(obj);
		if (s!=null) { return s; }

		s = (String) objectToMnemonic.get(obj);
		if (s!=null) { return s; }

		if (obj instanceof SDSIObject) {
			obj = ((SDSIObject) obj).getSrep();
		}

		if (obj instanceof SexpList) {
			String sr = "anonymous "+((SexpList) obj).getType();
			if (((SexpList) obj).getType().equals("hash")) {
				return sr+" "+((SexpList) obj).toReadableString(0,72,0);
			}
			return sr;
		}

		if (obj instanceof SexpString) {
			return "\""+((SexpString) obj).stringContent()+"\"";
		}

		if (obj instanceof Quoting) {
			Quoting q = (Quoting) obj;
			return getName(q.getQuoter())+"|"+getName(q.getQuotee());
		}

		if (obj == null) {
			return "(null)";
		}

		return "(unknown "+obj.getClass().getName()+")";
	}

	void setMnemonic(Object obj, String name, boolean replace) {
		if (replace || (objectToMnemonic.get(obj)==null)) {
			objectToMnemonic.put(obj, name);
		}
	}

	public String getSecureName(Object obj) {
		return (String) objectToSecureName.get(obj);
	}

	void setSecureName(Object obj, String name) {
		objectToSecureName.put(obj, name);
	}

	/**
	 * @param dirname the name of a directory in which to find
	 * certificates or keys that might help with constructing proofs.
	 */
	public Prover(String dirname) {
		proofSteps = new MultiMap();
		proofsByIssuer = new MultiMap();
//		{
//			/** debug routine to see what the stored certs are */
//			public void add(Object key, Object val) {
//				super.add(key, val);
//					String is, ss;
//					if (val instanceof UseAuth) {
//						Object i = ((UseAuth) val).getIssuer();
//						is = getName(i);
//						Subject s = ((UseAuth) val).getSubject();
//						ss = getName(s);
//					} else if (val instanceof BasicUnlockKey) {
//						ss = "<PrivateKey>";
//						SDSIPrincipal s = ((BasicUnlockKey) val).getIssuer();
//						is = getName(s);
//					} else if (val instanceof HashEquivalence) {
//						ss = getName(((HashEquivalence) val).getSubject());
//						is = getName(((HashEquivalence) val).getIssuer());
//					} else {
//						ss = "huh?";
//						is = "huh?";
//					}
//
//					System.out.println(ss+" => "+is+" ("+getName(key)+")");
//					int x=7;	// debugger fodder line -- somewhere to break.
//			}
//		};
		nameSteps = new MultiMap();
		objectToMnemonic = new HashMap();
		objectToSecureName = new HashMap();
		privateToPublic = new HashMap();
		publicToPrivate = new HashMap();
		this.dirname = dirname;
		cacheDir = new File(dirname);

		dt = new DebugTool();
		// iw = new IndentWriter(new OutputStreamWriter(System.err));
		iw = new IndentWriter(new OutputStreamWriter(new NullOutputStream()));
		pw = new PrintWriter(iw, true);
	}

	/**
	 * Pulls in any files in the directory <CODE>dirname</CODE> that
	 * have changed since we last checked the directory. 
	 */
	public void loadCache() {
		try {
			if (cacheDir.lastModified()<=lastCheck) {
				return;
			}
			long thisCheck = System.currentTimeMillis();

			File[] files = cacheDir.listFiles();

			// First pass through objects in cache dir
			for (int i=0; i<files.length; i++) {
				if (files[i].lastModified()<=lastCheck) {
					continue;
				}

				SDSIObject so = KeyTools.processFile(files[i]);
				String filename = files[i].getName();

				if (so!=null) {
					// put the object into a list to add to the graph
					// in a second pass

					if (so instanceof SDSIKeyPair) {
						// split pub and priv both into objectList
						SDSIPrivateKey priv =
							((SDSIKeyPair) so).getPrivateKey();
						SDSIPublicKey pub =
							((SDSIKeyPair) so).getPublicKey();
						objectList.add(pub);
						objectList.add(priv);

						privateToPublic.put(priv, pub);
						publicToPrivate.put(pub, priv);

						setMnemonic(pub, filename+"<public>", true);
						setMnemonic(priv, filename+"<private>", true);
					} else {
						objectList.add(so);
					}
	
					// if a .private file, crop suffix to figure out
					// what the corresponding public key is.
					if (filename.endsWith(".private")) {
						// This is the old, bad file format, where
						// we had to know the filenames to be able to
						// tie a public and a private key together.
						String pubname = filename
							.substring(0, filename.lastIndexOf('.'))
							+".public";
						SDSIObject publicKey = (SDSIPublicKey)
							KeyTools.processFile(new File(cacheDir, pubname));
						privateToPublic.put(so, publicKey);
						publicToPrivate.put(publicKey, so);
					}

					// Store the filename as the "mnemonic" name of
					// this object.
					setMnemonic(so, filename, true);
				} else {
					System.err.println("file "
						+files[i].getName()+" parses to null.");
				}
			}

			// Second pass
			Iterator iter = objectList.iterator();
			while (iter.hasNext()) {
				SDSIObject so = (SDSIObject) iter.next();
				introduceObject(so);
			}
			lastCheck = thisCheck;

			findSecureNames();
		} catch (FileNotFoundException ex) {
			ex.printStackTrace();
		}
	}

	public void introduceObject(SDSIObject so) {
		if (so instanceof SDSIPublicKey) {
			SDSIPublicKey pub = (SDSIPublicKey) so;
			// Set up equivalences between this key and all of its
			// known hashes
			hashKey(pub);
		} else if (so instanceof SDSIKeyPair) {
			// ignore it -- it was split apart in first pass.
		} else if (so instanceof SDSIPrivateKey) {
			SDSIPrivateKey priv = (SDSIPrivateKey) so;

			// put into the proofSteps map an AuthClosure
			// that knows how to sign for the public key using
			// the private key.
			SDSIPublicKey pub =
				(SDSIPublicKey) privateToPublic.get(priv);
			if (pub!=null) {
				proofSteps.add(pub, new BasicUnlockKey(priv, pub));
			}
		} else if (so instanceof SignedCertificate) {
			SignedCertificate sc = (SignedCertificate) so;
			Cert cert = sc.getCertificate();

			SDSIPrincipal issuer = cert.getIssuer();
			Subject subject = cert.getSubject();
			setMnemonic(issuer, "issuer of "+getName(so), false);
			setMnemonic(subject, "subject of "+getName(so), false);

			if (cert instanceof Auth) {
				proofSteps.add(issuer, new UseAuth(sc));
			} else if (cert instanceof Def) {
				Def def = (Def) cert;
//						System.out.println("defining name "
//							+getName(def.getIssuer())
//							+"'s "+def.getName().getNameIndex(0)
//							+" as "+getName(def.getSubject()));
				nameSteps.add(subject, cert);
			} else {
				System.err.println("***** what the heck is a "
					+cert.getClass()+"?");
			}

			// if subject (or issuer) is a public key, hash it up
			// in case related proof steps refer to its hash
			// directly.
			if (issuer instanceof SDSIPublicKey) {
				hashKey((SDSIPublicKey) issuer);
			}
			if (subject instanceof SDSIPublicKey) {
				hashKey((SDSIPublicKey) subject);
			}
		} else {
			System.err.println("*** discarding this thing: "
				+so.getClass());
		}
	}

	public SDSIPublicKey getPublicKeyForHash(Hash hash) {
		Iterator iter = proofSteps.iterator(hash);
		while (iter.hasNext()) {
			Object so = iter.next();
			if (so instanceof HashEquivalence) {
				HashEquivalence he = (HashEquivalence) so;
				if (he.getSubject() instanceof SDSIPublicKey) {
					return (SDSIPublicKey) he.getSubject();
				}
			}
		}
		return null;
	}

	public SDSIPublicKey getPublicKeyForPrivate(SDSIPrivateKey privateKey) {
		// TODO: security issue -- we shouldn't really be handing private
		// keys around to various parts of the application. They could be
		// sloppy with them! Instead, we should give out handles that we
		// can hash back to the real private key later.

/* A whole lotta debug'gin going on.
		System.out.println("Known private keys:");
		Iterator iter = privateToPublic.keySet().iterator();
		SDSIPrivateKey last=null;
		while (iter.hasNext()) {
			SDSIPrivateKey pk = (SDSIPrivateKey) iter.next();
			System.out.println(pk.toReadableString(72));
			last = pk;
		}
		System.out.println("Your private key:");
		System.out.println(privateKey.toReadableString(72));
		System.out.println("last.hashCode() = "+last.hashCode());
		System.out.println("your.hashCode() = "+privateKey.hashCode());
		System.out.println("your==last: "+privateKey.equals(last));
		System.out.println("Your public key:"+privateToPublic.get(privateKey));
*/

		return (SDSIPublicKey) privateToPublic.get(privateKey);
	}

	public SDSIPrivateKey getPrivateKeyForPublic(SDSIPublicKey publicKey) {
		return getBasicUnlockKeyForPublic(publicKey).getPrivateKey();
	}

	public BasicUnlockKey getBasicUnlockKeyForPublic(SDSIPublicKey publicKey) {
		Iterator iter = proofSteps.iterator(publicKey);
		while (iter.hasNext()) {
			Object so = iter.next();
			if (so instanceof BasicUnlockKey) {
				BasicUnlockKey buk = (BasicUnlockKey) so;
				return buk;
			}
		}
		return null;
	}

	void hashKey(SDSIPublicKey pub) {
		hashKey(pub, "md5");
		hashKey(pub, "sha1");
	}

	void hashKey(SDSIPublicKey pub, String hashType) {
		HashEquivalence he;
		he = new HashEquivalence(hashType, pub);
			setMnemonic(he.getIssuer(),
				hashType+" hash of "+getName(pub), true);
		proofSteps.add(he.getIssuer(), he);
		nameSteps.add(he.getSubject(), he);

		he = new HashEquivalence(pub, hashType);
		proofSteps.add(he.getIssuer(), he);
		nameSteps.add(he.getSubject(), he);
	}

	/**
	 * find a proof that the request speaks for the issuer (ultimate
	 * server). Algorithm is a DFS over the graph made by proofSteps.
	 * 
	 * <P> We knock out edges of the graph formed by
	 * certificates that don't apply to this request -- that is,
	 * those that don't have authTag as a subset. If authTag is the
	 * special Null tag, it is the subset of every certificate's
	 * authorization restriction set, so we consider every certificate.
	 * (That's how the abbreviated version of this method works.)
	 * 
	 * <P> When we find a principal for which we hold the corresponding
	 * private key, we're done. (We could find any principal that we
	 * could "make" equivalent to the subject, but short of creating a
	 * new certificate to do so, which takes a private key, the only
	 * current alternative would be to find the exact request itself.)
	 * 
	 * <p>TODO this algorithm won't do anything with conjuct or quoting
	 * principals
	 */
	public String getProofString(SDSIPrincipal issuer, Subject subject,
		Tag authTag) {
		Proof p = getProof(issuer, subject, authTag);
		if (p!=null) {
			return p.toReadableString(72);
		} else {
			return null;
		}
	}

	public String getProofString(SDSIPrincipal issuer, Subject subject) {
		return getProofString(issuer, subject, Tag.getNullTag());
	}

	public Proof getProof(SDSIPrincipal issuer, Subject subject,
		Tag authTag) {
		return prove(issuer, subject, new HashSet(), authTag, 0);
	}

	/**
	 * Try to find a proof, proceeding by finding anyone who speaks for
	 * the issuer, and trying to push that anyone back to the specified
	 * subject.
	 *
	 * visitedNodes is the list of DFS "marked" nodes,
	 * used to prevent traveling in cycles around the principal graph
	 */
	Proof prove(SDSIPrincipal issuer, Subject subject,
		HashSet visitedNodes, Tag authTag, int depth) {

		iw.setIndent(depth*4);
		if (issuer.equals(subject)) {
			// I don't think this case happens except on an initial
			// call; the visitedNodes() marks are designed to prevent
			// us hitting this case algorithmically.
			// TODO: not actually true: think harder, Homer.
			return new TrivialProof(issuer);
		}

		pw.println("*Issuer is:    "+dt.getDebugName(issuer));
		pw.println("*Want Subject: "+dt.getDebugName(subject));

		if (visitedNodes.contains(issuer)) {
			pw.println("*already explored "+getName(issuer));
			return null;
		}
		visitedNodes.add(issuer);

		Iterator iter;

		// See if there's a suitable proof already constructed
		iter = proofsByIssuer.iterator(issuer);
		while (iter.hasNext()) {
			Proof proof = (Proof) iter.next();
			if (KeyTools.arePrincipalsEquivalent(
				(SDSIObject) proof.getSubject(),
				(SDSIObject) subject)
				&& proof.getTag().hasSubset(authTag)) {
				// Hey, found one that'll work!
				return proof;
				// TODO check validity date on prooflet, too.
			}
		}
		// TODO: it may be possible that we're skipping a prooflet
		// because it's not precisely what we had in mind, and then
		// not being able to exploit that saved work. Bummer?

		// Okay, so we don't already have a prefab proof.
		// See if we can construct this proof using some nonProof
		// snippet of truth we have lying around, and possibly some
		// other proof (gotten by recursion).
		iter = proofSteps.iterator(issuer);
		while (iter.hasNext()) {
			AuthClosure midSubjectAuth = (AuthClosure) iter.next();
			pw.println("+Considerinc AC: "+dt.getDebugName(midSubjectAuth));
			pw.println(" Issuer spoken for by: "
				+dt.getDebugName(midSubjectAuth.getSubject()));
			if (midSubjectAuth instanceof BasicUnlockKey) {
				pw.println(" we have private key for mapee.");
				// found a private key -- put together the final
				// certificate for the request, and we're done.
				Proof proof = midSubjectAuth.getProof(issuer, subject);
				addProoflet(proof);
				return proof;
			}

			if (!midSubjectAuth.isUsefulForAuth(authTag)) {
				pw.println(	
			" discarding cert because it can't help prove we have this auth.");
				continue;
			} else {
				pw.println(" cert can satisfy authTag");
				if (midSubjectAuth instanceof UseAuth) {
					Tag t = ((UseAuth) midSubjectAuth).getAuth().getTag();
					pw.println(" cert tag is: "+t.toReadableString(72));
					pw.println(" auth tag is: "
						+authTag.toReadableString(72));
				}
			}

			// pw.println("mapee's subject: "
			//	+midSubjectAuth.getSubject());
			pw.println(" Looking for proof with subject: "
				+dt.getDebugName(midSubjectAuth.getSubject()));
			Proof rest = prove((SDSIPrincipal) midSubjectAuth.getSubject(),
				subject,
				visitedNodes, authTag,
				depth+1);
			iw.setIndent(depth*4);	// restore after subroutine call sets it
			if (rest!=null) {
				// found a compound proof. Return the composition of
				// the 'rest' of the proof with this step.
				Proof first = midSubjectAuth.getProof(issuer,
					midSubjectAuth.getSubject());
				if (first==null) {
					// happens when the proof step is self-evident, such
					// as "proving" that a hash is the hash of some public
					// key.
					// Don't need to add 'rest' prooflet; if it was new,
					// the recursive invocation that generated it would
					// have added it to the prooflet pool.
					return rest;
				} else {
					Proof proof = new TwoStepProof(first, rest);
					addProoflet(proof);
					return proof;
				}
			}
		}
		pw.println("...nada.");
		return null;
	}

	/**
	 * Finds every possible name for <code>p</code> rooted in a public
	 * key for which we have the private key.
	 * Algorithm is BFS, so that we can stop once we find a few good,
	 * short names.
	 * 
	 * @param numDesired the maximum number of name chains to return.
	 * Specify -1 to completely explore the name graph. @returns a List
	 * of Prover.NameNode objects, upon which you may call String[]
	 * getName().
	 */
	public List getNames(SDSIPrincipal key, int numDesired) {
		// TODO: verify signed Defs; don't just accept any
		// Def we stumble across...
		Vector output = new Vector();			// list of successful results
		Vector queue = new Vector();			// breadth-first search queue

		queue.addElement(new RootNameNode(key, getName(key)));
		while (queue.size()>0) {
			NameNode nameNode = (NameNode) queue.elementAt(0);
			queue.removeElementAt(0);

//			System.out.println("* Visiting node "+nameNode
//				+" (length "+nameNode.chainLength()+")"
//				+", principal "+getName(nameNode.getPrincipal()));
//			nameNode.debugChain(this);

			Iterator iter = nameSteps.iterator(nameNode.getPrincipal());
			while (iter.hasNext()) {
				Object o = iter.next();
				SDSIPrincipal issuer;
				NameNode prev;
				if (o instanceof Def) {
					Def def = (Def) o;
					issuer = def.getIssuer();
					prev = new DefNameNode(nameNode, def);
				} else if (o instanceof HashEquivalence) {
					HashEquivalence he = (HashEquivalence) o;
					issuer = (SDSIPrincipal) he.getIssuer();
					prev = new HashNameNode(issuer, nameNode);
				} else {
					System.out.println("Weird type in nameSteps: "
						+o.getClass());
					continue;
				}
				if (nameNode.containsPrincipal(issuer)) {
					// this path has a cycle
					continue;
				}
				// System.out.println("issuer "+getName(issuer));
				if (isFinal(issuer)) {
					// This is a grounded name
					// We used to worry that a name had at least one Def
					// in it, but cycle-elimination takes care of that.
					output.add(prev);
					if (numDesired>=0 && output.size()>numDesired) {
						return output;
					}
				} else {
					// well, it's not a name grounded in a key we hold,
					// but it's progress. Explore its children.
					// System.out.println("+ queueing node "+prev
					//	+", principal "+getName(prev.getPrincipal()));
					// prev.debugChain(this);
					queue.add(prev);
				}
			}
		}
		return output;
	}

	/**
	 * The subject is one for which we control the private key here.
	 */
	public boolean isFinal(Subject s) {
		Iterator iter = proofSteps.iterator(s);
		while (iter.hasNext()) {
			AuthClosure ac = (AuthClosure) iter.next();
			if (ac instanceof BasicUnlockKey) {
				// this guy has a corresponding private key
				// TODO: make some effort to ensure private key
				// matches public key?
				return true;
			}
		}
		return false;
	}

	/**
	 * go through objectList and give objects 'secure names' (using
	 * Def bindings) where possible.
	 */
	void findSecureNames() {
		Iterator iter = objectList.iterator();
		while (iter.hasNext()) {
			Object o = iter.next();
			if (!(o instanceof SDSIPrincipal)) {
				continue;
			}

			// TODO: 5 is kind of arbitrary, isn't it?
			List l = getNames((SDSIPrincipal) o, 5);
			// find the shortest name in the list
			NameNode shortest = null;
			Iterator i2 = l.iterator();
			while (i2.hasNext()) {
				NameNode nn = (NameNode) i2.next();
				if (shortest==null
					|| nn.getNameVector().size()
						< shortest.getNameVector().size()) {
					shortest = nn;
				}
			}
			if (shortest != null) {
				// a secure name is available! Install it.
				setSecureName(o,
					getName(shortest.getPrincipal())+":"+shortest.toString());
			}
		}
	}

	/**
	 * A test function. Tries to name all of the objects loaded into
	 * the cache.
	 */
	public static void main(String args[]) {
		Prover prover = new Prover("certs-client");
		prover.loadCache();

		System.out.println("----------");
		Iterator iter = prover.objectList.iterator();
		while (iter.hasNext()) {
			Object o = iter.next();
			String desc = prover.getSecureName(o)==null
				? " (mnemonic)"
				: " (secure)";
			System.out.println(" object: "+prover.getName(o)+desc);
		}
	}

	class FilenameTest implements BinarySearch.Test {
		String suffix;
		FilenameTest(String suffix) { this.suffix = suffix; }
		public boolean test(int value) {
			File f = new File(cacheDir, ""+value+"."+suffix);
			// System.out.println("does file "+f+" exist?");
			return !f.exists();
		}
	}

	File newFilename(String suffix) {
		int i = BinarySearch.unbounded(0, true, new FilenameTest(suffix));
		return new File(cacheDir, ""+i+"."+suffix);
	}

	public void saveSDSIObject(SDSIObject so)
		throws IOException {
		String type = "unknown";
		if (so.getSrep() instanceof SexpList) {
			type = ((SexpList) so.getSrep()).getType();
		}
		saveSDSIObject(so, type);
	}

	public void saveSDSIObject(SDSIObject so, String suffix)
		throws IOException {
		FileOutputStream fos = new FileOutputStream(newFilename(suffix));
		fos.write(so.toReadableString(72).getBytes("8859_1"));
		fos.close();
		loadCache();
	}

	void addProoflet(Proof proof) {
		proofsByIssuer.add(proof.getIssuer(), proof);
	}

	/**
	 * a hack for when someone sends us a proof. We take it all apart
	 * and save all the certificates, and then build our own proof.
	 * someday we'll actually cache these things.
	 */
	public void digestProof(Proof p) {
		Iterator iter;

		// Cache complete proofs/"prooflets" (little proofs that may
		// not be useful on their own, but saves us the time of discovering
		// & constructing them.) [In some cases, e.g. a signedcertificate
		// with handoff, we may not know *how* to construct them.]
		iter = p.preorderProofs().iterator();
		while (iter.hasNext()) {
			Proof pr = (Proof) iter.next();
			if (!proofsByIssuer.set(pr.getIssuer()).contains(pr)) {
				addProoflet(pr);
			}
		}

		// Cache proof components
		iter = p.preorderCertificates().iterator();
		while (iter.hasNext()) {
			SignedCertificate sc = (SignedCertificate) iter.next();
			if (!objectList.contains(sc)) {
				try {
					if (sc.getCertificate().getSubject()
						instanceof SDSIPrincipal) {
						// ignore certs delegating to a hash (a specific
						// request)
						pw.println("digesting "+sc.getSrep().getType());
						saveSDSIObject(sc, sc.getSrep().getType());
					}
				} catch (IOException ex) {
					// discard the object, I suppose.
					// that will probably be mysterious to the user.
					// TODO should generate a log warning?
				}
			}
		}
	}

	public class DebugTool {

		HashMap objectToAbbr = new HashMap();
		int nextObjAbbr = 1;

		public Object getAbbr(Object o) {
			Object abbr = objectToAbbr.get(o);
			if (abbr == null) {
				abbr = new Integer(nextObjAbbr++);
				objectToAbbr.put(o, abbr);
			}
			return abbr;
		}

		public void dumpObjects(Set s) {
			Iterator iter = s.iterator();
			while (iter.hasNext()) {
				Object o = iter.next();
				if (o instanceof AuthClosure) {
					AuthClosure ac = (AuthClosure) o;
					Subject sub = ac.getSubject();
					Subject iss = ac.getIssuer();

					Object acAbbr = getAbbr(ac);
					Object subAbbr = getAbbr(sub);
					Object issAbbr = getAbbr(iss);
					pw.println("ac "+acAbbr+": "+subAbbr+" => "+issAbbr
						+"   "+ac.getClass().getName());
				} else {
					pw.println(getAbbr(o)+" ("+o.getClass().getName()+")");
				}
			}
		}

		public void dumpObjects() {
			dumpObjects(getAuthClosures());
		}

		public Set getAuthClosures() {
			HashSet set = new HashSet();
			Iterator iter = proofSteps.keys().iterator();
			while (iter.hasNext()) {
				Iterator i2 = proofSteps.iterator(iter.next());
				while (i2.hasNext()) {
					AuthClosure ac = (AuthClosure) i2.next();
					set.add(ac);
				}
			}
			return set;
		}

		public String getDebugName(Object o) {
			Object abbr = getAbbr(o);
			return abbr.toString()+" ("+getName(o)+")";
		}
	}
}
