001/*
002 * Copyright (c) 2010-2026 Mark Allen, Norbert Bartels.
003 *
004 * Permission is hereby granted, free of charge, to any person obtaining a copy
005 * of this software and associated documentation files (the "Software"), to deal
006 * in the Software without restriction, including without limitation the rights
007 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
008 * copies of the Software, and to permit persons to whom the Software is
009 * furnished to do so, subject to the following conditions:
010 *
011 * The above copyright notice and this permission notice shall be included in
012 * all copies or substantial portions of the Software.
013 *
014 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
015 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
016 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
017 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
018 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
019 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
020 * THE SOFTWARE.
021 */
022package com.restfb.util;
023
024import static java.util.Comparator.comparingInt;
025
026import java.util.ArrayList;
027import java.util.Comparator;
028import java.util.List;
029
030import com.restfb.types.MessageTag;
031
032/**
033 * Helper to transform a Graph API {@code message} + {@code message_tags} pair into a normalized representation that
034 * uses the mention syntax (e.g. {@code @[12345]}).
035 */
036public final class MessageTagUtils {
037
038  private MessageTagUtils() {
039    // prevent instantiation
040  }
041
042  /**
043   * Rebuilds a message so that tagged segments are replaced by their mention syntax.
044   *
045   * @param message
046   *          original text returned by the Graph API
047   * @param tags
048   *          message tags as returned by the Graph API
049   * @return the normalized message or the original one if the tags don't provide enough information
050   */
051  public static String normalizeMessage(String message, List<MessageTag> tags) {
052    if (message == null || tags == null || tags.isEmpty()) {
053      return message;
054    }
055
056    List<MessageTag> applicableTags = new ArrayList<>();
057    for (MessageTag tag : tags) {
058      if (isApplicable(tag)) {
059        applicableTags.add(tag);
060      }
061    }
062
063    if (applicableTags.isEmpty()) {
064      return message;
065    }
066
067    applicableTags.sort(tagComparator());
068
069    StringBuilder normalized = new StringBuilder(message.length() + applicableTags.size() * 4);
070    int cursor = 0;
071
072    for (MessageTag tag : applicableTags) {
073      int start = tag.getOffset();
074      int end = start + tag.getLength();
075
076      if (start < cursor || start < 0 || end < start || end > message.length()) {
077        return message;
078      }
079
080      normalized.append(message, cursor, start);
081      normalized.append(buildMention(tag));
082      cursor = end;
083    }
084
085    normalized.append(message.substring(cursor));
086    return normalized.toString();
087  }
088
089  private static boolean isApplicable(MessageTag tag) {
090    return tag != null && tag.getOffset() != null && tag.getLength() != null && tag.getOffset() >= 0
091        && tag.getLength() >= 0;
092  }
093
094  private static Comparator<MessageTag> tagComparator() {
095    return comparingInt(MessageTag::getOffset).thenComparing((a, b) -> Integer.compare(b.getLength(), a.getLength()));
096  }
097
098  private static String buildMention(MessageTag tag) {
099    String id = tag.getId();
100    if (id != null && !id.isEmpty()) {
101      return "@[" + id + "]";
102    }
103
104    String name = tag.getName();
105    if (name != null) {
106      return name;
107    }
108
109    return "";
110  }
111}