public void setTabu(S source, T target, int expireAfter) {
        /* Pre-checks */
        if (expireAfter < 0) {
            return;
        }
        if (expireAfter > maxExpTime) {
            throw new IllegalArgumentException("expiration time not in [0.." + maxExpTime + "]");
        }
        if (   source == null
            || target == null ) {
            throw new IllegalArgumentException("cannot set null values as tabu");
        }

        /* Mark tabu */
        HashSet<S> sources = tabu.get(target);
        if (sources == null) {
            // 'target' doesn't have a tabu set: make it
            sources = new HashSet<S>();
            tabu.put(target, sources);
        }
        else {
            if (sources.contains(source)) {
                // 'source' is already tabu for 'target': return
                return;
            }
        }
        // add 'source' as tabu for 'target'
        sources.add(source);

        /* Schedule expiration */
        Integer expTime = (now + expireAfter + 1) % (maxExpTime + 1);
        HashMap<T, HashSet<S>> expMap = expirations.get(expTime);
        HashSet<S> expSet = expMap.get(target);
        if (expSet == null) {
            expSet = new HashSet<S>();
            expMap.put(target, expSet);
        }
        expSet.add(source);

        /* Add backpointer */
        HashSet<Integer> expTimes = backptrs.get(target);
        if (expTimes == null) {
            expTimes = new HashSet<Integer>();
            backptrs.put(target, expTimes);
        }
        expTimes.add(expTime);
    }