class TaggedMessagesContainer implements JSONable {

        private final String tag;
        private final SortedSet<MessagesContainer.AcceptedMessage> messages;

        TaggedMessagesContainer(String tag, SortedSet<MessagesContainer.AcceptedMessage> messages) {
            this.tag = tag;
            this.messages = messages;
        }

        public String getTag() {
            return tag;
        }

        public SortedSet<MessagesContainer.AcceptedMessage> getMessages() {
            return messages;
        }
    }

    /**
     * Print list of messages into output.
     *
     * @param out output
     * @param set set of messages
     */
    public static void printMessages(Writer out, SortedSet<MessagesContainer.AcceptedMessage> set) {
        printMessages(out, set, false);
    }

    /**
     * Print set of messages into output.
     * @param out output
     * @param set set of messages
     * @param limited if the container should be limited
     */
    private static void printMessages(Writer out, SortedSet<MessagesContainer.AcceptedMessage> set, boolean limited) {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
        if (!set.isEmpty()) {
            try {
                out.write("<ul class=\"message-group");
                if (limited) {
                    out.write(" limited");
                }
                out.write("\">\n");
                for (MessagesContainer.AcceptedMessage m : set) {
                    out.write("<li class=\"message-group-item ");
                    out.write(Util.encode(m.getMessage().getMessageLevel().toString()));
                    out.write("\" title=\"Expires on ");
                    out.write(Util.encode(df.format(Date.from(m.getExpirationTime()))));
                    out.write("\">");
                    out.write(Util.encode(df.format(Date.from(m.getAcceptedTime()))));
                    out.write(": ");
                    out.write(m.getMessage().getText());
                    out.write("</li>");
                }
                out.write("</ul>");
            } catch (IOException ex) {
                LOGGER.log(Level.WARNING,
                        "An error occurred for a group of messages", ex);
            }
        }
    }

    /**
     * Print set of tagged messages into JSON object.
     *
     * @return JSON string or empty string
     */
    private static String taggedMessagesToJson(Set<TaggedMessagesContainer> messages) {
        if (messages.isEmpty()) {
            return JSONable.EMPTY;
        }

        ObjectMapper mapper = new ObjectMapper();

        try {
            return mapper.writeValueAsString(messages);
        } catch (JsonProcessingException e) {
            LOGGER.log(Level.WARNING, String.format("failed to encode '%s' to JSON: ", messages), e);
            return JSONable.EMPTY;
        }
    }

    /**
     * Print messages for given tags into JSON array.
     *
     * @param tags list of tags
     * @return JSON array of the messages (the same as the parameter)
     */
    public static String messagesToJson(String... tags) {
        Set<TaggedMessagesContainer> messages = new HashSet<>();

        for (String tag : tags) {
            SortedSet<MessagesContainer.AcceptedMessage> messagesWithTag = RuntimeEnvironment.getInstance().getMessages(tag);
            if (messagesWithTag.isEmpty()) {
                continue;
            }

            TaggedMessagesContainer container = new TaggedMessagesContainer(tag, messagesWithTag);
            messages.add(container);
        }

        return taggedMessagesToJson(messages);
    }

    /**
     * Print messages for given tags into JSON array.
     *
     * @param tags list of tags
     * @return json array of the messages
     * @see #messagesToJson(String...)
     */
    private static String messagesToJson(List<String> tags) {
        return messagesToJson(tags.toArray(new String[0]));
    }

    /**
     * Convert messages for given project into JSON. These messages are
     * tagged by project description or tagged by any of the project's group name.
     *
     * @param project the project
     * @param additionalTags additional list of tags
     * @return JSON string
     * @see #messagesToJson(String...)
     */
    public static String messagesToJson(Project project, String... additionalTags) {<FILL_FUNCTION_BODY>

        if (Objects.isNull(project)) {
            return JSONable.EMPTY;
        }

        List<String> tags = new ArrayList<>(Arrays.asList(additionalTags));
        tags.add(project.getName());
        project.getGroups().stream().map(Group::getName).forEach(tags::add);

        return messagesToJson(tags);
    
<no_super_class>