• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
2 
3 package org.xbill.DNS;
4 
5 import java.io.*;
6 import java.util.*;
7 
8 /**
9  * A cache of DNS records.  The cache obeys TTLs, so items are purged after
10  * their validity period is complete.  Negative answers are cached, to
11  * avoid repeated failed DNS queries.  The credibility of each RRset is
12  * maintained, so that more credible records replace less credible records,
13  * and lookups can specify the minimum credibility of data they are requesting.
14  * @see RRset
15  * @see Credibility
16  *
17  * @author Brian Wellington
18  */
19 
20 public class Cache {
21 
22 private interface Element {
expired()23 	public boolean expired();
compareCredibility(int cred)24 	public int compareCredibility(int cred);
getType()25 	public int getType();
26 }
27 
28 private static int
limitExpire(long ttl, long maxttl)29 limitExpire(long ttl, long maxttl) {
30 	if (maxttl >= 0 && maxttl < ttl)
31 		ttl = maxttl;
32 	long expire = (System.currentTimeMillis() / 1000) + ttl;
33 	if (expire < 0 || expire > Integer.MAX_VALUE)
34 		return Integer.MAX_VALUE;
35 	return (int)expire;
36 }
37 
38 private static class CacheRRset extends RRset implements Element {
39 	private static final long serialVersionUID = 5971755205903597024L;
40 
41 	int credibility;
42 	int expire;
43 
44 	public
CacheRRset(Record rec, int cred, long maxttl)45 	CacheRRset(Record rec, int cred, long maxttl) {
46 		super();
47 		this.credibility = cred;
48 		this.expire = limitExpire(rec.getTTL(), maxttl);
49 		addRR(rec);
50 	}
51 
52 	public
CacheRRset(RRset rrset, int cred, long maxttl)53 	CacheRRset(RRset rrset, int cred, long maxttl) {
54 		super(rrset);
55 		this.credibility = cred;
56 		this.expire = limitExpire(rrset.getTTL(), maxttl);
57 	}
58 
59 	public final boolean
expired()60 	expired() {
61 		int now = (int)(System.currentTimeMillis() / 1000);
62 		return (now >= expire);
63 	}
64 
65 	public final int
compareCredibility(int cred)66 	compareCredibility(int cred) {
67 		return credibility - cred;
68 	}
69 
70 	public String
toString()71 	toString() {
72 		StringBuffer sb = new StringBuffer();
73 		sb.append(super.toString());
74 		sb.append(" cl = ");
75 		sb.append(credibility);
76 		return sb.toString();
77 	}
78 }
79 
80 private static class NegativeElement implements Element {
81 	int type;
82 	Name name;
83 	int credibility;
84 	int expire;
85 
86 	public
NegativeElement(Name name, int type, SOARecord soa, int cred, long maxttl)87 	NegativeElement(Name name, int type, SOARecord soa, int cred,
88 			long maxttl)
89 	{
90 		this.name = name;
91 		this.type = type;
92 		long cttl = 0;
93 		if (soa != null)
94 			cttl = soa.getMinimum();
95 		this.credibility = cred;
96 		this.expire = limitExpire(cttl, maxttl);
97 	}
98 
99 	public int
getType()100 	getType() {
101 		return type;
102 	}
103 
104 	public final boolean
expired()105 	expired() {
106 		int now = (int)(System.currentTimeMillis() / 1000);
107 		return (now >= expire);
108 	}
109 
110 	public final int
compareCredibility(int cred)111 	compareCredibility(int cred) {
112 		return credibility - cred;
113 	}
114 
115 	public String
toString()116 	toString() {
117 		StringBuffer sb = new StringBuffer();
118 		if (type == 0)
119 			sb.append("NXDOMAIN " + name);
120 		else
121 			sb.append("NXRRSET " + name + " " + Type.string(type));
122 		sb.append(" cl = ");
123 		sb.append(credibility);
124 		return sb.toString();
125 	}
126 }
127 
128 private static class CacheMap extends LinkedHashMap {
129 	private int maxsize = -1;
130 
CacheMap(int maxsize)131 	CacheMap(int maxsize) {
132 		super(16, (float) 0.75, true);
133 		this.maxsize = maxsize;
134 	}
135 
136 	int
getMaxSize()137 	getMaxSize() {
138 		return maxsize;
139 	}
140 
141 	void
setMaxSize(int maxsize)142 	setMaxSize(int maxsize) {
143 		/*
144 		 * Note that this doesn't shrink the size of the map if
145 		 * the maximum size is lowered, but it should shrink as
146 		 * entries expire.
147 		 */
148 		this.maxsize = maxsize;
149 	}
150 
removeEldestEntry(Map.Entry eldest)151 	protected boolean removeEldestEntry(Map.Entry eldest) {
152 		return maxsize >= 0 && size() > maxsize;
153 	}
154 }
155 
156 private CacheMap data;
157 private int maxncache = -1;
158 private int maxcache = -1;
159 private int dclass;
160 
161 private static final int defaultMaxEntries = 50000;
162 
163 /**
164  * Creates an empty Cache
165  *
166  * @param dclass The DNS class of this cache
167  * @see DClass
168  */
169 public
Cache(int dclass)170 Cache(int dclass) {
171 	this.dclass = dclass;
172 	data = new CacheMap(defaultMaxEntries);
173 }
174 
175 /**
176  * Creates an empty Cache for class IN.
177  * @see DClass
178  */
179 public
Cache()180 Cache() {
181 	this(DClass.IN);
182 }
183 
184 /**
185  * Creates a Cache which initially contains all records in the specified file.
186  */
187 public
Cache(String file)188 Cache(String file) throws IOException {
189 	data = new CacheMap(defaultMaxEntries);
190 	Master m = new Master(file);
191 	Record record;
192 	while ((record = m.nextRecord()) != null)
193 		addRecord(record, Credibility.HINT, m);
194 }
195 
196 private synchronized Object
exactName(Name name)197 exactName(Name name) {
198 	return data.get(name);
199 }
200 
201 private synchronized void
removeName(Name name)202 removeName(Name name) {
203 	data.remove(name);
204 }
205 
206 private synchronized Element []
allElements(Object types)207 allElements(Object types) {
208 	if (types instanceof List) {
209 		List typelist = (List) types;
210 		int size = typelist.size();
211 		return (Element []) typelist.toArray(new Element[size]);
212 	} else {
213 		Element set = (Element) types;
214 		return new Element[] {set};
215 	}
216 }
217 
218 private synchronized Element
oneElement(Name name, Object types, int type, int minCred)219 oneElement(Name name, Object types, int type, int minCred) {
220 	Element found = null;
221 
222 	if (type == Type.ANY)
223 		throw new IllegalArgumentException("oneElement(ANY)");
224 	if (types instanceof List) {
225 		List list = (List) types;
226 		for (int i = 0; i < list.size(); i++) {
227 			Element set = (Element) list.get(i);
228 			if (set.getType() == type) {
229 				found = set;
230 				break;
231 			}
232 		}
233 	} else {
234 		Element set = (Element) types;
235 		if (set.getType() == type)
236 			found = set;
237 	}
238 	if (found == null)
239 		return null;
240 	if (found.expired()) {
241 		removeElement(name, type);
242 		return null;
243 	}
244 	if (found.compareCredibility(minCred) < 0)
245 		return null;
246 	return found;
247 }
248 
249 private synchronized Element
findElement(Name name, int type, int minCred)250 findElement(Name name, int type, int minCred) {
251 	Object types = exactName(name);
252 	if (types == null)
253 		return null;
254 	return oneElement(name, types, type, minCred);
255 }
256 
257 private synchronized void
addElement(Name name, Element element)258 addElement(Name name, Element element) {
259 	Object types = data.get(name);
260 	if (types == null) {
261 		data.put(name, element);
262 		return;
263 	}
264 	int type = element.getType();
265 	if (types instanceof List) {
266 		List list = (List) types;
267 		for (int i = 0; i < list.size(); i++) {
268 			Element elt = (Element) list.get(i);
269 			if (elt.getType() == type) {
270 				list.set(i, element);
271 				return;
272 			}
273 		}
274 		list.add(element);
275 	} else {
276 		Element elt = (Element) types;
277 		if (elt.getType() == type)
278 			data.put(name, element);
279 		else {
280 			LinkedList list = new LinkedList();
281 			list.add(elt);
282 			list.add(element);
283 			data.put(name, list);
284 		}
285 	}
286 }
287 
288 private synchronized void
removeElement(Name name, int type)289 removeElement(Name name, int type) {
290 	Object types = data.get(name);
291 	if (types == null) {
292 		return;
293 	}
294 	if (types instanceof List) {
295 		List list = (List) types;
296 		for (int i = 0; i < list.size(); i++) {
297 			Element elt = (Element) list.get(i);
298 			if (elt.getType() == type) {
299 				list.remove(i);
300 				if (list.size() == 0)
301 					data.remove(name);
302 				return;
303 			}
304 		}
305 	} else {
306 		Element elt = (Element) types;
307 		if (elt.getType() != type)
308 			return;
309 		data.remove(name);
310 	}
311 }
312 
313 /** Empties the Cache. */
314 public synchronized void
clearCache()315 clearCache() {
316 	data.clear();
317 }
318 
319 /**
320  * Adds a record to the Cache.
321  * @param r The record to be added
322  * @param cred The credibility of the record
323  * @param o The source of the record (this could be a Message, for example)
324  * @see Record
325  */
326 public synchronized void
addRecord(Record r, int cred, Object o)327 addRecord(Record r, int cred, Object o) {
328 	Name name = r.getName();
329 	int type = r.getRRsetType();
330 	if (!Type.isRR(type))
331 		return;
332 	Element element = findElement(name, type, cred);
333 	if (element == null) {
334 		CacheRRset crrset = new CacheRRset(r, cred, maxcache);
335 		addRRset(crrset, cred);
336 	} else if (element.compareCredibility(cred) == 0) {
337 		if (element instanceof CacheRRset) {
338 			CacheRRset crrset = (CacheRRset) element;
339 			crrset.addRR(r);
340 		}
341 	}
342 }
343 
344 /**
345  * Adds an RRset to the Cache.
346  * @param rrset The RRset to be added
347  * @param cred The credibility of these records
348  * @see RRset
349  */
350 public synchronized void
addRRset(RRset rrset, int cred)351 addRRset(RRset rrset, int cred) {
352 	long ttl = rrset.getTTL();
353 	Name name = rrset.getName();
354 	int type = rrset.getType();
355 	Element element = findElement(name, type, 0);
356 	if (ttl == 0) {
357 		if (element != null && element.compareCredibility(cred) <= 0)
358 			removeElement(name, type);
359 	} else {
360 		if (element != null && element.compareCredibility(cred) <= 0)
361 			element = null;
362 		if (element == null) {
363 			CacheRRset crrset;
364 			if (rrset instanceof CacheRRset)
365 				crrset = (CacheRRset) rrset;
366 			else
367 				crrset = new CacheRRset(rrset, cred, maxcache);
368 			addElement(name, crrset);
369 		}
370 	}
371 }
372 
373 /**
374  * Adds a negative entry to the Cache.
375  * @param name The name of the negative entry
376  * @param type The type of the negative entry
377  * @param soa The SOA record to add to the negative cache entry, or null.
378  * The negative cache ttl is derived from the SOA.
379  * @param cred The credibility of the negative entry
380  */
381 public synchronized void
addNegative(Name name, int type, SOARecord soa, int cred)382 addNegative(Name name, int type, SOARecord soa, int cred) {
383 	long ttl = 0;
384 	if (soa != null)
385 		ttl = soa.getTTL();
386 	Element element = findElement(name, type, 0);
387 	if (ttl == 0) {
388 		if (element != null && element.compareCredibility(cred) <= 0)
389 			removeElement(name, type);
390 	} else {
391 		if (element != null && element.compareCredibility(cred) <= 0)
392 			element = null;
393 		if (element == null)
394 			addElement(name, new NegativeElement(name, type,
395 							     soa, cred,
396 							     maxncache));
397 	}
398 }
399 
400 /**
401  * Finds all matching sets or something that causes the lookup to stop.
402  */
403 protected synchronized SetResponse
lookup(Name name, int type, int minCred)404 lookup(Name name, int type, int minCred) {
405 	int labels;
406 	int tlabels;
407 	Element element;
408 	Name tname;
409 	Object types;
410 	SetResponse sr;
411 
412 	labels = name.labels();
413 
414 	for (tlabels = labels; tlabels >= 1; tlabels--) {
415 		boolean isRoot = (tlabels == 1);
416 		boolean isExact = (tlabels == labels);
417 
418 		if (isRoot)
419 			tname = Name.root;
420 		else if (isExact)
421 			tname = name;
422 		else
423 			tname = new Name(name, labels - tlabels);
424 
425 		types = data.get(tname);
426 		if (types == null)
427 			continue;
428 
429 		/*
430 		 * If this is the name, look for the actual type or a CNAME
431 		 * (unless it's an ANY query, where we return everything).
432 		 * Otherwise, look for a DNAME.
433 		 */
434 		if (isExact && type == Type.ANY) {
435 			sr = new SetResponse(SetResponse.SUCCESSFUL);
436 			Element [] elements = allElements(types);
437 			int added = 0;
438 			for (int i = 0; i < elements.length; i++) {
439 				element = elements[i];
440 				if (element.expired()) {
441 					removeElement(tname, element.getType());
442 					continue;
443 				}
444 				if (!(element instanceof CacheRRset))
445 					continue;
446 				if (element.compareCredibility(minCred) < 0)
447 					continue;
448 				sr.addRRset((CacheRRset)element);
449 				added++;
450 			}
451 			/* There were positive entries */
452 			if (added > 0)
453 				return sr;
454 		} else if (isExact) {
455 			element = oneElement(tname, types, type, minCred);
456 			if (element != null &&
457 			    element instanceof CacheRRset)
458 			{
459 				sr = new SetResponse(SetResponse.SUCCESSFUL);
460 				sr.addRRset((CacheRRset) element);
461 				return sr;
462 			} else if (element != null) {
463 				sr = new SetResponse(SetResponse.NXRRSET);
464 				return sr;
465 			}
466 
467 			element = oneElement(tname, types, Type.CNAME, minCred);
468 			if (element != null &&
469 			    element instanceof CacheRRset)
470 			{
471 				return new SetResponse(SetResponse.CNAME,
472 						       (CacheRRset) element);
473 			}
474 		} else {
475 			element = oneElement(tname, types, Type.DNAME, minCred);
476 			if (element != null &&
477 			    element instanceof CacheRRset)
478 			{
479 				return new SetResponse(SetResponse.DNAME,
480 						       (CacheRRset) element);
481 			}
482 		}
483 
484 		/* Look for an NS */
485 		element = oneElement(tname, types, Type.NS, minCred);
486 		if (element != null && element instanceof CacheRRset)
487 			return new SetResponse(SetResponse.DELEGATION,
488 					       (CacheRRset) element);
489 
490 		/* Check for the special NXDOMAIN element. */
491 		if (isExact) {
492 			element = oneElement(tname, types, 0, minCred);
493 			if (element != null)
494 				return SetResponse.ofType(SetResponse.NXDOMAIN);
495 		}
496 
497 	}
498 	return SetResponse.ofType(SetResponse.UNKNOWN);
499 }
500 
501 /**
502  * Looks up Records in the Cache.  This follows CNAMEs and handles negatively
503  * cached data.
504  * @param name The name to look up
505  * @param type The type to look up
506  * @param minCred The minimum acceptable credibility
507  * @return A SetResponse object
508  * @see SetResponse
509  * @see Credibility
510  */
511 public SetResponse
lookupRecords(Name name, int type, int minCred)512 lookupRecords(Name name, int type, int minCred) {
513 	return lookup(name, type, minCred);
514 }
515 
516 private RRset []
findRecords(Name name, int type, int minCred)517 findRecords(Name name, int type, int minCred) {
518 	SetResponse cr = lookupRecords(name, type, minCred);
519 	if (cr.isSuccessful())
520 		return cr.answers();
521 	else
522 		return null;
523 }
524 
525 /**
526  * Looks up credible Records in the Cache (a wrapper around lookupRecords).
527  * Unlike lookupRecords, this given no indication of why failure occurred.
528  * @param name The name to look up
529  * @param type The type to look up
530  * @return An array of RRsets, or null
531  * @see Credibility
532  */
533 public RRset []
findRecords(Name name, int type)534 findRecords(Name name, int type) {
535 	return findRecords(name, type, Credibility.NORMAL);
536 }
537 
538 /**
539  * Looks up Records in the Cache (a wrapper around lookupRecords).  Unlike
540  * lookupRecords, this given no indication of why failure occurred.
541  * @param name The name to look up
542  * @param type The type to look up
543  * @return An array of RRsets, or null
544  * @see Credibility
545  */
546 public RRset []
findAnyRecords(Name name, int type)547 findAnyRecords(Name name, int type) {
548 	return findRecords(name, type, Credibility.GLUE);
549 }
550 
551 private final int
getCred(int section, boolean isAuth)552 getCred(int section, boolean isAuth) {
553 	if (section == Section.ANSWER) {
554 		if (isAuth)
555 			return Credibility.AUTH_ANSWER;
556 		else
557 			return Credibility.NONAUTH_ANSWER;
558 	} else if (section == Section.AUTHORITY) {
559 		if (isAuth)
560 			return Credibility.AUTH_AUTHORITY;
561 		else
562 			return Credibility.NONAUTH_AUTHORITY;
563 	} else if (section == Section.ADDITIONAL) {
564 		return Credibility.ADDITIONAL;
565 	} else
566 		throw new IllegalArgumentException("getCred: invalid section");
567 }
568 
569 private static void
markAdditional(RRset rrset, Set names)570 markAdditional(RRset rrset, Set names) {
571 	Record first = rrset.first();
572 	if (first.getAdditionalName() == null)
573 		return;
574 
575 	Iterator it = rrset.rrs();
576 	while (it.hasNext()) {
577 		Record r = (Record) it.next();
578 		Name name = r.getAdditionalName();
579 		if (name != null)
580 			names.add(name);
581 	}
582 }
583 
584 /**
585  * Adds all data from a Message into the Cache.  Each record is added with
586  * the appropriate credibility, and negative answers are cached as such.
587  * @param in The Message to be added
588  * @return A SetResponse that reflects what would be returned from a cache
589  * lookup, or null if nothing useful could be cached from the message.
590  * @see Message
591  */
592 public SetResponse
addMessage(Message in)593 addMessage(Message in) {
594 	boolean isAuth = in.getHeader().getFlag(Flags.AA);
595 	Record question = in.getQuestion();
596 	Name qname;
597 	Name curname;
598 	int qtype;
599 	int qclass;
600 	int cred;
601 	int rcode = in.getHeader().getRcode();
602 	boolean completed = false;
603 	RRset [] answers, auth, addl;
604 	SetResponse response = null;
605 	boolean verbose = Options.check("verbosecache");
606 	HashSet additionalNames;
607 
608 	if ((rcode != Rcode.NOERROR && rcode != Rcode.NXDOMAIN) ||
609 	    question == null)
610 		return null;
611 
612 	qname = question.getName();
613 	qtype = question.getType();
614 	qclass = question.getDClass();
615 
616 	curname = qname;
617 
618 	additionalNames = new HashSet();
619 
620 	answers = in.getSectionRRsets(Section.ANSWER);
621 	for (int i = 0; i < answers.length; i++) {
622 		if (answers[i].getDClass() != qclass)
623 			continue;
624 		int type = answers[i].getType();
625 		Name name = answers[i].getName();
626 		cred = getCred(Section.ANSWER, isAuth);
627 		if ((type == qtype || qtype == Type.ANY) &&
628 		    name.equals(curname))
629 		{
630 			addRRset(answers[i], cred);
631 			completed = true;
632 			if (curname == qname) {
633 				if (response == null)
634 					response = new SetResponse(
635 							SetResponse.SUCCESSFUL);
636 				response.addRRset(answers[i]);
637 			}
638 			markAdditional(answers[i], additionalNames);
639 		} else if (type == Type.CNAME && name.equals(curname)) {
640 			CNAMERecord cname;
641 			addRRset(answers[i], cred);
642 			if (curname == qname)
643 				response = new SetResponse(SetResponse.CNAME,
644 							   answers[i]);
645 			cname = (CNAMERecord) answers[i].first();
646 			curname = cname.getTarget();
647 		} else if (type == Type.DNAME && curname.subdomain(name)) {
648 			DNAMERecord dname;
649 			addRRset(answers[i], cred);
650 			if (curname == qname)
651 				response = new SetResponse(SetResponse.DNAME,
652 							   answers[i]);
653 			dname = (DNAMERecord) answers[i].first();
654 			try {
655 				curname = curname.fromDNAME(dname);
656 			}
657 			catch (NameTooLongException e) {
658 				break;
659 			}
660 		}
661 	}
662 
663 	auth = in.getSectionRRsets(Section.AUTHORITY);
664 	RRset soa = null, ns = null;
665 	for (int i = 0; i < auth.length; i++) {
666 		if (auth[i].getType() == Type.SOA &&
667 		    curname.subdomain(auth[i].getName()))
668 			soa = auth[i];
669 		else if (auth[i].getType() == Type.NS &&
670 			 curname.subdomain(auth[i].getName()))
671 			ns = auth[i];
672 	}
673 	if (!completed) {
674 		/* This is a negative response or a referral. */
675 		int cachetype = (rcode == Rcode.NXDOMAIN) ? 0 : qtype;
676 		if (rcode == Rcode.NXDOMAIN || soa != null || ns == null) {
677 			/* Negative response */
678 			cred = getCred(Section.AUTHORITY, isAuth);
679 			SOARecord soarec = null;
680 			if (soa != null)
681 				soarec = (SOARecord) soa.first();
682 			addNegative(curname, cachetype, soarec, cred);
683 			if (response == null) {
684 				int responseType;
685 				if (rcode == Rcode.NXDOMAIN)
686 					responseType = SetResponse.NXDOMAIN;
687 				else
688 					responseType = SetResponse.NXRRSET;
689 				response = SetResponse.ofType(responseType);
690 			}
691 			/* DNSSEC records are not cached. */
692 		} else {
693 			/* Referral response */
694 			cred = getCred(Section.AUTHORITY, isAuth);
695 			addRRset(ns, cred);
696 			markAdditional(ns, additionalNames);
697 			if (response == null)
698 				response = new SetResponse(
699 							SetResponse.DELEGATION,
700 							ns);
701 		}
702 	} else if (rcode == Rcode.NOERROR && ns != null) {
703 		/* Cache the NS set from a positive response. */
704 		cred = getCred(Section.AUTHORITY, isAuth);
705 		addRRset(ns, cred);
706 		markAdditional(ns, additionalNames);
707 	}
708 
709 	addl = in.getSectionRRsets(Section.ADDITIONAL);
710 	for (int i = 0; i < addl.length; i++) {
711 		int type = addl[i].getType();
712 		if (type != Type.A && type != Type.AAAA && type != Type.A6)
713 			continue;
714 		Name name = addl[i].getName();
715 		if (!additionalNames.contains(name))
716 			continue;
717 		cred = getCred(Section.ADDITIONAL, isAuth);
718 		addRRset(addl[i], cred);
719 	}
720 	if (verbose)
721 		System.out.println("addMessage: " + response);
722 	return (response);
723 }
724 
725 /**
726  * Flushes an RRset from the cache
727  * @param name The name of the records to be flushed
728  * @param type The type of the records to be flushed
729  * @see RRset
730  */
731 public void
flushSet(Name name, int type)732 flushSet(Name name, int type) {
733 	removeElement(name, type);
734 }
735 
736 /**
737  * Flushes all RRsets with a given name from the cache
738  * @param name The name of the records to be flushed
739  * @see RRset
740  */
741 public void
flushName(Name name)742 flushName(Name name) {
743 	removeName(name);
744 }
745 
746 /**
747  * Sets the maximum length of time that a negative response will be stored
748  * in this Cache.  A negative value disables this feature (that is, sets
749  * no limit).
750  */
751 public void
setMaxNCache(int seconds)752 setMaxNCache(int seconds) {
753 	maxncache = seconds;
754 }
755 
756 /**
757  * Gets the maximum length of time that a negative response will be stored
758  * in this Cache.  A negative value indicates no limit.
759  */
760 public int
getMaxNCache()761 getMaxNCache() {
762 	return maxncache;
763 }
764 
765 /**
766  * Sets the maximum length of time that records will be stored in this
767  * Cache.  A negative value disables this feature (that is, sets no limit).
768  */
769 public void
setMaxCache(int seconds)770 setMaxCache(int seconds) {
771 	maxcache = seconds;
772 }
773 
774 /**
775  * Gets the maximum length of time that records will be stored
776  * in this Cache.  A negative value indicates no limit.
777  */
778 public int
getMaxCache()779 getMaxCache() {
780 	return maxcache;
781 }
782 
783 /**
784  * Gets the current number of entries in the Cache, where an entry consists
785  * of all records with a specific Name.
786  */
787 public int
getSize()788 getSize() {
789 	return data.size();
790 }
791 
792 /**
793  * Gets the maximum number of entries in the Cache, where an entry consists
794  * of all records with a specific Name.  A negative value is treated as an
795  * infinite limit.
796  */
797 public int
getMaxEntries()798 getMaxEntries() {
799 	return data.getMaxSize();
800 }
801 
802 /**
803  * Sets the maximum number of entries in the Cache, where an entry consists
804  * of all records with a specific Name.  A negative value is treated as an
805  * infinite limit.
806  *
807  * Note that setting this to a value lower than the current number
808  * of entries will not cause the Cache to shrink immediately.
809  *
810  * The default maximum number of entries is 50000.
811  *
812  * @param entries The maximum number of entries in the Cache.
813  */
814 public void
setMaxEntries(int entries)815 setMaxEntries(int entries) {
816 	data.setMaxSize(entries);
817 }
818 
819 /**
820  * Returns the DNS class of this cache.
821  */
822 public int
getDClass()823 getDClass() {
824 	return dclass;
825 }
826 
827 /**
828  * Returns the contents of the Cache as a string.
829  */
830 public String
toString()831 toString() {
832 	StringBuffer sb = new StringBuffer();
833 	synchronized (this) {
834 		Iterator it = data.values().iterator();
835 		while (it.hasNext()) {
836 			Element [] elements = allElements(it.next());
837 			for (int i = 0; i < elements.length; i++) {
838 				sb.append(elements[i]);
839 				sb.append("\n");
840 			}
841 		}
842 	}
843 	return sb.toString();
844 }
845 
846 }
847