/* * � Copyright IBM Corp. 2010 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License. */ package com.ibm.xsp.extlib.social.util; import com.ibm.commons.util.StringUtil; /** * Sophisticated 2 ways cache, which MRU list management. * <p> * This class is used to maintain a map between string, like a NotesID and another * external ID. One can use it to retrive a NotesID from the social ID, and vice * versa.<br> * It also maintain a maximum number of entries in memory, thus prevent the list from * hijacking the whole available memory in case of big directories.<br> * This class is abstract as it requires an actual implementation to find the string * correpondance. The implementation is required to ensure that the mapping are unique. * This is not checked by the map for performance reasons, and can have unpredictable * results if it happens. * </p> */ public abstract class NamesMap { private int slotCount; private int maxSize; private int size; // List for implementing MRUs private MapEntry listStart; private MapEntry listEnd; // Hash map entries private MapEntry[] idEntries; private MapEntry[] nameEntries; private static final class MapEntry { // List in the Hash table MapEntry idPrevHash; MapEntry idNextHash; MapEntry namePrevHash; MapEntry nameNextHash; // List in the Linked list MapEntry prevList; MapEntry nextList; int idHashCode; int nameHashCode; String id; String name; MapEntry( String id, String name ) { this.id = id; this.name = name; this.idHashCode = id.hashCode(); this.nameHashCode = name.hashCode(); } } public NamesMap(int maxSize) { this(maxSize,57); // See Aho for good values here... } public NamesMap(int maxSize, int slotCount) { this.maxSize = maxSize; this.slotCount = slotCount; this.idEntries = new MapEntry[slotCount]; this.nameEntries = new MapEntry[slotCount]; } protected abstract String findIdByName(String name); protected abstract String findNameByid(String id); public int size() { return size; } public int getCapacity() { return maxSize; } public synchronized void clear() { this.size = 0; this.idEntries = new MapEntry[slotCount]; this.nameEntries = new MapEntry[slotCount]; this.listStart = null; this.listEnd = null; } public synchronized String getNameById( String id ) { if(StringUtil.isEmpty(id)) { return id; } // Look if the name is already in the collection MapEntry e = getEntryById(id); if( e!=null ) { moveToStart(e); return e.name; } // Ok the entry does not exist, try to create it String name = findNameByid(id); if(name!=null) { e = new MapEntry(id,name); put(e); return name; } // Not found return null; } public synchronized String getIdByName( String name ) { if(StringUtil.isEmpty(name)) { return name; } // Look if the id is already in the collection MapEntry e = getEntryByName(name); if( e!=null ) { moveToStart(e); return e.id; } // Ok the entry does not exist, try to create it String id = findIdByName(name); if(id!=null) { e = new MapEntry(id,name); put(e); return id; } // Not found return null; } private final int getSlot(int hashCode) { return (hashCode & 0x7FFFFFFF) % slotCount; } private final MapEntry getEntryById( String id ) { int hashCode = id.hashCode(); for( MapEntry e=idEntries[getSlot(hashCode)]; e!=null; e=e.idNextHash ) { if( e.idHashCode==hashCode && e.id.equals(id) ) { return e; } } return null; } private final MapEntry getEntryByName( String name ) { int hashCode = name.hashCode(); for( MapEntry e=nameEntries[getSlot(hashCode)]; e!=null; e=e.nameNextHash ) { if( e.nameHashCode==hashCode && e.name.equals(name) ) { return e; } } return null; } private void put( MapEntry e ) { // Remove the oldest one? if( size==maxSize ) { removeEntry(listEnd); } // Insert the new entry in the ID HashTable int idSlot = getSlot(e.idHashCode); e.idNextHash = idEntries[idSlot]; if(e.idNextHash!=null) { e.idNextHash.idPrevHash = e; } idEntries[idSlot] = e; // Insert the new entry in the Name HashTable int nameSlot = getSlot(e.nameHashCode); e.nameNextHash = nameEntries[nameSlot]; if(e.nameNextHash!=null) { e.nameNextHash.namePrevHash = e; } nameEntries[nameSlot] = e; // And in the list if( listStart!=null ) { listStart.prevList = e; } e.nextList = listStart; listStart = e; if( listEnd==null ) { listEnd = e; } size++; } private final void moveToStart( MapEntry e ) { if( e!=listStart ) { // Remove it from the list e.prevList.nextList = e.nextList; if( e.nextList!=null ) { e.nextList.prevList = e.prevList; } else { listEnd = e.prevList; } // And add it a the top listStart.prevList = e; e.nextList = listStart; listStart = e; } } private final void removeEntry(MapEntry e) { // Remove the entry from the ID hashtable if(e.idPrevHash!=null) { e.idPrevHash.idNextHash = e.idNextHash; } else { idEntries[getSlot(e.idHashCode)] = e.idNextHash; } if(e.idNextHash!=null) { e.idNextHash.idPrevHash = e.idPrevHash; } // Remove the entry from the Name hashtable if(e.namePrevHash!=null) { e.namePrevHash.nameNextHash = e.nameNextHash; } else { nameEntries[getSlot(e.nameHashCode)] = e.nameNextHash; } if(e.nameNextHash!=null) { e.nameNextHash.namePrevHash = e.namePrevHash; } // Remove the entry from the linked list if( e.prevList!=null ) { e.prevList.nextList = e.nextList; } else { listStart = e.nextList; } if( e.nextList!=null ) { e.nextList.prevList = e.prevList; } else { listEnd = e.prevList; } // Decrease the map count size--; } }