mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2024-10-05 18:08:00 +02:00
Added Ping gathering and storage
This commit is contained in:
parent
7b5588e19a
commit
9872c9d209
@ -0,0 +1,23 @@
|
|||||||
|
package com.djrapitops.plan.data.container;
|
||||||
|
|
||||||
|
import com.djrapitops.plan.data.store.objects.DateObj;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class Ping extends DateObj<Integer> {
|
||||||
|
|
||||||
|
private UUID serverUUID;
|
||||||
|
|
||||||
|
public Ping(long date, Integer value, UUID serverUUID) {
|
||||||
|
super(date, value);
|
||||||
|
this.serverUUID = serverUUID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Ping(long date, Integer value) {
|
||||||
|
super(date, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getServerUUID() {
|
||||||
|
return serverUUID;
|
||||||
|
}
|
||||||
|
}
|
@ -5,10 +5,7 @@
|
|||||||
package com.djrapitops.plan.system.database.databases.operation;
|
package com.djrapitops.plan.system.database.databases.operation;
|
||||||
|
|
||||||
import com.djrapitops.plan.data.WebUser;
|
import com.djrapitops.plan.data.WebUser;
|
||||||
import com.djrapitops.plan.data.container.GeoInfo;
|
import com.djrapitops.plan.data.container.*;
|
||||||
import com.djrapitops.plan.data.container.Session;
|
|
||||||
import com.djrapitops.plan.data.container.TPS;
|
|
||||||
import com.djrapitops.plan.data.container.UserInfo;
|
|
||||||
import com.djrapitops.plan.data.store.objects.Nickname;
|
import com.djrapitops.plan.data.store.objects.Nickname;
|
||||||
import com.djrapitops.plan.system.info.server.Server;
|
import com.djrapitops.plan.system.info.server.Server;
|
||||||
|
|
||||||
@ -70,4 +67,6 @@ public interface SaveOperations {
|
|||||||
void serverInfoForThisServer(Server server);
|
void serverInfoForThisServer(Server server);
|
||||||
|
|
||||||
void webUser(WebUser webUser);
|
void webUser(WebUser webUser);
|
||||||
|
|
||||||
|
void ping(UUID uuid, Ping ping);
|
||||||
}
|
}
|
@ -22,6 +22,7 @@ import java.sql.PreparedStatement;
|
|||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -47,6 +48,7 @@ public abstract class SQLDB extends Database {
|
|||||||
private final WorldTimesTable worldTimesTable;
|
private final WorldTimesTable worldTimesTable;
|
||||||
private final ServerTable serverTable;
|
private final ServerTable serverTable;
|
||||||
private final TransferTable transferTable;
|
private final TransferTable transferTable;
|
||||||
|
private final PingTable pingTable;
|
||||||
|
|
||||||
private final SQLBackupOps backupOps;
|
private final SQLBackupOps backupOps;
|
||||||
private final SQLCheckOps checkOps;
|
private final SQLCheckOps checkOps;
|
||||||
@ -79,6 +81,7 @@ public abstract class SQLDB extends Database {
|
|||||||
worldTable = new WorldTable(this);
|
worldTable = new WorldTable(this);
|
||||||
worldTimesTable = new WorldTimesTable(this);
|
worldTimesTable = new WorldTimesTable(this);
|
||||||
transferTable = new TransferTable(this);
|
transferTable = new TransferTable(this);
|
||||||
|
pingTable = new PingTable(this);
|
||||||
|
|
||||||
backupOps = new SQLBackupOps(this);
|
backupOps = new SQLBackupOps(this);
|
||||||
checkOps = new SQLCheckOps(this);
|
checkOps = new SQLCheckOps(this);
|
||||||
@ -263,6 +266,7 @@ public abstract class SQLDB extends Database {
|
|||||||
tpsTable.clean();
|
tpsTable.clean();
|
||||||
transferTable.clean();
|
transferTable.clean();
|
||||||
geoInfoTable.clean();
|
geoInfoTable.clean();
|
||||||
|
pingTable.clean();
|
||||||
|
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
long keepActiveAfter = now - TimeAmount.DAY.ms() * Settings.KEEP_INACTIVE_PLAYERS_DAYS.getNumber();
|
long keepActiveAfter = now - TimeAmount.DAY.ms() * Settings.KEEP_INACTIVE_PLAYERS_DAYS.getNumber();
|
||||||
@ -420,6 +424,10 @@ public abstract class SQLDB extends Database {
|
|||||||
return transferTable;
|
return transferTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PingTable getPingTable() {
|
||||||
|
return pingTable;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isUsingMySQL() {
|
public boolean isUsingMySQL() {
|
||||||
return usingMySQL;
|
return usingMySQL;
|
||||||
}
|
}
|
||||||
@ -463,4 +471,17 @@ public abstract class SQLDB extends Database {
|
|||||||
public TransferOperations transfer() {
|
public TransferOperations transfer() {
|
||||||
return transferOps;
|
return transferOps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
SQLDB sqldb = (SQLDB) o;
|
||||||
|
return usingMySQL == sqldb.usingMySQL && getName().equals(sqldb.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(usingMySQL, getName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ public class SQLOps {
|
|||||||
protected final WorldTimesTable worldTimesTable;
|
protected final WorldTimesTable worldTimesTable;
|
||||||
protected final ServerTable serverTable;
|
protected final ServerTable serverTable;
|
||||||
protected final TransferTable transferTable;
|
protected final TransferTable transferTable;
|
||||||
|
protected final PingTable pingTable;
|
||||||
|
|
||||||
public SQLOps(SQLDB db) {
|
public SQLOps(SQLDB db) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
@ -37,5 +38,6 @@ public class SQLOps {
|
|||||||
worldTimesTable = db.getWorldTimesTable();
|
worldTimesTable = db.getWorldTimesTable();
|
||||||
serverTable = db.getServerTable();
|
serverTable = db.getServerTable();
|
||||||
transferTable = db.getTransferTable();
|
transferTable = db.getTransferTable();
|
||||||
|
pingTable = db.getPingTable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,7 @@
|
|||||||
package com.djrapitops.plan.system.database.databases.sql.operation;
|
package com.djrapitops.plan.system.database.databases.sql.operation;
|
||||||
|
|
||||||
import com.djrapitops.plan.data.WebUser;
|
import com.djrapitops.plan.data.WebUser;
|
||||||
import com.djrapitops.plan.data.container.GeoInfo;
|
import com.djrapitops.plan.data.container.*;
|
||||||
import com.djrapitops.plan.data.container.Session;
|
|
||||||
import com.djrapitops.plan.data.container.TPS;
|
|
||||||
import com.djrapitops.plan.data.container.UserInfo;
|
|
||||||
import com.djrapitops.plan.data.store.objects.Nickname;
|
import com.djrapitops.plan.data.store.objects.Nickname;
|
||||||
import com.djrapitops.plan.system.database.databases.operation.SaveOperations;
|
import com.djrapitops.plan.system.database.databases.operation.SaveOperations;
|
||||||
import com.djrapitops.plan.system.database.databases.sql.SQLDB;
|
import com.djrapitops.plan.system.database.databases.sql.SQLDB;
|
||||||
@ -133,4 +130,9 @@ public class SQLSaveOps extends SQLOps implements SaveOperations {
|
|||||||
public void webUser(WebUser webUser) {
|
public void webUser(WebUser webUser) {
|
||||||
securityTable.addNewUser(webUser);
|
securityTable.addNewUser(webUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void ping(UUID uuid, Ping ping) {
|
||||||
|
pingTable.insertPing(uuid, ping);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,181 @@
|
|||||||
|
package com.djrapitops.plan.system.database.databases.sql.tables;
|
||||||
|
|
||||||
|
import com.djrapitops.plan.api.exceptions.database.DBInitException;
|
||||||
|
import com.djrapitops.plan.data.container.Ping;
|
||||||
|
import com.djrapitops.plan.system.database.databases.sql.SQLDB;
|
||||||
|
import com.djrapitops.plan.system.database.databases.sql.processing.ExecStatement;
|
||||||
|
import com.djrapitops.plan.system.database.databases.sql.processing.QueryAllStatement;
|
||||||
|
import com.djrapitops.plan.system.database.databases.sql.processing.QueryStatement;
|
||||||
|
import com.djrapitops.plan.system.database.databases.sql.statements.Column;
|
||||||
|
import com.djrapitops.plan.system.database.databases.sql.statements.Sql;
|
||||||
|
import com.djrapitops.plan.system.database.databases.sql.statements.TableSqlParser;
|
||||||
|
import com.djrapitops.plan.system.info.server.ServerInfo;
|
||||||
|
import com.djrapitops.plugin.api.TimeAmount;
|
||||||
|
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class PingTable extends UserIDTable {
|
||||||
|
|
||||||
|
public static final String TABLE_NAME = "plan_ping";
|
||||||
|
private final String insertStatement;
|
||||||
|
private final ServerTable serverTable;
|
||||||
|
|
||||||
|
public PingTable(SQLDB db) {
|
||||||
|
super(TABLE_NAME, db);
|
||||||
|
serverTable = db.getServerTable();
|
||||||
|
insertStatement = "INSERT INTO " + tableName + " (" +
|
||||||
|
Col.USER_ID + ", " +
|
||||||
|
Col.SERVER_ID + ", " +
|
||||||
|
Col.DATE + ", " +
|
||||||
|
Col.MAX_PING +
|
||||||
|
") VALUES (" +
|
||||||
|
usersTable.statementSelectID + ", " +
|
||||||
|
serverTable.statementSelectServerID + ", ?, ?)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createTable() throws DBInitException {
|
||||||
|
createTable(TableSqlParser.createTable(TABLE_NAME)
|
||||||
|
.primaryKeyIDColumn(usingMySQL, Col.ID)
|
||||||
|
.column(Col.USER_ID, Sql.INT).notNull()
|
||||||
|
.column(Col.SERVER_ID, Sql.INT).notNull()
|
||||||
|
.column(Col.DATE, Sql.LONG).notNull()
|
||||||
|
.column(Col.MAX_PING, Sql.INT).notNull()
|
||||||
|
.primaryKey(usingMySQL, Col.ID)
|
||||||
|
.foreignKey(Col.USER_ID, usersTable.getTableName(), UsersTable.Col.ID)
|
||||||
|
.foreignKey(Col.SERVER_ID, ServerTable.TABLE_NAME, ServerTable.Col.SERVER_ID)
|
||||||
|
.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clean() {
|
||||||
|
String sql = "DELETE FROM " + tableName +
|
||||||
|
" WHERE (" + Col.DATE + "<?)";
|
||||||
|
|
||||||
|
execute(new ExecStatement(sql) {
|
||||||
|
@Override
|
||||||
|
public void prepare(PreparedStatement statement) throws SQLException {
|
||||||
|
long twoWeeks = TimeAmount.WEEK.ms() * 2L;
|
||||||
|
statement.setLong(1, System.currentTimeMillis() - twoWeeks);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void insertPing(UUID uuid, Ping ping) {
|
||||||
|
execute(new ExecStatement(insertStatement) {
|
||||||
|
@Override
|
||||||
|
public void prepare(PreparedStatement statement) throws SQLException {
|
||||||
|
statement.setString(1, uuid.toString());
|
||||||
|
statement.setString(2, ServerInfo.getServerUUID().toString());
|
||||||
|
statement.setLong(3, ping.getDate());
|
||||||
|
statement.setInt(4, ping.getValue());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Ping> getPing(UUID uuid) {
|
||||||
|
Map<Integer, UUID> serverUUIDs = serverTable.getServerUUIDsByID();
|
||||||
|
String sql = "SELECT * FROM " + tableName +
|
||||||
|
" WHERE " + Col.USER_ID + "=" + usersTable.statementSelectID;
|
||||||
|
|
||||||
|
return query(new QueryStatement<List<Ping>>(sql, 10000) {
|
||||||
|
@Override
|
||||||
|
public void prepare(PreparedStatement statement) throws SQLException {
|
||||||
|
statement.setString(1, uuid.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Ping> processResults(ResultSet set) throws SQLException {
|
||||||
|
List<Ping> pings = new ArrayList<>();
|
||||||
|
|
||||||
|
while (set.next()) {
|
||||||
|
pings.add(new Ping(
|
||||||
|
set.getLong(Col.DATE.get()),
|
||||||
|
set.getInt(Col.MAX_PING.get()),
|
||||||
|
serverUUIDs.get(set.getInt(Col.SERVER_ID.get()))));
|
||||||
|
}
|
||||||
|
|
||||||
|
return pings;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<UUID, List<Ping>> getAllPings() {
|
||||||
|
String usersIDColumn = usersTable + "." + UsersTable.Col.ID;
|
||||||
|
String usersUUIDColumn = usersTable + "." + UsersTable.Col.UUID + " as uuid";
|
||||||
|
String serverIDColumn = serverTable + "." + ServerTable.Col.SERVER_ID;
|
||||||
|
String serverUUIDColumn = serverTable + "." + ServerTable.Col.SERVER_UUID + " as s_uuid";
|
||||||
|
String sql = "SELECT " +
|
||||||
|
Col.DATE + ", " +
|
||||||
|
Col.MAX_PING + ", " +
|
||||||
|
usersUUIDColumn + ", " +
|
||||||
|
serverUUIDColumn +
|
||||||
|
" FROM " + tableName +
|
||||||
|
" INNER JOIN " + usersTable + " on " + usersIDColumn + "=" + UserInfoTable.Col.USER_ID +
|
||||||
|
" INNER JOIN " + serverTable + " on " + serverIDColumn + "=" + UserInfoTable.Col.SERVER_ID;
|
||||||
|
return query(new QueryAllStatement<Map<UUID, List<Ping>>>(sql, 100000) {
|
||||||
|
@Override
|
||||||
|
public Map<UUID, List<Ping>> processResults(ResultSet set) throws SQLException {
|
||||||
|
Map<UUID, List<Ping>> userPings = new HashMap<>();
|
||||||
|
|
||||||
|
while (set.next()) {
|
||||||
|
UUID uuid = UUID.fromString(set.getString("uuid"));
|
||||||
|
UUID serverUUID = UUID.fromString(set.getString("s_uuid"));
|
||||||
|
long date = set.getLong(Col.DATE.get());
|
||||||
|
int maxPing = set.getInt(Col.MAX_PING.get());
|
||||||
|
|
||||||
|
List<Ping> pings = userPings.getOrDefault(uuid, new ArrayList<>());
|
||||||
|
pings.add(new Ping(date, maxPing, serverUUID));
|
||||||
|
userPings.put(uuid, pings);
|
||||||
|
}
|
||||||
|
|
||||||
|
return userPings;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void insertAllPings(Map<UUID, List<Ping>> userPings) {
|
||||||
|
executeBatch(new ExecStatement(insertStatement) {
|
||||||
|
@Override
|
||||||
|
public void prepare(PreparedStatement statement) throws SQLException {
|
||||||
|
for (Map.Entry<UUID, List<Ping>> entry : userPings.entrySet()) {
|
||||||
|
UUID uuid = entry.getKey();
|
||||||
|
List<Ping> pings = entry.getValue();
|
||||||
|
for (Ping ping : pings) {
|
||||||
|
UUID serverUUID = ping.getServerUUID();
|
||||||
|
long date = ping.getDate();
|
||||||
|
int maxPing = ping.getValue();
|
||||||
|
|
||||||
|
statement.setString(1, uuid.toString());
|
||||||
|
statement.setString(2, serverUUID.toString());
|
||||||
|
statement.setLong(3, date);
|
||||||
|
statement.setInt(4, maxPing);
|
||||||
|
statement.addBatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Col implements Column {
|
||||||
|
ID("id"),
|
||||||
|
USER_ID(UserIDTable.Col.USER_ID.get()),
|
||||||
|
SERVER_ID("server_id"),
|
||||||
|
DATE("date"),
|
||||||
|
MAX_PING("max_ping");
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
Col(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String get() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -84,6 +84,14 @@ public class BatchOperationTable extends Table {
|
|||||||
copyNicknames(toDB);
|
copyNicknames(toDB);
|
||||||
copySessions(toDB);
|
copySessions(toDB);
|
||||||
copyUserInfo(toDB);
|
copyUserInfo(toDB);
|
||||||
|
copyPings(toDB);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyPings(BatchOperationTable toDB) {
|
||||||
|
if (toDB.equals(this)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toDB.db.getPingTable().insertAllPings(db.getPingTable().getAllPings());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void copyCommandUse(BatchOperationTable toDB) {
|
public void copyCommandUse(BatchOperationTable toDB) {
|
||||||
|
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* License is provided in the jar as LICENSE also here:
|
||||||
|
* https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/LICENSE
|
||||||
|
*/
|
||||||
|
package com.djrapitops.plan.system.processing.processors.player;
|
||||||
|
|
||||||
|
import com.djrapitops.plan.data.container.Ping;
|
||||||
|
import com.djrapitops.plan.data.store.objects.DateObj;
|
||||||
|
import com.djrapitops.plan.system.database.databases.Database;
|
||||||
|
import com.djrapitops.plan.system.info.server.ServerInfo;
|
||||||
|
import com.djrapitops.plan.system.processing.CriticalRunnable;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.OptionalInt;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes 60s average of a Ping list.
|
||||||
|
* <p>
|
||||||
|
* Ping list contains 30 values as ping is updated every 2 seconds.
|
||||||
|
*
|
||||||
|
* @author Rsl1122
|
||||||
|
*/
|
||||||
|
public class PingInsertProcessor implements CriticalRunnable {
|
||||||
|
|
||||||
|
private final UUID uuid;
|
||||||
|
private final List<Ping> pingList;
|
||||||
|
|
||||||
|
public PingInsertProcessor(UUID uuid, List<Ping> pingList) {
|
||||||
|
this.uuid = uuid;
|
||||||
|
this.pingList = pingList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
List<Ping> history = pingList;
|
||||||
|
long lastDate = history.get(history.size() - 1).getDate();
|
||||||
|
OptionalInt max = history.stream()
|
||||||
|
.mapToInt(DateObj::getValue)
|
||||||
|
.filter(i -> i != -1)
|
||||||
|
.max();
|
||||||
|
|
||||||
|
if (!max.isPresent()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int maxValue = max.getAsInt();
|
||||||
|
|
||||||
|
Ping ping = new Ping(lastDate, maxValue, ServerInfo.getServerUUID());
|
||||||
|
|
||||||
|
Database.getActive().save().ping(uuid, ping);
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,9 @@ package com.djrapitops.plan.system.tasks;
|
|||||||
import com.djrapitops.plan.Plan;
|
import com.djrapitops.plan.Plan;
|
||||||
import com.djrapitops.plan.system.tasks.server.BukkitTPSCountTimer;
|
import com.djrapitops.plan.system.tasks.server.BukkitTPSCountTimer;
|
||||||
import com.djrapitops.plan.system.tasks.server.PaperTPSCountTimer;
|
import com.djrapitops.plan.system.tasks.server.PaperTPSCountTimer;
|
||||||
|
import com.djrapitops.plan.system.tasks.server.PingCountTimer;
|
||||||
import com.djrapitops.plugin.api.Check;
|
import com.djrapitops.plugin.api.Check;
|
||||||
|
import com.djrapitops.plugin.task.RunnableFactory;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,6 +27,15 @@ public class BukkitTaskSystem extends ServerTaskSystem {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enable() {
|
||||||
|
super.enable();
|
||||||
|
PingCountTimer pingCountTimer = new PingCountTimer();
|
||||||
|
((Plan) plugin).registerListener(pingCountTimer);
|
||||||
|
RunnableFactory.createNew("PingCountTimer", pingCountTimer)
|
||||||
|
.runTaskTimer(20L, PingCountTimer.PING_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disable() {
|
public void disable() {
|
||||||
super.disable();
|
super.disable();
|
||||||
|
@ -0,0 +1,169 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2016-2018
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.djrapitops.plan.system.tasks.server;
|
||||||
|
|
||||||
|
import com.djrapitops.plan.data.container.Ping;
|
||||||
|
import com.djrapitops.plan.system.processing.Processing;
|
||||||
|
import com.djrapitops.plan.system.processing.processors.player.PingInsertProcessor;
|
||||||
|
import com.djrapitops.plan.utilities.java.Reflection;
|
||||||
|
import com.djrapitops.plugin.api.utility.log.Log;
|
||||||
|
import com.djrapitops.plugin.task.AbsRunnable;
|
||||||
|
import com.djrapitops.plugin.task.RunnableFactory;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
import org.bukkit.event.player.PlayerQuitEvent;
|
||||||
|
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.MethodHandles.Lookup;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Task that handles player ping calculation on Bukkit based servers.
|
||||||
|
* <p>
|
||||||
|
* Modified PingManager from LagMonitor plugin.
|
||||||
|
* https://github.com/games647/LagMonitor/blob/master/src/main/java/com/github/games647/lagmonitor/task/PingManager.java
|
||||||
|
*
|
||||||
|
* @author games647
|
||||||
|
*/
|
||||||
|
public class PingCountTimer extends AbsRunnable implements Listener {
|
||||||
|
|
||||||
|
//the server is pinging the client every 40 Ticks (2 sec) - so check it then
|
||||||
|
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/PlayerConnection.java#L178
|
||||||
|
public static final int PING_INTERVAL = 2 * 20;
|
||||||
|
|
||||||
|
private static final boolean pingMethodAvailable;
|
||||||
|
|
||||||
|
private static final MethodHandle pingField;
|
||||||
|
private static final MethodHandle getHandleMethod;
|
||||||
|
|
||||||
|
static {
|
||||||
|
pingMethodAvailable = isPingMethodAvailable();
|
||||||
|
|
||||||
|
MethodHandle localHandle = null;
|
||||||
|
MethodHandle localPing = null;
|
||||||
|
if (!pingMethodAvailable) {
|
||||||
|
Class<?> craftPlayerClass = Reflection.getCraftBukkitClass("entity.CraftPlayer");
|
||||||
|
Class<?> entityPlayer = Reflection.getMinecraftClass("EntityPlayer");
|
||||||
|
|
||||||
|
Lookup lookup = MethodHandles.publicLookup();
|
||||||
|
try {
|
||||||
|
Method getHandleMethod = craftPlayerClass.getDeclaredMethod("getHandle");
|
||||||
|
localHandle = lookup.unreflect(getHandleMethod);
|
||||||
|
|
||||||
|
localPing = lookup.findGetter(entityPlayer, "ping", Integer.TYPE);
|
||||||
|
} catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException reflectiveEx) {
|
||||||
|
Log.toLog(PingCountTimer.class, reflectiveEx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getHandleMethod = localHandle;
|
||||||
|
pingField = localPing;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Map<UUID, List<Ping>> playerHistory = new HashMap<>();
|
||||||
|
|
||||||
|
private static boolean isPingMethodAvailable() {
|
||||||
|
try {
|
||||||
|
//Only available in Paper
|
||||||
|
Player.Spigot.class.getDeclaredMethod("getPing");
|
||||||
|
return true;
|
||||||
|
} catch (NoSuchMethodException noSuchMethodEx) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
List<UUID> loggedOut = new ArrayList<>();
|
||||||
|
long time = System.currentTimeMillis();
|
||||||
|
playerHistory.forEach((uuid, history) -> {
|
||||||
|
Player player = Bukkit.getPlayer(uuid);
|
||||||
|
if (player != null) {
|
||||||
|
int ping = getPing(player);
|
||||||
|
history.add(new Ping(time, ping));
|
||||||
|
if (history.size() >= 30) {
|
||||||
|
Processing.submit(new PingInsertProcessor(uuid, new ArrayList<>(history)));
|
||||||
|
history.clear();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loggedOut.add(uuid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
loggedOut.forEach(playerHistory::remove);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addPlayer(Player player) {
|
||||||
|
playerHistory.put(player.getUniqueId(), new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removePlayer(Player player) {
|
||||||
|
playerHistory.remove(player.getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getPing(Player player) {
|
||||||
|
if (pingMethodAvailable) {
|
||||||
|
return player.spigot().getPing();
|
||||||
|
}
|
||||||
|
|
||||||
|
return getReflectionPing(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getReflectionPing(Player player) {
|
||||||
|
try {
|
||||||
|
Object entityPlayer = getHandleMethod.invoke(player);
|
||||||
|
return (int) pingField.invoke(entityPlayer);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return -1;
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
throw (Error) throwable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerJoin(PlayerJoinEvent joinEvent) {
|
||||||
|
Player player = joinEvent.getPlayer();
|
||||||
|
RunnableFactory.createNew("Add Player to Ping list", new AbsRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (player.isOnline()) {
|
||||||
|
addPlayer(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).runTaskLater(PING_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerQuit(PlayerQuitEvent quitEvent) {
|
||||||
|
removePlayer(quitEvent.getPlayer());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
playerHistory.clear();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,426 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2016-2018
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.djrapitops.plan.utilities.java;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An utility class that simplifies reflection in Bukkit plugins.
|
||||||
|
* <p>
|
||||||
|
* Modified Reflection utility from LagMonitor plugin.
|
||||||
|
* https://github.com/games647/LagMonitor/blob/master/src/main/java/com/github/games647/lagmonitor/traffic/Reflection.java
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public final class Reflection {
|
||||||
|
|
||||||
|
// Deduce the net.minecraft.server.v* package
|
||||||
|
private static final String OBC_PREFIX = Bukkit.getServer().getClass().getPackage().getName();
|
||||||
|
private static final String NMS_PREFIX = OBC_PREFIX.replace("org.bukkit.craftbukkit", "net.minecraft.server");
|
||||||
|
private static final String VERSION = OBC_PREFIX.replace("org.bukkit.craftbukkit", "").replace(".", "");
|
||||||
|
// Variable replacement
|
||||||
|
private static final Pattern MATCH_VARIABLE = Pattern.compile("\\{([^\\}]+)\\}");
|
||||||
|
|
||||||
|
private Reflection() {
|
||||||
|
// Seal class
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a field accessor for a specific field type and name.
|
||||||
|
*
|
||||||
|
* @param target - the target type.
|
||||||
|
* @param name - the name of the field, or NULL to ignore.
|
||||||
|
* @param fieldType - a compatible field type.
|
||||||
|
* @return The field accessor.
|
||||||
|
*/
|
||||||
|
public static <T> FieldAccessor<T> getField(Class<?> target, String name, Class<T> fieldType) {
|
||||||
|
return getField(target, name, fieldType, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a field accessor for a specific field type and name.
|
||||||
|
*
|
||||||
|
* @param className - lookup name of the class, see {@link #getClass(String)}.
|
||||||
|
* @param name - the name of the field, or NULL to ignore.
|
||||||
|
* @param fieldType - a compatible field type.
|
||||||
|
* @return The field accessor.
|
||||||
|
*/
|
||||||
|
public static <T> FieldAccessor<T> getField(String className, String name, Class<T> fieldType) {
|
||||||
|
return getField(getClass(className), name, fieldType, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a field accessor for a specific field type and name.
|
||||||
|
*
|
||||||
|
* @param target - the target type.
|
||||||
|
* @param fieldType - a compatible field type.
|
||||||
|
* @param index - the number of compatible fields to skip.
|
||||||
|
* @return The field accessor.
|
||||||
|
*/
|
||||||
|
public static <T> FieldAccessor<T> getField(Class<?> target, Class<T> fieldType, int index) {
|
||||||
|
return getField(target, null, fieldType, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a field accessor for a specific field type and name.
|
||||||
|
*
|
||||||
|
* @param className - lookup name of the class, see {@link #getClass(String)}.
|
||||||
|
* @param fieldType - a compatible field type.
|
||||||
|
* @param index - the number of compatible fields to skip.
|
||||||
|
* @return The field accessor.
|
||||||
|
*/
|
||||||
|
public static <T> FieldAccessor<T> getField(String className, Class<T> fieldType, int index) {
|
||||||
|
return getField(getClass(className), fieldType, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common method
|
||||||
|
private static <T> FieldAccessor<T> getField(Class<?> target, String name, Class<T> fieldType, int index) {
|
||||||
|
for (final Field field : target.getDeclaredFields()) {
|
||||||
|
if ((name == null || field.getName().equals(name)) && fieldType.isAssignableFrom(field.getType()) && index-- <= 0) {
|
||||||
|
field.setAccessible(true);
|
||||||
|
|
||||||
|
// A function for retrieving a specific field value
|
||||||
|
return new FieldAccessor<T>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public T get(Object target) {
|
||||||
|
try {
|
||||||
|
return (T) field.get(target);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new RuntimeException("Cannot access reflection.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(Object target, Object value) {
|
||||||
|
try {
|
||||||
|
field.set(target, value);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new RuntimeException("Cannot access reflection.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasField(Object target) {
|
||||||
|
// target instanceof DeclaringClass
|
||||||
|
return field.getDeclaringClass().isAssignableFrom(target.getClass());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search in parent classes
|
||||||
|
if (target.getSuperclass() != null) {
|
||||||
|
return getField(target.getSuperclass(), name, fieldType, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Cannot find field with type " + fieldType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for the first publicly and privately defined method of the given name and parameter count.
|
||||||
|
*
|
||||||
|
* @param className - lookup name of the class, see {@link #getClass(String)}.
|
||||||
|
* @param methodName - the method name, or NULL to skip.
|
||||||
|
* @param params - the expected parameters.
|
||||||
|
* @return An object that invokes this specific method.
|
||||||
|
* @throws IllegalStateException If we cannot find this method.
|
||||||
|
*/
|
||||||
|
public static MethodInvoker getMethod(String className, String methodName, Class<?>... params) {
|
||||||
|
return getTypedMethod(getClass(className), methodName, null, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for the first publicly and privately defined method of the given name and parameter count.
|
||||||
|
*
|
||||||
|
* @param clazz - a class to start with.
|
||||||
|
* @param methodName - the method name, or NULL to skip.
|
||||||
|
* @param params - the expected parameters.
|
||||||
|
* @return An object that invokes this specific method.
|
||||||
|
* @throws IllegalStateException If we cannot find this method.
|
||||||
|
*/
|
||||||
|
public static MethodInvoker getMethod(Class<?> clazz, String methodName, Class<?>... params) {
|
||||||
|
return getTypedMethod(clazz, methodName, null, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for the first publicly and privately defined method of the given name and parameter count.
|
||||||
|
*
|
||||||
|
* @param clazz - a class to start with.
|
||||||
|
* @param methodName - the method name, or NULL to skip.
|
||||||
|
* @param returnType - the expected return type, or NULL to ignore.
|
||||||
|
* @param params - the expected parameters.
|
||||||
|
* @return An object that invokes this specific method.
|
||||||
|
* @throws IllegalStateException If we cannot find this method.
|
||||||
|
*/
|
||||||
|
public static MethodInvoker getTypedMethod(Class<?> clazz, String methodName, Class<?> returnType, Class<?>... params) {
|
||||||
|
for (final Method method : clazz.getDeclaredMethods()) {
|
||||||
|
if ((methodName == null || method.getName().equals(methodName)) && (returnType == null) || method.getReturnType().equals(returnType) && Arrays.equals(method.getParameterTypes(), params)) {
|
||||||
|
method.setAccessible(true);
|
||||||
|
|
||||||
|
return (target, arguments) -> {
|
||||||
|
try {
|
||||||
|
return method.invoke(target, arguments);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Cannot invoke method " + method, e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search in every superclass
|
||||||
|
if (clazz.getSuperclass() != null) {
|
||||||
|
return getMethod(clazz.getSuperclass(), methodName, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalStateException(String.format("Unable to find method %s (%s).", methodName, Arrays.asList(params)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for the first publicly and privately defined constructor of the given name and parameter count.
|
||||||
|
*
|
||||||
|
* @param className - lookup name of the class, see {@link #getClass(String)}.
|
||||||
|
* @param params - the expected parameters.
|
||||||
|
* @return An object that invokes this constructor.
|
||||||
|
* @throws IllegalStateException If we cannot find this method.
|
||||||
|
*/
|
||||||
|
public static ConstructorInvoker getConstructor(String className, Class<?>... params) {
|
||||||
|
return getConstructor(getClass(className), params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for the first publicly and privately defined constructor of the given name and parameter count.
|
||||||
|
*
|
||||||
|
* @param clazz - a class to start with.
|
||||||
|
* @param params - the expected parameters.
|
||||||
|
* @return An object that invokes this constructor.
|
||||||
|
* @throws IllegalStateException If we cannot find this method.
|
||||||
|
*/
|
||||||
|
public static ConstructorInvoker getConstructor(Class<?> clazz, Class<?>... params) {
|
||||||
|
for (final Constructor<?> constructor : clazz.getDeclaredConstructors()) {
|
||||||
|
if (Arrays.equals(constructor.getParameterTypes(), params)) {
|
||||||
|
constructor.setAccessible(true);
|
||||||
|
|
||||||
|
return arguments -> {
|
||||||
|
try {
|
||||||
|
return constructor.newInstance(arguments);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Cannot invoke constructor " + constructor, e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalStateException(String.format("Unable to find constructor for %s (%s).", clazz, Arrays.asList(params)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a class from its full name, without knowing its type on compile time.
|
||||||
|
* <p>
|
||||||
|
* This is useful when looking up fields by a NMS or OBC type.
|
||||||
|
* <p>
|
||||||
|
*
|
||||||
|
* @param lookupName - the class name with variables.
|
||||||
|
* @return The class.
|
||||||
|
* @see Object#getClass()
|
||||||
|
*/
|
||||||
|
public static Class<Object> getUntypedClass(String lookupName) {
|
||||||
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
|
Class<Object> clazz = (Class) getClass(lookupName);
|
||||||
|
return clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a class from its full name.
|
||||||
|
* <p>
|
||||||
|
* Strings enclosed with curly brackets - such as {TEXT} - will be replaced according to the following table:
|
||||||
|
* </p>
|
||||||
|
* <table border="1">
|
||||||
|
* <tr>
|
||||||
|
* <th>Variable</th>
|
||||||
|
* <th>Content</th>
|
||||||
|
* </tr>
|
||||||
|
* <tr>
|
||||||
|
* <td>{nms}</td>
|
||||||
|
* <td>Actual package name of net.minecraft.server.VERSION</td>
|
||||||
|
* </tr>
|
||||||
|
* <tr>
|
||||||
|
* <td>{obc}</td>
|
||||||
|
* <td>Actual package name of org.bukkit.craftbukkit.VERSION</td>
|
||||||
|
* </tr>
|
||||||
|
* <tr>
|
||||||
|
* <td>{version}</td>
|
||||||
|
* <td>The current Minecraft package VERSION, if any.</td>
|
||||||
|
* </tr>
|
||||||
|
* </table>
|
||||||
|
*
|
||||||
|
* @param lookupName - the class name with variables.
|
||||||
|
* @return The looked up class.
|
||||||
|
* @throws IllegalArgumentException If a variable or class could not be found.
|
||||||
|
*/
|
||||||
|
public static Class<?> getClass(String lookupName) {
|
||||||
|
return getCanonicalClass(expandVariables(lookupName));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a class in the net.minecraft.server.VERSION.* package.
|
||||||
|
*
|
||||||
|
* @param name - the name of the class, excluding the package.
|
||||||
|
* @throws IllegalArgumentException If the class doesn't exist.
|
||||||
|
*/
|
||||||
|
public static Class<?> getMinecraftClass(String name) {
|
||||||
|
return getCanonicalClass(NMS_PREFIX + '.' + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a class in the org.bukkit.craftbukkit.VERSION.* package.
|
||||||
|
*
|
||||||
|
* @param name - the name of the class, excluding the package.
|
||||||
|
* @throws IllegalArgumentException If the class doesn't exist.
|
||||||
|
*/
|
||||||
|
public static Class<?> getCraftBukkitClass(String name) {
|
||||||
|
return getCanonicalClass(OBC_PREFIX + '.' + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a class by its canonical name.
|
||||||
|
*
|
||||||
|
* @param canonicalName - the canonical name.
|
||||||
|
* @return The class.
|
||||||
|
*/
|
||||||
|
private static Class<?> getCanonicalClass(String canonicalName) {
|
||||||
|
try {
|
||||||
|
return Class.forName(canonicalName);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new IllegalArgumentException("Cannot find " + canonicalName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expand variables such as "{nms}" and "{obc}" to their corresponding packages.
|
||||||
|
*
|
||||||
|
* @param name - the full name of the class.
|
||||||
|
* @return The expanded string.
|
||||||
|
*/
|
||||||
|
private static String expandVariables(String name) {
|
||||||
|
StringBuffer output = new StringBuffer();
|
||||||
|
Matcher matcher = MATCH_VARIABLE.matcher(name);
|
||||||
|
|
||||||
|
while (matcher.find()) {
|
||||||
|
String variable = matcher.group(1);
|
||||||
|
String replacement;
|
||||||
|
|
||||||
|
// Expand all detected variables
|
||||||
|
if ("nms".equalsIgnoreCase(variable)) {
|
||||||
|
replacement = NMS_PREFIX;
|
||||||
|
} else if ("obc".equalsIgnoreCase(variable)) {
|
||||||
|
replacement = OBC_PREFIX;
|
||||||
|
} else if ("version".equalsIgnoreCase(variable)) {
|
||||||
|
replacement = VERSION;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unknown variable: " + variable);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume the expanded variables are all packages, and append a dot
|
||||||
|
if (!replacement.isEmpty() && matcher.end() < name.length() && name.charAt(matcher.end()) != '.') {
|
||||||
|
replacement += ".";
|
||||||
|
}
|
||||||
|
matcher.appendReplacement(output, Matcher.quoteReplacement(replacement));
|
||||||
|
}
|
||||||
|
|
||||||
|
matcher.appendTail(output);
|
||||||
|
return output.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for invoking a specific constructor.
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ConstructorInvoker {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke a constructor for a specific class.
|
||||||
|
*
|
||||||
|
* @param arguments - the arguments to pass to the constructor.
|
||||||
|
* @return The constructed object.
|
||||||
|
*/
|
||||||
|
Object invoke(Object... arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for invoking a specific method.
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface MethodInvoker {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke a method on a specific target object.
|
||||||
|
*
|
||||||
|
* @param target - the target object, or NULL for a static method.
|
||||||
|
* @param arguments - the arguments to pass to the method.
|
||||||
|
* @return The return value, or NULL if is void.
|
||||||
|
*/
|
||||||
|
Object invoke(Object target, Object... arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for retrieving the field content.
|
||||||
|
*
|
||||||
|
* @param <T> - field type.
|
||||||
|
*/
|
||||||
|
public interface FieldAccessor<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the content of a field.
|
||||||
|
*
|
||||||
|
* @param target - the target object, or NULL for a static field.
|
||||||
|
* @return The value of the field.
|
||||||
|
*/
|
||||||
|
T get(Object target);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the content of a field.
|
||||||
|
*
|
||||||
|
* @param target - the target object, or NULL for a static field.
|
||||||
|
* @param value - the new value of the field.
|
||||||
|
*/
|
||||||
|
void set(Object target, Object value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the given object has this field.
|
||||||
|
*
|
||||||
|
* @param target - the object to test.
|
||||||
|
* @return TRUE if it does, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
boolean hasField(Object target);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user