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));
+ }
+
+}