/******************************************************************************
 * Copyright (c) 2002 - 2014 IBM Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *****************************************************************************/

package com.ibm.wala.ipa.callgraph.impl;

import com.ibm.wala.classLoader.CallSiteReference;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.ipa.callgraph.CGNode;
import com.ibm.wala.ipa.callgraph.Context;
import com.ibm.wala.ipa.callgraph.ContextSelector;
import com.ibm.wala.ipa.callgraph.DelegatingContext;
import com.ibm.wala.ipa.callgraph.propagation.InstanceKey;
import com.ibm.wala.util.intset.IntSet;

/**
 *  Checks ContextSelectors A and B, then returns the union of their findings.
 *
 *  The returned Context contains the ContextKeys from A and B. If for a given key a value is
 *  generated by A as well as B the value generated by A has the precedence.
 *
 *  As the UnionContextSelector optionally returns a DelegatingContext it cannot be used to 
 *  anihilate ContextValues of B to null.
 *
 *  @see com.ibm.wala.ipa.callgraph.impl.DelegatingContextSelector
 *
 *  @author  Tobias Blaschke &lt;code@tobiasblaschke.de&gt;
 */
public class UnionContextSelector implements ContextSelector {
    private final ContextSelector A;
    private final ContextSelector B;

    public UnionContextSelector(final ContextSelector A, final ContextSelector B) {
        if (A == null) {
            throw new IllegalArgumentException("The ContextSelector given as A may not be null");
        }
        if (B == null) {
            throw new IllegalArgumentException("The ContextSelector given as B may not be null");
        }
        this.A = A;
        this.B = B;
    }

    /**
     *  If only one Context exists return it, else return a DelegatingContext.
     */
    @Override
    public Context getCalleeTarget(CGNode caller, CallSiteReference site, IMethod callee, InstanceKey[] receiver) {
        final Context ctxA = A.getCalleeTarget(caller, site, callee, receiver);
        final Context ctxB = B.getCalleeTarget(caller, site, callee, receiver);

        if (ctxA == null) {
            return ctxB;
        } else if (ctxB == null) {
            return ctxA;
        } else {
            final Context ctxU = new DelegatingContext(ctxA, ctxB);
            return ctxU;
        }
    }

    @Override
    public IntSet getRelevantParameters(CGNode caller, CallSiteReference site) {
        return A.getRelevantParameters(caller, site).union(B.getRelevantParameters(caller, site));
    }

    @Override
    public String toString() {
        return "<UnionContextSelector A=" + A + " B=" + B + " />";
    }
}
