/* * This file is part of Player Analytics (Plan). * * Plan is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License v3 as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Plan is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Plan. If not, see . */ package com.djrapitops.plan.delivery.rendering.json; import com.djrapitops.plan.delivery.domain.TablePlayer; import com.djrapitops.plan.delivery.domain.mutators.ActivityIndex; import com.djrapitops.plan.delivery.formatting.Formatter; import com.djrapitops.plan.delivery.formatting.Formatters; import com.djrapitops.plan.delivery.rendering.html.Html; import com.djrapitops.plan.delivery.rendering.html.icon.Family; import com.djrapitops.plan.delivery.rendering.html.icon.Icon; import com.djrapitops.plan.extension.FormatType; import com.djrapitops.plan.extension.icon.Color; import com.djrapitops.plan.extension.implementation.results.*; import com.djrapitops.plan.settings.locale.Locale; import com.djrapitops.plan.settings.locale.lang.HtmlLang; import org.apache.commons.lang3.StringUtils; import org.apache.commons.text.StringEscapeUtils; import java.util.*; /** * Parsing utility for creating jQuery Datatables JSON for a Players Table. *

* See https://www.datatables.net/manual/data/orthogonal-data#HTML-5 for sort kinds * * @author Rsl1122 */ public class PlayersTableJSONParser { private final List players; private final List extensionDescriptives; private final Map extensionData; private final Locale locale; private final boolean openPlayerPageInNewTab; private final Map> numberFormatters; private final Formatter decimalFormatter; public PlayersTableJSONParser( List players, Map extensionData, // Settings boolean openPlayerPageInNewTab, Formatters formatters, Locale locale ) { // Data this.players = players; this.extensionData = extensionData; this.locale = locale; extensionDescriptives = new ArrayList<>(); addExtensionDescriptives(extensionData); extensionDescriptives.sort((one, two) -> String.CASE_INSENSITIVE_ORDER.compare(one.getName(), two.getName())); // Settings this.openPlayerPageInNewTab = openPlayerPageInNewTab; // Formatters numberFormatters = new EnumMap<>(FormatType.class); numberFormatters.put(FormatType.DATE_SECOND, formatters.secondLong()); numberFormatters.put(FormatType.DATE_YEAR, formatters.yearLong()); numberFormatters.put(FormatType.TIME_MILLISECONDS, formatters.timeAmount()); numberFormatters.put(FormatType.NONE, Object::toString); this.decimalFormatter = formatters.decimals(); } private void addExtensionDescriptives(Map extensionData) { Set foundDescriptives = new HashSet<>(); for (ExtensionTabData tabData : extensionData.values()) { for (ExtensionDescriptive descriptive : tabData.getDescriptives()) { if (!foundDescriptives.contains(descriptive.getName())) { extensionDescriptives.add(descriptive); foundDescriptives.add(descriptive.getName()); } } } } public String toJSONString() { String data = parseData(); String columnHeaders = parseColumnHeaders(); return "{\"columns\":" + columnHeaders + ",\"data\":" + data + '}'; } private String parseData() { StringBuilder dataJSON = new StringBuilder("["); int currentPlayerNumber = 0; for (TablePlayer player : players) { UUID playerUUID = player.getPlayerUUID(); if (playerUUID == null) { continue; } if (currentPlayerNumber > 0) { dataJSON.append(','); // Previous item } dataJSON.append('{'); // Start new item appendPlayerData(dataJSON, player); appendExtensionData(dataJSON, extensionData.getOrDefault(playerUUID, new ExtensionTabData.Builder(null).build())); dataJSON.append('}'); // Close new item currentPlayerNumber++; } return dataJSON.append(']').toString(); } private void appendPlayerData(StringBuilder dataJSON, TablePlayer player) { String name = player.getName().orElse(player.getPlayerUUID().toString()); String url = "../player/" + Html.encodeToURL(name); int loginTimes = player.getSessionCount().orElse(0); long playtime = player.getPlaytime().orElse(-1L); long registered = player.getRegistered().orElse(-1L); long lastSeen = player.getLastSeen().orElse(-1L); ActivityIndex activityIndex = player.getCurrentActivityIndex().orElseGet(() -> new ActivityIndex(0.0, 0)); boolean isBanned = player.isBanned(); String activityString = activityIndex.getFormattedValue(decimalFormatter) + (isBanned ? " (" + locale.get(HtmlLang.LABEL_BANNED) + ")" : " (" + activityIndex.getGroup() + ")"); String geolocation = player.getGeolocation().orElse("-"); Html link = openPlayerPageInNewTab ? Html.LINK_EXTERNAL : Html.LINK; dataJSON.append(makeDataEntry(link.parse(url, StringUtils.replace(StringEscapeUtils.escapeHtml4(name), "\\", "\\\\")), "name")).append(',') // Backslashes escaped to prevent json errors .append(makeDataEntry(activityIndex.getValue(), activityString, "index")).append(',') .append(makeDataEntry(playtime, numberFormatters.get(FormatType.TIME_MILLISECONDS).apply(playtime), "playtime")).append(',') .append(makeDataEntry(loginTimes, "sessions")).append(',') .append(makeDataEntry(registered, numberFormatters.get(FormatType.DATE_YEAR).apply(registered), "registered")).append(',') .append(makeDataEntry(lastSeen, numberFormatters.get(FormatType.DATE_YEAR).apply(lastSeen), "seen")).append(',') .append(makeDataEntry(geolocation, "geolocation")); } private String makeDataEntry(Object data, String dataName) { return "\"" + dataName + "\":\"" + data.toString().replace('"', '\'') + "\""; } private String makeDataEntry(Object data, String formatted, String dataName) { return "\"" + dataName + "\":{\"v\":\"" + data.toString().replace('"', '\'') + "\", \"d\":\"" + formatted.replace('"', '\'') + "\"}"; } private void appendExtensionData(StringBuilder dataJSON, ExtensionTabData tabData) { for (ExtensionDescriptive descriptive : extensionDescriptives) { dataJSON.append(','); String key = descriptive.getName(); // If it's a double, append a double Optional doubleValue = tabData.getDouble(key); if (doubleValue.isPresent()) { dataJSON.append(makeDataEntry(doubleValue.get().getRawValue(), doubleValue.get().getFormattedValue(decimalFormatter), key)); continue; } Optional numberValue = tabData.getNumber(key); if (numberValue.isPresent()) { ExtensionNumberData numberData = numberValue.get(); FormatType formatType = numberData.getFormatType(); dataJSON.append(makeDataEntry(numberData.getRawValue(), numberData.getFormattedValue(numberFormatters.get(formatType)), key)); continue; } // If it's a String append a String, otherwise the player has no value for this extension provider. String stringValue = tabData.getString(key).map(ExtensionStringData::getFormattedValue).orElse("-"); dataJSON.append(makeDataEntry(stringValue, stringValue, key)); } } private String parseColumnHeaders() { StringBuilder columnHeaders = new StringBuilder("["); // Is the data for the column formatted columnHeaders .append(makeColumnHeader(Icon.called("user") + " " + locale.get(HtmlLang.LABEL_NAME), "name")).append(',') .append(makeFColumnHeader(Icon.called("check") + " " + locale.get(HtmlLang.LABEL_ACTIVITY_INDEX), "index")).append(',') .append(makeFColumnHeader(Icon.called("clock").of(Family.REGULAR) + " " + locale.get(HtmlLang.LABEL_PLAYTIME), "playtime")).append(',') .append(makeColumnHeader(Icon.called("calendar-plus").of(Family.REGULAR) + " " + locale.get(HtmlLang.SIDE_SESSIONS), "sessions")).append(',') .append(makeFColumnHeader(Icon.called("user-plus") + " " + locale.get(HtmlLang.LABEL_REGISTERED), "registered")).append(',') .append(makeFColumnHeader(Icon.called("calendar-check").of(Family.REGULAR) + " " + locale.get(HtmlLang.LABEL_LAST_SEEN), "seen")).append(',') .append(makeColumnHeader(Icon.called("globe") + " " + locale.get(HtmlLang.TITLE_COUNTRY), "geolocation")); appendExtensionHeaders(columnHeaders); return columnHeaders.append(']').toString(); } private String makeColumnHeader(String title, String dataProperty) { return "{\"title\": \"" + title.replace('"', '\'') + "\",\"data\":\"" + dataProperty + "\"}"; } private String makeFColumnHeader(String title, String dataProperty) { return "{\"title\": \"" + title.replace('"', '\'') + "\",\"data\":{\"_\":\"" + dataProperty + ".v\",\"display\":\"" + dataProperty + ".d\"}}"; } private void appendExtensionHeaders(StringBuilder columnHeaders) { for (ExtensionDescriptive provider : extensionDescriptives) { columnHeaders.append(','); String headerText = Icon.fromExtensionIcon(provider.getIcon().setColor(Color.NONE)).toHtml().replace('"', '\'') + ' ' + provider.getText(); columnHeaders.append(makeFColumnHeader(headerText, provider.getName())); } } }