/*************************************************************************** * * * Entries.java * * ------------------- * * date : 28.02.2005 * * copyright : (C) 2004-2008 Distributed and * * Mobile Systems Group * * Lehrstuhl fuer Praktische Informatik * * Universitaet Bamberg * * http://www.uni-bamberg.de/pi/ * * email : sven.kaffille@uni-bamberg.de * * karsten.loesing@uni-bamberg.de * * * * * ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * A copy of the license can be found in the license.txt file supplied * * with this software or at: http://www.gnu.org/copyleft/gpl.html * * * ***************************************************************************/ package de.uniba.wiai.lspi.chord.service.impl; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.TreeMap; import de.uniba.wiai.lspi.chord.com.Entry; import de.uniba.wiai.lspi.chord.data.ID; import de.uniba.wiai.lspi.util.logging.Logger; /** * Stores entries for the local node in a local hash table and provides methods * for accessing them. It IS allowed, that multiple objects of type * {@link Entry} with same {@link ID} are stored! * * @author Karsten Loesing, Sven Kaffille * @version 1.0.5 * */ /* * 23.12.2006. Fixed synchronization. The Map<ID, Set<Entry>> entries must be * synchronized with a synchronized statement, when executing several methods * that depend on each other. This would also apply to the internal Set<Entry> * if it were not only used in the same synchronized statements for entries, * which than functions as a synchronization point. It must also be locked by a * synchronized statement, when iterating over it. TODO: What about fairness? * sven */ final class Entries { /** * Object logger. */ private final static Logger logger = Logger.getLogger(Entries.class); private final static boolean debugEnabled = logger .isEnabledFor(Logger.LogLevel.DEBUG); /** * Local hash table for entries. Is synchronized, st. methods do not have to * be synchronized. */ private Map<ID, Set<Entry>> entries = null; /** * Creates an empty repository for entries. */ Entries(){ this.entries = Collections .synchronizedMap(new TreeMap<ID, Set<Entry>>()); } /** * Stores a set of entries to the local hash table. * * @param entriesToAdd * Set of entries to add to the repository. * @throws NullPointerException * If set reference is <code>null</code>. */ final void addAll(Set<Entry> entriesToAdd) { if (entriesToAdd == null) { NullPointerException e = new NullPointerException( "Set of entries to be added to the local hash table may " + "not be null!"); Entries.logger.error("Null pointer", e); throw e; } for (Entry nextEntry : entriesToAdd) { this.add(nextEntry); } if (debugEnabled) { Entries.logger.debug("Set of entries of length " + entriesToAdd.size() + " was added."); } } /** * Stores one entry to the local hash table. * * @param entryToAdd * Entry to add to the repository. * @throws NullPointerException * If entry to add is <code>null</code>. */ final void add(Entry entryToAdd) { if (entryToAdd == null) { NullPointerException e = new NullPointerException( "Entry to add may not be null!"); Entries.logger.error("Null pointer", e); throw e; } Set<Entry> values; synchronized (this.entries) { if (this.entries.containsKey(entryToAdd.getId())) { values = this.entries.get(entryToAdd.getId()); } else { values = new HashSet<Entry>(); this.entries.put(entryToAdd.getId(), values); } values.add(entryToAdd); } if (debugEnabled) { Entries.logger.debug("Entry was added: " + entryToAdd); } } /** * Removes the given entry from the local hash table. * * @param entryToRemove * Entry to remove from the hash table. * @throws NullPointerException * If entry to remove is <code>null</code>. */ final void remove(Entry entryToRemove) { if (entryToRemove == null) { NullPointerException e = new NullPointerException( "Entry to remove may not be null!"); Entries.logger.error("Null pointer", e); throw e; } synchronized (this.entries) { if (this.entries.containsKey(entryToRemove.getId())) { Set<Entry> values = this.entries.get(entryToRemove.getId()); values.remove(entryToRemove); if (values.size() == 0) { this.entries.remove(entryToRemove.getId()); } } } if (debugEnabled) { Entries.logger.debug("Entry was removed: " + entryToRemove); } } /** * Returns a set of entries matching the given ID. If no entries match the * given ID, an empty set is returned. * * @param id * ID of entries to be returned. * @throws NullPointerException * If given ID is <code>null</code>. * @return Set of matching entries. Empty Set if no matching entries are * available. */ final Set<Entry> getEntries(ID id) { if (id == null) { NullPointerException e = new NullPointerException( "ID to find entries for may not be null!"); Entries.logger.error("Null pointer", e); throw e; } synchronized (this.entries) { /* * This has to be synchronized as the test if the map contains a set * associated with id can succeed and then the thread may hand * control over to another thread that removes the Set belonging to * id. In that case this.entries.get(id) would return null which * would break the contract of this method. */ if (this.entries.containsKey(id)) { Set<Entry> entriesForID = this.entries.get(id); /* * Return a copy of the set to avoid modification of Set stored * in this.entries from outside this class. (Avoids also * modifications concurrent to iteration over the Set by a * client of this class. */ if (debugEnabled) { Entries.logger.debug("Returning entries " + entriesForID); } return new HashSet<Entry>(entriesForID); } } if (debugEnabled) { Entries.logger.debug("No entries available for " + id + ". Returning empty set."); } return new HashSet<Entry>(); } /** * Returns all entries in interval, excluding lower bound, but including * upper bound * * @param fromID * Lower bound of IDs; entries matching this ID are NOT included * in result. * @param toID * Upper bound of IDs; entries matching this ID ARE included in * result. * @throws NullPointerException * If either or both of the given ID references have value * <code>null</code>. * @return Set of matching entries. */ final Set<Entry> getEntriesInInterval(ID fromID, ID toID) { if (fromID == null || toID == null) { NullPointerException e = new NullPointerException( "Neither of the given IDs may have value null!"); Entries.logger.error("Null pointer", e); throw e; } Set<Entry> result = new HashSet<Entry>(); synchronized (this.entries) { for (ID nextID : this.entries.keySet()) { if (nextID.isInInterval(fromID, toID)) { Set<Entry> entriesForID = this.entries.get(nextID); for (Entry entryToAdd : entriesForID) { result.add(entryToAdd); } } } } // add entries matching upper bound result.addAll(this.getEntries(toID)); return result; } /** * Removes the given entries from the local hash table. * * @param toRemove * Set of entries to remove from local hash table. * @throws NullPointerException * If the given set of entries is <code>null</code>. */ final void removeAll(Set<Entry> toRemove) { if (toRemove == null) { NullPointerException e = new NullPointerException( "Set of entries may not have value null!"); Entries.logger.error("Null pointer", e); throw e; } for (Entry nextEntry : toRemove) { this.remove(nextEntry); } if (debugEnabled) { Entries.logger.debug("Set of entries of length " + toRemove.size() + " was removed."); } } /** * Returns an unmodifiable map of all stored entries. * * @return Unmodifiable map of all stored entries. */ final Map<ID, Set<Entry>> getEntries() { return Collections.unmodifiableMap(this.entries); } /** * Returns the number of stored entries. * * @return Number of stored entries. */ final int getNumberOfStoredEntries() { return this.entries.size(); } /** * Returns a formatted string of all entries stored in the local hash table. * * @return String representation of all stored entries. */ public final String toString() { StringBuilder result = new StringBuilder("Entries:\n"); for (Map.Entry<ID, Set<Entry>> entry : this.entries.entrySet()) { result.append(" key = " + entry.getKey().toString() + ", value = " + entry.getValue() + "\n"); } return result.toString(); } }