@Test
public void testLoadFilteredPolicy() {
    Adapter adapter = new FilteredAdapter("examples/rbac_with_domains_policy.csv");
    Enforcer enforcer = new Enforcer("examples/rbac_with_domains_model.conf", "examples/rbac_with_domains_policy.csv", true);
    enforcer.setAdapter(adapter);
    testHasPolicy(enforcer, asList("admin", "domain1", "data1", "read"), true);
    testHasPolicy(enforcer, asList("admin", "domain2", "data2", "read"), true);
    FilteredAdapter.Filter f = new FilteredAdapter.Filter();
    f.g = new String[] { "", "", "domain1" };
    f.p = new String[] { "", "domain1" };
    enforcer.loadFilteredPolicy(f);
    testHasPolicy(enforcer, asList("admin", "domain1", "data1", "read"), true);
    testHasPolicy(enforcer, asList("admin", "domain2", "data2", "read"), false);
}

public void loadFilteredPolicy(Object filter) {
}

public void loadFilteredPolicy(Object filter) {
    model.clearPolicy();
    FilteredAdapter filteredAdapter;
    if (adapter instanceof FilteredAdapter) {
        filteredAdapter = (FilteredAdapter) adapter;
    } else {
        throw new CasbinAdapterException("Filtered policies are not supported by this adapter.");
    }
    try {
        filteredAdapter.loadFilteredPolicy(model, filter);
    } catch (Exception e) {
        e.printStackTrace();
    }
    model.printPolicy();
    if (autoBuildRoleLinks) {
        buildRoleLinks();
    }
}

@Test
public void testFilteredAdapterEmptyFilepath() {
    Adapter adapter = new FilteredAdapter("");
    Enforcer enforcer = new Enforcer("examples/rbac_with_domains_model.conf");
    enforcer.setAdapter(adapter);
    enforcer.loadFilteredPolicy(null);
}

@Test
public void testFilteredAdapterInvalidFilepath() {
    Adapter adapter = new FilteredAdapter("examples/does_not_exist_policy.csv");
    Enforcer enforcer = new Enforcer("examples/rbac_with_domains_model.conf");
    enforcer.setAdapter(adapter);
    try {
        enforcer.loadFilteredPolicy(null);
    } catch (CasbinAdapterException e) {
        e.printStackTrace();
        assert true;
    }
}

@Test
public void testFilteredPolicyEmptyFilter() {
    Adapter adapter = new FilteredAdapter("examples/rbac_with_domains_policy.csv");
    Enforcer enforcer = new Enforcer("examples/rbac_with_domains_model.conf");
    enforcer.setAdapter(adapter);
    enforcer.loadFilteredPolicy(null);
    Util.logPrint("Is adapter filtered:" + enforcer.isFiltered());
    assert !enforcer.isFiltered();
    enforcer.savePolicy();
}

public boolean isFiltered() {
    return false;
}

public boolean isFiltered() {
    if (adapter instanceof FilteredAdapter) {
        return ((FilteredAdapter) adapter).isFiltered();
    }
    return false;
}

@Test
public void testUnsupportedFilteredPolicy() {
    Enforcer enforcer = new Enforcer("examples/rbac_with_domains_model.conf", "examples/rbac_with_domains_policy.csv", true);
    FilteredAdapter.Filter f = new FilteredAdapter.Filter();
    f.g = new String[] { "", "", "domain1" };
    f.p = new String[] { "", "domain1" };
    try {
        enforcer.loadFilteredPolicy(f);
    } catch (CasbinAdapterException e) {
        e.printStackTrace();
        assert true;
    }
}

@Test
public void testFilteredPolicyInvalidFilte() {
    Adapter adapter = new FilteredAdapter("examples/rbac_with_domains_policy.csv");
    Enforcer enforcer = new Enforcer("examples/rbac_with_domains_model.conf");
    enforcer.setAdapter(adapter);
    try {
        enforcer.loadFilteredPolicy(new String[] { "", "domain1" });
    } catch (CasbinAdapterException e) {
        e.printStackTrace();
        assert true;
    }
}


@Test
public void testModifyPolicyAPI() {
    Enforcer e = new SyncedEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv");
    testGetPolicy(e, asList(asList("alice", "data1", "read"), asList("bob", "data2", "write"), asList("data2_admin", "data2", "read"), asList("data2_admin", "data2", "write")));
    e.removePolicy("alice", "data1", "read");
    e.removePolicy("bob", "data2", "write");
    e.removePolicy("alice", "data1", "read");
    e.addPolicy("eve", "data3", "read");
    e.addPolicy("eve", "data3", "read");
    List<String> namedPolicy = asList("eve", "data3", "read");
    e.removeNamedPolicy("p", namedPolicy);
    e.addNamedPolicy("p", namedPolicy);
    testGetPolicy(e, asList(asList("data2_admin", "data2", "read"), asList("data2_admin", "data2", "write"), asList("eve", "data3", "read")));
    e.removeFilteredPolicy(1, "data2");
    testGetPolicy(e, asList(asList("eve", "data3", "read")));
}

public boolean addPolicy(String... params) {
    try {
        READ_WRITE_LOCK.writeLock().lock();
        return super.addPolicy(params);
    } finally {
        READ_WRITE_LOCK.writeLock().unlock();
    }
}

@Test
public void testModifyGroupingPolicyAPI() {
    Enforcer e = new SyncedEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv");
    testGetRoles(e, "alice", asList("data2_admin"));
    testGetRoles(e, "bob", asList());
    testGetRoles(e, "eve", asList());
    testGetRoles(e, "non_exist", asList());
    e.removeGroupingPolicy("alice", "data2_admin");
    e.addGroupingPolicy("bob", "data1_admin");
    e.addGroupingPolicy("eve", "data3_admin");
    List<String> namedGroupingPolicy = asList("alice", "data2_admin");
    testGetRoles(e, "alice", asList());
    e.addNamedGroupingPolicy("g", namedGroupingPolicy);
    testGetRoles(e, "alice", asList("data2_admin"));
    e.removeNamedGroupingPolicy("g", namedGroupingPolicy);
    testGetRoles(e, "alice", asList());
    testGetRoles(e, "bob", asList("data1_admin"));
    testGetRoles(e, "eve", asList("data3_admin"));
    testGetRoles(e, "non_exist", asList());
    testGetUsers(e, "data1_admin", asList("bob"));
    testGetUsers(e, "data2_admin", asList());
    testGetUsers(e, "data3_admin", asList("eve"));
    e.removeFilteredGroupingPolicy(0, "bob");
    testGetRoles(e, "alice", asList());
    testGetRoles(e, "bob", asList());
    testGetRoles(e, "eve", asList("data3_admin"));
    testGetRoles(e, "non_exist", asList());
    testGetUsers(e, "data1_admin", asList());
    testGetUsers(e, "data2_admin", asList());
    testGetUsers(e, "data3_admin", asList("eve"));
}

public boolean addGroupingPolicy(String... params) {
    try {
        READ_WRITE_LOCK.writeLock().lock();
        return super.addGroupingPolicy(params);
    } finally {
        READ_WRITE_LOCK.writeLock().unlock();
    }
}


@Test
public void testRBACModelInMemory2() {
    String text = "[request_definition]\n" + "r = sub, obj, act\n" + "\n" + "[policy_definition]\n" + "p = sub, obj, act\n" + "\n" + "[role_definition]\n" + "g = _, _\n" + "\n" + "[policy_effect]\n" + "e = some(where (p.eft == allow))\n" + "\n" + "[matchers]\n" + "m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act\n";
    Model m = newModel(text);
    Enforcer e = new SyncedEnforcer(m);
    e.addPermissionForUser("alice", "data1", "read");
    e.addPermissionForUser("bob", "data2", "write");
    e.addPermissionForUser("data2_admin", "data2", "read");
    e.addPermissionForUser("data2_admin", "data2", "write");
    e.addRoleForUser("alice", "data2_admin");
    testEnforce(e, "alice", "data1", "read", true);
    testEnforce(e, "alice", "data1", "write", false);
    testEnforce(e, "alice", "data2", "read", true);
    testEnforce(e, "alice", "data2", "write", true);
    testEnforce(e, "bob", "data1", "read", false);
    testEnforce(e, "bob", "data1", "write", false);
    testEnforce(e, "bob", "data2", "read", false);
    testEnforce(e, "bob", "data2", "write", true);
}

@Override
public boolean addRoleForUser(String user, String role) {
    try {
        READ_WRITE_LOCK.writeLock().lock();
        return super.addRoleForUser(user, role);
    } finally {
        READ_WRITE_LOCK.writeLock().unlock();
    }
}

@Test
public void testRBACModelInMemory() {
    Model m = newModel();
    m.addDef("r", "r", "sub, obj, act");
    m.addDef("p", "p", "sub, obj, act");
    m.addDef("g", "g", "_, _");
    m.addDef("e", "e", "some(where (p.eft == allow))");
    m.addDef("m", "m", "g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act");
    Enforcer e = new SyncedEnforcer(m);
    e.addPermissionForUser("alice", "data1", "read");
    e.addPermissionForUser("bob", "data2", "write");
    e.addPermissionForUser("data2_admin", "data2", "read");
    e.addPermissionForUser("data2_admin", "data2", "write");
    e.addRoleForUser("alice", "data2_admin");
    testEnforce(e, "alice", "data1", "read", true);
    testEnforce(e, "alice", "data1", "write", false);
    testEnforce(e, "alice", "data2", "read", true);
    testEnforce(e, "alice", "data2", "write", true);
    testEnforce(e, "bob", "data1", "read", false);
    testEnforce(e, "bob", "data1", "write", false);
    testEnforce(e, "bob", "data2", "read", false);
    testEnforce(e, "bob", "data2", "write", true);
}

@Test
public void testRBACModelInMemoryIndeterminate() {
    Model m = newModel();
    m.addDef("r", "r", "sub, obj, act");
    m.addDef("p", "p", "sub, obj, act");
    m.addDef("g", "g", "_, _");
    m.addDef("e", "e", "some(where (p.eft == allow))");
    m.addDef("m", "m", "g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act");
    Enforcer e = new SyncedEnforcer(m);
    e.addPermissionForUser("alice", "data1", "invalid");
    testEnforce(e, "alice", "data1", "read", false);
}

@Override
public boolean addPermissionForUser(String user, String... permission) {
    try {
        READ_WRITE_LOCK.writeLock().lock();
        return super.addPermissionForUser(user, permission);
    } finally {
        READ_WRITE_LOCK.writeLock().unlock();
    }
}

@Test
public void testNotUsedRBACModelInMemory() {
    Model m = newModel();
    m.addDef("r", "r", "sub, obj, act");
    m.addDef("p", "p", "sub, obj, act");
    m.addDef("g", "g", "_, _");
    m.addDef("e", "e", "some(where (p.eft == allow))");
    m.addDef("m", "m", "g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act");
    Enforcer e = new SyncedEnforcer(m);
    e.addPermissionForUser("alice", "data1", "read");
    e.addPermissionForUser("bob", "data2", "write");
    testEnforce(e, "alice", "data1", "read", true);
    testEnforce(e, "alice", "data1", "write", false);
    testEnforce(e, "alice", "data2", "read", false);
    testEnforce(e, "alice", "data2", "write", false);
    testEnforce(e, "bob", "data1", "read", false);
    testEnforce(e, "bob", "data1", "write", false);
    testEnforce(e, "bob", "data2", "read", false);
    testEnforce(e, "bob", "data2", "write", true);
}


@Test
public void testRoleAPI() {
    Enforcer e = new SyncedEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv");
    testGetRoles(e, "alice", asList("data2_admin"));
    testGetRoles(e, "bob", asList());
    testGetRoles(e, "data2_admin", asList());
    testGetRoles(e, "non_exist", asList());
    testHasRole(e, "alice", "data1_admin", false);
    testHasRole(e, "alice", "data2_admin", true);
    e.addRoleForUser("alice", "data1_admin");
    testGetRoles(e, "alice", asList("data1_admin", "data2_admin"));
    testGetRoles(e, "bob", asList());
    testGetRoles(e, "data2_admin", asList());
    e.deleteRoleForUser("alice", "data1_admin");
    testGetRoles(e, "alice", asList("data2_admin"));
    testGetRoles(e, "bob", asList());
    testGetRoles(e, "data2_admin", asList());
    e.deleteRolesForUser("alice");
    testGetRoles(e, "alice", asList());
    testGetRoles(e, "bob", asList());
    testGetRoles(e, "data2_admin", asList());
    e.addRoleForUser("alice", "data1_admin");
    e.deleteUser("alice");
    testGetRoles(e, "alice", asList());
    testGetRoles(e, "bob", asList());
    testGetRoles(e, "data2_admin", asList());
    e.addRoleForUser("alice", "data2_admin");
    testEnforce(e, "alice", "data1", "read", true);
    testEnforce(e, "alice", "data1", "write", false);
    testEnforce(e, "alice", "data2", "read", true);
    testEnforce(e, "alice", "data2", "write", true);
    testEnforce(e, "bob", "data1", "read", false);
    testEnforce(e, "bob", "data1", "write", false);
    testEnforce(e, "bob", "data2", "read", false);
    testEnforce(e, "bob", "data2", "write", true);
    e.deleteRole("data2_admin");
    testEnforce(e, "alice", "data1", "read", true);
    testEnforce(e, "alice", "data1", "write", false);
    testEnforce(e, "alice", "data2", "read", false);
    testEnforce(e, "alice", "data2", "write", false);
    testEnforce(e, "bob", "data1", "read", false);
    testEnforce(e, "bob", "data1", "write", false);
    testEnforce(e, "bob", "data2", "read", false);
    testEnforce(e, "bob", "data2", "write", true);
}

@Override
public void deleteRole(String role) {
    try {
        READ_WRITE_LOCK.writeLock().lock();
        super.deleteRole(role);
    } finally {
        READ_WRITE_LOCK.writeLock().unlock();
    }
}

@Test
public void testImplicitPermissionAPI() {
    Enforcer e = new SyncedEnforcer("examples/rbac_model.conf", "examples/rbac_with_hierarchy_policy.csv");
    assertEquals(e.getImplicitPermissionsForUser("alice"), asList(asList("alice", "data1", "read"), asList("data1_admin", "data1", "read"), asList("data1_admin", "data1", "write"), asList("data2_admin", "data2", "read"), asList("data2_admin", "data2", "write")));
}

@Override
public List<List<String>> getImplicitPermissionsForUser(String user) {
    try {
        READ_WRITE_LOCK.readLock().lock();
        return super.getImplicitPermissionsForUser(user);
    } finally {
        READ_WRITE_LOCK.readLock().unlock();
    }
}

@Test
public void testImplicitRoleAPI() {
    Enforcer e = new SyncedEnforcer("examples/rbac_model.conf", "examples/rbac_with_hierarchy_policy.csv");
    assertEquals(e.getImplicitRolesForUser("alice"), asList("admin", "data1_admin", "data2_admin"));
}

@Override
public List<String> getImplicitRolesForUser(String name, String... domain) {
    try {
        READ_WRITE_LOCK.readLock().lock();
        return super.getImplicitRolesForUser(name, domain);
    } finally {
        READ_WRITE_LOCK.readLock().unlock();
    }
}

@Test
public void testPermissionAPI() {
    Enforcer e = new SyncedEnforcer("examples/basic_without_resources_model.conf", "examples/basic_without_resources_policy.csv");
    testEnforceWithoutUsers(e, "alice", "read", true);
    testEnforceWithoutUsers(e, "alice", "write", false);
    testEnforceWithoutUsers(e, "bob", "read", false);
    testEnforceWithoutUsers(e, "bob", "write", true);
    testGetPermissions(e, "alice", asList(asList("alice", "read")));
    testGetPermissions(e, "bob", asList(asList("bob", "write")));
    testHasPermission(e, "alice", asList("read"), true);
    testHasPermission(e, "alice", asList("write"), false);
    testHasPermission(e, "bob", asList("read"), false);
    testHasPermission(e, "bob", asList("write"), true);
    e.deletePermission("read");
    testEnforceWithoutUsers(e, "alice", "read", false);
    testEnforceWithoutUsers(e, "alice", "write", false);
    testEnforceWithoutUsers(e, "bob", "read", false);
    testEnforceWithoutUsers(e, "bob", "write", true);
    e.addPermissionForUser("bob", "read");
    testEnforceWithoutUsers(e, "alice", "read", false);
    testEnforceWithoutUsers(e, "alice", "write", false);
    testEnforceWithoutUsers(e, "bob", "read", true);
    testEnforceWithoutUsers(e, "bob", "write", true);
    e.deletePermissionForUser("bob", "read");
    testEnforceWithoutUsers(e, "alice", "read", false);
    testEnforceWithoutUsers(e, "alice", "write", false);
    testEnforceWithoutUsers(e, "bob", "read", false);
    testEnforceWithoutUsers(e, "bob", "write", true);
    e.deletePermissionsForUser("bob");
    testEnforceWithoutUsers(e, "alice", "read", false);
    testEnforceWithoutUsers(e, "alice", "write", false);
    testEnforceWithoutUsers(e, "bob", "read", false);
    testEnforceWithoutUsers(e, "bob", "write", false);
}

@Override
public boolean deletePermission(String... permission) {
    try {
        READ_WRITE_LOCK.writeLock().lock();
        return super.deletePermission(permission);
    } finally {
        READ_WRITE_LOCK.writeLock().unlock();
    }
}


@Test
public void testRoleAPIWithDomains() {
    Enforcer e = new SyncedEnforcer("examples/rbac_with_domains_model.conf", "examples/rbac_with_domains_policy.csv");
    testGetRolesInDomain(e, "alice", "domain1", asList("admin"));
    testGetRolesInDomain(e, "bob", "domain1", asList());
    testGetRolesInDomain(e, "admin", "domain1", asList());
    testGetRolesInDomain(e, "non_exist", "domain1", asList());
    testGetRolesInDomain(e, "alice", "domain2", asList());
    testGetRolesInDomain(e, "bob", "domain2", asList("admin"));
    testGetRolesInDomain(e, "admin", "domain2", asList());
    testGetRolesInDomain(e, "non_exist", "domain2", asList());
    e.deleteRoleForUserInDomain("alice", "admin", "domain1");
    e.addRoleForUserInDomain("bob", "admin", "domain1");
    testGetRolesInDomain(e, "alice", "domain1", asList());
    testGetRolesInDomain(e, "bob", "domain1", asList("admin"));
    testGetRolesInDomain(e, "admin", "domain1", asList());
    testGetRolesInDomain(e, "non_exist", "domain1", asList());
    testGetRolesInDomain(e, "alice", "domain2", asList());
    testGetRolesInDomain(e, "bob", "domain2", asList("admin"));
    testGetRolesInDomain(e, "admin", "domain2", asList());
    testGetRolesInDomain(e, "non_exist", "domain2", asList());
}

@Override
public boolean addRoleForUserInDomain(String user, String role, String domain) {
    try {
        READ_WRITE_LOCK.writeLock().lock();
        return super.addRoleForUserInDomain(user, role, domain);
    } finally {
        READ_WRITE_LOCK.writeLock().unlock();
    }
}


@Test
public void testGetPermittedActions() {
    Enforcer e = new Enforcer("examples/rbac_with_deny_model.conf", "examples/rbac_with_deny_policy.csv", true);
    String sub = "alice";
    String obj = "data1";
    Set<String> actions = e.getPermittedActions(sub, obj);
    Util.logPrint(sub + " has permissions to access " + obj + " with following actions:");
    for (String action : actions) {
        Util.logPrint(action);
    }
    obj = "data2";
    actions = e.getPermittedActions(sub, obj);
    Util.logPrint(sub + " has permissions to access " + obj + " with following actions:");
    for (String action : actions) {
        Util.logPrint(action);
    }
    e = new Enforcer("examples/abac_model.conf", "", true);
    ModelUnitTest.TestResource data1 = new ModelUnitTest.TestResource("data1", "alice");
    actions = e.getPermittedActions(sub, data1);
    Util.logPrint(sub + " has permissions to access " + data1.name + " with following actions:");
    for (String action : actions) {
        Util.logPrint(action);
    }
}

public Set<String> getPermittedActions(Object sub, Object obj) {
    Assertion ast = model.model.get("p").get("p");
    List<List<String>> relations;
    if (model.model.get("g") != null) {
        relations = model.model.get("g").get("g").policy;
    } else {
        relations = Collections.emptyList();
    }
    int actIndex = getElementIndex(ast, "act");
    int objIndex = getElementIndex(ast, "obj");
    int subIndex = getElementIndex(ast, "sub");
    int eftIndex = getElementIndex(ast, "eft");
    Set<String> users = new HashSet<String>() {

        @Override
        public boolean contains(Object o) {
            if (super.contains(o))
                return true;
            if (o == null)
                return super.contains(null);
            for (String s : this) {
                if (s.equals(o))
                    return true;
            }
            return false;
        }
    };
    users.add((String) sub);
    int size;
    do {
        size = users.size();
        for (List<String> relation : relations) {
            if (users.contains(relation.get(0))) {
                users.add(relation.get(1));
            }
        }
    } while (size != users.size());
    List<List<String>> policy = getPolicy();
    Set<String> actionSet = new HashSet<>();
    for (List<String> role : policy) {
        boolean isThisUser = false;
        for (String user : users) {
            if (role.get(subIndex).equals(user)) {
                isThisUser = true;
                break;
            }
        }
        if (isThisUser && role.get(objIndex).equals(obj)) {
            if (eftIndex == -1 || role.get(eftIndex).equalsIgnoreCase(Effect.Allow.toString())) {
                actionSet.add(role.get(actIndex));
            }
        }
    }
    return actionSet;
}


@Test
public void testDistributedAPI() {
    DistributedEnforcer de = new DistributedEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv");
    de.addPolicySelf(() -> false, "p", "p", asList(asList("alice", "data1", "read"), asList("bob", "data2", "write"), asList("data2_admin", "data2", "read"), asList("data2_admin", "data2", "write")));
    de.addPolicySelf(() -> false, "g", "g", asList(asList("alice", "data2_admin")));
    testEnforce(de, "alice", "data1", "read", true);
    testEnforce(de, "alice", "data1", "write", false);
    testEnforce(de, "bob", "data2", "read", false);
    testEnforce(de, "bob", "data2", "write", true);
    testEnforce(de, "data2_admin", "data2", "read", true);
    testEnforce(de, "data2_admin", "data2", "write", true);
    testEnforce(de, "alice", "data2", "read", true);
    testEnforce(de, "alice", "data2", "write", true);
    de.updatePolicySelf(() -> false, "p", "p", asList("alice", "data1", "read"), asList("alice", "data1", "write"));
    de.updatePolicySelf(() -> false, "g", "g", asList("alice", "data2_admin"), asList("tom", "alice"));
    testEnforce(de, "alice", "data1", "read", false);
    testEnforce(de, "alice", "data1", "write", true);
    testEnforce(de, "bob", "data2", "read", false);
    testEnforce(de, "bob", "data2", "write", true);
    testEnforce(de, "data2_admin", "data2", "read", true);
    testEnforce(de, "data2_admin", "data2", "write", true);
    testEnforce(de, "tom", "data1", "read", false);
    testEnforce(de, "tom", "data1", "write", true);
    de.removePolicySelf(() -> false, "p", "p", asList(asList("alice", "data1", "write")));
    de.removePolicySelf(() -> false, "g", "g", asList(asList("alice", "data2_admin")));
    testEnforce(de, "alice", "data1", "read", false);
    testEnforce(de, "alice", "data1", "write", false);
    testEnforce(de, "bob", "data2", "read", false);
    testEnforce(de, "bob", "data2", "write", true);
    testEnforce(de, "data2_admin", "data2", "read", true);
    testEnforce(de, "data2_admin", "data2", "write", true);
    testEnforce(de, "alice", "data2", "read", false);
    testEnforce(de, "alice", "data2", "write", false);
    de.removeFilteredPolicySelf(() -> false, "p", "p", 0, "bob", "data2", "write");
    de.removeFilteredPolicySelf(() -> false, "g", "g", 0, "tom", "data2_admin");
    testEnforce(de, "alice", "data1", "read", false);
    testEnforce(de, "alice", "data1", "write", false);
    testEnforce(de, "bob", "data2", "read", false);
    testEnforce(de, "bob", "data2", "write", false);
    testEnforce(de, "data2_admin", "data2", "read", true);
    testEnforce(de, "data2_admin", "data2", "write", true);
    testEnforce(de, "tom", "data1", "read", false);
    testEnforce(de, "tom", "data1", "write", false);
    de.clearPolicySelf(() -> false);
    testEnforce(de, "alice", "data1", "read", false);
    testEnforce(de, "alice", "data1", "write", false);
    testEnforce(de, "bob", "data2", "read", false);
    testEnforce(de, "bob", "data2", "write", false);
    testEnforce(de, "data2_admin", "data2", "read", false);
    testEnforce(de, "data2_admin", "data2", "write", false);
}

public List<List<String>> removeFilteredPolicySelf(BooleanSupplier shouldPersist, String sec, String ptype, int fieldIndex, String... fieldValues) {
    if (shouldPersist.getAsBoolean()) {
        try {
            adapter.removeFilteredPolicy(sec, ptype, fieldIndex, fieldValues);
        } catch (UnsupportedOperationException ignored) {
            Util.logPrintf("Method not implemented");
        } catch (Exception e) {
            Util.logPrint("An exception occurred:" + e.getMessage());
            return null;
        }
    }
    List<List<String>> effects = this.model.removeFilteredPolicyReturnsEffects(sec, ptype, fieldIndex, fieldValues);
    if (sec.equals("g")) {
        try {
            this.buildIncrementalRoleLinks(Model.PolicyOperations.POLICY_REMOVE, ptype, effects);
        } catch (Exception e) {
            Util.logPrint("An exception occurred:" + e.getMessage());
            return effects;
        }
    }
    return effects;
}


@Test
public void should_true_when_setAviatorEvaluator_given_customInstance() {
    AviatorEvaluatorInstance instance = AviatorEvaluator.newInstance();
    Enforcer enforcer = new Enforcer();
    enforcer.setAviatorEvaluator(instance);
    Assert.assertEquals(instance, enforcer.getAviatorEval());
}

public void setAviatorEvaluator(AviatorEvaluatorInstance evaluator) {
    this.aviatorEval = Objects.requireNonNull(evaluator, "The aviator evaluator cannot be null.");
}


@Test
public void testHasSection() {
    Model m = new Model();
    m.loadModelFromConfig(basicConfig);
    for (String sec : requiredSections) {
        assertTrue(sec + " section was expected in model", m.hasSection(sec));
    }
    m = new Model();
    try {
        m.loadModelFromConfig(new MockConfig());
    } catch (RuntimeException e) {
    } finally {
        for (String sec : requiredSections) {
            assertFalse(sec + " section was not expected in model", m.hasSection(sec));
        }
    }
}

public boolean hasSection(String sec) {
    Map<String, Assertion> section = model.get(sec);
    return section != null;
}

@Test
public void testNewModelFromFile() {
    Model m = Model.newModelFromFile(basicExample);
    assertNotNull(m);
}

public static Model newModelFromFile(String path) {
    Model model = new Model();
    model.loadModel(path);
    return model;
}

@Test
public void testNewModel() {
    Model m = Model.newModel();
    assertNotNull(m);
}

public static Model newModel() {
    Model model = new Model();
    model.setLogger(new DefaultLogger());
    return model;
}

@Test
public void testLoadModelFromConfig() {
    Model m = new Model();
    try {
        m.loadModelFromConfig(basicConfig);
    } catch (Exception e) {
        fail("basic config should not return an error");
    }
    m = new Model();
    try {
        m.loadModelFromConfig(new MockConfig());
        fail("empty config should return error");
    } catch (RuntimeException e) {
        for (String rs : requiredSections) {
            assertTrue("section name: " + sectionNameMap.get(rs) + " should be in message", e.getMessage().contains(sectionNameMap.get(rs)));
        }
    }
}

public void loadModelFromConfig(Config cfg) {
    for (String s : sectionNameMap.keySet()) {
        loadSection(this, cfg, s);
    }
    List<String> ms = new ArrayList<>();
    for (String rs : requiredSections) {
        if (!hasSection(rs)) {
            ms.add(sectionNameMap.get(rs));
        }
    }
    if (!ms.isEmpty()) {
        throw new RuntimeException("missing required sections: " + String.join(",", ms));
    }
}


@Test(expected = IllegalArgumentException.class)
public void testUnsupportedRoleManager() {
    RoleManager rm = new RoleManager() {

        @Override
        public void clear() {
        }

        @Override
        public void addLink(String name1, String name2, String... domain) {
        }

        @Override
        public void deleteLink(String name1, String name2, String... domain) {
        }

        @Override
        public boolean hasLink(String name1, String name2, String... domain) {
            return false;
        }

        @Override
        public java.util.List<String> getRoles(String name, String... domain) {
            return null;
        }

        @Override
        public java.util.List<String> getUsers(String name, String... domain) {
            return null;
        }

        @Override
        public void printRoles() {
        }
    };
    Detector detector = new DefaultDetector();
    detector.check(rm);
}

@Override
public String check(RoleManager rm) {
    if (!(rm instanceof DefaultRoleManager)) {
        throw new IllegalArgumentException("DefaultDetector only supports DefaultRoleManager");
    }
    DefaultRoleManager drm = (DefaultRoleManager) rm;
    Map<String, List<String>> graph = buildGraph(drm);
    Set<String> visited = new HashSet<>();
    Set<String> recursionStack = new HashSet<>();
    Map<String, String> parent = new HashMap<>();
    for (String node : graph.keySet()) {
        if (!visited.contains(node)) {
            String cycle = dfs(node, graph, visited, recursionStack, parent);
            if (cycle != null) {
                return cycle;
            }
        }
    }
    return null;
}

@Test
public void testCycleAfterClear() {
    RoleManager rm = new DefaultRoleManager(10);
    rm.addLink("A", "B");
    rm.addLink("B", "C");
    rm.addLink("C", "A");
    Detector detector = new DefaultDetector();
    String result = detector.check(rm);
    assertNotNull("Expected a cycle before clear", result);
    rm.clear();
    result = detector.check(rm);
    assertNull("Expected no cycle after clear", result);
}

@Test
public void testTwoNodeCycle() {
    RoleManager rm = new DefaultRoleManager(10);
    rm.addLink("A", "B");
    rm.addLink("B", "A");
    Detector detector = new DefaultDetector();
    String result = detector.check(rm);
    assertNotNull("Expected a cycle to be detected", result);
    assertTrue("Result should contain 'Cycle detected'", result.contains("Cycle detected:"));
}

@Test
public void testSimpleCycle() {
    RoleManager rm = new DefaultRoleManager(10);
    rm.addLink("A", "B");
    rm.addLink("B", "C");
    rm.addLink("C", "A");
    Detector detector = new DefaultDetector();
    String result = detector.check(rm);
    assertNotNull("Expected a cycle to be detected", result);
    assertTrue("Result should contain 'Cycle detected'", result.contains("Cycle detected:"));
    assertTrue("Result should contain role A", result.contains("A"));
    assertTrue("Result should contain role B", result.contains("B"));
    assertTrue("Result should contain role C", result.contains("C"));
}

@Test
public void testSingleNode() {
    RoleManager rm = new DefaultRoleManager(10);
    rm.getRoles("A");
    Detector detector = new DefaultDetector();
    String result = detector.check(rm);
    assertNull("Expected no cycle with single isolated node", result);
}

@Test
public void testLargeGraphWithCycle() {
    RoleManager rm = new DefaultRoleManager(10000);
    for (int i = 0; i < 9999; i++) {
        rm.addLink("r" + i, "r" + (i + 1));
    }
    rm.addLink("r9999", "r0");
    Detector detector = new DefaultDetector();
    String result = detector.check(rm);
    assertNotNull("Expected a cycle to be detected in large graph", result);
    assertTrue("Result should contain 'Cycle detected'", result.contains("Cycle detected:"));
}

@Test
public void testComplexGraph() {
    RoleManager rm = new DefaultRoleManager(10);
    rm.addLink("u1", "g1");
    rm.addLink("u2", "g1");
    rm.addLink("g1", "g3");
    rm.addLink("u4", "g2");
    rm.addLink("u4", "g3");
    rm.addLink("g2", "g3");
    rm.addLink("g3", "u4");
    Detector detector = new DefaultDetector();
    String result = detector.check(rm);
    assertNotNull("Expected a cycle to be detected", result);
    assertTrue("Result should contain 'Cycle detected'", result.contains("Cycle detected:"));
}

@Test
public void testLargeGraph() {
    RoleManager rm = new DefaultRoleManager(10000);
    long startTime = System.currentTimeMillis();
    for (int i = 0; i < 9999; i++) {
        rm.addLink("r" + i, "r" + (i + 1));
    }
    Detector detector = new DefaultDetector();
    String result = detector.check(rm);
    long endTime = System.currentTimeMillis();
    long duration = endTime - startTime;
    assertNull("Expected no cycle in large chain", result);
    assertTrue("Detection should complete in reasonable time (< 5 seconds)", duration < 5000);
}

@Test
public void testSelfLoop() {
    RoleManager rm = new DefaultRoleManager(10);
    rm.addLink("A", "A");
    Detector detector = new DefaultDetector();
    String result = detector.check(rm);
    assertNotNull("Expected a cycle to be detected", result);
    assertTrue("Result should contain 'Cycle detected'", result.contains("Cycle detected:"));
    assertTrue("Result should contain role A", result.contains("A"));
}

@Test
public void testCycleDetectionAfterDelete() {
    RoleManager rm = new DefaultRoleManager(10);
    rm.addLink("A", "B");
    rm.addLink("B", "C");
    rm.addLink("C", "A");
    Detector detector = new DefaultDetector();
    String result = detector.check(rm);
    assertNotNull("Expected a cycle before delete", result);
    rm.deleteLink("C", "A");
    result = detector.check(rm);
    assertNull("Expected no cycle after breaking the cycle", result);
}

@Test
public void testMultipleDisconnectedComponents() {
    RoleManager rm = new DefaultRoleManager(10);
    rm.addLink("u1", "g1");
    rm.addLink("g1", "g2");
    rm.addLink("u2", "g3");
    rm.addLink("g3", "g4");
    Detector detector = new DefaultDetector();
    String result = detector.check(rm);
    assertNull("Expected no cycle to be detected", result);
}

@Test
public void testEmptyRoleManager() {
    RoleManager rm = new DefaultRoleManager(10);
    Detector detector = new DefaultDetector();
    String result = detector.check(rm);
    assertNull("Expected no cycle in empty graph", result);
}

@Test
public void testCycleInOneComponent() {
    RoleManager rm = new DefaultRoleManager(10);
    rm.addLink("u1", "g1");
    rm.addLink("g1", "g2");
    rm.addLink("A", "B");
    rm.addLink("B", "C");
    rm.addLink("C", "A");
    Detector detector = new DefaultDetector();
    String result = detector.check(rm);
    assertNotNull("Expected a cycle to be detected", result);
    assertTrue("Result should contain 'Cycle detected'", result.contains("Cycle detected:"));
}

@Test
public void testNoCycle() {
    RoleManager rm = new DefaultRoleManager(10);
    rm.addLink("u1", "g1");
    rm.addLink("u2", "g1");
    rm.addLink("g1", "g2");
    Detector detector = new DefaultDetector();
    String result = detector.check(rm);
    assertNull("Expected no cycle to be detected", result);
}


@Test
public void iterateOverDotenv() {
    var dotenv = Dotenv.configure().ignoreIfMalformed().load();
    for (var e : dotenv.entries()) {
        assertEquals(dotenv.get(e.getKey()), e.getValue());
    }
}

@Override
public String get(String key) {
    var value = System.getenv(key);
    return value != null ? value : map.get(key);
}

@Override
public String get(String key) {
    var value = System.getenv(key);
    return value != null ? value : envVars.get(key);
}


@Test(expected = UnsupportedOperationException.class)
public void testGetExtrasShouldBeUnmodifiable() {
    Request request = new Request();
    request.getExtras().put("a", "1");
}

public Map<String, Object> getExtras() {
    return extras;
}

public Map<String, Object> getExtras() {
    return Collections.unmodifiableMap(extras);
}

@Test
public void testSetExtras() {
    Request request = new Request();
    Map<String, Object> extras = Collections.singletonMap("a", "1");
    request.setExtras(extras);
    request.putExtra("b", "2");
    assertThat(request.<String>getExtra("a")).isEqualTo("1");
    assertThat(request.<String>getExtra("b")).isEqualTo("2");
}

public Request setExtras(Map<String, Object> extras) {
    this.extras = extras;
    return this;
}

public Request setExtras(Map<String, Object> extras) {
    this.extras.putAll(extras);
    return this;
}


@Test
public void reverseNode1() throws Exception {
    ReverseNode.Node<String> node4 = new Node<>("4", null);
    Node<String> node3 = new Node<>("3", node4);
    Node<String> node2 = new Node<>("2", node3);
    Node<String> node1 = new Node("1", node2);
    ReverseNode reverseNode = new ReverseNode();
    reverseNode.reverseNode1(node1);
}

public void reverseNode1(Node node) {
    System.out.println("====翻转之前====");
    Stack<Node> stack = new Stack<>();
    while (node != null) {
        System.out.print(node.value + "===>");
        stack.push(node);
        node = node.next;
    }
    System.out.println("");
    System.out.println("====翻转之后====");
    while (!stack.isEmpty()) {
        System.out.print(stack.pop().value + "===>");
    }
}

@Test
public void recNodeTest31() {
    Node<String> node4 = new Node<>("4", null);
    Node<String> node3 = new Node<>("3", node4);
    Node<String> node2 = new Node<>("2", node3);
    Node<String> node1 = new Node("1", node2);
    ReverseNode reverseNode = new ReverseNode();
    reverseNode.recNode(node1);
}

public void recNode(Node node) {
    if (node == null) {
        return;
    }
    if (node.next != null) {
        recNode(node.next);
    }
    System.out.print(node.value + "===>");
}

@Test
public void reverseNode13() throws Exception {
    Node<String> node1 = null;
    ReverseNode reverseNode = new ReverseNode();
    reverseNode.reverseNode1(node1);
}

@Test
public void reverseHead21() throws Exception {
    Node<String> node4 = new Node<>("4", null);
    Node<String> node3 = new Node<>("3", node4);
    Node<String> node2 = new Node<>("2", node3);
    Node<String> node1 = new Node("1", node2);
    ReverseNode reverseNode = new ReverseNode();
    reverseNode.reverseNode(node1);
}

public void reverseNode(Node head) {
    if (head == null) {
        return;
    }
    Node node;
    Node pre = head;
    Node cur = head.next;
    Node next;
    while (cur != null) {
        next = cur.next;
        cur.next = pre;
        pre = cur;
        cur = next;
    }
    head.next = null;
    node = pre;
    while (node != null) {
        System.out.println(node.value);
        node = node.next;
    }
}

@Test
public void reverseNode12() throws Exception {
    Node<String> node1 = new Node("1", null);
    ReverseNode reverseNode = new ReverseNode();
    reverseNode.reverseNode1(node1);
}


@Test
public void isHappy() throws Exception {
    HappyNum happyNum = new HappyNum();
    boolean happy = happyNum.isHappy(19);
    Assert.assertEquals(happy, true);
}

public boolean isHappy(int number) {
    Set<Integer> set = new HashSet<>(30);
    while (number != 1) {
        int sum = 0;
        while (number > 0) {
            sum += (number % 10) * (number % 10);
            number = number / 10;
        }
        if (set.contains(sum)) {
            return false;
        } else {
            set.add(sum);
        }
        number = sum;
    }
    return true;
}

@Test
public void isHappy2() throws Exception {
    HappyNum happyNum = new HappyNum();
    boolean happy = happyNum.isHappy(11);
    Assert.assertEquals(happy, false);
}

@Test
public void isHappy3() throws Exception {
    HappyNum happyNum = new HappyNum();
    boolean happy = happyNum.isHappy(100);
    System.out.println(happy);
}


@Test
public void test1() {
    BinaryNode node = new BinaryNode();
    node = node.createNode();
    System.out.println(node);
    node.levelIterator(node);
}

public BinaryNode createNode() {
    BinaryNode node = new BinaryNode("1", null, null);
    BinaryNode left2 = new BinaryNode("2", null, null);
    BinaryNode left3 = new BinaryNode("3", null, null);
    BinaryNode left4 = new BinaryNode("4", null, null);
    BinaryNode left5 = new BinaryNode("5", null, null);
    BinaryNode left6 = new BinaryNode("6", null, null);
    node.setLeft(left2);
    left2.setLeft(left4);
    left2.setRight(left6);
    node.setRight(left3);
    left3.setRight(left5);
    return node;
}


@Test
public void mergeTwoLists3() throws Exception {
    ListNode l2 = new ListNode(1);
    ListNode l2_2 = new ListNode(3);
    l2.next = l2_2;
    ListNode l2_3 = new ListNode(6);
    l2_2.next = l2_3;
    ListNode l2_4 = new ListNode(9);
    l2_3.next = l2_4;
    ListNode listNode = mergeTwoSortedLists.mergeTwoLists(l2, null);
    ListNode node1 = new ListNode(1);
    ListNode node2 = new ListNode(3);
    node1.next = node2;
    ListNode node3 = new ListNode(6);
    node2.next = node3;
    ListNode node4 = new ListNode(9);
    node3.next = node4;
    Assert.assertEquals(node1.toString(), listNode.toString());
}

public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    ListNode head = new ListNode(0);
    ListNode lastNode = head;
    while (l1 != null && l2 != null) {
        if (l1.currentVal < l2.currentVal) {
            lastNode.next = l1;
            l1 = l1.next;
        } else {
            lastNode.next = l2;
            l2 = l2.next;
        }
        lastNode = lastNode.next;
    }
    if (l1 == null) {
        lastNode.next = l2;
    }
    if (l2 == null) {
        lastNode.next = l1;
    }
    return head.next;
}

@Test
public void mergeTwoLists() throws Exception {
    ListNode l1 = new ListNode(1);
    ListNode l1_2 = new ListNode(4);
    l1.next = l1_2;
    ListNode l1_3 = new ListNode(5);
    l1_2.next = l1_3;
    ListNode l2 = new ListNode(1);
    ListNode l2_2 = new ListNode(3);
    l2.next = l2_2;
    ListNode l2_3 = new ListNode(6);
    l2_2.next = l2_3;
    ListNode l2_4 = new ListNode(9);
    l2_3.next = l2_4;
    ListNode listNode = mergeTwoSortedLists.mergeTwoLists(l1, l2);
    ListNode node1 = new ListNode(1);
    ListNode node2 = new ListNode(1);
    node1.next = node2;
    ListNode node3 = new ListNode(3);
    node2.next = node3;
    ListNode node4 = new ListNode(4);
    node3.next = node4;
    ListNode node5 = new ListNode(5);
    node4.next = node5;
    ListNode node6 = new ListNode(6);
    node5.next = node6;
    ListNode node7 = new ListNode(9);
    node6.next = node7;
    Assert.assertEquals(node1.toString(), listNode.toString());
}

@Test
public void mergeTwoLists2() throws Exception {
    ListNode l2 = new ListNode(1);
    ListNode l2_2 = new ListNode(3);
    l2.next = l2_2;
    ListNode l2_3 = new ListNode(6);
    l2_2.next = l2_3;
    ListNode l2_4 = new ListNode(9);
    l2_3.next = l2_4;
    ListNode listNode = mergeTwoSortedLists.mergeTwoLists(null, l2);
    System.out.println(listNode.toString());
}


@Test
public void put() throws Exception {
    LRULinkedMap<String, Integer> map = new LRULinkedMap(3);
    map.put("1", 1);
    map.put("2", 2);
    map.put("3", 3);
    for (Map.Entry<String, Integer> e : map.getAll()) {
        System.out.print(e.getKey() + " : " + e.getValue() + "\t");
    }
    System.out.println("");
    map.put("4", 4);
    for (Map.Entry<String, Integer> e : map.getAll()) {
        System.out.print(e.getKey() + " : " + e.getValue() + "\t");
    }
}

public void put(K key, V value) {
    cacheMap.put(key, value);
}

@Test
public void put2() throws Exception {
    LRULinkedMap<String, Integer> map = new LRULinkedMap(4);
    map.put("1", 1);
    map.put("2", 2);
    map.put("3", 3);
    map.put("4", 4);
    for (Map.Entry<String, Integer> e : map.getAll()) {
        System.out.print(e.getKey() + " : " + e.getValue() + "\t");
    }
    System.out.println("");
    map.put("5", 5);
    for (Map.Entry<String, Integer> e : map.getAll()) {
        System.out.print(e.getKey() + " : " + e.getValue() + "\t");
    }
}

@Test
public void get() throws Exception {
    LRULinkedMap<String, Integer> map = new LRULinkedMap(4);
    map.put("1", 1);
    map.put("2", 2);
    map.put("3", 3);
    map.put("4", 4);
    for (Map.Entry<String, Integer> e : map.getAll()) {
        System.out.print(e.getKey() + " : " + e.getValue() + "\t");
    }
    System.out.println("");
    map.get("1");
    for (Map.Entry<String, Integer> e : map.getAll()) {
        System.out.print(e.getKey() + " : " + e.getValue() + "\t");
    }
}

public V get(K key) {
    return cacheMap.get(key);
}


@Test
public void isLoop() throws Exception {
    LinkLoop.Node node3 = new LinkLoop.Node("3");
    LinkLoop.Node node2 = new LinkLoop.Node("2");
    LinkLoop.Node node1 = new LinkLoop.Node("1");
    node1.next = node2;
    node2.next = node3;
    LinkLoop linkLoop = new LinkLoop();
    boolean loop = linkLoop.isLoop(node1);
    Assert.assertEquals(loop, false);
}

public boolean isLoop(Node node) {
    Node slow = node;
    Node fast = node.next;
    while (node.next != null) {
        Object dataSlow = slow.data;
        Object dataFast = fast.data;
        if (dataFast == dataSlow) {
            return true;
        }
        slow = slow.next;
        fast = fast.next.next;
        if (fast == null) {
            return false;
        }
    }
    return false;
}

@Test
public void isLoop2() throws Exception {
    LinkLoop.Node node3 = new LinkLoop.Node("3");
    LinkLoop.Node node2 = new LinkLoop.Node("2");
    LinkLoop.Node node1 = new LinkLoop.Node("1");
    node1.next = node2;
    node2.next = node3;
    node3.next = node1;
    LinkLoop linkLoop = new LinkLoop();
    boolean loop = linkLoop.isLoop(node1);
    Assert.assertEquals(loop, true);
}


@Test
public void getTwo2() {
    TwoSum twoSum = new TwoSum();
    int[] nums = { 1, 3, 5, 7 };
    int[] two = twoSum.getTwo2(nums, 10);
    System.out.println(JSON.toJSONString(two));
}

public int[] getTwo2(int[] nums, int target) {
    int[] result = new int[2];
    Map<Integer, Integer> map = new HashMap<>(2);
    for (int i = 0; i < nums.length; i++) {
        if (map.containsKey(nums[i])) {
            result = new int[] { map.get(nums[i]), i };
        }
        map.put(target - nums[i], i);
    }
    return result;
}


@Test
public void getTwo2() {
    TwoSum twoSum = new TwoSum();
    int[] nums = { 1, 3, 5, 7 };
    int[] two = twoSum.getTwo2(nums, 4);
    System.out.println(JSON.toJSONString(two));
}


@Test
public void queue() {
    TwoStackQueue<String> twoStackQueue = new TwoStackQueue<String>();
    twoStackQueue.appendTail("1");
    twoStackQueue.appendTail("2");
    twoStackQueue.appendTail("3");
    twoStackQueue.appendTail("4");
    twoStackQueue.appendTail("5");
    int size = twoStackQueue.getSize();
    for (int i = 0; i < size; i++) {
        LOGGER.info(twoStackQueue.deleteHead());
    }
    LOGGER.info("========第二次添加=========");
    twoStackQueue.appendTail("6");
    size = twoStackQueue.getSize();
    for (int i = 0; i < size; i++) {
        LOGGER.info(twoStackQueue.deleteHead());
    }
}

public int getSize() {
    return input.size() + out.size();
}


@Test
public void get3() throws Exception {
    LRUMap<String, Integer> lruMap = new LRUMap(3);
    lruMap.put("1", 1);
    lruMap.put("2", 2);
    lruMap.put("3", 3);
    System.out.println(lruMap.toString());
    System.out.println("==============");
    Integer integer = lruMap.get("2");
    System.out.println(integer);
    System.out.println("==============");
    System.out.println(lruMap.toString());
}

public V get(K key) {
    Node<K, V> node = getNode(key);
    moveToHead(node);
    return cacheMap.get(key);
}

@Test
public void get4() throws Exception {
    LRUMap<String, Integer> lruMap = new LRUMap(3);
    lruMap.put("1", 1);
    lruMap.put("2", 2);
    lruMap.put("3", 3);
    System.out.println(lruMap.toString());
    System.out.println("==============");
    Integer integer = lruMap.get("3");
    System.out.println(integer);
    System.out.println("==============");
    System.out.println(lruMap.toString());
}

@Test
public void get() throws Exception {
    LRUMap<String, Integer> lruMap = new LRUMap(3);
    lruMap.put("1", 1);
    lruMap.put("2", 2);
    lruMap.put("3", 3);
    System.out.println(lruMap.toString());
    System.out.println("==============");
    Integer integer = lruMap.get("1");
    System.out.println(integer);
    System.out.println("==============");
    System.out.println(lruMap.toString());
}


@Test
public void isLoop3() throws Exception {
    LinkLoop.Node node2 = new LinkLoop.Node("2");
    LinkLoop.Node node1 = new LinkLoop.Node("1");
    node1.next = node2;
    LinkLoop linkLoop = new LinkLoop();
    boolean loop = linkLoop.isLoop(node1);
    Assert.assertEquals(loop, false);
}

public boolean isLoop(Node node) {
    Node slow = node;
    Node fast = node.next;
    while (slow.next != null) {
        Object dataSlow = slow.data;
        Object dataFast = fast.data;
        if (dataFast == dataSlow) {
            return true;
        }
        slow = slow.next;
        fast = fast.next.next;
        if (fast == null) {
            return false;
        }
    }
    return false;
}

public boolean isLoop(Node node) {
    Node slow = node;
    Node fast = node.next;
    while (slow.next != null) {
        Object dataSlow = slow.data;
        Object dataFast = fast.data;
        if (dataFast == dataSlow) {
            return true;
        }
        if (fast.next == null) {
            return false;
        }
        slow = slow.next;
        fast = fast.next.next;
        if (fast == null) {
            return false;
        }
    }
    return false;
}


@Test
void centuryPrefix_forCitizens() {
    assertThat(centuryPrefixCitizen(LocalDate.parse("1999-12-31"))).as("19xx = S").isEqualTo('S');
    assertThat(centuryPrefixCitizen(LocalDate.parse("2000-12-31"))).as("20xx = T").isEqualTo('T');
    assertThat(centuryPrefixCitizen(LocalDate.parse("2001-01-01"))).as("20xx = T").isEqualTo('T');
    assertThat(centuryPrefixCitizen(LocalDate.parse("2101-01-01"))).as("21xx = U").isEqualTo('U');
    assertThat(centuryPrefixCitizen(LocalDate.parse("2201-01-01"))).as("22xx = V").isEqualTo('V');
}

static char centuryPrefixCitizen(LocalDate issueDate) {
    int century = issueDate.getYear() / 100;
    return (char) ('A' + century - 1);
}

@Test
void randomBirthDate_21th_century() {
    Faker faker = new Faker();
    for (int i = 0; i < 100; i++) {
        assertThat(randomBirthDate(faker, SINGAPOREAN_TWENTY_FIRST_CENTURY).getYear() / 100).isEqualTo(20);
        assertThat(randomBirthDate(faker, FOREIGNER_TWENTY_FIRST_CENTURY).getYear() / 100).isEqualTo(20);
    }
}

static LocalDate randomBirthDate(BaseProviders faker, Type type) {
    int now = LocalDate.now().getYear();
    return switch(type) {
        case SINGAPOREAN_TWENTIETH_CENTURY, FOREIGNER_TWENTIETH_CENTURY ->
            faker.timeAndDate().birthday(now - 1900, now - 1999);
        case SINGAPOREAN_TWENTY_FIRST_CENTURY, FOREIGNER_TWENTY_FIRST_CENTURY ->
            faker.timeAndDate().birthday(now - 2000, now - 2099);
    };
}

@Test
void randomBirthDate_20th_century() {
    Faker faker = new Faker();
    for (int i = 0; i < 100; i++) {
        assertThat(randomBirthDate(faker, SINGAPOREAN_TWENTIETH_CENTURY).getYear() / 100).isEqualTo(19);
        assertThat(randomBirthDate(faker, FOREIGNER_TWENTIETH_CENTURY).getYear() / 100).isEqualTo(19);
    }
}


@Test
void null_string() {
    assertThat(toJavaNames(null, false)).isNull();
    assertThat(toJavaNames(null, true)).isNull();
}

public static String toJavaNames(String string, boolean isMethod) {
    final int length;
    if (string == null || (length = string.length()) == 0) {
        return string;
    }
    int cnt = 0;
    for (int i = 0; i < length; i++) {
        if (string.charAt(i) == '_') {
            cnt++;
        }
    }
    if (cnt == 0 && (Character.isUpperCase(string.charAt(0)) && !isMethod || isMethod && Character.isLowerCase(string.charAt(0))))
        return string;
    final char[] res = new char[length - cnt];
    int pos = 0;
    for (int i = 0; i < length; i++) {
        char c = string.charAt(i);
        if (i == 0 && Character.isLetter(c)) {
            res[pos++] = isMethod ? Character.toLowerCase(c) : Character.toUpperCase(c);
        } else if (c == '_') {
            final char next = string.charAt(i + 1);
            if (i < length - 1 && Character.isLetter(next)) {
                res[pos++] = Character.toUpperCase(next);
                i++;
            }
        } else {
            res[pos++] = c;
        }
    }
    return new String(res);
}

@Test
void variety() {
    assertThat(toJavaNames("variety", false)).isEqualTo("Variety");
    assertThat(toJavaNames("variety", true)).isEqualTo("variety");
}

@Test
void empty_string() {
    assertThat(toJavaNames("", false)).isEqualTo("");
    assertThat(toJavaNames("", true)).isEqualTo("");
}

@Test
void upper_first_character_with_underscores() {
    assertThat(toJavaNames("IATA_airline", false)).isEqualTo("IATAAirline");
    assertThat(toJavaNames("IATA_airline", true)).isEqualTo("iATAAirline");
}

@Test
void upper_first_character_without_underscores() {
    assertThat(toJavaNames("A2013", false)).isEqualTo("A2013");
    assertThat(toJavaNames("A2013", true)).isEqualTo("a2013");
}

@Test
void big_bang_theory() {
    assertThat(toJavaNames("big_bang_theory", false)).isEqualTo("BigBangTheory");
    assertThat(toJavaNames("big_bang_theory", true)).isEqualTo("bigBangTheory");
}


@Test
void wordStartingWithDigit() {
    assertThat(toJavaNames("1love", false)).isEqualTo("1love");
    assertThat(toJavaNames("1love", true)).isEqualTo("1love");
}

public static String toJavaNames(String string, boolean isMethod) {
    if (string == null || string.isEmpty())
        return string;
    int length = string.length();
    char[] res = new char[length];
    int pos = 0;
    Transform next = isMethod ? TO_LOWER : TO_UPPER;
    for (int i = 0; i < length; i++) {
        char c = string.charAt(i);
        if (isLetter(c)) {
            res[pos++] = next.transform(c);
            next = SAME;
        } else if (c == '_') {
            next = TO_UPPER;
        } else {
            res[pos++] = c;
        }
    }
    return new String(res, 0, pos);
}

public static String toJavaNames(String string, boolean isMethod) {
    if (string == null || string.isEmpty())
        return string;
    int length = string.length();
    char[] res = new char[length];
    int pos = 0;
    Transform next = isMethod ? TO_LOWER : TO_UPPER;
    for (int i = 0; i < length; i++) {
        char c = string.charAt(i);
        if (isLetter(c)) {
            res[pos++] = next.transform(c);
            next = SAME;
        } else if (c == '_') {
            next = TO_UPPER;
        } else {
            res[pos++] = c;
            next = SAME;
        }
    }
    return new String(res, 0, pos);
}


@Test
public void testPredict() {
    logger.info("testPredict");
    Configuration configuration = Configuration.getConfiguration();
    Dataframe[] data = Datasets.recommenderSystemFood(configuration);
    Dataframe trainingData = data[0];
    Dataframe validationData = data[1];
    String storageName = this.getClass().getSimpleName();
    CollaborativeFiltering.TrainingParameters param = new CollaborativeFiltering.TrainingParameters();
    param.setSimilarityMethod(CollaborativeFiltering.TrainingParameters.SimilarityMeasure.PEARSONS_CORRELATION);
    CollaborativeFiltering instance = MLBuilder.create(param, configuration);
    instance.fit(trainingData);
    instance.save(storageName);
    instance.close();
    instance = MLBuilder.load(CollaborativeFiltering.class, storageName, configuration);
    instance.predict(validationData);
    RecommendationMetrics vm = new RecommendationMetrics(validationData);
    Map<Object, Double> expResult = new HashMap<>();
    expResult.put("pitta", 4.686394033077408);
    expResult.put("burger", 4.68408210680137);
    expResult.put("pizza", 4.6194430718558745);
    expResult.put("chocolate", 4.580630241051733);
    expResult.put("potato", 4.291658734729706);
    expResult.put("beer", 4.264285969929414);
    expResult.put("sparklewatter", 2.8034325749458997);
    expResult.put("salad", 1.496493323119103);
    expResult.put("risecookie", 1.372309723394662);
    expResult.put("tea", 1.3577402217087802);
    expResult.put("rise", 1.2243050068650592);
    AssociativeArray result = validationData.iterator().next().getYPredictedProbabilities();
    for (Map.Entry<Object, Object> entry : result.entrySet()) {
        assertEquals(expResult.get(entry.getKey()), TypeInference.toDouble(entry.getValue()), Constants.DOUBLE_ACCURACY_HIGH);
    }
    assertEquals(vm.getRMSE(), 0.7184568473420477, Constants.DOUBLE_ACCURACY_HIGH);
    instance.delete();
    trainingData.close();
    validationData.close();
}

public void setSimilarityMethod(SimilarityMeasure similarityMethod) {
    this.similarityMethod = similarityMethod;
}


@Test
public void testPredict() {
    logger.info("testPredict");
    Configuration conf = Configuration.getConfiguration();
    Dataframe[] data = Datasets.regressionNumeric(conf);
    Dataframe trainingData = data[0];
    Dataframe validationData = data[1];
    String dbName = this.getClass().getSimpleName();
    DummyXYMinMaxNormalizer df = new DummyXYMinMaxNormalizer(dbName, conf, new DummyXYMinMaxNormalizer.TrainingParameters());
    df.fit_transform(trainingData);
    StepwiseRegression.TrainingParameters param = new StepwiseRegression.TrainingParameters();
    param.setAout(0.05);
    MatrixLinearRegression.TrainingParameters trainingParams = new MatrixLinearRegression.TrainingParameters();
    param.setRegressionTrainingParameters(trainingParams);
    StepwiseRegression instance = new StepwiseRegression(dbName, conf, param);
    instance.fit(trainingData);
    df.denormalize(trainingData);
    instance.close();
    df.close();
    df = new DummyXYMinMaxNormalizer(dbName, conf);
    df.transform(validationData);
    instance = new StepwiseRegression(dbName, conf);
    instance.predict(validationData);
    df.denormalize(validationData);
    for (Record r : validationData) {
        Assert.assertEquals(TypeInference.toDouble(r.getY()), TypeInference.toDouble(r.getYPredicted()), Constants.DOUBLE_ACCURACY_HIGH);
    }
    df.delete();
    instance.delete();
    trainingData.delete();
    validationData.delete();
}

@Override
public void delete() {
    if (mlregressor != null) {
        mlregressor.delete();
        mlregressor = null;
    }
    super.delete();
}

@Override
public void delete() {
    loadRegressor().delete();
    super.delete();
}


@Test
public void testMinAbsolute() {
    logger.info("minAbsolute");
    FlatDataCollection flatDataCollection = generateFlatDataCollection();
    double expResult = 0.99;
    double result = Descriptives.minAbsolute(flatDataCollection);
    assertEquals(expResult, result, Constants.DOUBLE_ACCURACY_HIGH);
}

public static double minAbsolute(FlatDataCollection flatDataCollection) {
    if (flatDataCollection.isEmpty()) {
        throw new IllegalArgumentException("The provided collection can't be empty.");
    }
    double minAbs = Double.POSITIVE_INFINITY;
    Iterator<Double> it = flatDataCollection.iteratorDouble();
    while (it.hasNext()) {
        Double v = Math.abs(it.next());
        if (v != null && minAbs > v) {
            minAbs = v;
        }
    }
    return minAbs;
}

@Test
public void testMaxAbsolute() {
    logger.info("maxAbsolute");
    FlatDataCollection flatDataCollection = generateFlatDataCollection();
    double expResult = 50.85;
    double result = Descriptives.maxAbsolute(flatDataCollection);
    assertEquals(expResult, result, Constants.DOUBLE_ACCURACY_HIGH);
}

public static double maxAbsolute(FlatDataCollection flatDataCollection) {
    if (flatDataCollection.isEmpty()) {
        throw new IllegalArgumentException("The provided collection can't be empty.");
    }
    double max = 0.0;
    Iterator<Double> it = flatDataCollection.iteratorDouble();
    while (it.hasNext()) {
        Double v = Math.abs(it.next());
        if (v != null && max < v) {
            max = v;
        }
    }
    return max;
}


@Test
public void testShuffleValidation() {
    logger.info("testShuffleValidation");
    Configuration conf = Configuration.getConfiguration();
    double proportion = 0.8;
    int splits = 5;
    Dataframe[] data = Datasets.carsNumeric(conf);
    Dataframe trainingData = data[0];
    data[1].close();
    MultinomialNaiveBayes.TrainingParameters param = new MultinomialNaiveBayes.TrainingParameters();
    param.setMultiProbabilityWeighted(true);
    ClassificationMetrics vm = new Validator<>(ClassificationMetrics.class, conf).validate(new ShuffleSplitter(proportion, splits).split(trainingData), param);
    double expResult = 0.5983838383838384;
    double result = vm.getMacroF1();
    assertEquals(expResult, result, Constants.DOUBLE_ACCURACY_HIGH);
    trainingData.close();
}

@Override
public Iterable<Split> split(Dataframe dataset) {
    final int n = dataset.size();
    if (proportion <= 0.0 || proportion >= 1.0) {
        throw new IllegalArgumentException("The train size should be between 0.0 and 1.0.");
    }
    final int trainSize = (int) (n * proportion);
    return () -> new Iterator<Split>() {

        private int counter = 0;

        @Override
        public boolean hasNext() {
            return counter < splits;
        }

        @Override
        public Split next() {
            logger.info("Split {}", counter);
            final Integer[] ids = new Integer[n];
            int j = 0;
            for (Integer rId : dataset.index()) {
                ids[j++] = rId;
            }
            PHPMethods.shuffle(ids, random);
            FlatDataList trainIds = new FlatDataList(new ArrayList<>(trainSize));
            for (int i = 0; i < trainSize; i++) {
                trainIds.add(ids[i]);
            }
            FlatDataList testIds = new FlatDataList(new ArrayList<>(n - trainSize));
            for (int i = trainSize; i < n; i++) {
                testIds.add(ids[i]);
            }
            counter++;
            return new Split(dataset.getSubset(trainIds), dataset.getSubset(testIds));
        }
    };
}


@Test
public void testBulkLoadingEntryCount() {
    List<Entry<Integer, Geometry>> entries = new ArrayList<Entry<Integer, Geometry>>(10000);
    for (int i = 1; i <= 10000; i++) {
        Point point = nextPoint();
        entries.add(new EntryDefault<Integer, Geometry>(i, point));
    }
    RTree<Integer, Geometry> tree = RTree.create(entries);
    int entrySize = tree.entries().count().toBlocking().single();
    System.out.println("entry count: " + entrySize);
    assertEquals(entrySize, entries.size());
}

public static <T, S extends Geometry> RTree<T, S> create(List<Entry<T, S>> entries) {
    return new Builder().create(entries);
}

@Test
public void testSearchOnOneItemOnBulkLoadingRTree() {
    Entry<Object, Rectangle> entry = e(1);
    RTree<Object, Rectangle> tree = RTree.create(Arrays.asList(entry));
    assertEquals(Arrays.asList(entry), tree.search(r(1)).toList().toBlocking().single());
}

@Test
public void testBulkLoadingWithOneItemIsNotEmpty() {
    RTree<Object, Rectangle> tree = RTree.create(Arrays.asList(e(1)));
    assertFalse(tree.isEmpty());
}

@Test
public void testBulkLoadingTreeAndStarTreeReturnsSameAsStandardRTree() {
    RTree<Integer, Geometry> tree1 = RTree.create();
    RTree<Integer, Geometry> tree2 = RTree.star().create();
    Rectangle[] testRects = { rectangle(0, 0, 0, 0), rectangle(0, 0, 100, 100), rectangle(0, 0, 10, 10), rectangle(0.12, 0.25, 50.356, 50.756), rectangle(1, 0.252, 50, 69.23), rectangle(13.12, 23.123, 50.45, 80.9), rectangle(10, 10, 50, 50) };
    List<Entry<Integer, Geometry>> entries = new ArrayList<Entry<Integer, Geometry>>(10000);
    for (int i = 1; i <= 10000; i++) {
        Point point = nextPoint();
        tree1 = tree1.add(i, point);
        tree2 = tree2.add(i, point);
        entries.add(new EntryDefault<Integer, Geometry>(i, point));
    }
    RTree<Integer, Geometry> tree3 = RTree.create(entries);
    for (Rectangle r : testRects) {
        Set<Integer> res1 = new HashSet<Integer>(tree1.search(r).map(RTreeTest.<Integer>toValue()).toList().toBlocking().single());
        Set<Integer> res2 = new HashSet<Integer>(tree2.search(r).map(RTreeTest.<Integer>toValue()).toList().toBlocking().single());
        Set<Integer> res3 = new HashSet<Integer>(tree3.search(r).map(RTreeTest.<Integer>toValue()).toList().toBlocking().single());
        System.out.println("searchRect= rectangle(" + r.x1() + "," + r.y1() + "," + r.x2() + "," + r.y2() + ")");
        System.out.println("res1.size=" + res1.size() + ",res2.size=" + res2.size() + ",res3.size=" + res3.size());
        assertEquals(res1.size(), res2.size());
        assertEquals(res1.size(), res3.size());
    }
}


@Test
public void testStringLineDoubleSerialization() throws IOException {
    Serializer<String, Line> serializer = Serializers.flatBuffers().utf8();
    Entry<String, Line> a = Entries.entry("hello", Geometries.line(1.0000000001, 2, 3, 4));
    Entry<String, Line> b = Entries.entry("there", Geometries.line(3.0000000001, 4, 5, 6));
    check(serializer, a, b);
}

public static Line line(double x1, double y1, double x2, double y2) {
    return LineFloat.create(x1, y1, x2, y2);
}

public static Line line(double x1, double y1, double x2, double y2) {
    return LineDouble.create(x1, y1, x2, y2);
}


@Test
public void Colorize_SingleCommand() {
    Command command = Command.CLEAR_SCREEN();
    String text = createTextLine();
    String formattedCommand = Ansi.colorize(command);
    String expectedCode = Ansi.generateCode(command);
    assertThat(formattedCommand, equalTo(expectedCode));
}

public static String colorize(Command command) {
    return generateCode(command);
}

@Test
public void GenerateCode_OneCommand() {
    Command command = Command.CLEAR_SCREEN();
    String code = Ansi.generateCode(command);
    String expectedCode = PREFIX + command;
    assertThat(code, equalTo(expectedCode));
}

public static String generateCode(Command command) {
    return PREFIX + command;
}


@Test
public void Attribute_AnsiCode_ClearScreen() {
    Command command = Command.CLEAR_SCREEN();
    String code = command.toString();
    String expectedAnsiCode = "2J";
    assertThat(code, equalTo(expectedAnsiCode));
}

public static Command CLEAR_SCREEN() {
    return new Command("2J");
}


@Test
public void CanMakeItFabulous() {
    String text = "This text will be magenta";
    String formattedText = makeItFabulous(text, MAGENTA_TEXT);
    assertThat(formattedText, equalTo(colorize(text, MAGENTA_TEXT)));
}

public static String makeItFabulous(String text, Attribute... attributes) {
    return colorize(text, attributes);
}


@Test
public void CanFormatWithOneAttribute() {
    AnsiFormat fInfo = new AnsiFormat(CYAN_BACK);
    String formattedText = fInfo.format("This text will have a cyan back");
    String expectedCode = Ansi.generateCode(fInfo);
    assertThat(formattedText, startsWith(expectedCode));
    assertThat("Message should clear its format", formattedText, endsWith(Ansi.RESET));
}

public String format(String text) {
    return Ansi.colorize(text, this.toArray());
}

@Test
public void CanFormatWithMultipleAttributes() {
    AnsiFormat fWarning = new AnsiFormat(RED_TEXT, YELLOW_BACK, BOLD);
    String formattedText = fWarning.format("This bold text will be red on yellow");
    String expectedCode = Ansi.generateCode(fWarning);
    assertThat(formattedText, startsWith(expectedCode));
    assertThat("Message should clear its format", formattedText, endsWith(Ansi.RESET));
}

@Test
public void CanUseAnsiFormatOrArray() {
    AnsiFormat formatNotation = new AnsiFormat(BLACK_TEXT, BLACK_BACK);
    Attribute[] arrayNotation = new Attribute[] { BLACK_TEXT, BLACK_BACK };
    String text = "This text will be black on black";
    String formatNotationOutput = formatNotation.format(text);
    String arrayNotationOutput = Ansi.colorize(text, arrayNotation);
    assertThat(formatNotationOutput, equalTo(arrayNotationOutput));
    assertThat(formatNotationOutput.compareTo(arrayNotationOutput), equalTo(0));
}


@Test
public void CanFormatInline() {
    String formattedText = colorize("This text will be yellow on magenta", YELLOW_TEXT, MAGENTA_BACK);
    String expectedCode = Ansi.generateCode(YELLOW_TEXT, MAGENTA_BACK);
    assertThat(formattedText, startsWith(expectedCode));
    assertThat("Message should clear its format", formattedText, endsWith(Ansi.RESET));
}

public static String colorize(String text, Attribute... attributes) {
    String ansiCode = generateCode(attributes);
    return colorize(text, ansiCode);
}


@Test
public void CanFormatWithTrueColorAttributes() {
    AnsiFormat fWarning = new AnsiFormat(TextColor(202), BackColor(225, 255, 47), BOLD());
    String formattedText = fWarning.format("This bold text will be kinda-orange on greenish-yellow");
    String expectedCode = Ansi.generateCode(fWarning);
    assertThat(formattedText, startsWith(expectedCode));
    assertThat("Message should clear its format", formattedText, endsWith(Ansi.RESET));
}

public static Attribute BackColor(int r, int g, int b) {
    return new BackColorAttribute(r, g, b);
}


@Test
public void Attribute_AnsiCode_Back8bitColor() {
    int colorNumber = 225;
    Attribute attribute = Attribute.BackColor(colorNumber);
    String ansiCodeFor8bitBackColor = "48;5;";
    String code = attribute.toString();
    String expectedCode = ansiCodeFor8bitBackColor + colorNumber;
    assertThat(code, equalTo(expectedCode));
}

public static Attribute BackColor(int colorNumber) {
    return new BackColorAttribute(colorNumber);
}

@Test
public void Attribute_AnsiCode_BackTrueColor() {
    int r = 160, g = 122, b = 255;
    Attribute attribute = Attribute.BackColor(r, g, b);
    String ansiCodeForRGBBackColor = "48;2;";
    String code = attribute.toString();
    String expectedCode = ansiCodeForRGBBackColor + r + SEPARATOR + g + SEPARATOR + b;
    assertThat(code, equalTo(expectedCode));
}


@Test
public void GenerateCode_MultipleOptions() {
    Object[] options = new Object[] { Ansi.Attribute.BOLD, Ansi.FColor.CYAN };
    String code = Ansi.generateCode(options);
    String expectedCode = Ansi.PREFIX + options[0] + Ansi.SEPARATOR + options[1] + Ansi.POSTFIX;
    assertThat(code, equalTo(expectedCode));
}

public static String generateCode(Object... options) {
    StringBuilder builder = new StringBuilder();
    builder.append(Ansi.PREFIX);
    for (Object option : options) {
        String code = option.toString();
        if (code.equals(""))
            continue;
        builder.append(code);
        builder.append(Ansi.SEPARATOR);
    }
    builder.append(Ansi.POSTFIX);
    return builder.toString().replace(SEPARATOR + POSTFIX, POSTFIX);
}

@Test
public void GenerateCode_SomeOptionsWithValueNone() {
    Object[] options = new Object[] { Ansi.Attribute.NONE, Ansi.FColor.CYAN, Ansi.BColor.NONE };
    String code = Ansi.generateCode(options);
    String expectedCode = Ansi.PREFIX + options[1] + Ansi.POSTFIX;
    assertThat(code, equalTo(expectedCode));
}

@Test
public void GenerateCode_OneOption() {
    Object[] options = new Object[] { Ansi.Attribute.BOLD };
    String code = Ansi.generateCode(options);
    String expectedCode = Ansi.PREFIX + options[0] + Ansi.POSTFIX;
    assertThat(code, equalTo(expectedCode));
}

@Test
public void GenerateCode_ZeroOptions() {
    String code = Ansi.generateCode();
    String expectedCode = Ansi.PREFIX + Ansi.POSTFIX;
    assertThat(code, equalTo(expectedCode));
}

@Test
public void GenerateCode_MultipleOptionsWithValueNone() {
    Object[] options = new Object[] { Ansi.Attribute.NONE, Ansi.FColor.NONE, Ansi.BColor.NONE };
    String code = Ansi.generateCode(options);
    String expectedCode = Ansi.PREFIX + Ansi.POSTFIX;
    assertThat(code, equalTo(expectedCode));
}


@Test
public void extractGoodTarFile() throws Exception {
    File destination = temp.newFolder("destination");
    extractor.extract(GOOD_TAR, destination.getPath());
}

@Override
public void extract(String archive, String destinationDirectory) throws ArchiveExtractionException {
    final File archiveFile = new File(archive);
    try (FileInputStream fis = new FileInputStream(archiveFile)) {
        if ("msi".equals(FileUtils.getExtension(archiveFile.getAbsolutePath()))) {
            String command = "msiexec /a " + archiveFile.getAbsolutePath() + " /qn TARGETDIR=\"" + destinationDirectory + "\"";
            Process child = Runtime.getRuntime().exec(command);
            try {
                int result = child.waitFor();
                if (result != 0) {
                    throw new ArchiveExtractionException("Could not extract " + archiveFile.getAbsolutePath() + "; return code " + result);
                }
            } catch (InterruptedException e) {
                throw new ArchiveExtractionException("Unexpected interruption of while waiting for extraction process", e);
            }
        } else if ("zip".equals(FileUtils.getExtension(archiveFile.getAbsolutePath()))) {
            ZipFile zipFile = new ZipFile(archiveFile);
            try {
                Enumeration<? extends ZipEntry> entries = zipFile.entries();
                while (entries.hasMoreElements()) {
                    ZipEntry entry = entries.nextElement();
                    final File destPath = new File(destinationDirectory + File.separator + entry.getName());
                    prepDestination(destPath, entry.isDirectory());
                    if (!entry.isDirectory()) {
                        InputStream in = null;
                        OutputStream out = null;
                        try {
                            in = zipFile.getInputStream(entry);
                            out = new FileOutputStream(destPath);
                            IOUtils.copy(in, out);
                        } finally {
                            IOUtils.closeQuietly(in);
                            IOUtils.closeQuietly(out);
                        }
                    }
                }
            } finally {
                zipFile.close();
            }
        } else {
            TarArchiveInputStream tarIn = null;
            try {
                tarIn = new TarArchiveInputStream(new GzipCompressorInputStream(fis));
                TarArchiveEntry tarEntry = tarIn.getNextTarEntry();
                while (tarEntry != null) {
                    final File destPath = new File(destinationDirectory + File.separator + tarEntry.getName());
                    prepDestination(destPath, tarEntry.isDirectory());
                    if (!startsWithPath(destPath.getCanonicalPath(), destinationDirectory)) {
                        throw new IOException("Expanding " + tarEntry.getName() + " would create file outside of " + destinationDirectory);
                    }
                    if (!tarEntry.isDirectory()) {
                        destPath.createNewFile();
                        boolean isExecutable = (tarEntry.getMode() & 0100) > 0;
                        destPath.setExecutable(isExecutable);
                        OutputStream out = null;
                        try {
                            out = new FileOutputStream(destPath);
                            IOUtils.copy(tarIn, out);
                        } finally {
                            IOUtils.closeQuietly(out);
                        }
                    }
                    tarEntry = tarIn.getNextTarEntry();
                }
            } finally {
                IOUtils.closeQuietly(tarIn);
            }
        }
    } catch (IOException e) {
        throw new ArchiveExtractionException("Could not extract archive: '" + archive + "'", e);
    }
}

@Override
public void extract(String archive, String destinationDirectory) throws ArchiveExtractionException {
    final File archiveFile = new File(archive);
    try (FileInputStream fis = new FileInputStream(archiveFile)) {
        if ("msi".equals(FileUtils.getExtension(archiveFile.getAbsolutePath()))) {
            String command = "msiexec /a " + archiveFile.getAbsolutePath() + " /qn TARGETDIR=\"" + destinationDirectory + "\"";
            Process child = Runtime.getRuntime().exec(command);
            try {
                int result = child.waitFor();
                if (result != 0) {
                    throw new ArchiveExtractionException("Could not extract " + archiveFile.getAbsolutePath() + "; return code " + result);
                }
            } catch (InterruptedException e) {
                throw new ArchiveExtractionException("Unexpected interruption of while waiting for extraction process", e);
            }
        } else if ("zip".equals(FileUtils.getExtension(archiveFile.getAbsolutePath()))) {
            ZipFile zipFile = new ZipFile(archiveFile);
            try {
                Enumeration<? extends ZipEntry> entries = zipFile.entries();
                while (entries.hasMoreElements()) {
                    ZipEntry entry = entries.nextElement();
                    final File destPath = new File(destinationDirectory + File.separator + entry.getName());
                    prepDestination(destPath, entry.isDirectory());
                    if (!entry.isDirectory()) {
                        InputStream in = null;
                        OutputStream out = null;
                        try {
                            in = zipFile.getInputStream(entry);
                            out = new FileOutputStream(destPath);
                            IOUtils.copy(in, out);
                        } finally {
                            IOUtils.closeQuietly(in);
                            IOUtils.closeQuietly(out);
                        }
                    }
                }
            } finally {
                zipFile.close();
            }
        } else {
            TarArchiveInputStream tarIn = null;
            try {
                tarIn = new TarArchiveInputStream(new GzipCompressorInputStream(fis));
                TarArchiveEntry tarEntry = tarIn.getNextTarEntry();
                String canonicalDestinationDirectory = new File(destinationDirectory).getCanonicalPath();
                while (tarEntry != null) {
                    final File destPath = new File(destinationDirectory + File.separator + tarEntry.getName());
                    prepDestination(destPath, tarEntry.isDirectory());
                    if (!startsWithPath(destPath.getCanonicalPath(), canonicalDestinationDirectory)) {
                        throw new IOException("Expanding " + tarEntry.getName() + " would create file outside of " + canonicalDestinationDirectory);
                    }
                    if (!tarEntry.isDirectory()) {
                        destPath.createNewFile();
                        boolean isExecutable = (tarEntry.getMode() & 0100) > 0;
                        destPath.setExecutable(isExecutable);
                        OutputStream out = null;
                        try {
                            out = new FileOutputStream(destPath);
                            IOUtils.copy(tarIn, out);
                        } finally {
                            IOUtils.closeQuietly(out);
                        }
                    }
                    tarEntry = tarIn.getNextTarEntry();
                }
            } finally {
                IOUtils.closeQuietly(tarIn);
            }
        }
    } catch (IOException e) {
        throw new ArchiveExtractionException("Could not extract archive: '" + archive + "'", e);
    }
}

@Test(expected = ArchiveExtractionException.class)
public void extractBadTarFile() throws Exception {
    File destination = temp.newFolder("destination");
    extractor.extract(BAD_TAR, destination.getPath());
}


@Test
public void testModifyString() {
    {
        String string = UUID.randomUUID().toString();
        assertEquals(string, StringCodec.modifyString(string));
    }
    {
        String string = UUID.randomUUID().toString();
        assertEquals(string, StringCodec.modifyString(URN_PREFIX + string));
    }
    {
        String string = UUID.randomUUID().toString();
        assertEquals(string, StringCodec.modifyString("{" + string + "}"));
    }
}

protected static String modifyString(String string) {
    if (string.length() == URN_PREFIX_UUID_LENGTH && string.startsWith(URN_PREFIX)) {
        return string.substring(URN_PREFIX.length());
    }
    if (string.length() == CURLY_BRACES_UUID_LENGTH && string.startsWith("{") && string.endsWith("}")) {
        return string.substring(1, string.length() - 1);
    }
    return string;
}


@Test
public void testBufferOverflowException() {
    try {
        new UuidBuilder().put((byte) 0).put((long) 0).put((long) 0).build();
        fail("Should throw buffer overflow exception");
    } catch (BufferOverflowException e) {
    }
    try {
        new UuidBuilder().put((byte) 0).put((long) 0).put((int) 0).put((short) 0L).put((short) 0L).build();
        fail("Should throw buffer overflow exception");
    } catch (BufferOverflowException e) {
    }
}

public synchronized UuidBuilder put(byte value) {
    buffer.put(value);
    return this;
}

@Test
public void testBufferUnderflowException() {
    try {
        new UuidBuilder().build();
        fail("Should throw buffer underflow exception");
    } catch (BufferUnderflowException e) {
    }
    try {
        new UuidBuilder().put((long) 0).put((int) 0).build();
        fail("Should throw buffer underflow exception");
    } catch (BufferUnderflowException e) {
    }
    try {
        new UuidBuilder().put((long) 0).put((int) 0).put((short) 0L).build();
        fail("Should throw buffer underflow exception");
    } catch (BufferUnderflowException e) {
    }
}

public synchronized UuidBuilder put(short value) {
    buffer.putShort(value);
    return this;
}

@Test
public void testVersionNumber() {
    assertEquals(0x0L, new UuidBuilder().put((long) 0L).put((long) 0L).build().version());
    assertEquals(0xfL, new UuidBuilder().put((long) -1L).put((long) -1L).build().version());
    for (int i = 0; i < 15; i++) {
        Random random = new Random(seeder.nextLong());
        assertEquals(i, new UuidBuilder(i).put(random.nextLong()).put(random.nextLong()).build().version());
    }
}

public synchronized UuidBuilder put(long value) {
    buffer.putLong(value);
    return this;
}

@Test
public void testPutTwoLongsWithoutVersionNumber() {
    for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
        Random random = new Random(seeder.nextLong());
        long msb = random.nextLong();
        long lsb = random.nextLong();
        UUID expected = new UUID(msb, lsb);
        UUID actual = new UuidBuilder().put(msb).put(lsb).build();
        assertEquals(expected, actual);
    }
}


@Test
public void testClockSequencePool4() {
    ClockSeqPool pool = new ClockSeqPool();
    for (int i = 0; i < CLOCK_SEQUENCE_MAX; i++) {
        pool.take(i);
    }
    assertEquals("All values from the poll should be used", CLOCK_SEQUENCE_MAX, pool.countUsed());
    pool.take(0);
    assertEquals("The pool should be reset and only one value should be used in the end", 1, pool.countUsed());
}

public synchronized int countUsed() {
    int counter = 0;
    for (int i = 0; i < POOL_SIZE; i++) {
        if (this.isUsed(i)) {
            counter++;
        }
    }
    return counter;
}

@Test
public void testClockSequencePool2() {
    ClockSeqPool pool = new ClockSeqPool();
    HashSet<Integer> set = new HashSet<>();
    for (int i = 0; i < TEST_ARRAY.length; i++) {
        assertTrue("There are duplicate values: " + i, set.add(pool.take(TEST_ARRAY[i])));
        assertTrue("Value not used", pool.isUsed(TEST_ARRAY[i]));
    }
    assertEquals("The collection size should be equal to the test array size", TEST_ARRAY.length, set.size());
    assertEquals("All values from the test array should be used", TEST_ARRAY.length, pool.countUsed());
}

@Test
public void testClockSequencePool3() {
    ClockSeqPool pool = new ClockSeqPool();
    int take = -1;
    for (int i = 0; i < TEST_ARRAY.length; i++) {
        take = TEST_ARRAY[i];
        assertEquals(take, pool.take(take));
        assertTrue("The value should be used", pool.isUsed(take));
    }
    assertEquals(TEST_ARRAY.length, pool.countUsed());
}

public synchronized int take(final int take) {
    int value = take;
    for (int i = 0; i < POOL_SIZE; i++) {
        if (setBit(value)) {
            return value;
        }
        value = ++value % POOL_SIZE;
    }
    clearPool();
    setBit(value);
    return value;
}

@Test
public void testClockSequencePool1() {
    ClockSeqPool pool = new ClockSeqPool();
    HashSet<Integer> set = new HashSet<>();
    for (int i = 0; i < CLOCK_SEQUENCE_MAX; i++) {
        assertTrue("There are duplicate values", set.add(pool.take(i)));
        assertTrue("Value not used", pool.isUsed(i));
    }
    assertEquals("The collection size should be equal to the pool size", CLOCK_SEQUENCE_MAX, set.size());
    assertEquals("All values from the poll should be used", CLOCK_SEQUENCE_MAX, pool.countUsed());
}


@Test
public void testGetTimestampMonotonicity() {
    long lastTs = 0;
    WindowsTimeFunction supplier = new WindowsTimeFunction();
    for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
        long ts = supplier.getAsLong();
        assertTrue("The current timstamp should be greater than the last one", ts > lastTs);
        lastTs = ts;
    }
}

@Override
public long getAsLong() {
    counter++;
    final long time = calculatedTimeMillis();
    if (time == prevTime) {
        if (counter >= counterMax) {
            while (time == calculatedTimeMillis()) {
            }
        }
    } else {
        counter = counter % TICKS_PER_GRANULARITY;
        counterMax = counter + TICKS_PER_GRANULARITY;
    }
    prevTime = time;
    return (time * TICKS_PER_MILLI) + counter;
}

@Test
public void testGetTimestampTicks() {
    WindowsTimeFunction supplier = new WindowsTimeFunction();
    for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
        long m1 = System.currentTimeMillis() * 10000L;
        long ts = supplier.getAsLong();
        long m2 = System.currentTimeMillis() * 10000L + 600000L;
        assertTrue("The current timstamp millisecond is incorrect", ts >= m1 && ts <= m2);
    }
}

@Test
public void testGetTimestampMillisecond() {
    WindowsTimeFunction supplier = new WindowsTimeFunction();
    for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
        long m1 = System.currentTimeMillis();
        long ts = supplier.getAsLong() / 10000L;
        long m2 = System.currentTimeMillis() + 60;
        assertTrue("The current timstamp millisecond is incorrect", ts >= m1 && ts <= m2);
    }
}