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;
+
+/**
+ * <p>
+ * This is a special thread-safe version of {@link PlusAnonymousUserDataModel}
+ * which allow multiple concurrent anonymous requests.
+ * </p>
+ *
+ * <p>
+ * 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.
+ * </p>
+ *
+ * <p>
+ * If no more users are available in the pool, anonymous recommendations
cannot be produced.
+ * </p>
+ *
+ * </p>
+ *
+ * Setup:
+ * <pre>
+ * int concurrentUsers = 100;
+ * DataModel realModel = ..
+ * PlusAnonymousConcurrentUserDataModel plusModel =
+ * new PlusAnonymousConcurrentUserDataModel(realModel, concurrentUsers);
+ * Recommender recommender = ...;
+ * </pre>
+ *
+ * Real-time recommendation:
+ * <pre>
+ * 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);
+ * </pre>
+ *
+ * </p>
+ */
+public final class PlusAnonymousConcurrentUserDataModel extends
PlusAnonymousUserDataModel {
+
+ /** Preferences for all anonymous users */
+ private final Map<Long,PreferenceArray> tempPrefs;
+ /** Item IDs set for all anonymous users */
+ private final Map<Long,FastIDSet> prefItemIDs;
+ /** Pool of the users (FIFO) */
+ private Queue<Long> 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<Long, PreferenceArray>();
+ prefItemIDs = new ConcurrentHashMap<Long, FastIDSet>();
+
+ 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<Long>();
+ 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<Preference> anonymousPreferences = new ArrayList<Preference>();
+
+ for (Map.Entry<Long, PreferenceArray> 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<Long, PreferenceArray> 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<Long, PreferenceArray> 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
*
* </p>
*/
-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<PreferenceArray> delegatePreferences = new
FastByIDMap<PreferenceArray>();
+ return new PlusAnonymousConcurrentUserDataModel(new
GenericDataModel(delegatePreferences), maxConcurrentUsers);
+ }
+
+ /**
+ * Prepares a testable object with delegate data
+ */
+ private static PlusAnonymousConcurrentUserDataModel
getTestableWithDelegateData(
+ int maxConcurrentUsers, FastByIDMap<PreferenceArray>
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<PreferenceArray> delegatePreferences = new
FastByIDMap<PreferenceArray>();
+ 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<PreferenceArray> delegatePreferences = new
FastByIDMap<PreferenceArray>();
+ 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<PreferenceArray> delegatePreferences = new
FastByIDMap<PreferenceArray>();
+ 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<Long> 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<PreferenceArray> delegatePreferences = new
FastByIDMap<PreferenceArray>();
+ 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));
+ }
+
+}