public static boolean isUpdateAcceptable(IntentData currentData, IntentData newData) {

        if (currentData == null) {
            return true;
        } else if (currentData.version().isOlderThan(newData.version())) {
            return true;
        } else if (currentData.version().isNewerThan(newData.version())) {
            log.trace("{} update not acceptable: current is newer", newData.key());
            return false;
        }

        assert (currentData.version().equals(newData.version()));
        if (currentData.internalStateVersion >= newData.internalStateVersion) {
            log.trace("{} update not acceptable: current is newer internally", newData.key());
            return false;
        }

        // current and new data versions are the same
        IntentState currentState = currentData.state();
        IntentState newState = newData.state();

        switch (newState) {
        case INSTALLING:
            if (currentState == INSTALLING) {
                log.trace("{} update not acceptable: no-op INSTALLING", newData.key());
                return false;
            }
            // FALLTHROUGH
        case REALLOCATING:
            if (currentState == REALLOCATING) {
                log.trace("{} update not acceptable: no-op REALLOCATING", newData.key());
                return false;
            } else if (currentState == INSTALLED) {
                return true;
            }
            // FALLTHROUGH
        case INSTALLED:
            if (currentState == INSTALLED) {
                return false;
            } else if (currentState == WITHDRAWING || currentState == WITHDRAWN
                    || currentState == PURGE_REQ) {
                log.warn("Invalid state transition from {} to {} for intent {}",
                         currentState, newState, newData.key());
                return false;
            }
            return true;

        case WITHDRAWING:
            if (currentState == WITHDRAWING) {
                log.trace("{} update not acceptable: no-op WITHDRAWING", newData.key());
                return false;
            }
            // FALLTHROUGH
        case WITHDRAWN:
            if (currentState == WITHDRAWN) {
                log.trace("{} update not acceptable: no-op WITHDRAWN", newData.key());
                return false;
            } else if (currentState == INSTALLING || currentState == INSTALLED
                    || currentState == PURGE_REQ) {
                log.warn("Invalid state transition from {} to {} for intent {}",
                         currentState, newState, newData.key());
                return false;
            }
            return true;

        case FAILED:
            if (currentState == FAILED) {
                log.trace("{} update not acceptable: no-op FAILED", newData.key());
                return false;
            }
            return true;

        case CORRUPT:
            if (currentState == CORRUPT) {
                log.trace("{} update not acceptable: no-op CORRUPT", newData.key());
                return false;
            }
            return true;

        case PURGE_REQ:
            // TODO we should enforce that only WITHDRAWN intents can be purged
            return true;

        case COMPILING:
        case RECOMPILING:
        case INSTALL_REQ:
        case WITHDRAW_REQ:
        default:
            log.warn("Invalid state {} for intent {}", newState, newData.key());
            return false;
        }
    }
@Test
    public void testIsUpdateAcceptable() {
        // Going from null to something is always allowed
        assertTrue(IntentData.isUpdateAcceptable(null, data1));

        // we can go from older version to newer but not they other way
        assertTrue(IntentData.isUpdateAcceptable(data1, data2));
        assertFalse(IntentData.isUpdateAcceptable(data2, data1));

        IntentData installing = new IntentData(intent1, IntentState.INSTALLING, timestamp1);
        IntentData installed = IntentData.nextState(installing, IntentState.INSTALLED);
        IntentData withdrawing = new IntentData(intent1, IntentState.WITHDRAWING, timestamp1);
        IntentData withdrawn = IntentData.nextState(withdrawing, IntentState.WITHDRAWN);

        IntentData failed = new IntentData(intent1, IntentState.FAILED, timestamp1);

        IntentData compiling = new IntentData(intent1, IntentState.COMPILING, timestamp1);
        IntentData recompiling = new IntentData(intent1, IntentState.RECOMPILING, timestamp1);

        IntentData installReq = new IntentData(intent1, IntentState.INSTALL_REQ, timestamp2);
        IntentData withdrawReq = new IntentData(intent1, IntentState.WITHDRAW_REQ, timestamp2);
        IntentData purgeReq = new IntentData(intent1, IntentState.PURGE_REQ, timestamp2);

        // We can't change to the same state
        assertFalse(IntentData.isUpdateAcceptable(installing, installing));
        assertFalse(IntentData.isUpdateAcceptable(installed, installed));

        // From installing we can change to installed
        assertTrue(IntentData.isUpdateAcceptable(installing, installed));
        // transition in reverse should be rejected
        assertFalse(IntentData.isUpdateAcceptable(installed, installing));

        // Sanity checks in case the manager submits bogus state transitions
        assertFalse(IntentData.isUpdateAcceptable(installing, withdrawing));
        assertFalse(IntentData.isUpdateAcceptable(installing, withdrawn));
        assertFalse(IntentData.isUpdateAcceptable(installed, withdrawing));
        assertFalse(IntentData.isUpdateAcceptable(installed, withdrawn));

        // We can't change to the same state
        assertFalse(IntentData.isUpdateAcceptable(withdrawing, withdrawing));
        assertFalse(IntentData.isUpdateAcceptable(withdrawn, withdrawn));

        // From withdrawing we can change to withdrawn
        assertTrue(IntentData.isUpdateAcceptable(withdrawing, withdrawn));

        // Sanity checks in case the manager submits bogus state transitions
        assertFalse(IntentData.isUpdateAcceptable(withdrawing, installing));
        assertFalse(IntentData.isUpdateAcceptable(withdrawing, installed));
        assertFalse(IntentData.isUpdateAcceptable(withdrawn, installing));
        assertFalse(IntentData.isUpdateAcceptable(withdrawn, installed));

        // We can't go from failed to failed
        assertFalse(IntentData.isUpdateAcceptable(failed, failed));

        // But we can go from any install* or withdraw* state to failed
        assertTrue(IntentData.isUpdateAcceptable(installing, IntentData.nextState(installing, FAILED)));
        assertTrue(IntentData.isUpdateAcceptable(installed, IntentData.nextState(installed, FAILED)));
        assertTrue(IntentData.isUpdateAcceptable(withdrawing, IntentData.nextState(withdrawing, FAILED)));
        assertTrue(IntentData.isUpdateAcceptable(withdrawn, IntentData.nextState(withdrawn, FAILED)));

        // We can go from anything to purgeReq
        assertTrue(IntentData.isUpdateAcceptable(installing, purgeReq));
        assertTrue(IntentData.isUpdateAcceptable(installed, purgeReq));
        assertTrue(IntentData.isUpdateAcceptable(withdrawing, purgeReq));
        assertTrue(IntentData.isUpdateAcceptable(withdrawn, purgeReq));
        assertTrue(IntentData.isUpdateAcceptable(failed, purgeReq));

        // We can't go from purgeReq back to anything else
        assertFalse(IntentData.isUpdateAcceptable(purgeReq, withdrawn));
        assertFalse(IntentData.isUpdateAcceptable(purgeReq, withdrawing));
        assertFalse(IntentData.isUpdateAcceptable(purgeReq, installed));
        assertFalse(IntentData.isUpdateAcceptable(purgeReq, installing));

        // We're never allowed to store transient states
        assertFalse(IntentData.isUpdateAcceptable(installing, compiling));
        assertFalse(IntentData.isUpdateAcceptable(installing, recompiling));
        assertFalse(IntentData.isUpdateAcceptable(installing, installing));
        assertFalse(IntentData.isUpdateAcceptable(installing, withdrawing));
    }