2 * Copyright (c) 2001-2007 Sun Microsystems, Inc. All rights reserved.
4 * The Sun Project JXTA(TM) Software License
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
9 * 1. Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright notice,
13 * this list of conditions and the following disclaimer in the documentation
14 * and/or other materials provided with the distribution.
16 * 3. The end-user documentation included with the redistribution, if any, must
17 * include the following acknowledgment: "This product includes software
18 * developed by Sun Microsystems, Inc. for JXTA(TM) technology."
19 * Alternately, this acknowledgment may appear in the software itself, if
20 * and wherever such third-party acknowledgments normally appear.
22 * 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must
23 * not be used to endorse or promote products derived from this software
24 * without prior written permission. For written permission, please contact
25 * Project JXTA at http://www.jxta.org.
27 * 5. Products derived from this software may not be called "JXTA", nor may
28 * "JXTA" appear in their name, without prior written permission of Sun.
30 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
31 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
32 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN
33 * MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
34 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
36 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
37 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
38 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
39 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41 * JXTA is a registered trademark of Sun Microsystems, Inc. in the United
42 * States and other countries.
44 * Please see the license information page at :
45 * <http://www.jxta.org/project/www/license.html> for instructions on use of
46 * the license in source files.
48 * ====================================================================
50 * This software consists of voluntary contributions made by many individuals
51 * on behalf of Project JXTA. For more information on Project JXTA, please see
52 * http://www.jxta.org.
54 * This license is based on the BSD license adopted by the Apache Foundation.
56 package net.jxta.impl.cm;
58 import net.jxta.id.IDFactory;
59 import net.jxta.impl.util.TimeUtils;
60 import net.jxta.impl.xindice.core.DBException;
61 import net.jxta.impl.xindice.core.data.Key;
62 import net.jxta.impl.xindice.core.data.Record;
63 import net.jxta.impl.xindice.core.data.Value;
64 import net.jxta.impl.xindice.core.filer.BTreeCallback;
65 import net.jxta.impl.xindice.core.filer.BTreeFiler;
66 import net.jxta.impl.xindice.core.indexer.IndexQuery;
67 import net.jxta.impl.xindice.core.indexer.NameIndexer;
68 import net.jxta.logging.Logging;
69 import net.jxta.peer.PeerID;
70 import net.jxta.peergroup.PeerGroup;
72 import java.io.ByteArrayOutputStream;
73 import java.io.DataInputStream;
74 import java.io.DataOutputStream;
75 import java.io.EOFException;
77 import java.io.IOException;
78 import java.io.InputStream;
79 import java.lang.reflect.UndeclaredThrowableException;
81 import java.net.URISyntaxException;
82 import java.util.ArrayList;
83 import java.util.Collections;
84 import java.util.HashMap;
85 import java.util.HashSet;
86 import java.util.Iterator;
87 import java.util.List;
90 import java.util.logging.Level;
91 import java.util.logging.Logger;
96 public class SrdiIndex implements Runnable {
101 private final static transient Logger LOG = Logger.getLogger(SrdiIndex.class.getName());
103 private long interval = 1000 * 60 * 10;
104 private volatile boolean stop = false;
105 private final Indexer srdiIndexer;
106 private final BTreeFiler cacheDB;
107 private Thread gcThread = null;
108 private final Set<PeerID> gcPeerTBL = new HashSet<PeerID>();
110 private final String indexName;
113 * Constructor for the SrdiIndex
116 * @param indexName the index name
118 public SrdiIndex(PeerGroup group, String indexName) {
119 this.indexName = indexName;
126 pgdir = "srdi-index";
127 storeHome = new File(".jxta");
129 pgdir = group.getPeerGroupID().getUniqueValue().toString();
130 storeHome = new File(group.getStoreHome());
133 File rootDir = new File(new File(storeHome, "cm"), pgdir);
135 rootDir = new File(rootDir, "srdi");
136 if (!rootDir.exists()) {
137 // We need to create the directory
138 if (!rootDir.mkdirs()) {
139 throw new RuntimeException("Cm cannot create directory " + rootDir);
144 cacheDB = new BTreeFiler();
146 cacheDB.setSync(false);
147 cacheDB.setLocation(rootDir.getCanonicalPath(), indexName);
149 if (!cacheDB.open()) {
156 srdiIndexer = new Indexer(false);
157 srdiIndexer.setLocation(rootDir.getCanonicalPath(), indexName);
158 if (!srdiIndexer.open()) {
159 srdiIndexer.create();
164 if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
165 LOG.info("[" + ((group == null) ? "none" : group.toString()) + "] : Initialized " + indexName);
167 } catch (DBException de) {
168 if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
169 LOG.log(Level.SEVERE, "Unable to Initialize databases", de);
172 throw new UndeclaredThrowableException(de, "Unable to Initialize databases");
173 } catch (Throwable e) {
174 if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
175 LOG.log(Level.SEVERE, "Unable to create Cm", e);
178 if (e instanceof Error) {
180 } else if (e instanceof RuntimeException) {
181 throw (RuntimeException) e;
183 throw new UndeclaredThrowableException(e, "Unable to create Cm");
189 * Construct a SrdiIndex and starts a GC thread which runs every "interval"
192 * @param interval the interval at which the gc will run in milliseconds
193 * @param group group context
194 * @param indexName SrdiIndex name
197 public SrdiIndex(PeerGroup group, String indexName, long interval) {
198 this(group, indexName);
199 this.interval = interval;
200 startGC(group, indexName, interval);
204 * Start the GC thread
206 * @param group the PeerGroup
207 * @param indexName index name
208 * @param interval interval in milliseconds
210 protected void startGC(PeerGroup group, String indexName, long interval) {
211 if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
212 LOG.info("[" + ((group == null) ? "none" : group.toString()) + "] : Starting SRDI GC Thread for " + indexName);
215 gcThread = new Thread(group.getHomeThreadGroup(), this, "SrdiIndex GC :" + indexName + " every " + interval + "ms");
216 gcThread.setDaemon(true);
221 * Returns the name of this srdi index.
223 * @return index name.
225 public String getIndexName() {
232 * @param primaryKey primary key
233 * @param attribute Attribute String to query on
234 * @param value value of the attribute string
235 * @param expiration expiration associated with this entry relative time in
237 * @param pid peerid reference
239 public synchronized void add(String primaryKey, String attribute, String value, PeerID pid, long expiration) {
241 if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
242 LOG.fine("[" + indexName + "] Adding " + primaryKey + "/" + attribute + " = \'" + value + "\' for " + pid);
246 Key key = new Key(primaryKey + attribute + value);
247 long expiresin = TimeUtils.toAbsoluteTimeMillis(expiration);
249 // update the record if it exists
250 synchronized (cacheDB) {
251 // FIXME hamada 10/14/04 it is possible a peer re-appears with
252 // a different set of indexes since it's been marked for garbage
253 // collection. will address this issue in a subsequent patch
254 gcPeerTBL.remove(pid);
256 Record record = cacheDB.readRecord(key);
259 if (record != null) {
260 old = readRecord(record).list;
262 old = new ArrayList<Entry>();
264 Entry entry = new Entry(pid, expiresin);
266 if (!old.contains(entry)) {
269 // entry exists, replace it (effectively updating expiration)
270 old.remove(old.indexOf(entry));
273 // no sense in keeping expired entries.
274 old = removeExpired(old);
275 long t0 = TimeUtils.timeNow();
276 byte[] data = getData(key, old);
278 // if (LOG.isLoggable(Level.FINE)) {
279 // LOG.fine("Serialized result in : " + (TimeUtils.timeNow() - t0) + "ms.");
282 if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
283 LOG.severe("Failed to serialize data");
287 Value recordValue = new Value(data);
288 long pos = cacheDB.writeRecord(key, recordValue);
289 Map<String, String> indexables = getIndexMap(primaryKey + attribute, value);
291 srdiIndexer.addToIndex(indexables, pos);
293 } catch (IOException de) {
294 if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
295 LOG.log(Level.WARNING, "Failed to add SRDI", de);
297 } catch (DBException de) {
298 if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
299 LOG.log(Level.WARNING, "Failed to add SRDI", de);
307 * @param pkey primary key
308 * @param skey secondary key
310 * @return List of Entry objects
312 public List<Entry> getRecord(String pkey, String skey, String value) {
313 Record record = null;
316 Key key = new Key(pkey + skey + value);
318 synchronized (cacheDB) {
319 record = cacheDB.readRecord(key);
321 } catch (DBException de) {
322 if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
323 LOG.log(Level.WARNING, "Failed to retrieve SrdiIndex record", de);
326 // if record is null, readRecord returns an empty list
327 return readRecord(record).list;
332 * inserts a pkey into a map with a value of value
334 * @param primaryKey primary key
339 private Map<String, String> getIndexMap(String primaryKey, String value) {
340 if (primaryKey == null) {
346 Map<String, String> map = new HashMap<String, String>(1);
348 map.put(primaryKey, value);
353 * remove entries pointing to peer id from cache
355 * @param pid peer id to remove
357 public synchronized void remove(PeerID pid) {
358 if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
359 LOG.fine(" Adding " + pid + " to peer GC table");
367 * @param attribute Attribute String to query on
368 * @param value value of the attribute string
369 * @return an enumeration of canonical paths
370 * @param primaryKey primary key
371 * @param threshold max number of results
373 public synchronized List<PeerID> query(String primaryKey, String attribute, String value, int threshold) {
375 if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
376 LOG.fine("[" + indexName + "] Querying for " + threshold + " " + primaryKey + "/" + attribute + " = \'" + value + "\'");
380 if (primaryKey == null) {
381 return Collections.emptyList();
387 if (attribute == null) {
388 res = query(primaryKey);
390 res = new ArrayList<PeerID>();
392 IndexQuery iq = Cm.getIndexQuery(value);
395 srdiIndexer.search(iq, primaryKey + attribute, new SearchCallback(cacheDB, res, threshold, gcPeerTBL));
396 } catch (Exception ex) {
397 if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
398 LOG.log(Level.WARNING, "Exception while searching in index", ex);
403 if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
404 LOG.fine( "[" + indexName + "] Returning " + res.size() + " results for " + primaryKey + "/" + attribute + " = \'"
414 * @param primaryKey primary key
415 * @return A list of Peer IDs.
417 public synchronized List<PeerID> query(String primaryKey) {
418 if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
419 LOG.fine("[" + indexName + "] Querying for " + primaryKey);
422 List<PeerID> res = new ArrayList<PeerID>();
425 Map<String, NameIndexer> map = srdiIndexer.getIndexers();
427 for (Map.Entry<String, NameIndexer> index : map.entrySet()) {
428 String indexName = index.getKey();
429 // seperate the index name from attribute
430 if (indexName.startsWith(primaryKey)) {
431 NameIndexer idxr = index.getValue();
432 idxr.query(null, new SearchCallback(cacheDB, res, Integer.MAX_VALUE, gcPeerTBL));
435 } catch (Exception ex) {
436 if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
437 LOG.log(Level.WARNING, "Exception while searching in index", ex);
441 if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
442 LOG.fine("[" + indexName + "] Returning " + res.size() + " results for " + primaryKey);
448 private static final class SearchCallback implements BTreeCallback {
449 private final BTreeFiler cacheDB;
450 private final int threshold;
451 private final List<PeerID> results;
452 private final Set<PeerID> excludeTable;
454 SearchCallback(BTreeFiler cacheDB, List<PeerID> results, int threshold, Set<PeerID> excludeTable) {
455 this.cacheDB = cacheDB;
456 this.threshold = threshold;
457 this.results = results;
458 this.excludeTable = excludeTable;
464 public boolean indexInfo(Value val, long pos) {
466 if (results.size() >= threshold) {
467 if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
468 LOG.fine("SearchCallback.indexInfo reached Threshold :" + threshold);
472 if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
473 LOG.fine("Found " + val);
475 Record record = null;
478 record = cacheDB.readRecord(pos);
479 } catch (DBException ex) {
480 if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
481 LOG.log(Level.WARNING, "Exception while reading indexed", ex);
486 if (record != null) {
487 long t0 = TimeUtils.timeNow();
488 SrdiIndexRecord rec = readRecord(record);
490 if (Logging.SHOW_FINEST && LOG.isLoggable(Level.FINEST)) {
491 LOG.finest("Got result back in : " + (TimeUtils.timeNow() - t0) + "ms.");
494 copyIntoList(results, rec.list, excludeTable);
497 return results.size() < threshold;
502 private static final class GcCallback implements BTreeCallback {
503 private final BTreeFiler cacheDB;
504 private final Indexer idxr;
505 private final List<Long> list;
506 private final Set<PeerID> table;
508 GcCallback(BTreeFiler cacheDB, Indexer idxr, List<Long> list, Set<PeerID> table) {
509 this.cacheDB = cacheDB;
518 public boolean indexInfo(Value val, long pos) {
520 Record record = null;
521 synchronized (cacheDB) {
523 record = cacheDB.readRecord(pos);
524 } catch (DBException ex) {
525 if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
526 LOG.log(Level.WARNING, "Exception while reading indexed", ex);
530 if (record == null) {
533 SrdiIndexRecord rec = readRecord(record);
534 List<Entry> res = rec.list;
535 boolean changed = false;
537 Iterator<Entry> eachEntry = res.iterator();
538 while(eachEntry.hasNext()) {
539 Entry entry = eachEntry.next();
541 if (entry.isExpired() || table.contains(entry.peerid)) {
549 cacheDB.deleteRecord(rec.key);
551 } catch (DBException e) {
552 if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
553 LOG.log(Level.WARNING, "Exception while deleting empty record", e);
558 byte[] data = getData(rec.key, res);
559 Value recordValue = new Value(data);
562 cacheDB.writeRecord(pos, recordValue);
563 } catch (DBException ex) {
564 if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
565 LOG.log(Level.WARNING, "Exception while writing back record", ex);
576 * copies the content of List into a list. Expired entries are not
579 * @param to list to copy into
580 * @param from list to copy from
581 * @param table table of PeerID's
583 private static void copyIntoList(List<PeerID> to, List<Entry> from, Set<PeerID> table) {
584 for (Entry entry : from) {
585 boolean expired = entry.isExpired();
587 if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
588 LOG.finer("Entry peerid : " + entry.peerid + (expired ? " EXPIRED " : (" Expires at : " + entry.expiration)));
591 if (!to.contains(entry.peerid) && !expired) {
592 if (!table.contains(entry.peerid)) {
593 if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
594 LOG.finer("adding Entry :" + entry.peerid + " to list");
596 to.add(entry.peerid);
598 if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
599 LOG.finer("Skipping gc marked entry :" + entry.peerid);
603 if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
604 LOG.finer("Skipping expired Entry :" + entry.peerid);
611 * Converts a List of {@link Entry} into a byte[]
613 * @param key record key
614 * @param list List to convert
617 private static byte[] getData(Key key, List<Entry> list) {
619 ByteArrayOutputStream bos = new ByteArrayOutputStream();
620 DataOutputStream dos = new DataOutputStream(bos);
622 dos.writeUTF(key.toString());
623 dos.writeInt(list.size());
624 for (Entry anEntry : list) {
625 dos.writeUTF(anEntry.peerid.toString());
626 dos.writeLong(anEntry.expiration);
629 return bos.toByteArray();
630 } catch (IOException ie) {
631 if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
632 LOG.log(Level.FINE, "Exception while reading Entry", ie);
639 * Reads the content of a record into List
641 * @param record Btree Record
642 * @return List of entries
644 public static SrdiIndexRecord readRecord(Record record) {
645 List<Entry> result = new ArrayList<Entry>();
648 if (record == null) {
649 return new SrdiIndexRecord(null, result);
651 if (record.getValue().getLength() <= 0) {
652 return new SrdiIndexRecord(null, result);
654 InputStream is = record.getValue().getInputStream();
657 DataInputStream ois = new DataInputStream(is);
659 key = new Key(ois.readUTF());
660 int size = ois.readInt();
662 for (int i = 0; i < size; i++) {
664 String idstr = ois.readUTF();
665 PeerID pid = (PeerID) IDFactory.fromURI(new URI(idstr));
666 long exp = ois.readLong();
667 Entry entry = new Entry(pid, exp);
670 } catch (URISyntaxException badID) {
675 } catch (EOFException eofe) {
676 if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
677 LOG.log(Level.FINE, "Empty record", eofe);
679 } catch (IOException ie) {
680 if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
681 LOG.log(Level.WARNING, "Exception while reading Entry", ie);
684 return new SrdiIndexRecord(key, result);
688 * Empties the index completely.
689 * The entries are abandoned to the GC.
691 public synchronized void clear() {
692 // FIXME changing the behavior a bit
693 // instead of dropping all srdi entries, we let them expire
694 // if that is not a desired behavior the indexer could be dropped
695 // simply close it, and remove all index db created
699 } catch (Exception e) {
700 // bad bits we are done
701 if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
702 LOG.log(Level.WARNING, "failed to close index", e);
708 * Garbage Collect expired entries
710 public void garbageCollect() {
712 Map<String, NameIndexer> map = srdiIndexer.getIndexers();
714 for(NameIndexer idxr : map.values()) {
715 List<Long> list = new ArrayList<Long>();
722 idxr.query(null, new GcCallback(cacheDB, srdiIndexer, list, gcPeerTBL));
723 srdiIndexer.purge(list);
727 } catch (Exception ex) {
728 if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
729 LOG.log(Level.WARNING, "Failure during SRDI Garbage Collect", ex);
735 * Remove expired entries from a List
737 * @param list A list of entries.
738 * @return The same list with the expired entries removed.
740 private static List<Entry> removeExpired(List<Entry> list) {
741 Iterator<Entry> eachEntry = list.iterator();
743 while(eachEntry.hasNext()) {
744 Entry entry = eachEntry.next();
746 if (entry.isExpired()) {
748 if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
749 LOG.fine("Removing expired Entry peerid :" + entry.peerid + " Expires at :" + entry.expiration);
756 private static boolean isExpired(long expiration) {
757 return (TimeUtils.timeNow() > expiration);
761 * stop the current running thread
763 public synchronized void stop() {
772 Thread temp = gcThread;
774 synchronized (temp) {
778 } catch (Exception ignored) {
788 } catch (Exception ex) {
789 if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
790 LOG.log(Level.SEVERE, "Unable to stop the Srdi Indexer", ex);
798 * Periodic thread for GC
804 if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
805 LOG.fine("Waiting for " + interval + "ms before garbage collection");
807 synchronized (gcThread) {
808 gcThread.wait(interval);
810 } catch (InterruptedException woken) {
811 // The only reason we are woken is to stop.
812 Thread.interrupted();
820 if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
821 LOG.fine("Garbage collection started");
826 if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
827 LOG.fine("Garbage collection completed");
830 } catch (Throwable all) {
831 if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
832 LOG.log(Level.SEVERE, "Uncaught Throwable in thread :" + Thread.currentThread().getName(), all);
835 synchronized (this) {
842 * Flushes the Srdi directory for a specified group
843 * this method should only be called before initialization of a given group
844 * calling this method on a running group would have undefined results
846 * @param group group context
848 public static void clearSrdi(PeerGroup group) {
850 if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
851 LOG.info("Clearing SRDI for " + group.getPeerGroupName());
858 pgdir = "srdi-index";
860 pgdir = group.getPeerGroupID().getUniqueValue().toString();
865 rootDir = new File(new File(new File(group.getStoreHome()), "cm"), pgdir);
868 rootDir = new File(rootDir, "srdi");
869 if (rootDir.exists()) {
870 // remove it along with it's content
871 String[] list = rootDir.list();
873 for (String aList : list) {
874 if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
875 LOG.fine("Removing : " + aList);
877 File file = new File(rootDir, aList);
879 if (!file.delete()) {
880 if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
881 LOG.warning("Unable to delete the file");
887 } catch (Throwable t) {
888 if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
889 LOG.log(Level.WARNING, "Unable to clear Srdi", t);
895 * An entry in the index tables.
897 public final static class Entry {
899 public final PeerID peerid;
900 public final long expiration;
903 * Peer Pointer reference
905 * @param peerid PeerID for this entry
906 * @param expiration the expiration for this entry
908 public Entry(PeerID peerid, long expiration) {
909 this.peerid = peerid;
910 this.expiration = expiration;
917 public boolean equals(Object obj) {
918 return obj instanceof Entry && (peerid.equals(((Entry) obj).peerid));
925 public int hashCode() {
926 return peerid.hashCode();
930 * Return the absolute time in milliseconds at which this entry will
933 * @return The absolute time in milliseconds at which this entry will
936 public long getExpiration() {
941 * Return {@code true} if this entry is expired.
943 * @return {@code true} if this entry is expired otherwise {@code false}.
945 public boolean isExpired() {
946 return TimeUtils.timeNow() > expiration;
952 * an SrdiIndexRecord wrapper
954 public final static class SrdiIndexRecord {
956 public final Key key;
957 public final List<Entry> list;
962 * @param key record key
963 * @param list record entries
965 public SrdiIndexRecord(Key key,List<Entry> list) {
974 public boolean equals(Object obj) {
975 return obj instanceof SrdiIndexRecord && (key.equals(((SrdiIndexRecord) obj).key));
982 public int hashCode() {
983 return key.hashCode();