Return-Path: X-Original-To: apmail-mahout-commits-archive@www.apache.org Delivered-To: apmail-mahout-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 938929698 for ; Fri, 17 Feb 2012 15:34:42 +0000 (UTC) Received: (qmail 53632 invoked by uid 500); 17 Feb 2012 15:34:42 -0000 Delivered-To: apmail-mahout-commits-archive@mahout.apache.org Received: (qmail 53582 invoked by uid 500); 17 Feb 2012 15:34:41 -0000 Mailing-List: contact commits-help@mahout.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@mahout.apache.org Delivered-To: mailing list commits@mahout.apache.org Received: (qmail 53571 invoked by uid 99); 17 Feb 2012 15:34:41 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 17 Feb 2012 15:34:41 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 17 Feb 2012 15:34:39 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 2FE9A238897D; Fri, 17 Feb 2012 15:34:19 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1245615 - in /mahout/trunk/core/src: main/java/org/apache/mahout/cf/taste/impl/model/ test/java/org/apache/mahout/cf/taste/impl/model/ Date: Fri, 17 Feb 2012 15:34:19 -0000 To: commits@mahout.apache.org From: srowen@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20120217153419.2FE9A238897D@eris.apache.org> Author: srowen Date: Fri Feb 17 15:34:17 2012 New Revision: 1245615 URL: http://svn.apache.org/viewvc?rev=1245615&view=rev Log: MAHOUT-977 add multiple anonymous user support to DataModel Added: mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/PlusAnonymousConcurrentUserDataModel.java mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/model/PlusAnonymousConcurrentUserDataModelTest.java Modified: mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/PlusAnonymousUserDataModel.java Added: mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/PlusAnonymousConcurrentUserDataModel.java URL: http://svn.apache.org/viewvc/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/PlusAnonymousConcurrentUserDataModel.java?rev=1245615&view=auto ============================================================================== --- mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/PlusAnonymousConcurrentUserDataModel.java (added) +++ mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/PlusAnonymousConcurrentUserDataModel.java Fri Feb 17 15:34:17 2012 @@ -0,0 +1,344 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.mahout.cf.taste.impl.model; + +import com.google.common.base.Preconditions; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import org.apache.mahout.cf.taste.common.NoSuchItemException; +import org.apache.mahout.cf.taste.common.TasteException; +import org.apache.mahout.cf.taste.impl.common.FastIDSet; +import org.apache.mahout.cf.taste.impl.common.LongPrimitiveIterator; +import org.apache.mahout.cf.taste.model.DataModel; +import org.apache.mahout.cf.taste.model.Preference; +import org.apache.mahout.cf.taste.model.PreferenceArray; + +/** + *

+ * This is a special thread-safe version of {@link PlusAnonymousUserDataModel} + * which allow multiple concurrent anonymous requests. + *

+ * + *

+ * To use it, you have to estimate the number of concurrent anonymous users of your application. + * The pool of users with the given size will be created. For each anonymous recommendations request, + * a user has to be taken from the pool and returned back immediately afterwars. + *

+ * + *

+ * If no more users are available in the pool, anonymous recommendations cannot be produced. + *

+ * + *

+ * + * Setup: + *
+ * int concurrentUsers = 100;
+ * DataModel realModel = ..
+ * PlusAnonymousConcurrentUserDataModel plusModel =
+ *   new PlusAnonymousConcurrentUserDataModel(realModel, concurrentUsers);
+ * Recommender recommender = ...;
+ * 
+ * + * Real-time recommendation: + *
+ * PlusAnonymousConcurrentUserDataModel plusModel =
+ *   (PlusAnonymousConcurrentUserDataModel) recommender.getDataModel();
+ *
+ * // Take the next available anonymous user from the pool
+ * Long anonymousUserID = plusModel.takeAvailableUser();
+ *
+ * PreferenceArray tempPrefs = ..
+ * tempPrefs.setUserID(0, anonymousUserID);
+ * tempPrefs.setItemID(0, itemID);
+ * plusModel.setTempPrefs(tempPrefs);
+ *
+ * // Produce recommendations
+ * recommender.recommend(anonymousUserID, howMany);
+ *
+ * // It is very IMPORTANT to release user back to the pool
+ * plusModel.releaseUser(anonymousUserID);
+ * 
+ * + *

+ */ +public final class PlusAnonymousConcurrentUserDataModel extends PlusAnonymousUserDataModel { + + /** Preferences for all anonymous users */ + private final Map tempPrefs; + /** Item IDs set for all anonymous users */ + private final Map prefItemIDs; + /** Pool of the users (FIFO) */ + private Queue usersPool; + + /** + * @param delegate Real model where anonymous users will be added to + * @param maxConcurrentUsers Maximum allowed number of concurrent anonymous users + */ + public PlusAnonymousConcurrentUserDataModel(DataModel delegate, int maxConcurrentUsers) { + super(delegate); + + tempPrefs = new ConcurrentHashMap(); + prefItemIDs = new ConcurrentHashMap(); + + initializeUsersPools(maxConcurrentUsers); + } + + /** + * Initialize the pool of concurrent anonymous users. + * + * @param usersPoolSize Maximum allowed number of concurrent anonymous user. Depends on the consumer system. + */ + private void initializeUsersPools(int usersPoolSize) { + usersPool = new ConcurrentLinkedQueue(); + for (int i = 0; i < usersPoolSize; i++) { + usersPool.add(TEMP_USER_ID + i); + } + } + + /** + * Take the next available concurrent anonymous users from the pool. + * + * @return User ID or null if no more users are available + */ + public Long takeAvailableUser() { + Long takenUserID = usersPool.poll(); + if (takenUserID != null) { + // Initialize the preferences array to indicate that the user is taken. + tempPrefs.put(takenUserID, new GenericUserPreferenceArray(0)); + return takenUserID; + } + return null; + } + + /** + * Release previously taken anonymous user and return it to the pool. + * + * @param userID ID of a previously taken anonymous user + * @return true if the user was previously taken, false otherwise + */ + public boolean releaseUser(Long userID) { + if (tempPrefs.containsKey(userID)) { + this.clearTempPrefs(userID); + // Return previously taken user to the pool + usersPool.offer(userID); + return true; + } + return false; + } + + /** + * Checks whether a given user is a valid previously acquired anonymous user. + */ + private boolean isAnonymousUser(long userID) { + return tempPrefs.containsKey(userID); + } + + /** + * Sets temporary preferences for a given anonymous user. + */ + public void setTempPrefs(PreferenceArray prefs, long anonymousUserID) { + Preconditions.checkArgument(prefs != null && prefs.length() > 0, "prefs is null or empty"); + + this.tempPrefs.put(anonymousUserID, prefs); + FastIDSet userPrefItemIDs = new FastIDSet(); + + for (int i = 0; i < prefs.length(); i++) { + userPrefItemIDs.add(prefs.getItemID(i)); + } + + this.prefItemIDs.put(anonymousUserID, userPrefItemIDs); + } + + /** + * Clears temporary preferences for a given anonymous user. + */ + public void clearTempPrefs(long anonymousUserID) { + this.tempPrefs.remove(anonymousUserID); + this.prefItemIDs.remove(anonymousUserID); + } + + @Override + public LongPrimitiveIterator getUserIDs() throws TasteException { + // Anonymous users have short lifetime and should not be included into the neighbohoods of the real users. + // Thus exclude them from the universe. + return getDelegate().getUserIDs(); + } + + @Override + public PreferenceArray getPreferencesFromUser(long userID) throws TasteException { + if (isAnonymousUser(userID)) { + return tempPrefs.get(userID); + } + return getDelegate().getPreferencesFromUser(userID); + } + + @Override + public FastIDSet getItemIDsFromUser(long userID) throws TasteException { + if (isAnonymousUser(userID)) { + return prefItemIDs.get(userID); + } + return getDelegate().getItemIDsFromUser(userID); + } + + @Override + public PreferenceArray getPreferencesForItem(long itemID) throws TasteException { + if (tempPrefs.isEmpty()) { + return getDelegate().getPreferencesForItem(itemID); + } + + PreferenceArray delegatePrefs = null; + + try { + delegatePrefs = getDelegate().getPreferencesForItem(itemID); + } catch (NoSuchItemException nsie) { + // OK. Probably an item that only the anonymous user has + } + + List anonymousPreferences = new ArrayList(); + + for (Map.Entry prefsMap : tempPrefs.entrySet()) { + PreferenceArray singleUserTempPrefs = prefsMap.getValue(); + for (int i = 0; i < singleUserTempPrefs.length(); i++) { + if (singleUserTempPrefs.getItemID(i) == itemID) { + anonymousPreferences.add(singleUserTempPrefs.get(i)); + } + } + } + + int delegateLength = delegatePrefs == null ? 0 : delegatePrefs.length(); + int anonymousPrefsLength = anonymousPreferences.size(); + int prefsCounter = 0; + + // Merge the delegate and anonymous preferences into a single array + PreferenceArray newPreferenceArray = new GenericItemPreferenceArray(delegateLength + anonymousPrefsLength); + + for (int i = 0; i < delegateLength; i++) { + newPreferenceArray.set(prefsCounter++, delegatePrefs.get(i)); + } + + for (Preference anonymousPreference : anonymousPreferences) { + newPreferenceArray.set(prefsCounter++, anonymousPreference); + } + + if (newPreferenceArray.length() == 0) { + // No, didn't find it among the anonymous user prefs + throw new NoSuchItemException(itemID); + } + + return newPreferenceArray; + } + + @Override + public Float getPreferenceValue(long userID, long itemID) throws TasteException { + if (isAnonymousUser(userID)) { + PreferenceArray singleUserTempPrefs = tempPrefs.get(userID); + for (int i = 0; i < singleUserTempPrefs.length(); i++) { + if (singleUserTempPrefs.getItemID(i) == itemID) { + return singleUserTempPrefs.getValue(i); + } + } + return null; + } + return getDelegate().getPreferenceValue(userID, itemID); + } + + @Override + public Long getPreferenceTime(long userID, long itemID) throws TasteException { + if (isAnonymousUser(userID)) { + // Timestamps are not saved for anonymous preferences + return null; + } + return getDelegate().getPreferenceTime(userID, itemID); + } + + @Override + public int getNumUsers() throws TasteException { + // Anonymous users have short lifetime and should not be included into the neighbohoods of the real users. + // Thus exclude them from the universe. + return getDelegate().getNumUsers(); + } + + @Override + public int getNumUsersWithPreferenceFor(long itemID) throws TasteException { + if (tempPrefs.isEmpty()) { + return getDelegate().getNumUsersWithPreferenceFor(itemID); + } + + int countAnonymousUsersWithPreferenceFor = 0; + + for (Map.Entry singleUserTempPrefs : tempPrefs.entrySet()) { + for (int i = 0; i < singleUserTempPrefs.getValue().length(); i++) { + if (singleUserTempPrefs.getValue().getItemID(i) == itemID) { + countAnonymousUsersWithPreferenceFor++; + break; + } + } + } + return getDelegate().getNumUsersWithPreferenceFor(itemID) + countAnonymousUsersWithPreferenceFor; + } + + @Override + public int getNumUsersWithPreferenceFor(long itemID1, long itemID2) throws TasteException { + if (tempPrefs.isEmpty()) { + return getDelegate().getNumUsersWithPreferenceFor(itemID1, itemID2); + } + + int countAnonymousUsersWithPreferenceFor = 0; + + for (Map.Entry singleUserTempPrefs : tempPrefs.entrySet()) { + boolean found1 = false; + boolean found2 = false; + for (int i = 0; i < singleUserTempPrefs.getValue().length() && !(found1 && found2); i++) { + long itemID = singleUserTempPrefs.getValue().getItemID(i); + if (itemID == itemID1) { + found1 = true; + } + if (itemID == itemID2) { + found2 = true; + } + } + + if (found1 && found2) { + countAnonymousUsersWithPreferenceFor++; + } + } + + return getDelegate().getNumUsersWithPreferenceFor(itemID1, itemID2) + countAnonymousUsersWithPreferenceFor; + } + + @Override + public void setPreference(long userID, long itemID, float value) throws TasteException { + if (isAnonymousUser(userID)) { + throw new UnsupportedOperationException(); + } + getDelegate().setPreference(userID, itemID, value); + } + + @Override + public void removePreference(long userID, long itemID) throws TasteException { + if (isAnonymousUser(userID)) { + throw new UnsupportedOperationException(); + } + getDelegate().removePreference(userID, itemID); + } +} Modified: mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/PlusAnonymousUserDataModel.java URL: http://svn.apache.org/viewvc/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/PlusAnonymousUserDataModel.java?rev=1245615&r1=1245614&r2=1245615&view=diff ============================================================================== --- mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/PlusAnonymousUserDataModel.java (original) +++ mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/impl/model/PlusAnonymousUserDataModel.java Fri Feb 17 15:34:17 2012 @@ -76,7 +76,7 @@ import com.google.common.base.Preconditi * *

*/ -public final class PlusAnonymousUserDataModel implements DataModel { +public class PlusAnonymousUserDataModel implements DataModel { public static final long TEMP_USER_ID = Long.MIN_VALUE; @@ -88,6 +88,10 @@ public final class PlusAnonymousUserData this.delegate = delegate; this.prefItemIDs = new FastIDSet(); } + + protected DataModel getDelegate() { + return delegate; + } public void setTempPrefs(PreferenceArray prefs) { Preconditions.checkArgument(prefs != null && prefs.length() > 0, "prefs is null or empty"); Added: mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/model/PlusAnonymousConcurrentUserDataModelTest.java URL: http://svn.apache.org/viewvc/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/model/PlusAnonymousConcurrentUserDataModelTest.java?rev=1245615&view=auto ============================================================================== --- mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/model/PlusAnonymousConcurrentUserDataModelTest.java (added) +++ mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/model/PlusAnonymousConcurrentUserDataModelTest.java Fri Feb 17 15:34:17 2012 @@ -0,0 +1,313 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.mahout.cf.taste.impl.model; + +import java.util.Iterator; +import org.apache.mahout.cf.taste.common.NoSuchUserException; +import org.apache.mahout.cf.taste.common.TasteException; +import org.apache.mahout.cf.taste.model.PreferenceArray; +import org.apache.mahout.cf.taste.impl.common.FastByIDMap; +import org.apache.mahout.common.MahoutTestCase; +import org.junit.Test; + +public final class PlusAnonymousConcurrentUserDataModelTest extends MahoutTestCase { + + /** + * Prepares a testable object without delegate data + */ + private static PlusAnonymousConcurrentUserDataModel getTestableWithoutDelegateData(int maxConcurrentUsers) { + FastByIDMap delegatePreferences = new FastByIDMap(); + return new PlusAnonymousConcurrentUserDataModel(new GenericDataModel(delegatePreferences), maxConcurrentUsers); + } + + /** + * Prepares a testable object with delegate data + */ + private static PlusAnonymousConcurrentUserDataModel getTestableWithDelegateData( + int maxConcurrentUsers, FastByIDMap delegatePreferences) { + return new PlusAnonymousConcurrentUserDataModel(new GenericDataModel(delegatePreferences), maxConcurrentUsers); + } + + /** + * Test taking the first available user + */ + @Test + public void testTakeFirstAvailableUser() { + PlusAnonymousConcurrentUserDataModel instance = getTestableWithoutDelegateData(10); + Long expResult = PlusAnonymousUserDataModel.TEMP_USER_ID; + Long result = instance.takeAvailableUser(); + assertEquals(expResult, result); + } + + /** + * Test taking the next available user + */ + @Test + public void testTakeNextAvailableUser() { + PlusAnonymousConcurrentUserDataModel instance = getTestableWithoutDelegateData(10); + // Skip first user + instance.takeAvailableUser(); + Long result = instance.takeAvailableUser(); + Long expResult = PlusAnonymousUserDataModel.TEMP_USER_ID + 1; + assertEquals(expResult, result); + } + + /** + * Test taking an unavailable user + */ + @Test + public void testTakeUnavailableUser() { + PlusAnonymousConcurrentUserDataModel instance = getTestableWithoutDelegateData(1); + // Take the only available user + instance.takeAvailableUser(); + // There are no more users available + assertNull(instance.takeAvailableUser()); + } + + /** + * Test releasing a valid previously taken user + */ + @Test + public void testReleaseValidUser() { + PlusAnonymousConcurrentUserDataModel instance = getTestableWithoutDelegateData(10); + Long takenUserID = instance.takeAvailableUser(); + assertTrue(instance.releaseUser(takenUserID)); + } + + /** + * Test releasing an invalid user + */ + @Test + public void testReleaseInvalidUser() { + PlusAnonymousConcurrentUserDataModel instance = getTestableWithoutDelegateData(10); + assertFalse(instance.releaseUser(Long.MAX_VALUE)); + } + + /** + * Test releasing a user which had been released earlier + */ + @Test + public void testReleasePreviouslyReleasedUser() { + PlusAnonymousConcurrentUserDataModel instance = getTestableWithoutDelegateData(10); + Long takenUserID = instance.takeAvailableUser(); + assertTrue(instance.releaseUser(takenUserID)); + assertFalse(instance.releaseUser(takenUserID)); + } + + /** + * Test setting anonymous user preferences + */ + @Test + public void testSetAndGetTempPreferences() throws TasteException { + PlusAnonymousConcurrentUserDataModel instance = getTestableWithoutDelegateData(10); + Long anonymousUserID = instance.takeAvailableUser(); + PreferenceArray tempPrefs = new GenericUserPreferenceArray(1); + tempPrefs.setUserID(0, anonymousUserID); + tempPrefs.setItemID(0, 1); + instance.setTempPrefs(tempPrefs, anonymousUserID); + assertEquals(tempPrefs, instance.getPreferencesFromUser(anonymousUserID)); + instance.releaseUser(anonymousUserID); + } + + /** + * Test setting and getting preferences from several concurrent anonymous users + */ + @Test + public void testSetMultipleTempPreferences() throws TasteException { + PlusAnonymousConcurrentUserDataModel instance = getTestableWithoutDelegateData(10); + + Long anonymousUserID1 = instance.takeAvailableUser(); + Long anonymousUserID2 = instance.takeAvailableUser(); + + PreferenceArray tempPrefs1 = new GenericUserPreferenceArray(1); + tempPrefs1.setUserID(0, anonymousUserID1); + tempPrefs1.setItemID(0, 1); + + PreferenceArray tempPrefs2 = new GenericUserPreferenceArray(2); + tempPrefs2.setUserID(0, anonymousUserID2); + tempPrefs2.setItemID(0, 2); + tempPrefs2.setUserID(1, anonymousUserID2); + tempPrefs2.setItemID(1, 3); + + instance.setTempPrefs(tempPrefs1, anonymousUserID1); + instance.setTempPrefs(tempPrefs2, anonymousUserID2); + + assertEquals(tempPrefs1, instance.getPreferencesFromUser(anonymousUserID1)); + assertEquals(tempPrefs2, instance.getPreferencesFromUser(anonymousUserID2)); + } + + /** + * Test counting the number of delegate users + */ + @Test + public void testGetNumUsersWithDelegateUsersOnly() throws TasteException { + PreferenceArray prefs = new GenericUserPreferenceArray(1); + long sampleUserID = 1; + prefs.setUserID(0, sampleUserID); + long sampleItemID = 11; + prefs.setItemID(0, sampleItemID); + + FastByIDMap delegatePreferences = new FastByIDMap(); + delegatePreferences.put(sampleUserID, prefs); + + PlusAnonymousConcurrentUserDataModel instance = getTestableWithDelegateData(10, delegatePreferences); + + assertEquals(1, instance.getNumUsers()); + } + + /** + * Test counting the number of anonymous users + */ + @Test + public void testGetNumAnonymousUsers() throws TasteException { + PlusAnonymousConcurrentUserDataModel instance = getTestableWithoutDelegateData(10); + + Long anonymousUserID1 = instance.takeAvailableUser(); + + PreferenceArray tempPrefs1 = new GenericUserPreferenceArray(1); + tempPrefs1.setUserID(0, anonymousUserID1); + tempPrefs1.setItemID(0, 1); + + instance.setTempPrefs(tempPrefs1, anonymousUserID1); + + // Anonymous users should not be included into the universe. + assertEquals(0, instance.getNumUsers()); + } + + /** + * Test retrieve a single preference value of an anonymous user + */ + @Test + public void testGetPreferenceValue() throws TasteException { + PlusAnonymousConcurrentUserDataModel instance = getTestableWithoutDelegateData(10); + + Long anonymousUserID = instance.takeAvailableUser(); + + PreferenceArray tempPrefs = new GenericUserPreferenceArray(1); + tempPrefs.setUserID(0, anonymousUserID); + long sampleItemID = 1; + tempPrefs.setItemID(0, sampleItemID); + tempPrefs.setValue(0, Float.MAX_VALUE); + + instance.setTempPrefs(tempPrefs, anonymousUserID); + + assertEquals(Float.MAX_VALUE, instance.getPreferenceValue(anonymousUserID, sampleItemID), EPSILON); + } + + /** + * Test retrieve preferences for existing non-anonymous user + */ + @Test + public void testGetPreferencesForNonAnonymousUser() throws TasteException { + PreferenceArray prefs = new GenericUserPreferenceArray(1); + long sampleUserID = 1; + prefs.setUserID(0, sampleUserID); + long sampleItemID = 11; + prefs.setItemID(0, sampleItemID); + + FastByIDMap delegatePreferences = new FastByIDMap(); + delegatePreferences.put(sampleUserID, prefs); + + PlusAnonymousConcurrentUserDataModel instance = getTestableWithDelegateData(10, delegatePreferences); + + assertEquals(prefs, instance.getPreferencesFromUser(sampleUserID)); + } + + /** + * Test retrieve preferences for non-anonymous and non-existing user + */ + @Test(expected=NoSuchUserException.class) + public void testGetPreferencesForNonExistingUser() throws TasteException { + PlusAnonymousConcurrentUserDataModel instance = getTestableWithoutDelegateData(10); + // Exception is expected since such user does not exist + instance.getPreferencesFromUser(1); + } + + /** + * Test retrieving the user IDs and verifying that anonymous ones are not included + */ + @Test + public void testGetUserIDs() throws TasteException { + PreferenceArray prefs = new GenericUserPreferenceArray(1); + long sampleUserID = 1; + prefs.setUserID(0, sampleUserID); + long sampleItemID = 11; + prefs.setItemID(0, sampleItemID); + + FastByIDMap delegatePreferences = new FastByIDMap(); + delegatePreferences.put(sampleUserID, prefs); + + PlusAnonymousConcurrentUserDataModel instance = getTestableWithDelegateData(10, delegatePreferences); + + Long anonymousUserID = instance.takeAvailableUser(); + + PreferenceArray tempPrefs = new GenericUserPreferenceArray(1); + tempPrefs.setUserID(0, anonymousUserID); + tempPrefs.setItemID(0, 22); + + instance.setTempPrefs(tempPrefs, anonymousUserID); + + Iterator userIDs = instance.getUserIDs(); + + assertSame(sampleUserID, userIDs.next()); + assertFalse(userIDs.hasNext()); + } + + /** + * Test getting preferences for an item. + * + * @throws TasteException + */ + @Test + public void testGetPreferencesForItem() throws TasteException { + PreferenceArray prefs = new GenericUserPreferenceArray(2); + long sampleUserID = 4; + prefs.setUserID(0, sampleUserID); + long sampleItemID = 11; + prefs.setItemID(0, sampleItemID); + prefs.setUserID(1, sampleUserID); + long sampleItemID2 = 22; + prefs.setItemID(1, sampleItemID2); + + FastByIDMap delegatePreferences = new FastByIDMap(); + delegatePreferences.put(sampleUserID, prefs); + + PlusAnonymousConcurrentUserDataModel instance = getTestableWithDelegateData(10, delegatePreferences); + + Long anonymousUserID = instance.takeAvailableUser(); + + PreferenceArray tempPrefs = new GenericUserPreferenceArray(2); + tempPrefs.setUserID(0, anonymousUserID); + tempPrefs.setItemID(0, sampleItemID); + tempPrefs.setUserID(1, anonymousUserID); + long sampleItemID3 = 33; + tempPrefs.setItemID(1, sampleItemID3); + + instance.setTempPrefs(tempPrefs, anonymousUserID); + + assertEquals(sampleUserID, instance.getPreferencesForItem(sampleItemID).get(0).getUserID()); + assertEquals(2, instance.getPreferencesForItem(sampleItemID).length()); + assertEquals(1, instance.getPreferencesForItem(sampleItemID2).length()); + assertEquals(1, instance.getPreferencesForItem(sampleItemID3).length()); + + assertEquals(2, instance.getNumUsersWithPreferenceFor(sampleItemID)); + assertEquals(1, instance.getNumUsersWithPreferenceFor(sampleItemID, sampleItemID2)); + assertEquals(1, instance.getNumUsersWithPreferenceFor(sampleItemID, sampleItemID3)); + } + +}