/*
* PermissionsEx - Permissions plugin for Bukkit
* Copyright (C) 2011 t3hk0d3 http://www.tehkode.ru
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package pex.permissions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.server.MinecraftServer;
import pex.permissions.exceptions.RankingException;
/**
*
* @author code
*/
public abstract class PermissionUser extends PermissionEntity {
protected Map<String, List<PermissionGroup>> cachedGroups = new HashMap<String, List<PermissionGroup>>();
protected Map<String, String[]> cachedPermissions = new HashMap<String, String[]>();
protected Map<String, String> cachedPrefix = new HashMap<String, String>();
protected Map<String, String> cachedSuffix = new HashMap<String, String>();
protected HashMap<String, String> cachedAnwsers = new HashMap<String, String>();
protected HashMap<String, String> cachedOptions = new HashMap<String, String>();
public PermissionUser(String playerName, PermissionManager manager) {
super(playerName, manager);
}
@Override
public void initialize() {
super.initialize();
if (manager.getBackend().isCreateUserRecords() && isVirtual()) {
this.setGroups(this.getGroups(null), null);
save();
}
if (isDebug()) {
Logger.getLogger("Minecraft").info("[PermissionsEx] User " + getName() + " initialized");
}
}
/**
* Return non-inherited user prefix. This means if a user don't have has own
* prefix then empty string or null would be returned
*
* @return prefix as string
*/
public String getOwnPrefix() {
return this.getOwnPrefix(null);
}
public abstract String getOwnPrefix(String worldName);
/**
* Return non-inherited suffix prefix. This means if a user don't has own
* suffix then empty string or null would be returned
*
* @return suffix as string
*/
public final String getOwnSuffix() {
return this.getOwnSuffix(null);
}
public abstract String getOwnSuffix(String worldName);
/**
* Return non-inherited permissions of a user in world
*
* @param world
* world's name
* @return String array of owned Permissions
*/
public abstract String[] getOwnPermissions(String world);
@Override
public String getOption(String optionName, String worldName, String defaultValue) {
String cacheIndex = worldName + "|" + optionName;
if (cachedOptions.containsKey(cacheIndex)) {
return cachedOptions.get(cacheIndex);
}
String value = this.getOwnOption(optionName, worldName, null);
if (value != null) {
cachedOptions.put(cacheIndex, value);
return value;
}
if (worldName != null) { // world inheritance
for (String world : manager.getWorldInheritance(worldName)) {
value = this.getOption(optionName, world, null);
if (value != null) {
cachedOptions.put(cacheIndex, value);
return value;
}
}
// Check common space
value = this.getOption(optionName, null, null);
if (value != null) {
cachedOptions.put(cacheIndex, value);
return value;
}
}
// Inheritance
for (PermissionGroup group : this.getGroups(worldName)) {
value = group.getOption(optionName, worldName, null);
if (value != null) {
cachedOptions.put(cacheIndex, value); // put into cache
// inherited value
return value;
}
}
// Nothing found
return defaultValue;
}
/**
* Return non-inherited value of specified option for user in world
*
* @param option
* option string
* @param world
* world's name
* @param defaultValue
* default value
* @return option value or defaultValue if option is not set
*/
public abstract String getOwnOption(String option, String world, String defaultValue);
/**
* Return non-inherited value of specified option in common space (all
* worlds).
*
* @param option
* @return option value or empty string if option is not set
*/
public String getOwnOption(String option) {
return this.getOwnOption(option, null, null);
}
public String getOwnOption(String option, String world) {
return this.getOwnOption(option, world, null);
}
public int getOwnOptionInteger(String optionName, String world, int defaultValue) {
String option = this.getOwnOption(optionName, world, Integer.toString(defaultValue));
try {
return Integer.parseInt(option);
} catch (NumberFormatException e) {
}
return defaultValue;
}
public boolean getOwnOptionBoolean(String optionName, String world, boolean defaultValue) {
String option = this.getOwnOption(optionName, world, Boolean.toString(defaultValue));
if ("false".equalsIgnoreCase(option)) {
return false;
} else if ("true".equalsIgnoreCase(option)) {
return true;
}
return defaultValue;
}
public double getOwnOptionDouble(String optionName, String world, double defaultValue) {
String option = this.getOwnOption(optionName, world, Double.toString(defaultValue));
try {
return Double.parseDouble(option);
} catch (NumberFormatException e) {
}
return defaultValue;
}
protected abstract String[] getGroupsNamesImpl(String worldName);
/**
* Get group for this user, global inheritance only
*
* @return
*/
public PermissionGroup[] getGroups() {
return this.getGroups(null);
}
/**
* Get groups for this user for specified world
*
* @param worldName
* Name of world
* @return PermissionGroup groups
*/
public PermissionGroup[] getGroups(String worldName) {
if (!cachedGroups.containsKey(worldName)) {
cachedGroups.put(worldName, this.getGroups(worldName, manager.getDefaultGroup(worldName)));
}
return cachedGroups.get(worldName).toArray(new PermissionGroup[0]);
}
private List<PermissionGroup> getGroups(String worldName, PermissionGroup fallback) {
List<PermissionGroup> groups = new LinkedList<PermissionGroup>();
for (String groupName : getGroupsNamesImpl(worldName)) {
if (groupName == null || groupName.isEmpty()) {
continue;
}
PermissionGroup group = manager.getGroup(groupName);
if (!checkMembership(group, worldName)) {
continue;
}
if (!groups.contains(group)) {
groups.add(group);
}
}
if (worldName != null) { // also check world-inheritance
// world inheritance
for (String world : manager.getWorldInheritance(worldName)) {
groups.addAll(this.getGroups(world, null));
}
// common groups
groups.addAll(this.getGroups(null, null));
}
if (groups.isEmpty() && fallback != null) {
groups.add(fallback);
}
if (groups.size() > 1) {
Collections.sort(groups);
}
return groups;
}
public Map<String, PermissionGroup[]> getAllGroups() {
Map<String, PermissionGroup[]> allGroups = new HashMap<String, PermissionGroup[]>();
for (String worldName : getWorlds()) {
allGroups.put(worldName, getWorldGroups(worldName));
}
allGroups.put(null, getWorldGroups(null));
return allGroups;
}
protected PermissionGroup[] getWorldGroups(String worldName) {
List<PermissionGroup> groups = new LinkedList<PermissionGroup>();
for (String groupName : getGroupsNamesImpl(worldName)) {
if (groupName == null || groupName.isEmpty()) {
continue;
}
PermissionGroup group = manager.getGroup(groupName);
if (!groups.contains(group)) {
groups.add(group);
}
}
Collections.sort(groups);
return groups.toArray(new PermissionGroup[0]);
}
/**
* Get group names, common space only
*
* @return
*/
public String[] getGroupsNames() {
return this.getGroupsNames(null);
}
/**
* Get group names in specified world
*
* @return String array of user's group names
*/
public String[] getGroupsNames(String worldName) {
List<String> groups = new LinkedList<String>();
for (PermissionGroup group : this.getGroups(worldName)) {
if (group != null) {
groups.add(group.getName());
}
}
return groups.toArray(new String[0]);
}
/**
* Set parent groups for user
*
* @param groups
* array of parent group names
*/
public abstract void setGroups(String[] groups, String worldName);
public void setGroups(String[] groups) {
this.setGroups(groups, null);
}
/**
* Set parent groups for user
*
* @param groups
* array of parent group objects
*/
public void setGroups(PermissionGroup[] parentGroups, String worldName) {
List<String> groups = new LinkedList<String>();
for (PermissionGroup group : parentGroups) {
groups.add(group.getName());
}
this.setGroups(groups.toArray(new String[0]), worldName);
}
public void setGroups(PermissionGroup[] parentGroups) {
this.setGroups(parentGroups, null);
}
/**
* Add user to group
*
* @param groupName
* group's name as String
*/
public void addGroup(String groupName, String worldName) {
if (groupName == null || groupName.isEmpty()) {
return;
}
List<String> groups = new ArrayList<String>(Arrays.asList(getGroupsNamesImpl(worldName)));
if (groups.contains(groupName)) {
return;
}
groups.add(0, groupName); // add group to start of list
this.setGroups(groups.toArray(new String[0]), worldName);
}
public void addGroup(String groupName) {
this.addGroup(groupName, null);
}
/**
* Add user to group
*
* @param group
* as PermissionGroup object
*/
public void addGroup(PermissionGroup group, String worldName) {
if (group == null) {
return;
}
this.addGroup(group.getName(), worldName);
}
public void addGroup(PermissionGroup group) {
this.addGroup(group, null);
}
public void addGroup(String groupName, String worldName, long lifetime) {
this.addGroup(groupName, worldName);
if (lifetime > 0) {
this.setOption("group-" + groupName + "-until", Long.toString(System.currentTimeMillis() / 1000 + lifetime), worldName);
}
}
/**
* Remove user from group
*
* @param groupName
* group's name as String
*/
public void removeGroup(String groupName, String worldName) {
if (groupName == null || groupName.isEmpty()) {
return;
}
List<String> groups = new ArrayList<String>(Arrays.asList(getGroupsNamesImpl(worldName)));
if (!groups.contains(groupName)) {
return;
}
groups.remove(groupName);
this.setGroups(groups.toArray(new String[0]), worldName);
}
public void removeGroup(String groupName) {
this.removeGroup(manager.getGroup(groupName));
}
/**
* Remove user from group
*
* @param group
* group as PermissionGroup object
*/
public void removeGroup(PermissionGroup group, String worldName) {
if (group == null) {
return;
}
this.removeGroup(group.getName(), worldName);
}
public void removeGroup(PermissionGroup group) {
for (String worldName : getWorlds()) {
this.removeGroup(group, worldName);
}
this.removeGroup(group, null);
}
/**
* Check if this user is member of group or one of its descendant groups
* (optionally)
*
* @param group
* group as PermissionGroup object
* @param worldName
* @param checkInheritance
* if true then descendant groups of the given group would be
* checked too
* @return true on success, false otherwise
*/
public boolean inGroup(PermissionGroup group, String worldName, boolean checkInheritance) {
for (PermissionGroup parentGroup : this.getGroups(worldName)) {
if (parentGroup.equals(group)) {
return true;
}
if (checkInheritance && parentGroup.isChildOf(group, worldName, true)) {
return true;
}
}
return false;
}
public boolean inGroup(PermissionGroup group, boolean checkInheritance) {
for (String worldName : getWorlds()) {
if (this.inGroup(group, worldName, checkInheritance)) {
return true;
}
}
return this.inGroup(group, null, checkInheritance);
}
/**
* Check if this user is member of group or one of its descendant groups
* (optionally)
*
* @param groupName
* group's name to check
* @param worldName
* @param checkInheritance
* if true than descendant groups of specified group would be
* checked too
* @return true on success, false otherwise
*/
public boolean inGroup(String groupName, String worldName, boolean checkInheritance) {
return this.inGroup(manager.getGroup(groupName), worldName, checkInheritance);
}
public boolean inGroup(String groupName, boolean checkInheritance) {
return this.inGroup(manager.getGroup(groupName), checkInheritance);
}
/**
* Check if this user is member of group or one of its descendant groups
*
* @param group
* @param worldName
* @return true on success, false otherwise
*/
public boolean inGroup(PermissionGroup group, String worldName) {
return this.inGroup(group, worldName, true);
}
public boolean inGroup(PermissionGroup group) {
return this.inGroup(group, true);
}
/**
* Checks if this user is member of specified group or one of its descendant
* groups
*
* @param group
* group's name
* @return true on success, false otherwise
*/
public boolean inGroup(String groupName, String worldName) {
return this.inGroup(manager.getGroup(groupName), worldName, true);
}
public boolean inGroup(String groupName) {
return this.inGroup(groupName, true);
}
/**
* Promotes user in specified ladder. If user is not member of the ladder
* RankingException will be thrown If promoter is not null and he is member
* of the ladder and his rank is lower then user's RankingException will be
* thrown too. If there is no group to promote the user to RankingException
* would be thrown
*
*
* @param promoter
* null if action is performed from console or by a plugin
* @param ladderName
* Ladder name
* @throws RankingException
*/
public PermissionGroup promote(PermissionUser promoter, String ladderName) throws RankingException {
if (ladderName == null || ladderName.isEmpty()) {
ladderName = "default";
}
int promoterRank = getPromoterRankAndCheck(promoter, ladderName);
int rank = getRank(ladderName);
PermissionGroup sourceGroup = getRankLadders().get(ladderName);
PermissionGroup targetGroup = null;
for (Map.Entry<Integer, PermissionGroup> entry : manager.getRankLadder(ladderName).entrySet()) {
int groupRank = entry.getValue().getRank();
if (groupRank >= rank) { // group have equal or lower than current
// rank
continue;
}
if (groupRank <= promoterRank) { // group have higher rank than
// promoter
continue;
}
if (targetGroup != null && groupRank <= targetGroup.getRank()) { // group
// have
// higher
// rank
// than
// target
// group
continue;
}
targetGroup = entry.getValue();
}
if (targetGroup == null) {
throw new RankingException("User are not promoteable", this, promoter);
}
swapGroups(sourceGroup, targetGroup);
return targetGroup;
}
/**
* Demotes user in specified ladder. If user is not member of the ladder
* RankingException will be thrown If demoter is not null and he is member
* of the ladder and his rank is lower then user's RankingException will be
* thrown too. If there is no group to demote the user to RankingException
* would be thrown
*
* @param promoter
* Specify null if action performed from console or by plugin
* @param ladderName
* @throws RankingException
*/
public PermissionGroup demote(PermissionUser demoter, String ladderName) throws RankingException {
if (ladderName == null || ladderName.isEmpty()) {
ladderName = "default";
}
int promoterRank = getPromoterRankAndCheck(demoter, ladderName);
int rank = getRank(ladderName);
PermissionGroup sourceGroup = getRankLadders().get(ladderName);
PermissionGroup targetGroup = null;
for (Map.Entry<Integer, PermissionGroup> entry : manager.getRankLadder(ladderName).entrySet()) {
int groupRank = entry.getValue().getRank();
if (groupRank <= rank) { // group have equal or higher than current
// rank
continue;
}
if (groupRank <= promoterRank) { // group have higher rank than
// promoter
continue;
}
if (targetGroup != null && groupRank >= targetGroup.getRank()) { // group
// have
// lower
// rank
// than
// target
// group
continue;
}
targetGroup = entry.getValue();
}
if (targetGroup == null) {
throw new RankingException("User are not demoteable", this, demoter);
}
swapGroups(sourceGroup, targetGroup);
return targetGroup;
}
/**
* Check if the user is in the specified ladder
*
* @param ladder
* Ladder name
* @return true on success, false otherwise
*/
public boolean isRanked(String ladder) {
return getRank(ladder) > 0;
}
/**
* Return user rank in specified ladder
*
* @param ladder
* Ladder name
* @return rank as int
*/
public int getRank(String ladder) {
Map<String, PermissionGroup> ladders = getRankLadders();
if (ladders.containsKey(ladder)) {
return ladders.get(ladder).getRank();
}
return 0;
}
/**
* Return user's group in specified ladder
*
* @param ladder
* Ladder name
* @return PermissionGroup object of ranked ladder group
*/
public PermissionGroup getRankLadderGroup(String ladder) {
if (ladder == null || ladder.isEmpty()) {
ladder = "default";
}
return getRankLadders().get(ladder);
}
/**
* Return all ladders the user is participating in
*
* @return Map, key - name of ladder, group - corresponding group of that
* ladder
*/
public Map<String, PermissionGroup> getRankLadders() {
Map<String, PermissionGroup> ladders = new HashMap<String, PermissionGroup>();
for (PermissionGroup group : this.getGroups()) {
if (!group.isRanked()) {
continue;
}
ladders.put(group.getRankLadder(), group);
}
return ladders;
}
@Override
public String[] getPermissions(String worldName) {
if (!cachedPermissions.containsKey(worldName)) {
List<String> permissions = new LinkedList<String>();
getInheritedPermissions(worldName, permissions, true, false);
cachedPermissions.put(worldName, permissions.toArray(new String[0]));
}
return cachedPermissions.get(worldName);
}
@Override
public void addPermission(String permission, String worldName) {
List<String> permissions = new LinkedList<String>(Arrays.asList(getOwnPermissions(worldName)));
if (permissions.contains(permission)) { // remove old permission
permissions.remove(permission);
}
// add permission on the top of list
permissions.add(0, permission);
this.setPermissions(permissions.toArray(new String[0]), worldName);
}
@Override
public void removePermission(String permission, String worldName) {
List<String> permissions = new LinkedList<String>(Arrays.asList(getOwnPermissions(worldName)));
permissions.remove(permission);
this.setPermissions(permissions.toArray(new String[0]), worldName);
}
protected void getInheritedPermissions(String worldName, List<String> permissions, boolean groupInheritance, boolean worldInheritance) {
permissions.addAll(Arrays.asList(getTimedPermissions(worldName)));
permissions.addAll(Arrays.asList(getOwnPermissions(worldName)));
if (worldName != null) {
// World inheritance
for (String parentWorld : manager.getWorldInheritance(worldName)) {
getInheritedPermissions(parentWorld, permissions, false, true);
}
// Common permissions
if (!worldInheritance) { // skip common world permissions if we are
// inside world-inheritance tree
getInheritedPermissions(null, permissions, false, true);
}
}
// Group inhertance
if (groupInheritance) {
for (PermissionGroup parentGroup : this.getGroups(worldName)) {
parentGroup.getInheritedPermissions(worldName, permissions, true, false, true);
}
}
}
@Override
public void addTimedPermission(String permission, String world, int lifeTime) {
super.addTimedPermission(permission, world, lifeTime);
clearCache();
}
@Override
public void removeTimedPermission(String permission, String world) {
super.removeTimedPermission(permission, world);
clearCache();
}
protected int getPromoterRankAndCheck(PermissionUser promoter, String ladderName) throws RankingException {
if (!isRanked(ladderName)) { // not ranked
throw new RankingException("User are not in this ladder", this, promoter);
}
int rank = getRank(ladderName);
int promoterRank = 0;
if (promoter != null && promoter.isRanked(ladderName)) {
promoterRank = promoter.getRank(ladderName);
if (promoterRank >= rank) {
throw new RankingException("Promoter don't have high enough rank to change " + getName() + "'s rank", this, promoter);
}
}
return promoterRank;
}
protected void swapGroups(PermissionGroup src, PermissionGroup dst) {
List<PermissionGroup> groups = new ArrayList<PermissionGroup>(Arrays.asList(this.getGroups()));
groups.remove(src);
groups.add(dst);
this.setGroups(groups.toArray(new PermissionGroup[0]));
}
@Override
public String getPrefix(String worldName) {
// @TODO This method should be refactored
if (!cachedPrefix.containsKey(worldName)) {
String localPrefix = this.getOwnPrefix(worldName);
if (worldName != null && (localPrefix == null || localPrefix.isEmpty())) {
// World-inheritance
for (String parentWorld : manager.getWorldInheritance(worldName)) {
String prefix = this.getOwnPrefix(parentWorld);
if (prefix != null && !prefix.isEmpty()) {
localPrefix = prefix;
break;
}
}
// Common space
if (localPrefix == null || localPrefix.isEmpty()) {
localPrefix = this.getOwnPrefix(null);
}
}
if (localPrefix == null || localPrefix.isEmpty()) {
for (PermissionGroup group : this.getGroups(worldName)) {
localPrefix = group.getPrefix(worldName);
if (localPrefix != null && !localPrefix.isEmpty()) {
break;
}
}
}
if (localPrefix == null) { // just for NPE safety
localPrefix = "";
}
localPrefix = localPrefix.replaceAll("\\$", "§");
cachedPrefix.put(worldName, localPrefix);
}
return cachedPrefix.get(worldName);
}
@Override
public boolean has(String permission) {
EntityPlayer player = MinecraftServer.getServer().getConfigurationManager().getPlayerForUsername(getName());
if (player != null) {
return this.has(permission, String.valueOf(player.dimension));
}
return super.has(permission);
}
@Override
public String getSuffix(String worldName) {
// @TODO This method should be refactored
if (!cachedSuffix.containsKey(worldName)) {
String localSuffix = this.getOwnSuffix(worldName);
if (worldName != null && (localSuffix == null || localSuffix.isEmpty())) {
// World-inheritance
for (String parentWorld : manager.getWorldInheritance(worldName)) {
String suffix = this.getOwnSuffix(parentWorld);
if (suffix != null && !suffix.isEmpty()) {
localSuffix = suffix;
break;
}
}
// Common space
if (localSuffix == null || localSuffix.isEmpty()) {
localSuffix = this.getOwnSuffix(null);
}
}
if (localSuffix == null || localSuffix.isEmpty()) {
for (PermissionGroup group : this.getGroups(worldName)) {
localSuffix = group.getSuffix(worldName);
if (localSuffix != null && !localSuffix.isEmpty()) {
break;
}
}
}
if (localSuffix == null) { // just for NPE safety
localSuffix = "";
}
localSuffix = localSuffix.replaceAll("\\$", "§");
cachedSuffix.put(worldName, localSuffix);
}
return cachedSuffix.get(worldName);
}
@Override
public String getMatchingExpression(String permission, String world) {
String cacheId = world + ":" + permission;
if (!cachedAnwsers.containsKey(cacheId)) {
cachedAnwsers.put(cacheId, super.getMatchingExpression(permission, world));
}
return cachedAnwsers.get(cacheId);
}
protected void clearCache() {
cachedPrefix.clear();
cachedSuffix.clear();
cachedGroups.clear();
cachedPermissions.clear();
cachedAnwsers.clear();
cachedOptions.clear();
}
@Override
public void setPrefix(String prefix, String worldName) {
clearCache();
}
@Override
public void setSuffix(String postfix, String worldName) {
clearCache();
}
@Override
public void remove() {
clearCache();
}
@Override
public void save() {
clearCache();
}
@Override
public boolean explainExpression(String expression) {
if (expression == null && manager.allowOps) {
EntityPlayer player = MinecraftServer.getServer().getConfigurationManager().getPlayerForUsername(getName());
if (player != null && MinecraftServer.getServer().getConfigurationManager().getOps().contains(player.username.toLowerCase())) {
return true;
}
}
return super.explainExpression(expression);
}
protected boolean checkMembership(PermissionGroup group, String worldName) {
int groupLifetime = getOwnOptionInteger("group-" + group.getName() + "-until", worldName, 0);
if (groupLifetime > 0 && groupLifetime < System.currentTimeMillis() / 1000) { // check
// for
// expiration
this.setOption("group-" + group.getName() + "-until", null, worldName); // remove
// option
this.removeGroup(group, worldName); // remove membership
// @TODO Make notification of player about expired memebership
return false;
}
return true;
}
}