Solved Loop doesn't do what it's supposed to do

Discussion in 'Spigot Plugin Development' started by TechBug2012, May 29, 2016.

  1. Hi,

    I'm making a plugin that, if a player toggles it on, replaces every swear word in the chat with stars. It works fine, but I also have some words that I do not want to censor. For example, glass, associate, and compass, which all contain a banned word that I do want to censor. In my code, I specifically told it to censor only if the word contains a banned word AND it does not equal any of the pardoned words. However, the pardoned words are still censored when I type them ingame. My last problems with this plugin were all due to unorganized loops, so I have the feeling this one is similar, but I just can't figure it out no matter how much I rubber-duck.
    Here's the code that is giving me trouble:
    Code (Java):

        @EventHandler
        public void onChat(AsyncPlayerChatEvent event) {
            Set<Player> filteredPlayers = new HashSet<>();
            String newMessage = event.getMessage();
            String[] splitMessage = newMessage.split(" ");
            StringBuilder sb = new StringBuilder();
            StringBuilder mb = new StringBuilder();

            for (String split : splitMessage) {
                for (String word : wordList) {
                    if (StringUtils.containsIgnoreCase(split, word)) {
                        for (String pardoned : pardonedList) {
                            if (!(split.equalsIgnoreCase(pardoned))) {
                                for (int i = 0; i < word.length(); i++) {
                                    sb.append("*");
                                }
                                split = split.replaceAll("(?i)" + word, sb.toString());
                                sb.setLength(0);
                            }
                        }
                    }
                }
                mb.append(split);
                mb.append(" ");
            }
            newMessage = mb.toString();
    Any help would be very much appreciated. Thanks! :)
     
  2. Search up regex (regular expressions) and know how to use them to filter out certain things. Much more effective than all those loops you're using.
     
  3. @Lyxnx
    I used a regex method before I tried out this method. Here's a snippet from the old version:
    Code (Java):

        @EventHandler
        public void onChat(AsyncPlayerChatEvent event) {
            Set<Player> filteredPlayers = new HashSet<>();
            String newMessage = event.getMessage();

            for (String word : wordList) {
                Pattern pattern = Pattern.compile(".*\\b(" + word + ")\\b.*", Pattern.CASE_INSENSITIVE);
                Matcher matcher = pattern.matcher(newMessage);
                if (matcher.find()) {
                    StringBuilder sb = new StringBuilder();
                    String match = matcher.group(1);
                    for (int i = 0; i < match.length(); i++) {
                        sb.append("*");
                    }
                    newMessage = newMessage.replaceAll(match, sb.toString());
                }
            }
    The problem with this is that it doesn't use the contains method that I like. In the case of this regex, if you have Trump as a banned word, and someone says DonaldTrump it won't censor. I could use something like \w(word)\w, but then what's the point? I got so far with the loop method that I don't want to delete all of that and use regexes unless there is no way to workaround and leave words like glass uncensored. I want the plugin to be done before I delete half of it.
     
  4. How I did regex replacing in the past:
    http://hastebin.com/jozibijawo.java

    Then have a list of words, in your case, 'trump'.
    Add each of these words as a regex expression and create a new WordFilter instance for each of them.

    Pass them through the event, and everything should be fine.
     
  5. EDIT:

    Here's something I made with regexes. The problem is that it doesn't finish looping through the pardoned list. It only leaves the first word in the list uncensored. Any ideas as to why?
    Code (Java):

            for (String split : splitMessage) {
                for (String word : wordList) {
                    Pattern pattern = Pattern.compile("\\w*(" + word + ")\\w*", Pattern.CASE_INSENSITIVE);
                    Matcher matcher = pattern.matcher(split);
                    for (String pardoned : pardonedList) {
                        Pattern pattern1 = Pattern.compile("\\w*(" + pardoned + ")\\w*", Pattern.CASE_INSENSITIVE);
                        Matcher matcher1 = pattern1.matcher(split);
                        if (matcher.find() && !(matcher1.find())) {
                            String match = matcher.group(1);
                            for (int i = 0; i < match.length(); i++) {
                                sb.append("*");
                            }
                            split = split.replaceAll(match, sb.toString());
                            sb.setLength(0);
                        }
                    }
                }
                mb.append(split);
                mb.append(" ");
            }
            newMessage = mb.toString();
    Thanks! :)
     
    #5 TechBug2012, May 29, 2016
    Last edited: May 29, 2016
  6. Bump. Any ideas from anyone?
     
  7. Verify a word isn't whitelisted before checking if it's blacklisted.

    Aside from that, I would look into a hash-based solution. Store a set of (lowercase) offenders and compare the lowercased word, instead of loops.
     
  8. I'll try this if all else fails. Thanks :)
    I don't think this would work. Even if I check for it being whitelisted first, the blacklist loop that would come after it would make it obsolete. That's why I put the whitelist loop inside of the blacklist loop. Am I missing something?
     
  9. The whole point is that if a word is whitelisted, it makes no sense to check for a blacklist since it's already allowed.
     
  10. The blacklist regex checks for if the word contains a blacklisted word. Does this mean I need to make my regex check for if the word equals a blacklisted word? Because then, I would need to censor every word that contains it, which I don't want to do. Separating the loops would mean that it appends the word to the StringBuilder uncensored, but then afterwards, since the word also contains a banned word, it will append it again censored. I must be missing something...
     
  11. Get word -> Check if word is whitelisted

    If whitelisted -> Cool, word is fine, do absolutely nothing else
    If not whitelisted -> check blacklist -> remove if blacklisted.
     
  12. Is this what you mean?
    Code (Java):

            for (String split : splitMessage) {
                for (String pardoned : pardonedList) {
                    if (split.equalsIgnoreCase(pardoned)) {
                        mb.append(split);
                        mb.append(" ");
                    }
                    else {
                        for (String word : wordList) {
                            Pattern pattern = Pattern.compile("\\w*(" + word + ")\\w*");
                            Matcher matcher = pattern.matcher(split);

                            if (matcher.find()) {
                                String match = matcher.group(1);
                                for (int i = 0; i < match.length(); i++) {
                                    sb.append("*");
                                }
                                split = split.replaceAll(word, sb.toString());
                                sb.setLength(0);
                               
                                mb.append(split);
                                mb.append(" ");
                            }
                        }
                    }
                }
            }
            newMessage = mb.toString();
     
  13. That would very cpu intensive though...
     
  14. What's a more efficient way?

    @1Rogue The method that I just did is still 1) Not finishing the loop through the pardoned list and 2) going forward and checking if it contains the banned word, thus printing out both the word censored and uncensored, if it is the method you had in mind.
     
  15. No, because you are still nesting your loops. Your code does this:

    Check if word 1 in whitelist matches:
    Yes -> continue
    No -> Check entire blacklist, go to word 2

    You should check ALL of your whitelist, then ALL of your blacklist. Or opt for hashing.
     
  16. I'm sorry, but I just don't understand your solution. No matter what, the words are appended to a string builder. So, if I check all of my whitelist, then all of my blacklist, and they must be separate, then, checking the whitelist first, my code would look something like this:
    Code (Java):

    for (String split : splitMessage) {
        for (String pardoned : pardonedList) {
            if (!(split.equalsIgnoreCase(pardoned))) {
                continue;
            }
        }
    Now that I've looped through all of my whitelisted words, I loop through all my blacklisted words:
    Code (Java):

            for (String split : splitMessage) {
                for (String pardoned : pardonedList) {
                    if (!(split.equalsIgnoreCase(pardoned))) {
                        continue;
                    }
                }
                for (String word : wordList) {
                    Pattern pattern = Pattern.compile("\\w*(" + word + ")\\w*");
                    Matcher matcher = pattern.matcher(split);

                    if (matcher.find()) {
                        String match = matcher.group(1);
                        for (int i = 0; i < match.length(); i++) {
                            sb.append("*");
                        }
                        split = split.replaceAll(word, sb.toString());
                        sb.setLength(0);
                    }
                }
    Now that I've distinguished what is censored and what is not, everything is appened to a StringBuilder, then the filtered message is set to the finished StringBuilder.
    Code (Java):

            for (String split : splitMessage) {
                for (String pardoned : pardonedList) {
                    if (!(split.equalsIgnoreCase(pardoned))) {
                        continue;
                    }
                }
                for (String word : wordList) {
                    Pattern pattern = Pattern.compile("\\w*(" + word + ")\\w*");
                    Matcher matcher = pattern.matcher(split);

                    if (matcher.find()) {
                        String match = matcher.group(1);
                        for (int i = 0; i < match.length(); i++) {
                            sb.append("*");
                        }
                        split = split.replaceAll(word, sb.toString());
                        sb.setLength(0);
                    }
                }
                mb.append(split);
                mb.append(" ");
            }
            newMessage = mb.toString();
    But this doesn't work, because after it finishes the whitelist loop, it goes straight to the blacklist loop and censors it anyway. I just don't understand how separating my loops is supposed to make it work, because the blacklist loop does not know what has been decided by the whitelist loop.

    I think I should just start using the hash method.
     
  17. Code (Java):
       boolean whitelisted = false;
        for(String pardoned : pardonedList){
           if(split.equalsIgnoreCase(pardoned)){
               whitelisted = true;
               break;
           }
       }
       if (!whitelisted) {
          //check blacklist
       }
     
  18. Code (Java):

        @EventHandler
        public void onChat(AsyncPlayerChatEvent event) {
            Set<Player> filteredPlayers = new HashSet<>();
            String newMessage = event.getMessage();
            String[] splitMessage = newMessage.split(" ");
            StringBuilder sb = new StringBuilder();
            StringBuilder mb = new StringBuilder();
            boolean whitelisted = false;

            for (String split : splitMessage) {
                for (String pardoned : pardonedList) {
                    if (split.equalsIgnoreCase(pardoned)) {
                        whitelisted = true;
                        break;
                    }
                }
                for (String word : wordList) {
                    if (StringUtils.containsIgnoreCase(split, word) && !whitelisted) {
                        for (int i = 0; i < word.length(); i++) {
                            sb.append("*");
                        }
                        split = split.replaceAll(word, sb.toString());
                        sb.setLength(0);
                    }
                }
                mb.append(split);
                mb.append(" ");
            }
            newMessage = mb.toString();
    This works! I hadn't thought of using a boolean. Thanks for your help!
     
  19. Keep in mind you're re-using a single boolean each time, so if it's true once it will always be true. Best to keep that in the loop.
     
  20. Caught that just before I got notified of your post. Thanks.