/*
 * LensKit, an open source recommender systems toolkit.
 * Copyright 2010-2013 Regents of the University of Minnesota and contributors
 * Work on LensKit has been funded by the National Science Foundation under
 * grants IIS 05-34939, 08-08692, 08-12148, and 10-17697.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package org.grouplens.lenskit.scored;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import it.unimi.dsi.fastutil.objects.Reference2DoubleArrayMap;
import it.unimi.dsi.fastutil.objects.Reference2DoubleMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import org.grouplens.lenskit.symbols.Symbol;
import org.grouplens.lenskit.symbols.TypedSymbol;

import java.io.Serializable;
import java.util.Collections;
import java.util.Set;

class ScoredIdImpl extends AbstractScoredId implements Serializable {
    private static final long serialVersionUID = 1L;

    private final long id;
    private final double score;
    @SuppressFBWarnings("SE_BAD_FIELD")
    private final Reference2DoubleMap<Symbol> channelMap;
    @SuppressFBWarnings("SE_BAD_FIELD")
    private final Reference2ObjectMap<TypedSymbol<?>, ?> typedChannelMap;

    public ScoredIdImpl(long id, double score) {
        this(id, score, null, null);
    }

    /**
     * @param typedChannelMap a map from TypedSymbol<K> to the object in that side channel. 
     *                        It is assumed that for each key TypedSymbol<K> in the map that the value
     *                        is of type K.  
     */
    public ScoredIdImpl(long id, double score, Reference2DoubleMap<Symbol> channelMap, 
            Reference2ObjectMap<TypedSymbol<?>, ?> typedChannelMap) {
        this.id = id;
        this.score = score;
        if (channelMap != null) {
            this.channelMap = new Reference2DoubleArrayMap<Symbol>(channelMap);
        } else {
            this.channelMap = null;
        }
        if (typedChannelMap != null) {
            this.typedChannelMap = new Reference2ObjectArrayMap<TypedSymbol<?>, Object>(typedChannelMap);
        } else {
            this.typedChannelMap = null;
        }
    }

    @Override
    public long getId() {
        return id;
    }

    @Override
    public double getScore() {
        return score;
    }

    @Override
    public boolean hasChannel(Symbol s) {
        return channelMap != null && channelMap.containsKey(s);
    }

    @Override
    public boolean hasChannel(TypedSymbol<?> s) {
        return typedChannelMap != null && typedChannelMap.containsKey(s);
    }

    @Override
    public double channel(Symbol s) {
        if (hasChannel(s)) {
            return channelMap.get(s);
        }
        throw new IllegalArgumentException("No existing channel under name " + s.getName());
    }

    @SuppressWarnings("unchecked")
    @Override
    public <K> K channel(TypedSymbol<K> s) {
        if (hasChannel(s)) {
            return (K) typedChannelMap.get(s);
        }
        throw new IllegalArgumentException("No existing typed channel under name " + s.getName());
    }

    @Override
    public Set<Symbol> getChannels() {
        if (channelMap != null) {
            return Collections.unmodifiableSet(channelMap.keySet());
        } else {
            return Collections.emptySet();
        }
    }

    @Override
    public Set<TypedSymbol<?>> getTypedChannels() {
        if (typedChannelMap != null) {
            return Collections.unmodifiableSet(typedChannelMap.keySet());
        } else {
            return Collections.emptySet();
        }
    }
}

@@ -1044,16 +1044,63 @@
 %0Aimport 
+com.google.common.collect.ImmutableMap;%0Aimport 
 edu.umd.
@@ -1145,192 +1145,8 @@
 ings
-;%0Aimport it.unimi.dsi.fastutil.objects.Reference2DoubleArrayMap;%0Aimport it.unimi.dsi.fastutil.objects.Reference2DoubleMap;%0Aimport it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap
 ;%0Aim
@@ -1180,35 +1180,17 @@
 objects.
-Reference2ObjectMap
+*
 ;%0Aimport
@@ -1279,16 +1279,49 @@
 ymbol;%0A%0A
+import javax.annotation.Nonnull;%0A
 import j
@@ -1371,16 +1371,38 @@
 ctions;%0A
+import java.util.Map;%0A
 import j
@@ -1631,32 +1631,45 @@
 %22SE_BAD_FIELD%22)%0A
+    @Nonnull%0A
     private fina
@@ -1746,24 +1746,37 @@
 BAD_FIELD%22)%0A
+    @Nonnull%0A
     private 
@@ -1781,32 +1781,16 @@
 e final 
-Reference2Object
 Map%3CType
@@ -2534,36 +2534,62 @@
 is.channelMap = 
-null
+Reference2DoubleMaps.EMPTY_MAP
 ;%0A        %7D%0A    
@@ -2662,60 +2662,27 @@
 p = 
-new Reference2ObjectArrayMap%3CTypedSymbol%3C?%3E, Object%3E
+ImmutableMap.copyOf
 (typ
@@ -2748,20 +2748,38 @@
 elMap = 
-null
+Collections.emptyMap()
 ;%0A      
@@ -3003,30 +3003,8 @@
 turn
- channelMap != null &&
  cha
@@ -3116,35 +3116,8 @@
 turn
- typedChannelMap != null &&
  typ
@@ -3724,46 +3724,8 @@
 ) %7B%0A
-        if (channelMap != null) %7B%0A    
     
@@ -3789,78 +3789,8 @@
 ));%0A
-        %7D else %7B%0A            return Collections.emptySet();%0A        %7D%0A
     
@@ -3870,182 +3870,40 @@
     
-if (typedChannelMap != null) %7B%0A            return Collections.unmodifiableSet(typedChannelMap.keySet());%0A        %7D else %7B%0A            return Collections.emptySet();%0A        %7D
+return typedChannelMap.keySet();
 %0A   

Use empty maps rather than null for scored IDs without channels