I m trying to loop a list of users to find a person. Every person has a friends list. So i m using recursive call to check if the person is in someones friends list.
My Junit test is look like
#Test
public void IsInFriendsCicle() throws UserAlreadyInFriendListException, NoFriendFoundException, UsersNotConnectedException {
User one = new UserImpl("John","Snow");
User two = new UserImpl("Richard","Gerns");
User three = new UserImpl("Natalie","Portman");
User four = new UserImpl("Brad","Pitt");
User five = new UserImpl("Angelina","Jolie");
one.addFriend(two);
two.addFriend(three);
three.addFriend(four);
four.addFriend(five);
assertTrue(one.isInFriendsCycle(five, one.getFriends(), new Stack()));
}
So as it can be seen here, i want to know if Angelina is in the friends list of john. So it supposed to give back true.
The responsible method is for that :
public boolean isInFriendsCycle(User userToFind, ArrayList<User> list, Stack stack){
Stack s = stack;
ArrayList<User> groupList = list;
if(groupList.contains(userToFind)){
return true;
}else{
for (User user : groupList) {
if(!s.contains(user)){
s.push(user);
if(user.getFriends().contains(userToFind)){
return true;
}else{
return isInFriendsCycle(userToFind, user.getFriends(), s);
}
}
}
}
return false;
}
So the class is :
public class UserImpl implements User{
private String name;
private String surname;
private static int count = 0;
private int id;
private ArrayList<User> friends;
private ArrayList<Message> messagebox;
final static Logger logger = Logger.getLogger(UserImpl.class);
public UserImpl(String name, String surname) {
this.name = name;
this.surname = surname;
this.id = ++count;
this.friends = new ArrayList<User>();
this.messagebox = new ArrayList<Message>();
}
#Override
public User addFriend(User person) throws UserAlreadyInFriendListException,IllegalArgumentException{
if(this.getFriends().contains(person)){
throw new UserAlreadyInFriendListException("user is already in the friendlist");
}else if(person == null || this.equals(person) ){
throw new IllegalArgumentException("parameter is null or user trying to add himself as friend");
}else{
this.getFriends().add(person);
person.getFriends().add(this);
logger.debug(this.name + " added the user "+person.getName());
return person;
}
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final UserImpl other = (UserImpl) obj;
if (this.id != other.id) {
return false;
}
return true;
}
}
There is a problem with stack somehow. I m using it to mark the persons so i dont get in the infinitive loop. There is a reason for passing user.getFriends() so it should stay in that way.
Any help will be appreciated !
replace
return isInFriendsCycle(userToFind, user.getFriends(), s);
with
if (isInFriendsCycle(userToFind, user.getFriends(), s)) return true;
As you have it, you prematurely exit if you didn't find it in the first branch of the recursive call. You don't want that - you want to continue until you find it, or there is nothing left to search through.
As an aside - your unit test wasn't helping you as you made the nesting too deep. If you had a few unit tests, first testing a friend-friend, then a friend-friend-friend, etc., you would have started to see where it was going wrong.
Also, as another aside: I would not use Stack (it is a legacy class) use Deque instead, which is the Java 6+ replacement. However in this case I would use a Set<User> in the form HashSet<User> as it fits your use case and probably performance requirements. I would also make the list variable types a List<User> not an ArrayList<User> inside your UserImpl class and isInFriendsCycle method, just to concentrate on the contracts and not the implementation.
I'd implement it as follows:
private Set<User> friends = new HashSet<User>();
public void addFriend(User friend) {
friends.add(friend);
}
public boolean isImmediateFriend(User user) {
return friends.contains(user);
}
public boolean isInFriendNetwork(User user) {
Set<User> visited = new HashSet<>();
List<User> stack = new LinkedList<>();
stack.push(this);
while (!stack.isEmpty()) {
User current = stack.removeFirst();
if (current.isImmediateFriend(user)) {
return true;
}
visited.add(current);
for (User friend : current.getFriends()) {
// never visit the same user twice
if (!visited.contains(friend)) {
stack.addLast(friend);
}
}
}
return false;
}
Pseudocode for the recursive algorithm with marking:
IsFriendOf(A, B):
if A == B:
return True
B.Marked= True
for all C in B.Friends:
if not C.Marked and IsFriendOf(A, C):
B.Marked= False
return True
B.Marked= False
return False
Related
I'm writing a function to check multiple conditions in an array, if they are all true then return true.
For example:
public class Attribute {
private final String key;
private final String value;
//...
}
boolean canContactDogOwner(List<Attribute> attributes) {
boolean hasDog = false;
boolean isSubscribed = false;
boolean isOkToCall = false;
for (var attribute : attributes) {
if (attribute.key().equals("dogName")) {
hasDog = true;
} else if (attribute.key().equals("isSubscribed") && attribute.value().equals("Y")) {
isSubscribed = true;
} else if (attribute.key().equals("okToCall") && attribute.value().equals("Y")) {
isOkToCall = true;
}
// 1.
}
return hasDog && isSubscribed && isOkToCall;
}
void foo() {
List<Attribute> attributes = new ArrayList<>();
attributes.add(new Attribute("isSubscribed", "Y"));
attributes.add(new Attribute("okToCall", "Y"));
attributes.add(new Attribute("mobile", "12345678"));
attributes.add(new Attribute("landline", "1346346"));
attributes.add(new Attribute("email", "white#email.com"));
attributes.add(new Attribute("dogName", "Alex"));
boolean canContact = canContactDogOwner(attributes);
}
Two questions:
When all conditions are meet, the loop can be break, but if I add a check there, we would be checking every step in the loop, which doesn't look good. Any suggestions?
Is there a better / concise way to do it?
Like following?
boolean canContactDogOwner(List<Attribute> attributes) {
return attributes.stream().allMatch(A,B,C);
}
You can modify method canContactDogOwner to be like this,
boolean canContactDogOwner(List<Attribute> attributes) {
List<Attribute> conditions = new ArrayList<>();
conditions.add(new Attribute("isSubscribed", "Y"));
conditions.add(new Attribute("okToCall", "Y"));
return attributes.containsAll(conditions) &&
attributes.stream().anyMatch((attribute -> attribute.key.equals("dogName")));
}
A working and cleaner approach (IMO) will be to use some abstract data type like Map in this case..
static boolean canContactDogOwner(List<Attribute> attributes){
Map<String, String> attributeMap = new HashMap<>(); // empty map
attributes.forEach(attr -> attributeMap.put(attr.getKey(), attr.getValue())); // populate map
return attributeMap.containsKey("dogName") &&
"Y".equals(attributeMap.get("isSubscribed")) &&
"Y".equals(attributeMap.get("okToCall")); // Constant-String-first on equals check to avoid nullPointerExc with less code, yet clean
}
The code above with the comment is self-explanatory, so not adding details of the code.
But it is worth mentioning that
the complexity is still O(n) like other solutions here, n - number of elements (attribute objects)
flexibility to add or remove more conditions in the return statement
map as a chosen data-type and <Constant>.equals check avoids key validation and nullPointerException respectively.
If you are fascinated with Java-Streams, you can modify the code like this too..
static boolean canContactDogOwner(List<Attribute> attributes){
Map<String, String> attributeMap = attributes.stream()
.collect(Collectors.toMap(Attribute::getKey, Attribute::getValue));
return attributeMap.containsKey("dogName") &&
"Y".equals(attributeMap.get("isSubscribed")) &&
"Y".equals(attributeMap.get("okToCall"));
}
You could check if all condition is meet only when you set a value to true,
it will happen only 3 time.
And more concise way, probably with stream().anyMatch() but i'm not sure it will be more readable
Stream and allMatch(Predicate predicate) is a better way to do it in my opinion, but keep in mind that allMatch() take a Predicate as an argument, so you need to provide one.
I would suggest you encapsulate the attributes and create a class
something like Owner.
public class Owner {
private boolean isSubscribed;
private boolean okToCall;
private String mobile;
private String landline;
private String email;
private Optional<String> dogName;
public Owner(boolean isSubscribed, boolean okToCall, String mobile, String landline, String email, Optional<String> dogName) {
this.isSubscribed = isSubscribed;
this.okToCall = okToCall;
this.mobile = mobile;
this.landline = landline;
this.email = email;
this.dogName = dogName;
}
public boolean canContact() {
return this.isSubscribed && this.okToCall;
}
public boolean hasDog() {
return dogName.isPresent();
}
}
This way you do not have to deal with the if loops, the Owner object will say if they have a dog and can be contacted, etc.
public static void main(String[] args) {
Owner owner = new Owner(true, true, "12345678", "1346346", "white#email.com", Optional.of("Alex"));
boolean canContact = owner.hasDog() && owner.canContact();
}
I think you can have two lists of your conditions and attributes and then check whether attributes contain all condition or not.
public static Boolean allConditionsExist(List<String> attributes, List<String> conditions) {
return attributes.containsAll(conditions);
}
To convert your conditions and attributes to a list you can do something like this.
List<String> conditions = Arrays.asList("dogName","isSubscribed", "okToCall"); // add all your conditions
and
List<String> attributeKeys = attributes.stream().map(Attribute::getKey).collect(Collectors.toList());
Then call
allConditionExist(attributeKeys, conditions);
Assuming that every attribute is present only once, you could write
boolean canContactDogOwner(List<Attribute> attributes) {
int matches = 0;
for (var attribute : attributes) {
if (attribute.key().equals("dogName")) ||
attribute.key().equals("isSubscribed") && attribute.value().equals("Y") ||
attribute.key().equals("okToCall") && attribute.value().equals("Y"))
{
matches++;
if (matches >= 3) {
return true;
}
}
}
return false;
}
For the stream way you could write a Collector, constructed with a list of Predicates and returning a boolean. Wouldn't be the fastest...
Something like:
public class AllMatch<T> implements Collector<T, Set<Predicate<T>>, Boolean>
{
private Set<Predicate<T>> filter;
public AllMatch(Predicate<T>... filter)
{
super();
this.filter = new HashSet(Arrays.asList(filter));
}
#Override
public Supplier<Set<Predicate<T>>> supplier()
{
return () -> new HashSet<>();
}
#Override
public BinaryOperator<Set<Predicate<T>>> combiner()
{
return this::combiner;
}
#Override
public Set<Characteristics> characteristics()
{
return Stream.of(Characteristics.UNORDERED).collect(Collectors.toCollection(HashSet::new));
}
public Set<Predicate<T>> combiner(Set<Predicate<T>> left, Set<Predicate<T>> right)
{
left.addAll(right);
return left;
}
public Set<Predicate<T>> accumulator(Set<Predicate<T>> acc, T t)
{
filter.stream().filter(f -> f.test(t)).forEach(f ->
{
acc.add(f);
});
return acc;
}
#Override
public Function<Set<Predicate<T>>, Boolean> finisher()
{
return (s) -> s.equals(filter);
}
#Override
public BiConsumer<Set<Predicate<T>>, T> accumulator()
{
return this::accumulator;
}
public static void main(String[] args) {
Integer[] numbers = {1,2,3,4,5,6,7,8};
System.out.println(Arrays.stream(numbers).collect(new AllMatch<Integer>((i)-> i.equals(5),(i)-> i.equals(6))));
System.out.println(Arrays.stream(numbers).collect(new AllMatch<Integer>((i)-> i.equals(5),(i)-> i.equals(9))));
}
}
Let's assume I have a class Person
public class Person {
private final String name;
private final int age;
private boolean rejected;
private String rejectionComment;
public void reject(String comment) {
this.rejected = true;
this.rejectionComment = comment;
}
// constructor & getters are ommited
}
and my app is something like that
class App {
public static void main(String[] args) {
List<Person> persons = Arrays.asList(
new Person("John", 10),
new Person("Sarah", 20),
new Person("Daniel", 30)
)
persons.forEach(p -> {
rejectIfYoungerThan15(p);
rejectIfNameStartsWithD(p);
// other rejection functions
}
}
private static void rejectIfYoungerThan15(Person p) {
if (!p.isRejected() && p.getAge() < 15) {
p.reject("Too young")
}
}
private static void rejectIfNameStartsWithD(Person p) {
if (!p.isRejected() && p.getName().startsWith("D")) {
p.reject("Name starts with 'D'")
}
}
// other rejection functions
}
The thing is I don't like that I have to perform !p.isRejected() check in every rejection function. Moreover, it doesn't make sense to pass an already rejected person to next filters.
So my idea is to use a mechanism of Stream.filter and make something like
persons.stream().filter(this::rejectIfYoungerThan15).filter(this::rejectIfNameStartsWithD)...
And change signature for these methods to return true if a passed Person has not been rejected and false otherwise.
But it seems to me that it's a very bad idea to use filter with non-pure functions.
Do you have any ideas of how to make it in more elegant way?
When you change the check functions to only check the condition (i.e. not to call p.isRejected()) and return boolean, you already made the necessary steps to short-circuit:
private static boolean rejectIfYoungerThan15(Person p) {
if(p.getAge() < 15) {
p.reject("Too young");
return true;
}
return false;
}
private static boolean rejectIfNameStartsWithD(Person p) {
if(p.getName().startsWith("D")) {
p.reject("Name starts with 'D'");
return true;
}
return false;
}
usable as
persons.forEach(p -> {
if(rejectIfYoungerThan15(p)) return;
if(rejectIfNameStartsWithD(p)) return;
// other rejection functions
}
}
A Stream’s filter operation wouldn’t do anything other than checking the returned boolean value and bail out. But depending on the Stream’s actual terminal operation the short-circuiting could go even farther and end up in not checking all elements, so you should not bring in a Stream operation here.
Calling these methods from lambda is fine, however, for better readability, you can rename these methods to show what they are doing and return boolean, e.g.:
private boolean hasEligibleAge(Person p){..}
private boolean hasValidName(Person p){..}
Another approach would be to wrap these methods into another method (to reflect the business logic/flow), e.g.:
private boolean isEligible(Person p){
//check age
//check name
}
You should make Person immutable, and let the reject-methods return a new Person. That will allow you to chain map-calls. Something like this:
public class Person {
private final String name;
private final int age;
private final boolean rejected;
private final String rejectionComment;
public Person reject(String comment) {
return new Person(name, age, true, comment);
}
// ...
}
class App {
// ...
private static Person rejectIfYoungerThan15(Person p) {
if (!p.isRejected() && p.getAge() < 15) {
return p.reject("Too young");
}
return p;
}
}
Now you can do this:
persons.stream()
.map(App::rejectIfYoungerThan15)
.map(App::rejectIfNameStartsWithD)
.collect(Collectors.toList());
If you want to remove rejected persons, you can add a filter after the mapping:
.filter(person -> !person.isRejected())
EDIT:
If you need to short circuit the rejections, you could compose your rejection functions into a new function and make it stop after the first rejection. Something like this:
/* Remember that the stream is lazy, so it will only call new rejections
* while the person isn't rejected.
*/
public Function<Person, Person> shortCircuitReject(List<Function<Person, Person>> rejections) {
return person -> rejections.stream()
.map(rejection -> rejection.apply(person))
.filter(Person::isRejected)
.findFirst()
.orElse(person);
}
Now your stream can look like this:
List<Function<Person, Person>> rejections = Arrays.asList(
App::rejectIfYoungerThan15,
App::rejectIfNameStartsWithD);
List<Person> persons1 = persons.stream()
.map(shortCircuitReject(rejections))
.collect(Collectors.toList());
I am having trouble getting the "grandparent" of a child.
//main class
User finalAlternate = new User();
for(User user: userList) {
if(user.getAlternateContactId() != null) {
finalAlternate = checkAlternate(user);
approverMap.put(finalAlternate.getId(), user.getId());
}
}
//helper method
public User checkAlternate(User user) { //returns the alternate
User alternateUser = new User();
if(user.getAlternateContactId() != null) {
alternateUser = userMQService.getUser(user.getAlternateContactId(), new UserCollection[]{UserCollection.ROLES, UserCollection.GROUPS});
} else {
return user;
}
return checkAlternate(alternateUser);
}
I have a collection of Users that look something like:
User A -> User B -> User C
All these Users are related with each other due to their alternate contact Id. So User A's alternateContactId is the Id of User B and User B's alternateContacId is the ID of User C. When I run my method my map's result looks something like this:
User C -> User B
So, how would I fix my method so that the map looks like this:
User C -> User A
Basically how can I fix my method so I can get the first ancestor of any child?
Thanks for all the help!
The problem is that you are overwriting previous entries in the Child/GrandParent map. For example.
User finalAlternate = new User();
for(User user: userList) {
if(user.getAlternateContactId() != null) {
// Assuming the users are in the following order in the list [A, B, C]
// In the first iteration you pass A
finalAlternate = checkAlternate(user);
// checkAlternate(A) will traverse A->B->C until it finds C, so when
// this method returns, finalAlternate should be equal to C.
// All good! that is what we wanted
// After the first iteration we have approveMap.put(C, A);
approverMap.put(finalAlternate.getId(), user.getId());
}
}
In the second iteration
for(User user: userList) {
if(user.getAlternateContactId() != null) {
// In the second iteration you pass B
finalAlternate = checkAlternate(user);
// checkAlternate(B) will traverse B->C until it finds C, so when
// this method returns, finalAlternate should be equal to C.
// After the second iteration we have approveMap.put(C, B); // here is the problem
approverMap.put(finalAlternate.getId(), user.getId());
}
}
If and only if, your list is sorted in such a way that grandparents occur before parents then a simple check for containsKey should do the trick:
if(!approveMap.containsKey(finalAlternate.getId()) {
approverMap.put(finalAlternate.getId(), user.getId());
}
However I don't know if this will help you much since in the list a grandparent might show up after a parent for example [B, A, C] will give you the "correct" output. To address this issue, you can add a parent field to each User and then you can check whether or not that User is a grandparent by setting the parent of each grandparent User to null:
public class User {
private User parent = null; // all grandparent users should have parent = null;
// .. some good code
public boolean isGrandparent() {
return parent == null;
}
}
Then you can add that comparison
if(user.isGrandparent()) {
approverMap.put(finalAlternate.getId(), user.getId());
}
You store the Users you have created inside a hashMap and give each user a unique identifier, i chose to use an integer that increases by 1 each time it's used.
Here is a sample implementation just to get you started:
public class User {
private static final HashMap<Integer, User> hashMap = new HashMap();
private static int index = 0;
private static int NO_PARENT = -1;
public final String name;
public final int id;
public final int parentId;
public User(String name) {
this.name = name;
this.id = ++index;
this.parentId = NO_PARENT;
hashMap.put(id, this);
}
public User(String name, int parentId) {
this.name = name;
this.id = ++index;
this.parentId = parentId;
hashMap.put(id, this);
}
private User getGrandParent(User u, int generation) {
if (generation == 2)
return u;
if (u != null && u.parentId != NO_PARENT)
return getGrandParent(hashMap.get(u.parentId), generation +1);
return null;
}
public User getGrandParen() {
return getGrandParent(this, 0);
}
}
Test
public static void main(String[] args) {
User u1 = new User("A");
User u2 = new User("B", u1.id);
User u3 = new User("C", u2.id);
User u4 = new User("D", u3.id);
System.out.println("grandparent of C is: " + u3.getGrandParen().name);
System.out.println("grandparent of D is: " + u4.getGrandParen().name);
}
Output:
grandparent of C is: A
grandparent of D is: B
Note that you would have to flush the HashMap of Users that you no longer need.
It wasn't clear to me what your userMQService did, if it's asynchronous task it's a whole nother ball game but you specificed HashMap and array so I assumed you wanted something like this.
To get the first ancestor you do:
private User getFirstAncestor(User u) {
if (u != null && u.parentId != NO_PARENT)
return getFirstAncestor(hashMap.get(u.parentId));
return u;
}
public User getFirstAncestor() {
if (parentId == NO_PARENT)
return null; // there is no ancestry.
return getFirstAncestor(this);
}
Here is another example that doesn't modify the User class:'
public class UserAncestry {
private static final HashMap<User, User> mapParent = new HashMap<>();
public static void setParent(User child, User parent) {
mapParent.put(child, parent);
}
public static User getParent(User user) {
return mapParent.get(user);
}
public static User getFirstAncenstor(User user) {
User parent = mapParent.get(user);
if (parent != null)
return getFirstAncenstor(parent);
return user;
}
}
public static void main(String[] args) {
User u1 = new User("A");
User u2 = new User("B");
User u3 = new User("C");
User u4 = new User("D");
UserAncestry.setParent(u2, u1);
UserAncestry.setParent(u3, u2);
UserAncestry.setParent(u4, u3);
User uu1 = UserAncestry.getFirstAncenstor(u4);
User uu2 = UserAncestry.getFirstAncenstor(u3);
System.out.println("uu1:: " + uu1.name);
System.out.println("uu1:: " + uu2.name);
}
I have conceptual problem understanding how to compose between streams\Observables which have different return type.
Here is a draft method I'm trying to code:
public void findSeat() {
rx.Observable<GameObject> userObs = context.getUser();
rx.Observable<ActiveGame> gameObs = context.findGame();
rx.Observable.zip(userObs, gameObs, (userObj, game) -> {
User user = ...;
final List<Object> results = new ArrayList<Object>(3);
if(userObj.getStatus() != ErrorCodes.STATUS_OK) {
results.add(-1);
return results;
}
...
...
//***********************************
// THE PROBLEM IS HERE:
// "context.getActiveGameManager().updateGame(game)" returns Observable<GameOBject> and not List<Object> like .zip() expects.
// because of that I cannot do:
// "return context.getActiveGameManager().updateGame(game);"
// How can I do this convertion from Observable<GameObject> to List<Object>
//************************************
context.getActiveGameManager().updateGame(game)
.map((gameObj) -> {
if(gameObj.getStatus() != ErrorCodes.STATUS_OK) {
results.add(-2);
return (Observable<? extends Object>) results;
}
results.add(ErrorCodes.STATUS_OK);
results.add(user);
results.add(gameObj);
return gameObs;
});
return Observable.empty();
}).subscribe((results) -> {
int status = (int) results.get(0);
User user = (User) results.get(1);
ActiveGame game = (ActiveGame) results.get(2);
replyObj.reply(new JsonObject()
.putString("action", CommandActions.FIND_SEAT)
.putNumber("status", status);
.putNumber("game_id", game.getGameId())
);
});
}
The flow is as follow:
1. emit 2 Observable using .zip method.
2. do some logic on the return value of streams and if it results in error-code --> put it in list and return it so "subscribe" can return the error to user.
3. if no error, emit another "update" method using flatMap() - and this is where I have my problem.
4. eventually, all the results should be processed in "subscribe" because this is the point I acknowledge the user about his request.
Hope it's clear enough...
by the way, I'm trying to learn rxJava, but it's very hard I find there are enough\good sources - can someone recommend to me the best way to learn it?? I trying looking at tutorials at Youtube, Wikipedia, Github...most of them teaches using Scala and other scripting languages - couldn't find anything in Java.
Thank you for everyone that put the effort trying understand it!!
I think you were almost there, but try breaking down the code inside your .zip lambda into smaller Rx operations. For example:
rx.Observable
.zip(userObs, gameObs, (userObj, game) -> {
// Combine the user & game objects and pass them to the
// next Rx operation.
return new UserAndActiveGame(userObj, game);
})
.filter(userAndActiveGame -> {
// Remove this filter if you want errors to make it to the subscriber.
return userAndActiveGame.getUserObj().getStatus() == ErrorCodes.STATUS_OK;
})
.flatMap(userAndActiveGame -> {
// Remove this check if you filter errors above.
if (userAndActiveGame.getUserObj().getStatus() != ErrorCodes.STATUS_OK) {
return Observable.just(new FindSeatResult(-1));
}
return context.getActiveGameManager().updateGame(userAndActiveGame.getGame())
.map(gameObj -> {
if (gameObj.getStatus() != ErrorCodes.STATUS_OK) {
return new FindSeatResult(-2);
}
User user =...; // Whatever you are doing to get this in your example code.
return new FindSeatResult(ErrorCodes.STATUS_OK, user, gameObj);
});
})
The following classes are used for passing intermediate and final results:
private class UserAndActiveGame {
private final GameObject userObj;
private final ActiveGame game;
public UserAndActiveGame(GameObject userObj, ActiveGame game) {
this.userObj = userObj;
this.game = game;
}
public GameObject getUserObj() {
return userObj;
}
public ActiveGame getGame() {
return game;
}
}
private class FindSeatResult {
private final int status;
private final User user;
private final ActiveGame game;
public FindSeatResult(int status) {
this(status, null, null);
}
public FindSeatResult(int status, User user, ActiveGame game) {
this.status = status;
this.user = user;
this.game = game;
}
public User getUser() {
return user;
}
public int getStatus() {
return status;
}
public ActiveGame getGame() {
return game;
}
}
Your subscriber then uses the packaged result similar to what you are already doing.
.subscribe((results) -> {
// You don't need this if you filter errors above.
if (findSeatResult.getStatus() == -1) {
return;
}
int status = findSeatResult.getStatus();
User user = findSeatResult.getUser();
ActiveGame game = findSeatResult.getGame();
replyObj.reply(new JsonObject()
.putString("action", CommandActions.FIND_SEAT)
.putNumber("status", status);
.putNumber("game_id", game.getGameId())
);
});
By using the intermediate and final results classes instead of passing around your results in a List<Object> your code is much more forgiving to changes and the compiler will type check everything for you.
I have 2 domain objects, User and SystemRights (It's a many to many, so 1 user can have many rights and 1 right can be owned by many users). I'm looking for a simple way to check if a user has the required rights.
User Domain
class User {
static hasMany = [systemRights: SystemRight, enterpriseUsers: EnterpriseUser]
String email;
String passwordHash;
}
SystemRight Domain
class SystemRight {
public static final String LOGIN = "LOGIN"
public static final String MODIFY_ALL_ENTERPRISES = "MODIFY_ALL_ENTERPRISES"
public static final String ADMINISTER_SYSTEM = "ADMINISTER_SYSTEM"
public static final String VALIDATE_SUPPLIER_SCORECARDS = "VALIDATE_SUPPLIER_SCORECARDS"
static hasMany = [users:User]
static belongsTo = User
String name
}
The following did not work for me:
In User.class
public boolean hasRights(List<String> requiredRights) {
def userHasRight = SystemRight.findByUserAndSystemRight (this, SystemRight.findByName(requiredRight));
// Nor this
def userHasRight = this.systemRights.contains(SystemRight.findByName(requiredRight));
}
Current Horrible Solution
public boolean hasRights(List<String> requiredRights) {
for (String requiredRight : requiredRights) {
def has = false
for (SystemRight userRight : user.systemRights) {
if (userRight.name == requiredRight) {
has = true
break;
}
}
if (has == false) {
return false;
}
}
return true
}
If you're able/willing to change things up a bit, I'd highly recommend doing the following. It will make you're life so much easier.
First, remove the hasMany for SystemRight and User from both Domains and remove the belongsTo User from SystemRight.
Next, create the Domain to represent the join table.
class UserSystemRight {
User user
SystemRight systemRight
boolean equals(other) {
if (!(other instanceof UserSystemRight)) {
return false
}
other.user?.id == user?.id && other.systemRight?.id == systemRight?.id
}
int hashCode() {
def builder = new HashCodeBuilder()
if (user) builder.append(user.id)
if (systemRight) builder.append(systemRight.id)
builder.toHashCode()
}
// add some more convenience methods here if you want like...
static UserSystemRight get(long userId, long systemRightId, String systemRightName) {
find 'from UserSystemRight where user.id=:userId and systemRight.id=:systemRightId and systemRight.name=:systemRightName',
[userId: userId, systemRightId: systemRightId, systemRightName: systemRightName]
}
}
Then, in your User class you can add this method:
Set<SystemRight> getSystemRights() {
UserSystemRight.findAllByUser(this).collect { it.systemRight } as Set
}
Then, add this to your SystemRight Domain:
Set<User> getUsers() {
UserSystemRight.findAllBySystemRight(this).collect { it.user } as Set
}
For a more detailed explenation of why this approach is full of win, aside from actually solving your problem, take a gander at this.
I would definitely try to solve this in the database.
def relevantUserRights = SystemRight.withCriteria {
eq("user", this)
"in"("name", requiredRights);
}
return relevantUserRights.size() == requiredRights.size()
How about the following?
public boolean hasRights(List<String> requiredRights) {
return null != (this.systemRights.find { requiredRights.contains(it) });
}
(Not tested: Groovy newbie here)