This commit is contained in:
MobiusDev
2017-08-16 17:59:12 +00:00
parent d5d1c8c773
commit 9f1daf467a
21308 changed files with 3766390 additions and 0 deletions

View File

@@ -0,0 +1,195 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jmobius.gameserver.enums.ChatType;
import com.l2jmobius.gameserver.model.L2World;
import com.l2jmobius.gameserver.model.actor.L2Character;
import com.l2jmobius.gameserver.model.actor.L2Summon;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.network.serverpackets.CharInfo;
import com.l2jmobius.gameserver.network.serverpackets.CreatureSay;
import com.l2jmobius.gameserver.network.serverpackets.ExShowScreenMessage;
import com.l2jmobius.gameserver.network.serverpackets.IClientOutgoingPacket;
import com.l2jmobius.gameserver.network.serverpackets.RelationChanged;
/**
* This class ...
* @version $Revision: 1.2 $ $Date: 2004/06/27 08:12:59 $
*/
public final class Broadcast
{
private static Logger _log = Logger.getLogger(Broadcast.class.getName());
/**
* Send a packet to all L2PcInstance in the _KnownPlayers of the L2Character that have the Character targeted.<BR>
* <B><U> Concept</U> :</B><BR>
* L2PcInstance in the detection area of the L2Character are identified in <B>_knownPlayers</B>.<BR>
* In order to inform other players of state modification on the L2Character, server just need to go through _knownPlayers to send Server->Client Packet<BR>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : This method DOESN'T SEND Server->Client packet to this L2Character (to do this use method toSelfAndKnownPlayers)</B></FONT><BR>
* @param character
* @param mov
*/
public static void toPlayersTargettingMyself(L2Character character, IClientOutgoingPacket mov)
{
L2World.getInstance().forEachVisibleObject(character, L2PcInstance.class, player ->
{
if (player.getTarget() == character)
{
player.sendPacket(mov);
}
});
}
/**
* Send a packet to all L2PcInstance in the _KnownPlayers of the L2Character.<BR>
* <B><U> Concept</U> :</B><BR>
* L2PcInstance in the detection area of the L2Character are identified in <B>_knownPlayers</B>.<BR>
* In order to inform other players of state modification on the L2Character, server just need to go through _knownPlayers to send Server->Client Packet<BR>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : This method DOESN'T SEND Server->Client packet to this L2Character (to do this use method toSelfAndKnownPlayers)</B></FONT><BR>
* @param character
* @param mov
*/
public static void toKnownPlayers(L2Character character, IClientOutgoingPacket mov)
{
L2World.getInstance().forEachVisibleObject(character, L2PcInstance.class, player ->
{
try
{
player.sendPacket(mov);
if ((mov instanceof CharInfo) && (character.isPlayer()))
{
final int relation = ((L2PcInstance) character).getRelation(player);
final Integer oldrelation = character.getKnownRelations().get(player.getObjectId());
if ((oldrelation != null) && (oldrelation != relation))
{
final RelationChanged rc = new RelationChanged();
rc.addRelation((L2PcInstance) character, relation, character.isAutoAttackable(player));
if (character.hasSummon())
{
final L2Summon pet = character.getPet();
if (pet != null)
{
rc.addRelation(pet, relation, character.isAutoAttackable(player));
}
if (character.hasServitors())
{
character.getServitors().values().forEach(s -> rc.addRelation(s, relation, character.isAutoAttackable(player)));
}
}
player.sendPacket(rc);
character.getKnownRelations().put(player.getObjectId(), relation);
}
}
}
catch (NullPointerException e)
{
_log.log(Level.WARNING, e.getMessage(), e);
}
});
}
/**
* Send a packet to all L2PcInstance in the _KnownPlayers (in the specified radius) of the L2Character.<BR>
* <B><U> Concept</U> :</B><BR>
* L2PcInstance in the detection area of the L2Character are identified in <B>_knownPlayers</B>.<BR>
* In order to inform other players of state modification on the L2Character, server just needs to go through _knownPlayers to send Server->Client Packet and check the distance between the targets.<BR>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : This method DOESN'T SEND Server->Client packet to this L2Character (to do this use method toSelfAndKnownPlayers)</B></FONT><BR>
* @param character
* @param mov
* @param radius
*/
public static void toKnownPlayersInRadius(L2Character character, IClientOutgoingPacket mov, int radius)
{
if (radius < 0)
{
radius = 1500;
}
L2World.getInstance().forEachVisibleObjectInRange(character, L2PcInstance.class, radius, mov::sendTo);
}
/**
* Send a packet to all L2PcInstance in the _KnownPlayers of the L2Character and to the specified character.<BR>
* <B><U> Concept</U> :</B><BR>
* L2PcInstance in the detection area of the L2Character are identified in <B>_knownPlayers</B>.<BR>
* In order to inform other players of state modification on the L2Character, server just need to go through _knownPlayers to send Server->Client Packet<BR>
* @param character
* @param mov
*/
public static void toSelfAndKnownPlayers(L2Character character, IClientOutgoingPacket mov)
{
if (character instanceof L2PcInstance)
{
character.sendPacket(mov);
}
toKnownPlayers(character, mov);
}
// To improve performance we are comparing values of radius^2 instead of calculating sqrt all the time
public static void toSelfAndKnownPlayersInRadius(L2Character character, IClientOutgoingPacket mov, int radius)
{
if (radius < 0)
{
radius = 600;
}
if (character instanceof L2PcInstance)
{
character.sendPacket(mov);
}
L2World.getInstance().forEachVisibleObjectInRange(character, L2PcInstance.class, radius, mov::sendTo);
}
/**
* Send a packet to all L2PcInstance present in the world.<BR>
* <B><U> Concept</U> :</B><BR>
* In order to inform other players of state modification on the L2Character, server just need to go through _allPlayers to send Server->Client Packet<BR>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : This method DOESN'T SEND Server->Client packet to this L2Character (to do this use method toSelfAndKnownPlayers)</B></FONT><BR>
* @param packet
*/
public static void toAllOnlinePlayers(IClientOutgoingPacket packet)
{
for (L2PcInstance player : L2World.getInstance().getPlayers())
{
if (player.isOnline())
{
player.sendPacket(packet);
}
}
}
public static void toAllOnlinePlayers(String text)
{
toAllOnlinePlayers(text, false);
}
public static void toAllOnlinePlayers(String text, boolean isCritical)
{
toAllOnlinePlayers(new CreatureSay(0, isCritical ? ChatType.CRITICAL_ANNOUNCE : ChatType.ANNOUNCEMENT, "", text));
}
public static void toAllOnlinePlayersOnScreen(String text)
{
toAllOnlinePlayers(new ExShowScreenMessage(text, 10000));
}
}

View File

@@ -0,0 +1,117 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
/**
* @author UnAfraid
*/
public class BypassBuilder
{
private final String _bypass;
private final List<BypassParam> _params = new ArrayList<>();
public BypassBuilder(String bypass)
{
_bypass = bypass;
}
public void addParam(BypassParam param)
{
Objects.requireNonNull(param, "param cannot be null!");
_params.add(param);
}
public void addParam(String name, String separator, Object value)
{
Objects.requireNonNull(name, "name cannot be null!");
addParam(new BypassParam(name, Optional.ofNullable(separator), Optional.ofNullable(value)));
}
public void addParam(String name, Object value)
{
addParam(name, "=", value);
}
public void addParam(String name)
{
addParam(name, null, null);
}
public StringBuilder toStringBuilder()
{
final StringBuilder sb = new StringBuilder(_bypass);
for (BypassParam param : _params)
{
sb.append(" ").append(param.getName().trim());
if (param.getSeparator().isPresent() && param.getValue().isPresent())
{
sb.append(param.getSeparator().get().trim());
final Object value = param.getValue().get();
if (value instanceof String)
{
sb.append('"');
}
sb.append(String.valueOf(value).trim());
if (value instanceof String)
{
sb.append('"');
}
}
}
return sb;
}
@Override
public String toString()
{
return toStringBuilder().toString();
}
private static class BypassParam
{
private final String _name;
private final Optional<String> _separator;
private final Optional<Object> _value;
public BypassParam(String name, Optional<String> separator, Optional<Object> value)
{
_name = name;
_separator = separator;
_value = value;
}
public String getName()
{
return _name;
}
public Optional<String> getSeparator()
{
return _separator;
}
public Optional<Object> getValue()
{
return _value;
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util;
import java.util.LinkedHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.l2jmobius.gameserver.model.StatsSet;
/**
* @author UnAfraid
*/
public class BypassParser extends StatsSet
{
private static final String ALLOWED_CHARS = "a-zA-Z0-9-_`!@#%^&*()\\[\\]|\\\\/";
private static final Pattern PATTERN = Pattern.compile(String.format("([%s]*)=('([%s ]*)'|[%s]*)", ALLOWED_CHARS, ALLOWED_CHARS, ALLOWED_CHARS));
public BypassParser(String bypass)
{
super(LinkedHashMap::new);
process(bypass);
}
private void process(String bypass)
{
final Matcher regexMatcher = PATTERN.matcher(bypass);
while (regexMatcher.find())
{
final String name = regexMatcher.group(1);
final String escapedValue = regexMatcher.group(2).trim();
final String unescapedValue = regexMatcher.group(3);
set(name, unescapedValue != null ? unescapedValue.trim() : escapedValue);
}
}
}

View File

@@ -0,0 +1,155 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util;
/**
* @author HorridoJoho
* @param <E> The enum type
*/
public final class EnumIntBitmask<E extends Enum<E>> implements Cloneable
{
public static <E extends Enum<E>> int getAllBitmask(Class<E> enumClass)
{
int allBitmask = 0;
final E[] values = enumClass.getEnumConstants();
if (values.length > 32)
{
throw new IllegalArgumentException("Enum too big for an integer bitmask.");
}
for (E value : values)
{
allBitmask |= 1 << value.ordinal();
}
return allBitmask;
}
private final Class<E> _enumClass;
private int _bitmask;
public EnumIntBitmask(Class<E> enumClass, boolean all)
{
_enumClass = enumClass;
final E[] values = _enumClass.getEnumConstants();
if (values.length > 32)
{
throw new IllegalArgumentException("Enum too big for an integer bitmask.");
}
if (all)
{
setAll();
}
else
{
clear();
}
}
public EnumIntBitmask(Class<E> enumClass, int bitmask)
{
_enumClass = enumClass;
_bitmask = bitmask;
}
public void setAll()
{
set(_enumClass.getEnumConstants());
}
public void clear()
{
_bitmask = 0;
}
@SafeVarargs
public final void set(E... many)
{
clear();
for (E one : many)
{
_bitmask |= 1 << one.ordinal();
}
}
@SafeVarargs
public final void set(E first, E... more)
{
clear();
add(first, more);
}
public void setBitmask(int bitmask)
{
_bitmask = bitmask;
}
@SafeVarargs
public final void add(E first, E... more)
{
_bitmask |= 1 << first.ordinal();
if (more != null)
{
for (E one : more)
{
_bitmask |= 1 << one.ordinal();
}
}
}
@SafeVarargs
public final void remove(E first, E... more)
{
_bitmask &= ~(1 << first.ordinal());
if (more != null)
{
for (E one : more)
{
_bitmask &= ~(1 << one.ordinal());
}
}
}
@SafeVarargs
public final boolean has(E first, E... more)
{
if ((_bitmask & (1 << first.ordinal())) == 0)
{
return false;
}
for (E one : more)
{
if ((_bitmask & (1 << one.ordinal())) == 0)
{
return false;
}
}
return true;
}
@Override
public EnumIntBitmask<E> clone()
{
return new EnumIntBitmask<>(_enumClass, _bitmask);
}
public int getBitmask()
{
return _bitmask;
}
}

View File

@@ -0,0 +1,327 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jmobius.Config;
import com.l2jmobius.commons.database.DatabaseFactory;
import com.l2jmobius.gameserver.ThreadPoolManager;
import com.l2jmobius.gameserver.data.xml.impl.NpcData;
import com.l2jmobius.gameserver.data.xml.impl.PetDataTable;
import com.l2jmobius.gameserver.model.L2PetData;
import com.l2jmobius.gameserver.model.L2World;
import com.l2jmobius.gameserver.model.actor.L2Npc;
import com.l2jmobius.gameserver.model.actor.L2Summon;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2PetInstance;
import com.l2jmobius.gameserver.model.actor.templates.L2NpcTemplate;
import com.l2jmobius.gameserver.model.items.instance.L2ItemInstance;
import com.l2jmobius.gameserver.network.SystemMessageId;
import com.l2jmobius.gameserver.network.serverpackets.InventoryUpdate;
import com.l2jmobius.gameserver.network.serverpackets.MagicSkillLaunched;
import com.l2jmobius.gameserver.network.serverpackets.MagicSkillUse;
import com.l2jmobius.gameserver.network.serverpackets.SystemMessage;
/**
* UnAfraid: TODO: MOVE IT TO DP AI
*/
public final class Evolve
{
protected static final Logger _log = Logger.getLogger(Evolve.class.getName());
public static boolean doEvolve(L2PcInstance player, L2Npc npc, int itemIdtake, int itemIdgive, int petminlvl)
{
if ((itemIdtake == 0) || (itemIdgive == 0) || (petminlvl == 0))
{
return false;
}
final L2Summon pet = player.getPet();
if (pet == null)
{
return false;
}
final L2PetInstance currentPet = (L2PetInstance) pet;
if (currentPet.isAlikeDead())
{
Util.handleIllegalPlayerAction(player, "Player " + player.getName() + " tried to use death pet exploit!", Config.DEFAULT_PUNISH);
return false;
}
L2ItemInstance item = null;
long petexp = currentPet.getStat().getExp();
final String oldname = currentPet.getName();
final int oldX = currentPet.getX();
final int oldY = currentPet.getY();
final int oldZ = currentPet.getZ();
final L2PetData oldData = PetDataTable.getInstance().getPetDataByItemId(itemIdtake);
if (oldData == null)
{
return false;
}
final int oldnpcID = oldData.getNpcId();
if ((currentPet.getStat().getLevel() < petminlvl) || (currentPet.getId() != oldnpcID))
{
return false;
}
final L2PetData petData = PetDataTable.getInstance().getPetDataByItemId(itemIdgive);
if (petData == null)
{
return false;
}
final int npcID = petData.getNpcId();
if (npcID == 0)
{
return false;
}
final L2NpcTemplate npcTemplate = NpcData.getInstance().getTemplate(npcID);
currentPet.unSummon(player);
// deleting old pet item
currentPet.destroyControlItem(player, true);
item = player.getInventory().addItem("Evolve", itemIdgive, 1, player, npc);
// Summoning new pet
final L2PetInstance petSummon = L2PetInstance.spawnPet(npcTemplate, player, item);
if (petSummon == null)
{
return false;
}
// Fix for non-linear baby pet exp
final long _minimumexp = petSummon.getStat().getExpForLevel(petminlvl);
if (petexp < _minimumexp)
{
petexp = _minimumexp;
}
petSummon.getStat().addExp(petexp);
petSummon.setCurrentHp(petSummon.getMaxHp());
petSummon.setCurrentMp(petSummon.getMaxMp());
petSummon.setCurrentFed(petSummon.getMaxFed());
petSummon.setTitle(player.getName());
petSummon.setName(oldname);
petSummon.setRunning();
petSummon.storeMe();
player.setPet(petSummon);
player.sendPacket(new MagicSkillUse(npc, 2046, 1, 1000, 600000));
player.sendPacket(SystemMessageId.SUMMONING_YOUR_PET);
// L2World.getInstance().storeObject(petSummon);
petSummon.spawnMe(oldX, oldY, oldZ);
petSummon.startFeed();
item.setEnchantLevel(petSummon.getLevel());
ThreadPoolManager.getInstance().scheduleGeneral(new EvolveFinalizer(player, petSummon), 900);
if (petSummon.getCurrentFed() <= 0)
{
ThreadPoolManager.getInstance().scheduleGeneral(new EvolveFeedWait(player, petSummon), 60000);
}
else
{
petSummon.startFeed();
}
return true;
}
public static boolean doRestore(L2PcInstance player, L2Npc npc, int itemIdtake, int itemIdgive, int petminlvl)
{
if ((itemIdtake == 0) || (itemIdgive == 0) || (petminlvl == 0))
{
return false;
}
final L2ItemInstance item = player.getInventory().getItemByItemId(itemIdtake);
if (item == null)
{
return false;
}
int oldpetlvl = item.getEnchantLevel();
if (oldpetlvl < petminlvl)
{
oldpetlvl = petminlvl;
}
final L2PetData oldData = PetDataTable.getInstance().getPetDataByItemId(itemIdtake);
if (oldData == null)
{
return false;
}
final L2PetData petData = PetDataTable.getInstance().getPetDataByItemId(itemIdgive);
if (petData == null)
{
return false;
}
final int npcId = petData.getNpcId();
if (npcId == 0)
{
return false;
}
final L2NpcTemplate npcTemplate = NpcData.getInstance().getTemplate(npcId);
// deleting old pet item
final L2ItemInstance removedItem = player.getInventory().destroyItem("PetRestore", item, player, npc);
final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.S1_DISAPPEARED);
sm.addItemName(removedItem);
player.sendPacket(sm);
// Give new pet item
final L2ItemInstance addedItem = player.getInventory().addItem("PetRestore", itemIdgive, 1, player, npc);
// Summoning new pet
final L2PetInstance petSummon = L2PetInstance.spawnPet(npcTemplate, player, addedItem);
if (petSummon == null)
{
return false;
}
final long _maxexp = petSummon.getStat().getExpForLevel(oldpetlvl);
petSummon.getStat().addExp(_maxexp);
petSummon.setCurrentHp(petSummon.getMaxHp());
petSummon.setCurrentMp(petSummon.getMaxMp());
petSummon.setCurrentFed(petSummon.getMaxFed());
petSummon.setTitle(player.getName());
petSummon.setRunning();
petSummon.storeMe();
player.setPet(petSummon);
player.sendPacket(new MagicSkillUse(npc, 2046, 1, 1000, 600000));
player.sendPacket(SystemMessageId.SUMMONING_YOUR_PET);
// L2World.getInstance().storeObject(petSummon);
petSummon.spawnMe(player.getX(), player.getY(), player.getZ());
petSummon.startFeed();
addedItem.setEnchantLevel(petSummon.getLevel());
// Inventory update
final InventoryUpdate iu = new InventoryUpdate();
iu.addRemovedItem(removedItem);
player.sendInventoryUpdate(iu);
player.broadcastUserInfo();
final L2World world = L2World.getInstance();
world.removeObject(removedItem);
ThreadPoolManager.getInstance().scheduleGeneral(new EvolveFinalizer(player, petSummon), 900);
if (petSummon.getCurrentFed() <= 0)
{
ThreadPoolManager.getInstance().scheduleGeneral(new EvolveFeedWait(player, petSummon), 60000);
}
else
{
petSummon.startFeed();
}
// pet control item no longer exists, delete the pet from the db
try (Connection con = DatabaseFactory.getInstance().getConnection();
PreparedStatement ps = con.prepareStatement("DELETE FROM pets WHERE item_obj_id=?"))
{
ps.setInt(1, removedItem.getObjectId());
ps.execute();
}
catch (Exception e)
{
}
return true;
}
static final class EvolveFeedWait implements Runnable
{
private final L2PcInstance _activeChar;
private final L2PetInstance _petSummon;
EvolveFeedWait(L2PcInstance activeChar, L2PetInstance petSummon)
{
_activeChar = activeChar;
_petSummon = petSummon;
}
@Override
public void run()
{
try
{
if (_petSummon.getCurrentFed() <= 0)
{
_petSummon.unSummon(_activeChar);
}
else
{
_petSummon.startFeed();
}
}
catch (Exception e)
{
_log.log(Level.WARNING, "", e);
}
}
}
static final class EvolveFinalizer implements Runnable
{
private final L2PcInstance _activeChar;
private final L2PetInstance _petSummon;
EvolveFinalizer(L2PcInstance activeChar, L2PetInstance petSummon)
{
_activeChar = activeChar;
_petSummon = petSummon;
}
@Override
public void run()
{
try
{
_activeChar.sendPacket(new MagicSkillLaunched(_activeChar, 2046, 1));
_petSummon.setFollowStatus(true);
_petSummon.setShowSummonAnimation(false);
}
catch (Throwable e)
{
_log.log(Level.WARNING, "", e);
}
}
}
}

View File

@@ -0,0 +1,242 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jmobius.gameserver.GameTimeController;
import com.l2jmobius.gameserver.instancemanager.PunishmentManager;
import com.l2jmobius.gameserver.model.PcCondOverride;
import com.l2jmobius.gameserver.model.punishment.PunishmentAffect;
import com.l2jmobius.gameserver.model.punishment.PunishmentTask;
import com.l2jmobius.gameserver.model.punishment.PunishmentType;
import com.l2jmobius.gameserver.network.ConnectionState;
import com.l2jmobius.gameserver.network.L2GameClient;
/**
* Flood protector implementation.
* @author fordfrog
*/
public final class FloodProtectorAction
{
/**
* Logger
*/
private static final Logger _log = Logger.getLogger(FloodProtectorAction.class.getName());
/**
* Client for this instance of flood protector.
*/
private final L2GameClient _client;
/**
* Configuration of this instance of flood protector.
*/
private final FloodProtectorConfig _config;
/**
* Next game tick when new request is allowed.
*/
private volatile int _nextGameTick = GameTimeController.getInstance().getGameTicks();
/**
* Request counter.
*/
private final AtomicInteger _count = new AtomicInteger(0);
/**
* Flag determining whether exceeding request has been logged.
*/
private boolean _logged;
/**
* Flag determining whether punishment application is in progress so that we do not apply punisment multiple times (flooding).
*/
private volatile boolean _punishmentInProgress;
/**
* Creates new instance of FloodProtectorAction.
* @param client the game client for which flood protection is being created
* @param config flood protector configuration
*/
public FloodProtectorAction(L2GameClient client, FloodProtectorConfig config)
{
super();
_client = client;
_config = config;
}
/**
* Checks whether the request is flood protected or not.
* @param command command issued or short command description
* @return true if action is allowed, otherwise false
*/
public boolean tryPerformAction(String command)
{
final int curTick = GameTimeController.getInstance().getGameTicks();
if ((_client.getActiveChar() != null) && _client.getActiveChar().canOverrideCond(PcCondOverride.FLOOD_CONDITIONS))
{
return true;
}
if ((curTick < _nextGameTick) || _punishmentInProgress)
{
if (_config.LOG_FLOODING && !_logged && (_log.getLevel() == Level.WARNING))
{
log(" called command ", command, " ~", String.valueOf((_config.FLOOD_PROTECTION_INTERVAL - (_nextGameTick - curTick)) * GameTimeController.MILLIS_IN_TICK), " ms after previous command");
_logged = true;
}
_count.incrementAndGet();
if (!_punishmentInProgress && (_config.PUNISHMENT_LIMIT > 0) && (_count.get() >= _config.PUNISHMENT_LIMIT) && (_config.PUNISHMENT_TYPE != null))
{
_punishmentInProgress = true;
if ("kick".equals(_config.PUNISHMENT_TYPE))
{
kickPlayer();
}
else if ("ban".equals(_config.PUNISHMENT_TYPE))
{
banAccount();
}
else if ("jail".equals(_config.PUNISHMENT_TYPE))
{
jailChar();
}
_punishmentInProgress = false;
}
return false;
}
if (_count.get() > 0)
{
if (_config.LOG_FLOODING && (_log.getLevel() == Level.WARNING))
{
log(" issued ", String.valueOf(_count), " extra requests within ~", String.valueOf(_config.FLOOD_PROTECTION_INTERVAL * GameTimeController.MILLIS_IN_TICK), " ms");
}
}
_nextGameTick = curTick + _config.FLOOD_PROTECTION_INTERVAL;
_logged = false;
_count.set(0);
return true;
}
/**
* Kick player from game (close network connection).
*/
private void kickPlayer()
{
if (_client.getActiveChar() != null)
{
_client.getActiveChar().logout(false);
}
else
{
_client.closeNow();
}
if (_log.getLevel() == Level.WARNING)
{
log("kicked for flooding");
}
}
/**
* Bans char account and logs out the char.
*/
private void banAccount()
{
PunishmentManager.getInstance().startPunishment(new PunishmentTask(_client.getAccountName(), PunishmentAffect.ACCOUNT, PunishmentType.BAN, System.currentTimeMillis() + _config.PUNISHMENT_TIME, "", getClass().getSimpleName()));
if (_log.getLevel() == Level.WARNING)
{
log(" banned for flooding ", _config.PUNISHMENT_TIME <= 0 ? "forever" : "for " + (_config.PUNISHMENT_TIME / 60000) + " mins");
}
}
/**
* Jails char.
*/
private void jailChar()
{
if (_client.getActiveChar() != null)
{
final int charId = _client.getActiveChar().getObjectId();
if (charId > 0)
{
PunishmentManager.getInstance().startPunishment(new PunishmentTask(charId, PunishmentAffect.CHARACTER, PunishmentType.JAIL, System.currentTimeMillis() + _config.PUNISHMENT_TIME, "", getClass().getSimpleName()));
}
if (_log.getLevel() == Level.WARNING)
{
log(" jailed for flooding ", _config.PUNISHMENT_TIME <= 0 ? "forever" : "for " + (_config.PUNISHMENT_TIME / 60000) + " mins");
}
}
}
private void log(String... lines)
{
final StringBuilder output = new StringBuilder(100);
output.append(_config.FLOOD_PROTECTOR_TYPE);
output.append(": ");
String address = null;
try
{
if (!_client.isDetached())
{
address = _client.getConnectionAddress().getHostAddress();
}
}
catch (Exception e)
{
}
final ConnectionState state = (ConnectionState) _client.getConnectionState();
switch (state)
{
case IN_GAME:
if (_client.getActiveChar() != null)
{
output.append(_client.getActiveChar().getName());
output.append("(");
output.append(_client.getActiveChar().getObjectId());
output.append(") ");
}
break;
case AUTHENTICATED:
if (_client.getAccountName() != null)
{
output.append(_client.getAccountName());
output.append(" ");
}
break;
case CONNECTED:
if (address != null)
{
output.append(address);
}
break;
default:
throw new IllegalStateException("Missing state on switch");
}
Arrays.stream(lines).forEach(output::append);
_log.warning(output.toString());
}
}

View File

@@ -0,0 +1,59 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util;
/**
* Flood protector configuration
* @author fordfrog
*/
public final class FloodProtectorConfig
{
/**
* Type used for identification of logging output.
*/
public String FLOOD_PROTECTOR_TYPE;
/**
* Flood protection interval in game ticks.
*/
public int FLOOD_PROTECTION_INTERVAL;
/**
* Whether flooding should be logged.
*/
public boolean LOG_FLOODING;
/**
* If specified punishment limit is exceeded, punishment is applied.
*/
public int PUNISHMENT_LIMIT;
/**
* Punishment type. Either 'none', 'kick', 'ban' or 'jail'.
*/
public String PUNISHMENT_TYPE;
/**
* For how long should the char/account be punished.
*/
public long PUNISHMENT_TIME;
/**
* Creates new instance of FloodProtectorConfig.
* @param floodProtectorType {@link #FLOOD_PROTECTOR_TYPE}
*/
public FloodProtectorConfig(String floodProtectorType)
{
super();
FLOOD_PROTECTOR_TYPE = floodProtectorType;
}
}

View File

@@ -0,0 +1,260 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util;
import com.l2jmobius.Config;
import com.l2jmobius.gameserver.network.L2GameClient;
/**
* Collection of flood protectors for single player.
* @author fordfrog
*/
public final class FloodProtectors
{
/**
* Use-item flood protector.
*/
private final FloodProtectorAction _useItem;
/**
* Roll-dice flood protector.
*/
private final FloodProtectorAction _rollDice;
/**
* Firework flood protector.
*/
private final FloodProtectorAction _firework;
/**
* Item-pet-summon flood protector.
*/
private final FloodProtectorAction _itemPetSummon;
/**
* Hero-voice flood protector.
*/
private final FloodProtectorAction _heroVoice;
/**
* Global-chat flood protector.
*/
private final FloodProtectorAction _globalChat;
/**
* Subclass flood protector.
*/
private final FloodProtectorAction _subclass;
/**
* Drop-item flood protector.
*/
private final FloodProtectorAction _dropItem;
/**
* Server-bypass flood protector.
*/
private final FloodProtectorAction _serverBypass;
/**
* Multisell flood protector.
*/
private final FloodProtectorAction _multiSell;
/**
* Transaction flood protector.
*/
private final FloodProtectorAction _transaction;
/**
* Manufacture flood protector.
*/
private final FloodProtectorAction _manufacture;
/**
* Manor flood protector.
*/
private final FloodProtectorAction _manor;
/**
* Send mail flood protector.
*/
private final FloodProtectorAction _sendMail;
/**
* Character Select protector
*/
private final FloodProtectorAction _characterSelect;
/**
* Item Auction
*/
private final FloodProtectorAction _itemAuction;
/**
* Creates new instance of FloodProtectors.
* @param client game client for which the collection of flood protectors is being created.
*/
public FloodProtectors(L2GameClient client)
{
super();
_useItem = new FloodProtectorAction(client, Config.FLOOD_PROTECTOR_USE_ITEM);
_rollDice = new FloodProtectorAction(client, Config.FLOOD_PROTECTOR_ROLL_DICE);
_firework = new FloodProtectorAction(client, Config.FLOOD_PROTECTOR_FIREWORK);
_itemPetSummon = new FloodProtectorAction(client, Config.FLOOD_PROTECTOR_ITEM_PET_SUMMON);
_heroVoice = new FloodProtectorAction(client, Config.FLOOD_PROTECTOR_HERO_VOICE);
_globalChat = new FloodProtectorAction(client, Config.FLOOD_PROTECTOR_GLOBAL_CHAT);
_subclass = new FloodProtectorAction(client, Config.FLOOD_PROTECTOR_SUBCLASS);
_dropItem = new FloodProtectorAction(client, Config.FLOOD_PROTECTOR_DROP_ITEM);
_serverBypass = new FloodProtectorAction(client, Config.FLOOD_PROTECTOR_SERVER_BYPASS);
_multiSell = new FloodProtectorAction(client, Config.FLOOD_PROTECTOR_MULTISELL);
_transaction = new FloodProtectorAction(client, Config.FLOOD_PROTECTOR_TRANSACTION);
_manufacture = new FloodProtectorAction(client, Config.FLOOD_PROTECTOR_MANUFACTURE);
_manor = new FloodProtectorAction(client, Config.FLOOD_PROTECTOR_MANOR);
_sendMail = new FloodProtectorAction(client, Config.FLOOD_PROTECTOR_SENDMAIL);
_characterSelect = new FloodProtectorAction(client, Config.FLOOD_PROTECTOR_CHARACTER_SELECT);
_itemAuction = new FloodProtectorAction(client, Config.FLOOD_PROTECTOR_ITEM_AUCTION);
}
/**
* Returns {@link #_useItem}.
* @return {@link #_useItem}
*/
public FloodProtectorAction getUseItem()
{
return _useItem;
}
/**
* Returns {@link #_rollDice}.
* @return {@link #_rollDice}
*/
public FloodProtectorAction getRollDice()
{
return _rollDice;
}
/**
* Returns {@link #_firework}.
* @return {@link #_firework}
*/
public FloodProtectorAction getFirework()
{
return _firework;
}
/**
* Returns {@link #_itemPetSummon}.
* @return {@link #_itemPetSummon}
*/
public FloodProtectorAction getItemPetSummon()
{
return _itemPetSummon;
}
/**
* Returns {@link #_heroVoice}.
* @return {@link #_heroVoice}
*/
public FloodProtectorAction getHeroVoice()
{
return _heroVoice;
}
/**
* Returns {@link #_globalChat}.
* @return {@link #_globalChat}
*/
public FloodProtectorAction getGlobalChat()
{
return _globalChat;
}
/**
* Returns {@link #_subclass}.
* @return {@link #_subclass}
*/
public FloodProtectorAction getSubclass()
{
return _subclass;
}
/**
* Returns {@link #_dropItem}.
* @return {@link #_dropItem}
*/
public FloodProtectorAction getDropItem()
{
return _dropItem;
}
/**
* Returns {@link #_serverBypass}.
* @return {@link #_serverBypass}
*/
public FloodProtectorAction getServerBypass()
{
return _serverBypass;
}
/**
* @return {@link #_multiSell}
*/
public FloodProtectorAction getMultiSell()
{
return _multiSell;
}
/**
* Returns {@link #_transaction}.
* @return {@link #_transaction}
*/
public FloodProtectorAction getTransaction()
{
return _transaction;
}
/**
* Returns {@link #_manufacture}.
* @return {@link #_manufacture}
*/
public FloodProtectorAction getManufacture()
{
return _manufacture;
}
/**
* Returns {@link #_manor}.
* @return {@link #_manor}
*/
public FloodProtectorAction getManor()
{
return _manor;
}
/**
* Returns {@link #_sendMail}.
* @return {@link #_sendMail}
*/
public FloodProtectorAction getSendMail()
{
return _sendMail;
}
/**
* Returns {@link #_characterSelect}.
* @return {@link #_characterSelect}
*/
public FloodProtectorAction getCharacterSelect()
{
return _characterSelect;
}
/**
* Returns {@link #_itemAuction}.
* @return {@link #_itemAuction}
*/
public FloodProtectorAction getItemAuction()
{
return _itemAuction;
}
}

View File

@@ -0,0 +1,79 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jmobius.Config;
/**
* Audits Game Master's actions.
*/
public class GMAudit
{
private static final Logger _log = Logger.getLogger(GMAudit.class.getName());
static
{
new File("log/GMAudit").mkdirs();
}
/**
* Logs a Game Master's action into a file.
* @param gmName the Game Master's name
* @param action the performed action
* @param target the target's name
* @param params the parameters
*/
public static void auditGMAction(String gmName, String action, String target, String params)
{
final SimpleDateFormat _formatter = new SimpleDateFormat("dd/MM/yyyy H:mm:ss");
final String date = _formatter.format(new Date());
String name = com.l2jmobius.commons.util.CommonUtil.replaceIllegalCharacters(gmName);
if (!com.l2jmobius.commons.util.CommonUtil.isValidFileName(name))
{
name = "INVALID_GM_NAME_" + date;
}
final File file = new File("log/GMAudit/" + name + ".txt");
try (FileWriter save = new FileWriter(file, true))
{
save.write(date + ">" + gmName + ">" + action + ">" + target + ">" + params + Config.EOL);
}
catch (IOException e)
{
_log.log(Level.SEVERE, "GMAudit for GM " + gmName + " could not be saved: ", e);
}
}
/**
* Wrapper method.
* @param gmName the Game Master's name
* @param action the performed action
* @param target the target's name
*/
public static void auditGMAction(String gmName, String action, String target)
{
auditGMAction(gmName, action, target, "");
}
}

View File

@@ -0,0 +1,234 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util;
import java.awt.Color;
import com.l2jmobius.gameserver.geodata.GeoData;
import com.l2jmobius.gameserver.geodata.geodriver.Cell;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive;
/**
* @author HorridoJoho
*/
public final class GeoUtils
{
public static void debug2DLine(L2PcInstance player, int x, int y, int tx, int ty, int z)
{
final int gx = GeoData.getInstance().getGeoX(x);
final int gy = GeoData.getInstance().getGeoY(y);
final int tgx = GeoData.getInstance().getGeoX(tx);
final int tgy = GeoData.getInstance().getGeoY(ty);
final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z);
prim.addLine(Color.BLUE, GeoData.getInstance().getWorldX(gx), GeoData.getInstance().getWorldY(gy), z, GeoData.getInstance().getWorldX(tgx), GeoData.getInstance().getWorldY(tgy), z);
final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy);
while (iter.next())
{
final int wx = GeoData.getInstance().getWorldX(iter.x());
final int wy = GeoData.getInstance().getWorldY(iter.y());
prim.addPoint(Color.RED, wx, wy, z);
}
player.sendPacket(prim);
}
public static void debug3DLine(L2PcInstance player, int x, int y, int z, int tx, int ty, int tz)
{
final int gx = GeoData.getInstance().getGeoX(x);
final int gy = GeoData.getInstance().getGeoY(y);
final int tgx = GeoData.getInstance().getGeoX(tx);
final int tgy = GeoData.getInstance().getGeoY(ty);
final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z);
prim.addLine(Color.BLUE, GeoData.getInstance().getWorldX(gx), GeoData.getInstance().getWorldY(gy), z, GeoData.getInstance().getWorldX(tgx), GeoData.getInstance().getWorldY(tgy), tz);
final LinePointIterator3D iter = new LinePointIterator3D(gx, gy, z, tgx, tgy, tz);
iter.next();
int prevX = iter.x();
int prevY = iter.y();
int wx = GeoData.getInstance().getWorldX(prevX);
int wy = GeoData.getInstance().getWorldY(prevY);
int wz = iter.z();
prim.addPoint(Color.RED, wx, wy, wz);
while (iter.next())
{
final int curX = iter.x();
final int curY = iter.y();
if ((curX != prevX) || (curY != prevY))
{
wx = GeoData.getInstance().getWorldX(curX);
wy = GeoData.getInstance().getWorldY(curY);
wz = iter.z();
prim.addPoint(Color.RED, wx, wy, wz);
prevX = curX;
prevY = curY;
}
}
player.sendPacket(prim);
}
private static Color getDirectionColor(int x, int y, int z, int nswe)
{
if (GeoData.getInstance().checkNearestNswe(x, y, z, nswe))
{
return Color.GREEN;
}
return Color.RED;
}
public static void debugGrid(L2PcInstance player)
{
final int geoRadius = 20;
final int blocksPerPacket = 40;
int iBlock = blocksPerPacket;
int iPacket = 0;
ExServerPrimitive exsp = null;
final GeoData gd = GeoData.getInstance();
final int playerGx = gd.getGeoX(player.getX());
final int playerGy = gd.getGeoY(player.getY());
for (int dx = -geoRadius; dx <= geoRadius; ++dx)
{
for (int dy = -geoRadius; dy <= geoRadius; ++dy)
{
if (iBlock >= blocksPerPacket)
{
iBlock = 0;
if (exsp != null)
{
++iPacket;
player.sendPacket(exsp);
}
exsp = new ExServerPrimitive("DebugGrid_" + iPacket, player.getX(), player.getY(), -16000);
}
if (exsp == null)
{
throw new IllegalStateException();
}
final int gx = playerGx + dx;
final int gy = playerGy + dy;
final int x = gd.getWorldX(gx);
final int y = gd.getWorldY(gy);
final int z = gd.getNearestZ(gx, gy, player.getZ());
// north arrow
Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH);
exsp.addLine(col, x - 1, y - 7, z, x + 1, y - 7, z);
exsp.addLine(col, x - 2, y - 6, z, x + 2, y - 6, z);
exsp.addLine(col, x - 3, y - 5, z, x + 3, y - 5, z);
exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z);
// east arrow
col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST);
exsp.addLine(col, x + 7, y - 1, z, x + 7, y + 1, z);
exsp.addLine(col, x + 6, y - 2, z, x + 6, y + 2, z);
exsp.addLine(col, x + 5, y - 3, z, x + 5, y + 3, z);
exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z);
// south arrow
col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH);
exsp.addLine(col, x - 1, y + 7, z, x + 1, y + 7, z);
exsp.addLine(col, x - 2, y + 6, z, x + 2, y + 6, z);
exsp.addLine(col, x - 3, y + 5, z, x + 3, y + 5, z);
exsp.addLine(col, x - 4, y + 4, z, x + 4, y + 4, z);
col = getDirectionColor(gx, gy, z, Cell.NSWE_WEST);
exsp.addLine(col, x - 7, y - 1, z, x - 7, y + 1, z);
exsp.addLine(col, x - 6, y - 2, z, x - 6, y + 2, z);
exsp.addLine(col, x - 5, y - 3, z, x - 5, y + 3, z);
exsp.addLine(col, x - 4, y - 4, z, x - 4, y + 4, z);
++iBlock;
}
}
player.sendPacket(exsp);
}
/**
* difference between x values: never above 1<br>
* difference between y values: never above 1
* @param lastX
* @param lastY
* @param x
* @param y
* @return
*/
public static int computeNswe(int lastX, int lastY, int x, int y)
{
if (x > lastX) // east
{
if (y > lastY)
{
return Cell.NSWE_SOUTH_EAST; // Direction.SOUTH_EAST;
}
else if (y < lastY)
{
return Cell.NSWE_NORTH_EAST; // Direction.NORTH_EAST;
}
else
{
return Cell.NSWE_EAST; // Direction.EAST;
}
}
else if (x < lastX) // west
{
if (y > lastY)
{
return Cell.NSWE_SOUTH_WEST; // Direction.SOUTH_WEST;
}
else if (y < lastY)
{
return Cell.NSWE_NORTH_WEST; // Direction.NORTH_WEST;
}
else
{
return Cell.NSWE_WEST; // Direction.WEST;
}
}
else
// unchanged x
{
if (y > lastY)
{
return Cell.NSWE_SOUTH; // Direction.SOUTH;
}
else if (y < lastY)
{
return Cell.NSWE_NORTH; // Direction.NORTH;
}
else
{
throw new RuntimeException();
}
}
}
}

View File

@@ -0,0 +1,219 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util;
import com.l2jmobius.commons.util.CommonUtil;
/**
* A class containing useful methods for constructing HTML
* @author Nos
*/
public class HtmlUtil
{
/**
* Gets the HTML representation of CP gauge.
* @param width the width
* @param current the current value
* @param max the max value
* @param displayAsPercentage if {@code true} the text in middle will be displayed as percent else it will be displayed as "current / max"
* @return the HTML
*/
public static String getCpGauge(int width, long current, long max, boolean displayAsPercentage)
{
return getGauge(width, current, max, displayAsPercentage, "L2UI_CT1.Gauges.Gauge_DF_Large_CP_bg_Center", "L2UI_CT1.Gauges.Gauge_DF_Large_CP_Center", 17, -13);
}
/**
* Gets the HTML representation of HP gauge.
* @param width the width
* @param current the current value
* @param max the max value
* @param displayAsPercentage if {@code true} the text in middle will be displayed as percent else it will be displayed as "current / max"
* @return the HTML
*/
public static String getHpGauge(int width, long current, long max, boolean displayAsPercentage)
{
return getGauge(width, current, max, displayAsPercentage, "L2UI_CT1.Gauges.Gauge_DF_Large_HP_bg_Center", "L2UI_CT1.Gauges.Gauge_DF_Large_HP_Center", 21, -13);
}
/**
* Gets the HTML representation of HP Warn gauge.
* @param width the width
* @param current the current value
* @param max the max value
* @param displayAsPercentage if {@code true} the text in middle will be displayed as percent else it will be displayed as "current / max"
* @return the HTML
*/
public static String getHpWarnGauge(int width, long current, long max, boolean displayAsPercentage)
{
return getGauge(width, current, max, displayAsPercentage, "L2UI_CT1.Gauges.Gauge_DF_Large_HPWarn_bg_Center", "L2UI_CT1.Gauges.Gauge_DF_Large_HPWarn_Center", 17, -13);
}
/**
* Gets the HTML representation of HP Fill gauge.
* @param width the width
* @param current the current value
* @param max the max value
* @param displayAsPercentage if {@code true} the text in middle will be displayed as percent else it will be displayed as "current / max"
* @return the HTML
*/
public static String getHpFillGauge(int width, long current, long max, boolean displayAsPercentage)
{
return getGauge(width, current, max, displayAsPercentage, "L2UI_CT1.Gauges.Gauge_DF_Large_HPFill_bg_Center", "L2UI_CT1.Gauges.Gauge_DF_Large_HPFill_Center", 17, -13);
}
/**
* Gets the HTML representation of MP Warn gauge.
* @param width the width
* @param current the current value
* @param max the max value
* @param displayAsPercentage if {@code true} the text in middle will be displayed as percent else it will be displayed as "current / max"
* @return the HTML
*/
public static String getMpGauge(int width, long current, long max, boolean displayAsPercentage)
{
return getGauge(width, current, max, displayAsPercentage, "L2UI_CT1.Gauges.Gauge_DF_Large_MP_bg_Center", "L2UI_CT1.Gauges.Gauge_DF_Large_MP_Center", 17, -13);
}
/**
* Gets the HTML representation of EXP Warn gauge.
* @param width the width
* @param current the current value
* @param max the max value
* @param displayAsPercentage if {@code true} the text in middle will be displayed as percent else it will be displayed as "current / max"
* @return the HTML
*/
public static String getExpGauge(int width, long current, long max, boolean displayAsPercentage)
{
return getGauge(width, current, max, displayAsPercentage, "L2UI_CT1.Gauges.Gauge_DF_Large_EXP_bg_Center", "L2UI_CT1.Gauges.Gauge_DF_Large_EXP_Center", 17, -13);
}
/**
* Gets the HTML representation of Food gauge.
* @param width the width
* @param current the current value
* @param max the max value
* @param displayAsPercentage if {@code true} the text in middle will be displayed as percent else it will be displayed as "current / max"
* @return the HTML
*/
public static String getFoodGauge(int width, long current, long max, boolean displayAsPercentage)
{
return getGauge(width, current, max, displayAsPercentage, "L2UI_CT1.Gauges.Gauge_DF_Large_Food_Bg_Center", "L2UI_CT1.Gauges.Gauge_DF_Large_Food_Center", 17, -13);
}
/**
* Gets the HTML representation of Weight gauge automatically changing level depending on current/max.
* @param width the width
* @param current the current value
* @param max the max value
* @param displayAsPercentage if {@code true} the text in middle will be displayed as percent else it will be displayed as "current / max"
* @return the HTML
*/
public static String getWeightGauge(int width, long current, long max, boolean displayAsPercentage)
{
return getWeightGauge(width, current, max, displayAsPercentage, CommonUtil.map(current, 0, max, 1, 5));
}
/**
* Gets the HTML representation of Weight gauge.
* @param width the width
* @param current the current value
* @param max the max value
* @param displayAsPercentage if {@code true} the text in middle will be displayed as percent else it will be displayed as "current / max"
* @param level a number from 1 to 5 for the 5 different colors of weight gauge
* @return the HTML
*/
public static String getWeightGauge(int width, long current, long max, boolean displayAsPercentage, long level)
{
return getGauge(width, current, max, displayAsPercentage, "L2UI_CT1.Gauges.Gauge_DF_Large_Weight_bg_Center" + level, "L2UI_CT1.Gauges.Gauge_DF_Large_Weight_Center" + level, 17, -13);
}
/**
* Gets the HTML representation of a gauge.
* @param width the width
* @param current the current value
* @param max the max value
* @param displayAsPercentage if {@code true} the text in middle will be displayed as percent else it will be displayed as "current / max"
* @param backgroundImage the background image
* @param image the foreground image
* @param imageHeight the image height
* @param top the top adjustment
* @return the HTML
*/
private static String getGauge(int width, long current, long max, boolean displayAsPercentage, String backgroundImage, String image, long imageHeight, long top)
{
current = Math.min(current, max);
final StringBuilder sb = new StringBuilder();
sb.append("<table width=");
sb.append(width);
sb.append(" cellpadding=0 cellspacing=0>");
sb.append("<tr>");
sb.append("<td background=\"");
sb.append(backgroundImage);
sb.append("\">");
sb.append("<img src=\"");
sb.append(image);
sb.append("\" width=");
sb.append((long) (((double) current / max) * width));
sb.append(" height=");
sb.append(imageHeight);
sb.append(">");
sb.append("</td>");
sb.append("</tr>");
sb.append("<tr>");
sb.append("<td align=center>");
sb.append("<table cellpadding=0 cellspacing=");
sb.append(top);
sb.append(">");
sb.append("<tr>");
sb.append("<td>");
if (displayAsPercentage)
{
sb.append("<table cellpadding=0 cellspacing=2>");
sb.append("<tr><td>");
sb.append(String.format("%.2f%%", ((double) current / max) * 100));
sb.append("</td></tr>");
sb.append("</table>");
}
else
{
final int tdWidth = (width - 10) / 2;
sb.append("<table cellpadding=0 cellspacing=0>");
sb.append("<tr>");
sb.append("<td width=");
sb.append(tdWidth);
sb.append(" align=right>");
sb.append(current);
sb.append("</td>");
sb.append("<td width=10 align=center>/</td>");
sb.append("<td width=");
sb.append(tdWidth);
sb.append(">");
sb.append(max);
sb.append("</td>");
sb.append("</tr>");
sb.append("</table>");
}
sb.append("</td>");
sb.append("</tr>");
sb.append("</table>");
sb.append("</td>");
sb.append("</tr>");
sb.append("</table>");
return sb.toString();
}
}

View File

@@ -0,0 +1,113 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util;
/**
* @author HorridoJoho
*/
public final class LinePointIterator
{
// src is moved towards dst in next()
private int _srcX;
private int _srcY;
private final int _dstX;
private final int _dstY;
private final long _dx;
private final long _dy;
private final long _sx;
private final long _sy;
private long _error;
private boolean _first;
public LinePointIterator(int srcX, int srcY, int dstX, int dstY)
{
_srcX = srcX;
_srcY = srcY;
_dstX = dstX;
_dstY = dstY;
_dx = Math.abs((long) dstX - srcX);
_dy = Math.abs((long) dstY - srcY);
_sx = srcX < dstX ? 1 : -1;
_sy = srcY < dstY ? 1 : -1;
if (_dx >= _dy)
{
_error = _dx / 2;
}
else
{
_error = _dy / 2;
}
_first = true;
}
public boolean next()
{
if (_first)
{
_first = false;
return true;
}
else if (_dx >= _dy)
{
if (_srcX != _dstX)
{
_srcX += _sx;
_error += _dy;
if (_error >= _dx)
{
_srcY += _sy;
_error -= _dx;
}
return true;
}
}
else
{
if (_srcY != _dstY)
{
_srcY += _sy;
_error += _dx;
if (_error >= _dy)
{
_srcX += _sx;
_error -= _dy;
}
return true;
}
}
return false;
}
public int x()
{
return _srcX;
}
public int y()
{
return _srcY;
}
}

View File

@@ -0,0 +1,171 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util;
/**
* @author HorridoJoho
*/
public final class LinePointIterator3D
{
private int _srcX;
private int _srcY;
private int _srcZ;
private final int _dstX;
private final int _dstY;
private final int _dstZ;
private final long _dx;
private final long _dy;
private final long _dz;
private final long _sx;
private final long _sy;
private final long _sz;
private long _error;
private long _error2;
private boolean _first;
public LinePointIterator3D(int srcX, int srcY, int srcZ, int dstX, int dstY, int dstZ)
{
_srcX = srcX;
_srcY = srcY;
_srcZ = srcZ;
_dstX = dstX;
_dstY = dstY;
_dstZ = dstZ;
_dx = Math.abs((long) dstX - srcX);
_dy = Math.abs((long) dstY - srcY);
_dz = Math.abs((long) dstZ - srcZ);
_sx = srcX < dstX ? 1 : -1;
_sy = srcY < dstY ? 1 : -1;
_sz = srcZ < dstZ ? 1 : -1;
if ((_dx >= _dy) && (_dx >= _dz))
{
_error = _error2 = _dx / 2;
}
else if ((_dy >= _dx) && (_dy >= _dz))
{
_error = _error2 = _dy / 2;
}
else
{
_error = _error2 = _dz / 2;
}
_first = true;
}
public boolean next()
{
if (_first)
{
_first = false;
return true;
}
else if ((_dx >= _dy) && (_dx >= _dz))
{
if (_srcX != _dstX)
{
_srcX += _sx;
_error += _dy;
if (_error >= _dx)
{
_srcY += _sy;
_error -= _dx;
}
_error2 += _dz;
if (_error2 >= _dx)
{
_srcZ += _sz;
_error2 -= _dx;
}
return true;
}
}
else if ((_dy >= _dx) && (_dy >= _dz))
{
if (_srcY != _dstY)
{
_srcY += _sy;
_error += _dx;
if (_error >= _dy)
{
_srcX += _sx;
_error -= _dy;
}
_error2 += _dz;
if (_error2 >= _dy)
{
_srcZ += _sz;
_error2 -= _dy;
}
return true;
}
}
else
{
if (_srcZ != _dstZ)
{
_srcZ += _sz;
_error += _dx;
if (_error >= _dz)
{
_srcX += _sx;
_error -= _dz;
}
_error2 += _dy;
if (_error2 >= _dz)
{
_srcY += _sy;
_error2 -= _dz;
}
return true;
}
}
return false;
}
public int x()
{
return _srcX;
}
public int y()
{
return _srcY;
}
public int z()
{
return _srcZ;
}
@Override
public String toString()
{
return "[" + _srcX + ", " + _srcY + ", " + _srcZ + "]";
}
}

View File

@@ -0,0 +1,420 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Logger;
import com.l2jmobius.Config;
import com.l2jmobius.commons.util.Rnd;
import com.l2jmobius.gameserver.ThreadPoolManager;
import com.l2jmobius.gameserver.data.xml.impl.NpcData;
import com.l2jmobius.gameserver.model.Location;
import com.l2jmobius.gameserver.model.actor.L2Character;
import com.l2jmobius.gameserver.model.actor.instance.L2MonsterInstance;
import com.l2jmobius.gameserver.model.actor.templates.L2NpcTemplate;
import com.l2jmobius.gameserver.model.holders.MinionHolder;
/**
* @author luisantonioa, DS
*/
public class MinionList
{
private static final Logger _log = Logger.getLogger(MinionList.class.getName());
protected final L2MonsterInstance _master;
/** List containing the current spawned minions */
private final List<L2MonsterInstance> _minionReferences = new CopyOnWriteArrayList<>();
/** List containing the cached deleted minions for reuse */
protected List<L2MonsterInstance> _reusedMinionReferences = null;
public MinionList(L2MonsterInstance pMaster)
{
if (pMaster == null)
{
throw new NullPointerException("MinionList: master is null");
}
_master = pMaster;
}
/**
* @return list of the spawned (alive) minions.
*/
public List<L2MonsterInstance> getSpawnedMinions()
{
return _minionReferences;
}
/**
* Manage the spawn of Minions.<BR>
* <BR>
* <B><U> Actions</U> :</B><BR>
* <BR>
* <li>Get the Minion data of all Minions that must be spawn</li>
* <li>For each Minion type, spawn the amount of Minion needed</li><BR>
* <BR>
* @param minions
*/
public final void spawnMinions(List<MinionHolder> minions)
{
if (_master.isAlikeDead())
{
return;
}
// List<MinionHolder> minions = _master.getParameters().getMinionList("Privates");
if (minions == null)
{
return;
}
int minionCount, minionId, minionsToSpawn;
for (MinionHolder minion : minions)
{
minionCount = minion.getCount();
minionId = minion.getId();
minionsToSpawn = minionCount - countSpawnedMinionsById(minionId);
if (minionsToSpawn > 0)
{
for (int i = 0; i < minionsToSpawn; i++)
{
spawnMinion(minionId);
}
}
}
// remove non-needed minions
deleteReusedMinions();
}
/**
* Delete all spawned minions and try to reuse them.
*/
public void deleteSpawnedMinions()
{
if (!_minionReferences.isEmpty())
{
for (L2MonsterInstance minion : _minionReferences)
{
if (minion != null)
{
minion.setLeader(null);
minion.deleteMe();
if (_reusedMinionReferences != null)
{
_reusedMinionReferences.add(minion);
}
}
}
_minionReferences.clear();
}
}
/**
* Delete all reused minions to prevent memory leaks.
*/
public void deleteReusedMinions()
{
if (_reusedMinionReferences != null)
{
_reusedMinionReferences.clear();
}
}
// hooks
/**
* Called on the master spawn Old minions (from previous spawn) are deleted. If master can respawn - enabled reuse of the killed minions.
*/
public void onMasterSpawn()
{
deleteSpawnedMinions();
// if master has spawn and can respawn - try to reuse minions
if ((_reusedMinionReferences == null) && (_master.getParameters().getSet().get("SummonPrivateRate") == null) && !_master.getParameters().getMinionList("Privates").isEmpty() && (_master.getSpawn() != null) && _master.getSpawn().isRespawnEnabled())
{
_reusedMinionReferences = new CopyOnWriteArrayList<>();
}
}
/**
* Called on the minion spawn and added them in the list of the spawned minions.
* @param minion
*/
public void onMinionSpawn(L2MonsterInstance minion)
{
_minionReferences.add(minion);
}
/**
* Called on the master death/delete.
* @param force if true - force delete of the spawned minions By default minions deleted only for raidbosses
*/
public void onMasterDie(boolean force)
{
if (_master.isRaid() || force)
{
deleteSpawnedMinions();
}
}
/**
* Called on the minion death/delete. Removed minion from the list of the spawned minions and reuse if possible.
* @param minion
* @param respawnTime (ms) enable respawning of this minion while master is alive. -1 - use default value: 0 (disable) for mobs and config value for raids.
*/
public void onMinionDie(L2MonsterInstance minion, int respawnTime)
{
minion.setLeader(null); // prevent memory leaks
_minionReferences.remove(minion);
if (_reusedMinionReferences != null)
{
_reusedMinionReferences.add(minion);
}
final int time = respawnTime < 0 ? _master.isRaid() ? (int) Config.RAID_MINION_RESPAWN_TIMER : 0 : respawnTime;
if ((time > 0) && !_master.isAlikeDead())
{
ThreadPoolManager.getInstance().scheduleGeneral(new MinionRespawnTask(minion), time);
}
}
/**
* Called if master/minion was attacked. Master and all free minions receive aggro against attacker.
* @param caller
* @param attacker
*/
public void onAssist(L2Character caller, L2Character attacker)
{
if (attacker == null)
{
return;
}
if (!_master.isAlikeDead() && !_master.isInCombat())
{
_master.addDamageHate(attacker, 0, 1);
}
final boolean callerIsMaster = caller == _master;
int aggro = callerIsMaster ? 10 : 1;
if (_master.isRaid())
{
aggro *= 10;
}
for (L2MonsterInstance minion : _minionReferences)
{
if ((minion != null) && !minion.isDead() && (callerIsMaster || !minion.isInCombat()))
{
minion.addDamageHate(attacker, 0, aggro);
}
}
}
/**
* Called from onTeleported() of the master Alive and able to move minions teleported to master.
*/
public void onMasterTeleported()
{
final int offset = 200;
final int minRadius = (int) _master.getCollisionRadius() + 30;
for (L2MonsterInstance minion : _minionReferences)
{
if ((minion != null) && !minion.isDead() && !minion.isMovementDisabled())
{
int newX = Rnd.get(minRadius * 2, offset * 2); // x
int newY = Rnd.get(newX, offset * 2); // distance
newY = (int) Math.sqrt((newY * newY) - (newX * newX)); // y
if (newX > (offset + minRadius))
{
newX = (_master.getX() + newX) - offset;
}
else
{
newX = (_master.getX() - newX) + minRadius;
}
if (newY > (offset + minRadius))
{
newY = (_master.getY() + newY) - offset;
}
else
{
newY = (_master.getY() - newY) + minRadius;
}
minion.teleToLocation(new Location(newX, newY, _master.getZ()));
}
}
}
private final void spawnMinion(int minionId)
{
if (minionId == 0)
{
return;
}
// searching in reused minions
if (_reusedMinionReferences != null)
{
final L2MonsterInstance minion = _reusedMinionReferences.stream().filter(m -> (m.getId() == minionId)).findFirst().orElse(null);
if (minion != null)
{
_reusedMinionReferences.remove(minion);
minion.refreshID();
initializeNpcInstance(_master, minion);
return;
}
}
// not found in cache
spawnMinion(_master, minionId);
}
private final class MinionRespawnTask implements Runnable
{
private final L2MonsterInstance _minion;
public MinionRespawnTask(L2MonsterInstance minion)
{
_minion = minion;
}
@Override
public void run()
{
if (!_master.isAlikeDead() && _master.isSpawned())
{
// minion can be already spawned or deleted
if (!_minion.isSpawned())
{
if (_reusedMinionReferences != null)
{
_reusedMinionReferences.remove(_minion);
}
_minion.refreshID();
initializeNpcInstance(_master, _minion);
}
}
}
}
/**
* Init a Minion and add it in the world as a visible object.<BR>
* <BR>
* <B><U> Actions</U> :</B><BR>
* <BR>
* <li>Get the template of the Minion to spawn</li>
* <li>Create and Init the Minion and generate its Identifier</li>
* <li>Set the Minion HP, MP and Heading</li>
* <li>Set the Minion leader to this RaidBoss</li>
* <li>Init the position of the Minion and add it in the world as a visible object</li><BR>
* <BR>
* @param master L2MonsterInstance used as master for this minion
* @param minionId The L2NpcTemplate Identifier of the Minion to spawn
* @return
*/
public static L2MonsterInstance spawnMinion(L2MonsterInstance master, int minionId)
{
// Get the template of the Minion to spawn
final L2NpcTemplate minionTemplate = NpcData.getInstance().getTemplate(minionId);
if (minionTemplate == null)
{
return null;
}
return initializeNpcInstance(master, new L2MonsterInstance(minionTemplate));
}
protected static L2MonsterInstance initializeNpcInstance(L2MonsterInstance master, L2MonsterInstance minion)
{
minion.stopAllEffects();
minion.setIsDead(false);
minion.setDecayed(false);
// Set the Minion HP, MP and Heading
minion.setCurrentHpMp(minion.getMaxHp(), minion.getMaxMp());
minion.setHeading(master.getHeading());
// Set the Minion leader to this RaidBoss
minion.setLeader(master);
// move monster to masters instance
minion.setInstance(master.getInstanceWorld());
// Init the position of the Minion and add it in the world as a visible object
final int offset = 200;
final int minRadius = (int) master.getCollisionRadius() + 30;
int newX = Rnd.get(minRadius * 2, offset * 2); // x
int newY = Rnd.get(newX, offset * 2); // distance
newY = (int) Math.sqrt((newY * newY) - (newX * newX)); // y
if (newX > (offset + minRadius))
{
newX = (master.getX() + newX) - offset;
}
else
{
newX = (master.getX() - newX) + minRadius;
}
if (newY > (offset + minRadius))
{
newY = (master.getY() + newY) - offset;
}
else
{
newY = (master.getY() - newY) + minRadius;
}
minion.spawnMe(newX, newY, master.getZ());
if (Config.DEBUG)
{
_log.info("Spawned minion template " + minion.getId() + " with objid: " + minion.getObjectId() + " to boss " + master.getObjectId() + " ,at: " + minion.getX() + " x, " + minion.getY() + " y, " + minion.getZ() + " z");
}
return minion;
}
// Statistics part
private final int countSpawnedMinionsById(int minionId)
{
int count = 0;
for (L2MonsterInstance minion : _minionReferences)
{
if ((minion != null) && (minion.getId() == minionId))
{
count++;
}
}
return count;
}
public final int countSpawnedMinions()
{
return _minionReferences.size();
}
public final long lazyCountSpawnedMinionsGroups()
{
return _minionReferences.stream().distinct().count();
}
}

View File

@@ -0,0 +1,359 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util;
import java.util.Arrays;
/**
* <b>Modified for Trove to use the java.util.Arrays sort/search<br>
* algorithms instead of those provided with colt.</b><br>
* Used to keep hash table capacities prime numbers.<br>
* Not of interest for users; only for implementors of hashtables.<br>
* <p>
* Choosing prime numbers as hash table capacities is a good idea<br>
* to keep them working fast, particularly under hash table expansions.<br>
* <p>
* However, JDK 1.2, JGL 3.1 and many other toolkits do nothing to keep capacities prime.<br>
* This class provides efficient means to choose prime capacities.
* <p>
* Choosing a prime is <tt>O(log 300)</tt> (binary search in a list of 300 ints).<br>
* Memory requirements: 1 KB static memory.<br>
* @author wolfgang.hoschek@cern.ch
* @version 1.0, 09/24/99
*/
public final class PrimeFinder
{
/**
* The largest prime this class can generate; currently equal to <tt>Integer.MAX_VALUE</tt>.
*/
public static final int LARGEST_PRIME = Integer.MAX_VALUE; // yes, it is prime.
/**
* The prime number list consists of 11 chunks.<br>
* Each chunk contains prime numbers.<br>
* A chunk starts with a prime P1.<br>
* The next element is a prime P2.<br>
* P2 is the smallest prime for which holds: P2 >= 2*P1.<br>
* The next element is P3, for which the same holds with respect to P2, and so on. Chunks are chosen such that for any desired capacity >= 1000<br>
* the list includes a prime number <= desired capacity * 1.11.<br>
* Therefore, primes can be retrieved which are quite close to any<br>
* desired capacity, which in turn avoids wasting memory.<br>
* For example, the list includes<br>
* 1039,1117,1201,1277,1361,1439,1523,1597,1759,1907,2081.<br>
* So if you need a prime >= 1040, you will find a prime <= 1040*1.11=1154.<br> Chunks are chosen such that they are optimized for a hashtable growthfactor of 2.0;<br>
* If your hashtable has such a growthfactor then, after initially<br>
* "rounding to a prime" upon hashtable construction, it will<br>
* later expand to prime capacities such that there exist no better primes.<br>
* In total these are about 32*10=320 numbers -> 1 KB of static memory needed.<br>
* If you are stingy, then delete every second or fourth chunk.
*/
private static final int[] PRIME_CAPACITIES =
{
// chunk #0
LARGEST_PRIME,
// chunk #1
5,
11,
23,
47,
97,
197,
397,
797,
1597,
3203,
6421,
12853,
25717,
51437,
102877,
205759,
411527,
823117,
1646237,
3292489,
6584983,
13169977,
26339969,
52679969,
105359939,
210719881,
421439783,
842879579,
1685759167,
// chunk #2
433,
877,
1759,
3527,
7057,
14143,
28289,
56591,
113189,
226379,
452759,
905551,
1811107,
3622219,
7244441,
14488931,
28977863,
57955739,
115911563,
231823147,
463646329,
927292699,
1854585413,
// chunk #3
953,
1907,
3821,
7643,
15287,
30577,
61169,
122347,
244703,
489407,
978821,
1957651,
3915341,
7830701,
15661423,
31322867,
62645741,
125291483,
250582987,
501165979,
1002331963,
2004663929,
// chunk #4
1039,
2081,
4177,
8363,
16729,
33461,
66923,
133853,
267713,
535481,
1070981,
2141977,
4283963,
8567929,
17135863,
34271747,
68543509,
137087021,
274174111,
548348231,
1096696463,
// chunk #5
31,
67,
137,
277,
557,
1117,
2237,
4481,
8963,
17929,
35863,
71741,
143483,
286973,
573953,
1147921,
2295859,
4591721,
9183457,
18366923,
36733847,
73467739,
146935499,
293871013,
587742049,
1175484103,
// chunk #6
599,
1201,
2411,
4831,
9677,
19373,
38747,
77509,
155027,
310081,
620171,
1240361,
2480729,
4961459,
9922933,
19845871,
39691759,
79383533,
158767069,
317534141,
635068283,
1270136683,
// chunk #7
311,
631,
1277,
2557,
5119,
10243,
20507,
41017,
82037,
164089,
328213,
656429,
1312867,
2625761,
5251529,
10503061,
21006137,
42012281,
84024581,
168049163,
336098327,
672196673,
1344393353,
// chunk #8
3,
7,
17,
37,
79,
163,
331,
673,
1361,
2729,
5471,
10949,
21911,
43853,
87719,
175447,
350899,
701819,
1403641,
2807303,
5614657,
11229331,
22458671,
44917381,
89834777,
179669557,
359339171,
718678369,
1437356741,
// chunk #9
43,
89,
179,
359,
719,
1439,
2879,
5779,
11579,
23159,
46327,
92657,
185323,
370661,
741337,
1482707,
2965421,
5930887,
11861791,
23723597,
47447201,
94894427,
189788857,
379577741,
759155483,
1518310967,
// chunk #10
379,
761,
1523,
3049,
6101,
12203,
24407,
48817,
97649,
195311,
390647,
781301,
1562611,
3125257,
6250537,
12501169,
25002389,
50004791,
100009607,
200019221,
400038451,
800076929,
1600153859
};
static
{ // initializer
// The above prime numbers are formatted for human readability.
// To find numbers fast, we sort them once and for all.
Arrays.sort(PRIME_CAPACITIES);
}
/**
* Returns a prime number which is <code>&gt;= desiredCapacity</code> and very close to <code>desiredCapacity</code> (within 11% if <code>desiredCapacity &gt;= 1000</code>).
* @param desiredCapacity the capacity desired by the user.
* @return the capacity which should be used for a hashtable.
*/
public static int nextPrime(int desiredCapacity)
{
int i = Arrays.binarySearch(PRIME_CAPACITIES, desiredCapacity);
if (i < 0)
{
// desired capacity not found, choose next prime greater
// than desired capacity
i = -i - 1; // remember the semantics of binarySearch...
}
return PRIME_CAPACITIES[i];
}
}

View File

@@ -0,0 +1,791 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Locale;
import java.util.logging.Logger;
import com.l2jmobius.Config;
import com.l2jmobius.commons.util.Rnd;
import com.l2jmobius.commons.util.file.filter.ExtFilter;
import com.l2jmobius.gameserver.ThreadPoolManager;
import com.l2jmobius.gameserver.enums.HtmlActionScope;
import com.l2jmobius.gameserver.enums.IllegalActionPunishmentType;
import com.l2jmobius.gameserver.model.L2Object;
import com.l2jmobius.gameserver.model.L2World;
import com.l2jmobius.gameserver.model.Location;
import com.l2jmobius.gameserver.model.actor.L2Character;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.model.actor.tasks.player.IllegalPlayerActionTask;
import com.l2jmobius.gameserver.model.interfaces.ILocational;
import com.l2jmobius.gameserver.network.serverpackets.AbstractHtmlPacket;
import com.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage;
import com.l2jmobius.gameserver.network.serverpackets.ShowBoard;
/**
* General Utility functions related to game server.
*/
public final class Util
{
private static final Logger LOGGER = Logger.getLogger(Util.class.getName());
private static final NumberFormat ADENA_FORMATTER = NumberFormat.getIntegerInstance(Locale.ENGLISH);
public static void handleIllegalPlayerAction(L2PcInstance actor, String message, IllegalActionPunishmentType punishment)
{
ThreadPoolManager.getInstance().scheduleGeneral(new IllegalPlayerActionTask(actor, message, punishment), 5000);
}
/**
* @param from
* @param to
* @return degree value of object 2 to the horizontal line with object 1 being the origin.
*/
public static double calculateAngleFrom(ILocational from, ILocational to)
{
return calculateAngleFrom(from.getX(), from.getY(), to.getX(), to.getY());
}
/**
* @param fromX
* @param fromY
* @param toX
* @param toY
* @return degree value of object 2 to the horizontal line with object 1 being the origin
*/
public static double calculateAngleFrom(int fromX, int fromY, int toX, int toY)
{
double angleTarget = Math.toDegrees(Math.atan2(toY - fromY, toX - fromX));
if (angleTarget < 0)
{
angleTarget = 360 + angleTarget;
}
return angleTarget;
}
/**
* Gets a random position around the specified location.
* @param loc the center location
* @param minRange the minimum range from the center to pick a point.
* @param maxRange the maximum range from the center to pick a point.
* @return a random location between minRange and maxRange of the center location.
*/
public static Location getRandomPosition(ILocational loc, int minRange, int maxRange)
{
final int randomX = Rnd.get(minRange, maxRange);
final int randomY = Rnd.get(minRange, maxRange);
final double rndAngle = Math.toRadians(Rnd.get(360));
final int newX = (int) (loc.getX() + (randomX * Math.cos(rndAngle)));
final int newY = (int) (loc.getY() + (randomY * Math.sin(rndAngle)));
return new Location(newX, newY, loc.getZ());
}
public static double convertHeadingToDegree(int clientHeading)
{
final double degree = clientHeading / 182.044444444;
return degree;
}
public static int convertDegreeToClientHeading(double degree)
{
if (degree < 0)
{
degree = 360 + degree;
}
return (int) (degree * 182.044444444);
}
public static int calculateHeadingFrom(ILocational from, ILocational to)
{
return calculateHeadingFrom(from.getX(), from.getY(), to.getX(), to.getY());
}
public static int calculateHeadingFrom(int fromX, int fromY, int toX, int toY)
{
double angleTarget = Math.toDegrees(Math.atan2(toY - fromY, toX - fromX));
if (angleTarget < 0)
{
angleTarget = 360 + angleTarget;
}
return (int) (angleTarget * 182.044444444);
}
public static int calculateHeadingFrom(double dx, double dy)
{
double angleTarget = Math.toDegrees(Math.atan2(dy, dx));
if (angleTarget < 0)
{
angleTarget = 360 + angleTarget;
}
return (int) (angleTarget * 182.044444444);
}
/**
* Calculates distance between one set of x, y, z and another set of x, y, z.
* @param x1 - X coordinate of first point.
* @param y1 - Y coordinate of first point.
* @param z1 - Z coordinate of first point.
* @param x2 - X coordinate of second point.
* @param y2 - Y coordinate of second point.
* @param z2 - Z coordinate of second point.
* @param includeZAxis - If set to true, Z coordinates will be included.
* @param squared - If set to true, distance returned will be squared.
* @return {@code double} - Distance between object and given x, y , z.
*/
public static double calculateDistance(double x1, double y1, double z1, double x2, double y2, double z2, boolean includeZAxis, boolean squared)
{
final double distance = Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2) + (includeZAxis ? Math.pow(z1 - z2, 2) : 0);
return (squared) ? distance : Math.sqrt(distance);
}
/**
* Calculates distance between 2 locations.
* @param loc1 - First location.
* @param loc2 - Second location.
* @param includeZAxis - If set to true, Z coordinates will be included.
* @param squared - If set to true, distance returned will be squared.
* @return {@code double} - Distance between object and given location.
*/
public static double calculateDistance(ILocational loc1, ILocational loc2, boolean includeZAxis, boolean squared)
{
return calculateDistance(loc1.getX(), loc1.getY(), loc1.getZ(), loc2.getX(), loc2.getY(), loc2.getZ(), includeZAxis, squared);
}
/**
* @param range
* @param obj1
* @param obj2
* @param includeZAxis
* @return {@code true} if the two objects are within specified range between each other, {@code false} otherwise
*/
public static boolean checkIfInRange(int range, L2Object obj1, L2Object obj2, boolean includeZAxis)
{
if ((obj1 == null) || (obj2 == null) || (obj1.getInstanceWorld() != obj2.getInstanceWorld()))
{
return false;
}
if (range == -1)
{
return true; // not limited
}
int radius = 0;
if (obj1.isCharacter())
{
radius += ((L2Character) obj1).getTemplate().getCollisionRadius();
}
if (obj2.isCharacter())
{
radius += ((L2Character) obj2).getTemplate().getCollisionRadius();
}
return calculateDistance(obj1, obj2, includeZAxis, false) <= (range + radius);
}
/**
* Checks if object is within short (sqrt(int.max_value)) radius, not using collisionRadius. Faster calculation than checkIfInRange if distance is short and collisionRadius isn't needed. Not for long distance checks (potential teleports, far away castles etc).
* @param range
* @param obj1
* @param obj2
* @param includeZAxis if true, check also Z axis (3-dimensional check), otherwise only 2D
* @return {@code true} if objects are within specified range between each other, {@code false} otherwise
*/
public static boolean checkIfInShortRange(int range, L2Object obj1, L2Object obj2, boolean includeZAxis)
{
if ((obj1 == null) || (obj2 == null))
{
return false;
}
if (range == -1)
{
return true; // not limited
}
return calculateDistance(obj1, obj2, includeZAxis, false) <= range;
}
/**
* Checks if the cube intersects the sphere.
* @param x1 the cube's first point x
* @param y1 the cube's first point y
* @param z1 the cube's first point z
* @param x2 the cube's second point x
* @param y2 the cube's second point y
* @param z2 the cube's second point z
* @param sX the sphere's middle x
* @param sY the sphere's middle y
* @param sZ the sphere's middle z
* @param radius the sphere's radius
* @return {@code true} if cube intersects sphere, {@code false} otherwise
*/
public static boolean cubeIntersectsSphere(int x1, int y1, int z1, int x2, int y2, int z2, int sX, int sY, int sZ, int radius)
{
double d = radius * radius;
if (sX < x1)
{
d -= Math.pow(sX - x1, 2);
}
else if (sX > x2)
{
d -= Math.pow(sX - x2, 2);
}
if (sY < y1)
{
d -= Math.pow(sY - y1, 2);
}
else if (sY > y2)
{
d -= Math.pow(sY - y2, 2);
}
if (sZ < z1)
{
d -= Math.pow(sZ - z1, 2);
}
else if (sZ > z2)
{
d -= Math.pow(sZ - z2, 2);
}
return d > 0;
}
/**
* @param text - the text to check
* @return {@code true} if {@code text} contains only numbers, {@code false} otherwise
*/
public static boolean isDigit(String text)
{
if ((text == null) || text.isEmpty())
{
return false;
}
for (char c : text.toCharArray())
{
if (!Character.isDigit(c))
{
return false;
}
}
return true;
}
/**
* @param text - the text to check
* @return {@code true} if {@code text} is integer, {@code false} otherwise
*/
public static boolean isInteger(String text)
{
if ((text == null) || text.isEmpty())
{
return false;
}
try
{
Integer.parseInt(text);
return true;
}
catch (Exception e)
{
return false;
}
}
/**
* @param text - the text to check
* @return {@code true} if {@code text} is float, {@code false} otherwise
*/
public static boolean isFloat(String text)
{
if ((text == null) || text.isEmpty())
{
return false;
}
try
{
Float.parseFloat(text);
return true;
}
catch (Exception e)
{
return false;
}
}
/**
* @param text - the text to check
* @return {@code true} if {@code text} is double, {@code false} otherwise
*/
public static boolean isDouble(String text)
{
if ((text == null) || text.isEmpty())
{
return false;
}
try
{
Double.parseDouble(text);
return true;
}
catch (Exception e)
{
return false;
}
}
/**
* @param <T>
* @param name - the text to check
* @param enumType
* @return {@code true} if {@code text} is enum, {@code false} otherwise
*/
public static <T extends Enum<T>> boolean isEnum(String name, Class<T> enumType)
{
if ((name == null) || name.isEmpty())
{
return false;
}
try
{
return Enum.valueOf(enumType, name) != null;
}
catch (Exception e)
{
return false;
}
}
/**
* @param text - the text to check
* @return {@code true} if {@code text} contains only letters and/or numbers, {@code false} otherwise
*/
public static boolean isAlphaNumeric(String text)
{
if ((text == null) || text.isEmpty())
{
return false;
}
for (char c : text.toCharArray())
{
if (!Character.isLetterOrDigit(c))
{
return false;
}
}
return true;
}
/**
* Format the specified digit using the digit grouping symbol "," (comma).<br>
* For example, 123456789 becomes 123,456,789.
* @param amount - the amount of adena
* @return the formatted adena amount
*/
public static String formatAdena(long amount)
{
synchronized (ADENA_FORMATTER)
{
return ADENA_FORMATTER.format(amount);
}
}
/**
* @param val
* @param format
* @return formatted double value by specified format.
*/
public static String formatDouble(double val, String format)
{
final DecimalFormat formatter = new DecimalFormat(format, new DecimalFormatSymbols(Locale.ENGLISH));
return formatter.format(val);
}
/**
* Format the given date on the given format
* @param date : the date to format.
* @param format : the format to correct by.
* @return a string representation of the formatted date.
*/
public static String formatDate(Date date, String format)
{
if (date == null)
{
return null;
}
final DateFormat dateFormat = new SimpleDateFormat(format);
return dateFormat.format(date);
}
public static File[] getDatapackFiles(String dirname, String extention)
{
final File dir = new File(Config.DATAPACK_ROOT, "data/" + dirname);
if (!dir.exists())
{
return null;
}
return dir.listFiles(new ExtFilter(extention));
}
public static String getDateString(Date date)
{
final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
return dateFormat.format(date.getTime());
}
private static void buildHtmlBypassCache(L2PcInstance player, HtmlActionScope scope, String html)
{
final String htmlLower = html.toLowerCase(Locale.ENGLISH);
int bypassEnd = 0;
int bypassStart = htmlLower.indexOf("=\"bypass ", bypassEnd);
int bypassStartEnd;
while (bypassStart != -1)
{
bypassStartEnd = bypassStart + 9;
bypassEnd = htmlLower.indexOf("\"", bypassStartEnd);
if (bypassEnd == -1)
{
break;
}
final int hParamPos = htmlLower.indexOf("-h ", bypassStartEnd);
String bypass;
if ((hParamPos != -1) && (hParamPos < bypassEnd))
{
bypass = html.substring(hParamPos + 3, bypassEnd).trim();
}
else
{
bypass = html.substring(bypassStartEnd, bypassEnd).trim();
}
final int firstParameterStart = bypass.indexOf(AbstractHtmlPacket.VAR_PARAM_START_CHAR);
if (firstParameterStart != -1)
{
bypass = bypass.substring(0, firstParameterStart + 1);
}
if (Config.HTML_ACTION_CACHE_DEBUG)
{
LOGGER.info("Cached html bypass(" + scope + "): '" + bypass + "'");
}
player.addHtmlAction(scope, bypass);
bypassStart = htmlLower.indexOf("=\"bypass ", bypassEnd);
}
}
private static void buildHtmlLinkCache(L2PcInstance player, HtmlActionScope scope, String html)
{
final String htmlLower = html.toLowerCase(Locale.ENGLISH);
int linkEnd = 0;
int linkStart = htmlLower.indexOf("=\"link ", linkEnd);
int linkStartEnd;
while (linkStart != -1)
{
linkStartEnd = linkStart + 7;
linkEnd = htmlLower.indexOf("\"", linkStartEnd);
if (linkEnd == -1)
{
break;
}
final String htmlLink = html.substring(linkStartEnd, linkEnd).trim();
if (htmlLink.isEmpty())
{
LOGGER.warning("Html link path is empty!");
continue;
}
if (htmlLink.contains(".."))
{
LOGGER.warning("Html link path is invalid: " + htmlLink);
continue;
}
if (Config.HTML_ACTION_CACHE_DEBUG)
{
LOGGER.info("Cached html link(" + scope + "): '" + htmlLink + "'");
}
// let's keep an action cache with "link " lowercase literal kept
player.addHtmlAction(scope, "link " + htmlLink);
linkStart = htmlLower.indexOf("=\"link ", linkEnd);
}
}
/**
* Builds the html action cache for the specified scope.<br>
* An {@code npcObjId} of 0 means, the cached actions can be clicked<br>
* without beeing near an npc which is spawned in the world.
* @param player the player to build the html action cache for
* @param scope the scope to build the html action cache for
* @param npcObjId the npc object id the html actions are cached for
* @param html the html code to parse
*/
public static void buildHtmlActionCache(L2PcInstance player, HtmlActionScope scope, int npcObjId, String html)
{
if ((player == null) || (scope == null) || (npcObjId < 0) || (html == null))
{
throw new IllegalArgumentException();
}
if (Config.HTML_ACTION_CACHE_DEBUG)
{
LOGGER.info("Set html action npc(" + scope + "): " + npcObjId);
}
player.setHtmlActionOriginObjectId(scope, npcObjId);
buildHtmlBypassCache(player, scope, html);
buildHtmlLinkCache(player, scope, html);
}
/**
* Helper method to send a NpcHtmlMessage to the specified player.
* @param activeChar the player to send the html content to
* @param html the html content
*/
public static void sendHtml(L2PcInstance activeChar, String html)
{
final NpcHtmlMessage npcHtml = new NpcHtmlMessage();
npcHtml.setHtml(html);
activeChar.sendPacket(npcHtml);
}
/**
* Helper method to send a community board html to the specified player.<br>
* HtmlActionCache will be build with npc origin 0 which means the<br>
* links on the html are not bound to a specific npc.
* @param activeChar the player
* @param html the html content
*/
public static void sendCBHtml(L2PcInstance activeChar, String html)
{
sendCBHtml(activeChar, html, 0);
}
/**
* Helper method to send a community board html to the specified player.<br>
* When {@code npcObjId} is greater -1 the HtmlActionCache will be build<br>
* with the npcObjId as origin. An origin of 0 means the cached bypasses<br>
* are not bound to a specific npc.
* @param activeChar the player to send the html content to
* @param html the html content
* @param npcObjId bypass origin to use
*/
public static void sendCBHtml(L2PcInstance activeChar, String html, int npcObjId)
{
sendCBHtml(activeChar, html, null, npcObjId);
}
/**
* Helper method to send a community board html to the specified player.<br>
* HtmlActionCache will be build with npc origin 0 which means the<br>
* links on the html are not bound to a specific npc. It also fills a<br>
* multiedit field in the send html if fillMultiEdit is not null.
* @param activeChar the player
* @param html the html content
* @param fillMultiEdit text to fill the multiedit field with(may be null)
*/
public static void sendCBHtml(L2PcInstance activeChar, String html, String fillMultiEdit)
{
sendCBHtml(activeChar, html, fillMultiEdit, 0);
}
/**
* Helper method to send a community board html to the specified player.<br>
* It fills a multiedit field in the send html if {@code fillMultiEdit}<br>
* is not null. When {@code npcObjId} is greater -1 the HtmlActionCache will be build<br>
* with the npcObjId as origin. An origin of 0 means the cached bypasses<br>
* are not bound to a specific npc.
* @param activeChar the player
* @param html the html content
* @param fillMultiEdit text to fill the multiedit field with(may be null)
* @param npcObjId bypass origin to use
*/
public static void sendCBHtml(L2PcInstance activeChar, String html, String fillMultiEdit, int npcObjId)
{
if ((activeChar == null) || (html == null))
{
return;
}
activeChar.clearHtmlActions(HtmlActionScope.COMM_BOARD_HTML);
if (npcObjId > -1)
{
buildHtmlActionCache(activeChar, HtmlActionScope.COMM_BOARD_HTML, npcObjId, html);
}
if (fillMultiEdit != null)
{
activeChar.sendPacket(new ShowBoard(html, "1001"));
fillMultiEditContent(activeChar, fillMultiEdit);
}
else if (html.length() < 16250)
{
activeChar.sendPacket(new ShowBoard(html, "101"));
activeChar.sendPacket(new ShowBoard(null, "102"));
activeChar.sendPacket(new ShowBoard(null, "103"));
}
else if (html.length() < (16250 * 2))
{
activeChar.sendPacket(new ShowBoard(html.substring(0, 16250), "101"));
activeChar.sendPacket(new ShowBoard(html.substring(16250), "102"));
activeChar.sendPacket(new ShowBoard(null, "103"));
}
else if (html.length() < (16250 * 3))
{
activeChar.sendPacket(new ShowBoard(html.substring(0, 16250), "101"));
activeChar.sendPacket(new ShowBoard(html.substring(16250, 16250 * 2), "102"));
activeChar.sendPacket(new ShowBoard(html.substring(16250 * 2), "103"));
}
else
{
activeChar.sendPacket(new ShowBoard("<html><body><br><center>Error: HTML was too long!</center></body></html>", "101"));
activeChar.sendPacket(new ShowBoard(null, "102"));
activeChar.sendPacket(new ShowBoard(null, "103"));
}
}
/**
* Fills the community board's multiedit window with text. Must send after sendCBHtml
* @param activeChar
* @param text
*/
public static void fillMultiEditContent(L2PcInstance activeChar, String text)
{
activeChar.sendPacket(new ShowBoard(Arrays.asList("0", "0", "0", "0", "0", "0", activeChar.getName(), Integer.toString(activeChar.getObjectId()), activeChar.getAccountName(), "9", " ", " ", text.replaceAll("<br>", Config.EOL), "0", "0", "0", "0")));
}
public static boolean isInsideRangeOfObjectId(L2Object obj, int targetObjId, int radius)
{
final L2Object target = L2World.getInstance().findObject(targetObjId);
return (target != null) && (obj.calculateDistance(target, true, false) <= radius);
}
public static String readAllLines(File file, Charset cs, String newLineDelimiter) throws IOException
{
final StringBuilder sb = new StringBuilder();
try (InputStream in = new FileInputStream(file);
final InputStreamReader reader = new InputStreamReader(in, cs);
final BufferedReader buffer = new BufferedReader(reader))
{
String line;
while ((line = buffer.readLine()) != null)
{
sb.append(line);
if (newLineDelimiter != null)
{
sb.append(newLineDelimiter);
}
}
}
return sb.toString();
}
/**
* Re-Maps a value from one range to another.
* @param input
* @param inputMin
* @param inputMax
* @param outputMin
* @param outputMax
* @return The mapped value
*/
public static int map(int input, int inputMin, int inputMax, int outputMin, int outputMax)
{
input = constrain(input, inputMin, inputMax);
return (((input - inputMin) * (outputMax - outputMin)) / (inputMax - inputMin)) + outputMin;
}
/**
* Re-Maps a value from one range to another.
* @param input
* @param inputMin
* @param inputMax
* @param outputMin
* @param outputMax
* @return The mapped value
*/
public static long map(long input, long inputMin, long inputMax, long outputMin, long outputMax)
{
input = constrain(input, inputMin, inputMax);
return (((input - inputMin) * (outputMax - outputMin)) / (inputMax - inputMin)) + outputMin;
}
/**
* Re-Maps a value from one range to another.
* @param input
* @param inputMin
* @param inputMax
* @param outputMin
* @param outputMax
* @return The mapped value
*/
public static double map(double input, double inputMin, double inputMax, double outputMin, double outputMax)
{
input = constrain(input, inputMin, inputMax);
return (((input - inputMin) * (outputMax - outputMin)) / (inputMax - inputMin)) + outputMin;
}
/**
* Constrains a number to be within a range.
* @param input the number to constrain, all data types
* @param min the lower end of the range, all data types
* @param max the upper end of the range, all data types
* @return input: if input is between min and max, min: if input is less than min, max: if input is greater than max
*/
public static int constrain(int input, int min, int max)
{
return (input < min) ? min : (input > max) ? max : input;
}
/**
* Constrains a number to be within a range.
* @param input the number to constrain, all data types
* @param min the lower end of the range, all data types
* @param max the upper end of the range, all data types
* @return input: if input is between min and max, min: if input is less than min, max: if input is greater than max
*/
public static long constrain(long input, long min, long max)
{
return (input < min) ? min : (input > max) ? max : input;
}
/**
* Constrains a number to be within a range.
* @param input the number to constrain, all data types
* @param min the lower end of the range, all data types
* @param max the upper end of the range, all data types
* @return input: if input is between min and max, min: if input is less than min, max: if input is greater than max
*/
public static double constrain(double input, double min, double max)
{
return (input < min) ? min : (input > max) ? max : input;
}
}

View File

@@ -0,0 +1,35 @@
/*
* cron4j - A pure Java cron-like scheduler
*
* Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version
* 2.1, as published by the Free Software Foundation.
*
* 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 Lesser General Public License 2.1 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License version 2.1 along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util.cron4j;
/**
* This ValueMatcher always returns true!
* @author Carlo Pelliccia
*/
class AlwaysTrueValueMatcher implements ValueMatcher
{
/**
* Always true!
*/
@Override
public boolean match(int value)
{
return true;
}
}

View File

@@ -0,0 +1,606 @@
/*
* cron4j - A pure Java cron-like scheduler
*
* Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version
* 2.1, as published by the Free Software Foundation.
*
* 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 Lesser General Public License 2.1 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License version 2.1 along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util.cron4j;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.util.ArrayList;
/**
* <p>
* A parser for crontab-like formatted files and streams.
* </p>
* <p>
* If you want to schedule a list of tasks declared in a crontab-like file you don't need the CronParser, since you can do it by adding the file to the scheduler, with the {@link Scheduler#scheduleFile(File)} method.
* </p>
* <p>
* Consider to use the CronParser if the {@link Scheduler#scheduleFile(File)} method is not enough for you. In example, you may need to fetch the task list from a remote source which is not representable as a {@link File} object (a document on a remote server, a DBMS result set and so on). To solve
* the problem you can implement your own {@link TaskCollector}, getting the advantage of the CronParser to parse easily any crontab-like content.
* </p>
* <p>
* You can parse a whole file/stream, but you can also parse a single line.
* </p>
* <p>
* A line can be empty, can contain a comment or it can be a scheduling line.
* </p>
* <p>
* A line containing no characters or a line with only space characters is considered an empty line.
* </p>
* <p>
* A line whose first non-space character is a number sign (#) is considered a comment.
* </p>
* <p>
* Empty lines and comment lines are ignored by the parser.
* </p>
* <p>
* Any other kind of line is parsed as a scheduling line.
* </p>
* <p>
* A valid scheduling line respects the following structure:
* </p>
*
* <pre>
* scheduling-pattern [options] command [args]
* </pre>
* <ul>
* <li><em>scheduling-pattern</em> is a valid scheduling pattern, according with the definition given by the {@link SchedulingPattern} class.</li>
* <li><em>options</em> is a list of optional informations used by cron4j to prepare the task execution environment. See below for a more detailed description.</li>
* <li><em>command</em> is a system valid command, such an executable call.</li>
* <li><em>args</em> is a list of optional arguments for the command.</li>
* </ul>
* <p>
* After the scheduling pattern item, other tokens in each line are space separated or delimited with double quotation marks (&quot;).
* </p>
* <p>
* Double quotation marks delimited items can take advantage of the following escape sequences:
* </p>
* <ul>
* <li>\&quot; - quotation mark</li>
* <li>\\ - back slash</li>
* <li>\/ - slash</li>
* <li>\b - back space</li>
* <li>\f - form feed</li>
* <li>\n - new line</li>
* <li>\r - carriage return</li>
* <li>\t - horizontal tab</li>
* <li>\u005c\u0075<em>four-hex-digits</em> - the character at the given unicode index</li>
* </ul>
* <p>
* The <em>options</em> token collection can include one or more of the following elements:
* </p>
* <ul>
* <li>IN:<em>file-path</em> - Redirects the command standard input channel to the specified file.</li>
* <li>OUT:<em>file-path</em> - Redirects the command standard output channel to the specified file.</li>
* <li>ERR:<em>file-path</em> - Redirects the command standard error channel to the specified file.</li>
* <li>ENV:<em>name</em>=<em>value</em> - Defines an environment variable in the scope of the command.</li>
* <li>DIR:<em>directory-path</em> - Sets the path of the working directory for the command. This feature is not supported if the executing JVM is less than 1.3.</li>
* </ul>
* <p>
* It is also possible to schedule the invocation of a method of a Java class in the scope of the parser ClassLoader. The method has to be static and it must accept an array of strings as its sole argument. To invoke a method of this kind the syntax is:
* </p>
*
* <pre>
* scheduling-pattern java:className#methodName [args]
* </pre>
* <p>
* The <em>#methodName</em> part can be omitted: in this case the <em>main(String[])</em> method will be assumed.
* </p>
* <p>
* Please note that static methods are invoked within the scheduler same JVM, without spawning any external process. Thus IN, OUT, ERR, ENV and DIR options can't be applied.
* </p>
* <p>
* Invalid scheduling lines are discarded without blocking the parsing procedure, but an error message is printed in the application standard error channel.
* </p>
* <p>
* Valid examples:
* </p>
*
* <pre>
* 0 5 * * * sol.exe
* 0,30 * * * * OUT:C:\ping.txt ping 10.9.43.55
* 0,30 4 * * * &quot;OUT:C:\Documents and Settings\Carlo\ping.txt&quot; ping 10.9.43.55
* 0 3 * * * ENV:JAVA_HOME=C:\jdks\1.4.2_15 DIR:C:\myproject OUT:C:\myproject\build.log C:\myproject\build.bat &quot;Nightly Build&quot;
* 0 4 * * * java:mypackage.MyClass#startApplication myOption1 myOption2
* </pre>
*
* @author Carlo Pelliccia
* @since 2.0
*/
public class CronParser
{
/**
* Instantiation prohibited.
*/
private CronParser()
{
}
/**
* <p>
* Builds a task list reading it from a file.
* </p>
* <p>
* The file is treated as UTF-8. If your source file is not UTF-8 encoded establish by yourself a {@link Reader} using the right charset and pass it to the {@link CronParser#parse(Reader)} method.
* </p>
* <p>
* Syntax and semantics errors in the source file are not blocking. Invalid lines are discarded, and they cause just a stack trace to be printed in the standard error channel as a notification.
* </p>
* @param file The file.
* @return The task table parsed from the file.
* @throws IOException I/O error.
*/
public static TaskTable parse(File file) throws IOException
{
InputStream stream = null;
try
{
stream = new FileInputStream(file);
return parse(stream);
}
finally
{
if (stream != null)
{
try
{
stream.close();
}
catch (Throwable t)
{
}
}
}
}
/**
* <p>
* Builds a task list reading it from an URL.
* </p>
* <p>
* Contents fetched from the URL are treated as UTF-8. If your source is not UTF-8 encoded establish by yourself a {@link Reader} using the right charset and pass it to the {@link CronParser#parse(Reader)} method.
* </p>
* <p>
* Syntax and semantics errors in the retrieved document are not blocking. Invalid lines are discarded, and they cause just a stack trace to be printed in the standard error channel as a notification.
* </p>
* @param url The URL.
* @return The task table parsed from the contents fetched from the given URL.
* @throws IOException I/O error.
*/
public static TaskTable parse(URL url) throws IOException
{
InputStream stream = null;
try
{
stream = url.openStream();
return parse(stream);
}
finally
{
if (stream != null)
{
try
{
stream.close();
}
catch (Throwable t)
{
}
}
}
}
/**
* <p>
* Builds a task list reading it from an input stream.
* </p>
* <p>
* The stream is treated as UTF-8. If your source is not UTF-8 encoded establish by yourself a {@link Reader} using the right charset and pass it to the {@link CronParser#parse(Reader)} method.
* </p>
* <p>
* Syntax and semantics errors in the source stream are not blocking. Invalid lines are discarded, and they cause just a stack trace to be printed in the standard error channel as a notification.
* </p>
* @param stream The input stream.
* @return The task table parsed from the stream contents.
* @throws IOException I/O error.
*/
public static TaskTable parse(InputStream stream) throws IOException
{
return parse(new InputStreamReader(stream, "UTF-8"));
}
/**
* <p>
* Builds a task list reading it from a reader.
* </p>
* <p>
* Syntax and semantics errors in the source reader are not blocking. Invalid lines are discarded, and they cause just a stack trace to be printed in the standard error channel as a notification.
* </p>
* @param reader The reader.
* @return The task table parsed from the contents in the reader.
* @throws IOException I/O error.
*/
public static TaskTable parse(Reader reader) throws IOException
{
TaskTable table = new TaskTable();
BufferedReader bufferedReader = new BufferedReader(reader);
try
{
String line;
while ((line = bufferedReader.readLine()) != null)
{
try
{
parseLine(table, line);
}
catch (Exception e)
{
e.printStackTrace();
continue;
}
}
}
finally
{
reader.close();
}
return table;
}
/**
* Parses a crontab-like line.
* @param table The table on which the parsed task will be stored, by side-effect.
* @param line The crontab-like line.
* @throws Exception The supplied line doesn't represent a valid task line.
*/
public static void parseLine(TaskTable table, String line) throws Exception
{
line = line.trim();
if ((line.length() == 0) || (line.charAt(0) == '#'))
{
return;
}
// Detecting the pattern.
int size = line.length();
String pattern = null;
for (int i = size; i >= 0; i--)
{
String aux = line.substring(0, i);
if (SchedulingPattern.validate(aux))
{
pattern = aux;
break;
}
}
if (pattern == null)
{
throw new Exception("Invalid cron line: " + line);
}
line = line.substring(pattern.length());
size = line.length();
// Splitting the line
ArrayList<String> splitted = new ArrayList<>();
StringBuffer current = null;
boolean quotes = false;
for (int i = 0; i < size; i++)
{
char c = line.charAt(i);
if (current == null)
{
if (c == '"')
{
current = new StringBuffer();
quotes = true;
}
else if (c > ' ')
{
current = new StringBuffer();
current.append(c);
quotes = false;
}
}
else
{
boolean closeCurrent;
if (quotes)
{
closeCurrent = (c == '"');
}
else
{
closeCurrent = (c <= ' ');
}
if (closeCurrent)
{
if (current.length() > 0)
{
String str = current.toString();
if (quotes)
{
str = escape(str);
}
splitted.add(str);
}
current = null;
}
else
{
current.append(c);
}
}
}
if ((current != null) && (current.length() > 0))
{
String str = current.toString();
if (quotes)
{
str = escape(str);
}
splitted.add(str);
current = null;
}
// Analyzing
size = splitted.size();
int status = 0;
// Status values:
// 0 -> fetching environment variables, working directory and channels
// 1 -> fetching the command and its arguments
String dirString = null;
File stdinFile = null;
File stdoutFile = null;
File stderrFile = null;
ArrayList<String> envsList = new ArrayList<>();
String command = null;
ArrayList<String> argsList = new ArrayList<>();
for (int i = 0; i < size; i++)
{
String tk = splitted.get(i);
// Check the local status.
if (status == 0)
{
// Environment variables, working directory and channels
if (tk.startsWith("ENV:"))
{
envsList.add(tk.substring(4));
continue;
}
else if (tk.startsWith("DIR:"))
{
dirString = tk.substring(4);
continue;
}
else if (tk.startsWith("IN:"))
{
stdinFile = new File(tk.substring(3));
continue;
}
else if (tk.startsWith("OUT:"))
{
stdoutFile = new File(tk.substring(4));
continue;
}
else if (tk.startsWith("ERR:"))
{
stderrFile = new File(tk.substring(4));
continue;
}
else
{
status = 1;
}
}
if (status == 1)
{
// Command or argument?
if (command == null)
{
command = tk;
}
else
{
argsList.add(tk);
}
}
}
// Task preparing.
Task task;
// Command evaluation.
if (command == null)
{
// No command!
throw new Exception("Invalid cron line: " + line);
}
else if (command.startsWith("java:"))
{
// Java inner-process.
String className = command.substring(5);
if (className.length() == 0)
{
throw new Exception("Invalid Java class name on line: " + line);
}
String methodName;
int sep = className.indexOf('#');
if (sep == -1)
{
methodName = "main";
}
else
{
methodName = className.substring(sep + 1);
className = className.substring(0, sep);
if (methodName.length() == 0)
{
throw new Exception("Invalid Java method name on line: " + line);
}
}
String[] args = new String[argsList.size()];
for (int i = 0; i < argsList.size(); i++)
{
args[i] = argsList.get(i);
}
task = new StaticMethodTask(className, methodName, args);
}
else
{
// External command.
String[] cmdarray = new String[1 + argsList.size()];
cmdarray[0] = command;
for (int i = 0; i < argsList.size(); i++)
{
cmdarray[i + 1] = argsList.get(i);
}
// Environments.
String[] envs = null;
size = envsList.size();
if (size > 0)
{
envs = new String[size];
for (int i = 0; i < size; i++)
{
envs[i] = envsList.get(i);
}
}
// Working directory.
File dir = null;
if (dirString != null)
{
dir = new File(dirString);
if (!dir.exists() || !dir.isDirectory())
{
throw new Exception("Invalid cron working directory parameter at line: " + line, new FileNotFoundException(dirString + " doesn't exist or it is not a directory"));
}
}
// Builds the task.
ProcessTask process = new ProcessTask(cmdarray, envs, dir);
// Channels.
if (stdinFile != null)
{
process.setStdinFile(stdinFile);
}
if (stdoutFile != null)
{
process.setStdoutFile(stdoutFile);
}
if (stderrFile != null)
{
process.setStderrFile(stderrFile);
}
task = process;
}
// End.
table.add(new SchedulingPattern(pattern), task);
}
/**
* Escapes special chars occurrences.
* @param str The input stream.
* @return The decoded output stream.
*/
private static String escape(String str)
{
int size = str.length();
StringBuffer b = new StringBuffer();
for (int i = 0; i < size; i++)
{
int skip = 0;
char c = str.charAt(i);
if (c == '\\')
{
if (i < (size - 1))
{
char d = str.charAt(i + 1);
if (d == '"')
{
b.append('"');
skip = 2;
}
else if (d == '\\')
{
b.append('\\');
skip = 2;
}
else if (d == '/')
{
b.append('/');
skip = 2;
}
else if (d == 'b')
{
b.append('\b');
skip = 2;
}
else if (d == 'f')
{
b.append('\f');
skip = 2;
}
else if (d == 'n')
{
b.append('\n');
skip = 2;
}
else if (d == 'r')
{
b.append('\r');
skip = 2;
}
else if (d == 't')
{
b.append('\t');
skip = 2;
}
else if (d == 'u')
{
if (i < (size - 5))
{
String hex = str.substring(i + 2, i + 6);
try
{
int code = Integer.parseInt(hex, 16);
if (code >= 0)
{
b.append((char) code);
skip = 6;
}
}
catch (NumberFormatException e)
{
}
}
}
}
}
if (skip == 0)
{
b.append(c);
}
else
{
i += (skip - 1);
}
}
return b.toString();
}
}

View File

@@ -0,0 +1,76 @@
/*
* cron4j - A pure Java cron-like scheduler
*
* Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version
* 2.1, as published by the Free Software Foundation.
*
* 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 Lesser General Public License 2.1 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License version 2.1 along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util.cron4j;
import java.util.ArrayList;
/**
* <p>
* A ValueMatcher whose rules are in a plain array of integer values. When asked to validate a value, this ValueMatcher checks if it is in the array and, if not, checks whether the last-day-of-month setting applies.
* </p>
* @author Paul Fernley
*/
class DayOfMonthValueMatcher extends IntArrayValueMatcher
{
private static final int[] lastDays =
{
31,
28,
31,
30,
31,
30,
31,
31,
30,
31,
30,
31
};
/**
* Builds the ValueMatcher.
* @param values An ArrayList of Integer elements, one for every value accepted by the matcher. The match() method will return true only if its parameter will be one of this list or the last-day-of-month setting applies.
*/
public DayOfMonthValueMatcher(ArrayList<?> values)
{
super(values);
}
/**
* Returns true if the given value is included in the matcher list or the last-day-of-month setting applies.
* @param value
* @param month
* @param isLeapYear
* @return
*/
public boolean match(int value, int month, boolean isLeapYear)
{
return (super.match(value) || ((value > 27) && match(32) && isLastDayOfMonth(value, month, isLeapYear)));
}
public boolean isLastDayOfMonth(int value, int month, boolean isLeapYear)
{
if (isLeapYear && (month == 2))
{
return value == 29;
}
return value == lastDays[month - 1];
}
}

View File

@@ -0,0 +1,104 @@
/*
* cron4j - A pure Java cron-like scheduler
*
* Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version
* 2.1, as published by the Free Software Foundation.
*
* 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 Lesser General Public License 2.1 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License version 2.1 along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util.cron4j;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
/**
* <p>
* A {@link TaskCollector} implementation, reading the task list from a group of files.
* </p>
* @author Carlo Pelliccia
* @since 2.0
*/
class FileTaskCollector implements TaskCollector
{
/**
* File list.
*/
private final ArrayList<File> files = new ArrayList<>();
/**
* Adds a file.
* @param file The file.
*/
public synchronized void addFile(File file)
{
files.add(file);
}
/**
* Removes a file.
* @param file The file.
*/
public synchronized void removeFile(File file)
{
files.remove(file);
}
/**
* Returns the file list.
* @return The file list.
*/
public synchronized File[] getFiles()
{
int size = files.size();
File[] ret = new File[size];
for (int i = 0; i < size; i++)
{
ret[i] = files.get(i);
}
return ret;
}
/**
* Implements {@link TaskCollector#getTasks()}.
*/
@Override
public synchronized TaskTable getTasks()
{
TaskTable ret = new TaskTable();
int size = files.size();
for (int i = 0; i < size; i++)
{
File f = files.get(i);
TaskTable aux = null;
try
{
aux = CronParser.parse(f);
}
catch (IOException e)
{
Exception e1 = new Exception("Cannot parse cron file: " + f.getAbsolutePath(), e);
e1.printStackTrace();
}
if (aux != null)
{
int auxSize = aux.size();
for (int j = 0; j < auxSize; j++)
{
ret.add(aux.getSchedulingPattern(j), aux.getTask(j));
}
}
}
return ret;
}
}

View File

@@ -0,0 +1,244 @@
/*
* cron4j - A pure Java cron-like scheduler
*
* Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version
* 2.1, as published by the Free Software Foundation.
*
* 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 Lesser General Public License 2.1 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License version 2.1 along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util.cron4j;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.Enumeration;
/**
* A GUID generator.
* @author Carlo Pelliccia
* @since 2.0
*/
class GUIDGenerator
{
/**
* The machine descriptor, which is used to identified the underlying hardware machine.
*/
private static String MACHINE_DESCRIPTOR = getMachineDescriptor();
/**
* Generates a GUID (48 chars).
* @return The generated GUID.
*/
public static String generate()
{
StringBuffer id = new StringBuffer();
encode(id, MACHINE_DESCRIPTOR);
encode(id, Runtime.getRuntime());
encode(id, Thread.currentThread());
encode(id, System.currentTimeMillis());
encode(id, getRandomInt());
return id.toString();
}
/**
* Calculates a machine id, as an integer value.
* @return The calculated machine id.
*/
private static String getMachineDescriptor()
{
StringBuffer descriptor = new StringBuffer();
descriptor.append(System.getProperty("os.name"));
descriptor.append("::");
descriptor.append(System.getProperty("os.arch"));
descriptor.append("::");
descriptor.append(System.getProperty("os.version"));
descriptor.append("::");
descriptor.append(System.getProperty("user.name"));
descriptor.append("::");
StringBuffer b = buildNetworkInterfaceDescriptor();
if (b != null)
{
descriptor.append(b);
}
else
{
// plain old InetAddress...
InetAddress addr;
try
{
addr = InetAddress.getLocalHost();
descriptor.append(addr.getHostAddress());
}
catch (UnknownHostException e)
{
}
}
return descriptor.toString();
}
/**
* Builds a descriptor fragment using the {@link NetworkInterface} class, available since Java 1.4.
* @return A descriptor fragment, or null if the method fails.
*/
private static StringBuffer buildNetworkInterfaceDescriptor()
{
Enumeration<?> e1;
try
{
e1 = NetworkInterface.getNetworkInterfaces();
}
catch (Throwable t)
{
// not available
return null;
}
StringBuffer b = new StringBuffer();
while (e1.hasMoreElements())
{
NetworkInterface ni = (NetworkInterface) e1.nextElement();
StringBuffer b1 = getMACAddressDescriptor(ni);
StringBuffer b2 = getInetAddressDescriptor(ni);
StringBuffer b3 = new StringBuffer();
if (b1 != null)
{
b3.append(b1);
}
if (b2 != null)
{
if (b3.length() > 0)
{
b3.append('=');
}
b3.append(b2);
}
if (b3.length() > 0)
{
if (b.length() > 0)
{
b.append(';');
}
b.append(b3);
}
}
return b;
}
/**
* Builds a descriptor fragment using the machine MAC address.
* @param ni NetworkInterface
* @return A descriptor fragment, or null if the method fails.
*/
private static StringBuffer getMACAddressDescriptor(NetworkInterface ni)
{
byte[] haddr;
try
{
haddr = ni.getHardwareAddress();
}
catch (Throwable t)
{
// not available.
haddr = null;
}
StringBuffer b = new StringBuffer();
if (haddr != null)
{
for (byte element : haddr)
{
if (b.length() > 0)
{
b.append("-");
}
String hex = Integer.toHexString(0xff & element);
if (hex.length() == 1)
{
b.append('0');
}
b.append(hex);
}
}
return b;
}
/**
* Builds a descriptor fragment using the machine inet address.
* @param ni NetworkInterface
* @return A descriptor fragment, or null if the method fails.
*/
private static StringBuffer getInetAddressDescriptor(NetworkInterface ni)
{
StringBuffer b = new StringBuffer();
Enumeration<?> e2 = ni.getInetAddresses();
while (e2.hasMoreElements())
{
InetAddress addr = (InetAddress) e2.nextElement();
if (b.length() > 0)
{
b.append(',');
}
b.append(addr.getHostAddress());
}
return b;
}
/**
* Returns a random integer value.
* @return A random integer value.
*/
private static int getRandomInt()
{
return (int) Math.round((Math.random() * Integer.MAX_VALUE));
}
/**
* Encodes an object and appends it to the buffer.
* @param b The buffer.
* @param obj The object.
*/
private static void encode(StringBuffer b, Object obj)
{
encode(b, obj.hashCode());
}
/**
* Encodes an integer value and appends it to the buffer.
* @param b The buffer.
* @param value The value.
*/
private static void encode(StringBuffer b, int value)
{
String hex = Integer.toHexString(value);
int hexSize = hex.length();
for (int i = 8; i > hexSize; i--)
{
b.append('0');
}
b.append(hex);
}
/**
* Encodes a long value and appends it to the buffer.
* @param b The buffer.
* @param value The value.
*/
private static void encode(StringBuffer b, long value)
{
String hex = Long.toHexString(value);
int hexSize = hex.length();
for (int i = 16; i > hexSize; i--)
{
b.append('0');
}
b.append(hex);
}
}

View File

@@ -0,0 +1,74 @@
/*
* cron4j - A pure Java cron-like scheduler
*
* Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version
* 2.1, as published by the Free Software Foundation.
*
* 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 Lesser General Public License 2.1 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License version 2.1 along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util.cron4j;
import java.util.ArrayList;
/**
* <p>
* A ValueMatcher whose rules are in a plain array of integer values. When asked to validate a value, this ValueMatcher checks if it is in the array.
* </p>
* @author Carlo Pelliccia
*/
class IntArrayValueMatcher implements ValueMatcher
{
/**
* The accepted values.
*/
private final int[] values;
/**
* Builds the ValueMatcher.
* @param integers An ArrayList of Integer elements, one for every value accepted by the matcher. The match() method will return true only if its parameter will be one of this list.
*/
public IntArrayValueMatcher(ArrayList<?> integers)
{
int size = integers.size();
values = new int[size];
for (int i = 0; i < size; i++)
{
try
{
values[i] = ((Integer) integers.get(i)).intValue();
}
catch (Exception e)
{
throw new IllegalArgumentException(e.getMessage());
}
}
}
/**
* Returns true if the given value is included in the matcher list.
* @param value
* @return
*/
@Override
public boolean match(int value)
{
for (int value2 : values)
{
if (value2 == value)
{
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,44 @@
/*
* cron4j - A pure Java cron-like scheduler
*
* Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version
* 2.1, as published by the Free Software Foundation.
*
* 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 Lesser General Public License 2.1 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License version 2.1 along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util.cron4j;
/**
* <p>
* This kind of exception is thrown if an invalid scheduling pattern is encountered by the scheduler.
* </p>
* @author Carlo Pelliccia
*/
public class InvalidPatternException extends RuntimeException
{
/**
* Package-reserved construction.
*/
InvalidPatternException()
{
}
/**
* Package-reserved construction.
* @param message String
*/
InvalidPatternException(String message)
{
super(message);
}
}

View File

@@ -0,0 +1,100 @@
/*
* cron4j - A pure Java cron-like scheduler
*
* Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version
* 2.1, as published by the Free Software Foundation.
*
* 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 Lesser General Public License 2.1 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License version 2.1 along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util.cron4j;
/**
* LauncherThreads are used by {@link Scheduler} instances. A LauncherThread retrieves a list of task from a set of {@link TaskCollector}s. Then it launches, within a separate {@link TaskExecutor}, every retrieved task whose scheduling pattern matches the given reference time.
* @author Carlo Pelliccia
* @since 2.0
*/
class LauncherThread extends Thread
{
/**
* A GUID for this object.
*/
private final String guid = GUIDGenerator.generate();
/**
* The owner scheduler.
*/
private final Scheduler scheduler;
/**
* Task collectors, used to retrieve registered tasks.
*/
private final TaskCollector[] collectors;
/**
* A reference time for task launching.
*/
private final long referenceTimeInMillis;
/**
* Builds the launcher.
* @param scheduler The owner scheduler.
* @param collectors Task collectors, used to retrieve registered tasks.
* @param referenceTimeInMillis A reference time for task launching.
*/
public LauncherThread(Scheduler scheduler, TaskCollector[] collectors, long referenceTimeInMillis)
{
this.scheduler = scheduler;
this.collectors = collectors;
this.referenceTimeInMillis = referenceTimeInMillis;
// Thread name.
String name = "cron4j::scheduler[" + scheduler.getGuid() + "]::launcher[" + guid + "]";
setName(name);
}
/**
* Returns the GUID for this object.
* @return The GUID for this object.
*/
public Object getGuid()
{
return guid;
}
/**
* Overrides {@link Thread#run()}.
*/
@Override
public void run()
{
outer: for (int i = 0; i < collectors.length; i++)
{
TaskTable taskTable = collectors[i].getTasks();
int size = taskTable.size();
for (int j = 0; j < size; j++)
{
if (isInterrupted())
{
break outer;
}
SchedulingPattern pattern = taskTable.getSchedulingPattern(j);
if (pattern.match(scheduler.getTimeZone(), referenceTimeInMillis))
{
Task task = taskTable.getTask(j);
scheduler.spawnExecutor(task);
}
}
}
// Notifies completed.
scheduler.notifyLauncherCompleted(this);
}
}

View File

@@ -0,0 +1,152 @@
/*
* cron4j - A pure Java cron-like scheduler
*
* Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version
* 2.1, as published by the Free Software Foundation.
*
* 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 Lesser General Public License 2.1 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License version 2.1 along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util.cron4j;
import java.util.ArrayList;
/**
* <p>
* A {@link TaskCollector} implementation managing a task list in memory.
* </p>
* @author Carlo Pelliccia
* @since 2.0
*/
class MemoryTaskCollector implements TaskCollector
{
/**
* Size.
*/
private final int size = 0;
/**
* The inner scheduling pattern list.
*/
private final ArrayList<SchedulingPattern> patterns = new ArrayList<>();
/**
* The inner task list.
*/
private final ArrayList<Task> tasks = new ArrayList<>();
/**
* IDs for task-pattern couples.
*/
private final ArrayList<String> ids = new ArrayList<>();
/**
* Counts how many task are currently collected by this collector.
* @return The size of the currently collected task list.
*/
public synchronized int size()
{
return size;
}
/**
* Adds a pattern and a task to the collector.
* @param pattern The scheduling pattern.
* @param task The task.
* @return An ID for the scheduled operation.
*/
public synchronized String add(SchedulingPattern pattern, Task task)
{
String id = GUIDGenerator.generate();
patterns.add(pattern);
tasks.add(task);
ids.add(id);
return id;
}
/**
* Updates a scheduling pattern in the collector.
* @param id The ID of the scheduled couple.
* @param pattern SchedulingPattern
*/
public synchronized void update(String id, SchedulingPattern pattern)
{
int index = ids.indexOf(id);
if (index > -1)
{
patterns.set(index, pattern);
}
}
/**
* Removes a task and its scheduling pattern from the collector.
* @param id The ID of the scheduled couple.
* @throws IndexOutOfBoundsException
*/
public synchronized void remove(String id) throws IndexOutOfBoundsException
{
int index = ids.indexOf(id);
if (index > -1)
{
tasks.remove(index);
patterns.remove(index);
ids.remove(index);
}
}
/**
* Retrieves a task from the collector.
* @param id The ID of the scheduled couple.
* @return The task with the specified assigned ID, or null if it doesn't exist.
*/
public synchronized Task getTask(String id)
{
int index = ids.indexOf(id);
if (index > -1)
{
return tasks.get(index);
}
return null;
}
/**
* Retrieves a scheduling pattern from the collector.
* @param id The ID of the scheduled couple.
* @return The scheduling pattern with the specified assigned ID, or null if it doesn't exist.
*/
public synchronized SchedulingPattern getSchedulingPattern(String id)
{
int index = ids.indexOf(id);
if (index > -1)
{
return patterns.get(index);
}
return null;
}
/**
* Implements {@link TaskCollector#getTasks()}.
*/
@Override
public synchronized TaskTable getTasks()
{
TaskTable ret = new TaskTable();
int size = tasks.size();
for (int i = 0; i < size; i++)
{
Task t = tasks.get(i);
SchedulingPattern p = patterns.get(i);
ret.add(p, t);
}
return ret;
}
}

View File

@@ -0,0 +1,315 @@
/*
* This file is part of the L2J Mobius project.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util.cron4j;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
/**
* @author UnAfraid
*/
public class PastPredictor
{
/**
* The scheduling pattern on which the predictor works.
*/
private final SchedulingPattern _schedulingPattern;
/**
* The start time for the next prediction.
*/
private long _time;
/**
* The time zone for the prediction.
*/
private TimeZone _timeZone = TimeZone.getDefault();
/**
* It builds a predictor with the given scheduling pattern and start time.
* @param schedulingPattern The pattern on which the prediction will be based.
* @param start The start time of the prediction.
* @throws InvalidPatternException In the given scheduling pattern isn't valid.
*/
public PastPredictor(String schedulingPattern, long start) throws InvalidPatternException
{
_schedulingPattern = new SchedulingPattern(schedulingPattern);
_time = (start / (1000 * 60)) * 1000 * 60;
}
/**
* It builds a predictor with the given scheduling pattern and start time.
* @param schedulingPattern The pattern on which the prediction will be based.
* @param start The start time of the prediction.
* @throws InvalidPatternException In the given scheduling pattern isn't valid.
*/
public PastPredictor(String schedulingPattern, Date start) throws InvalidPatternException
{
this(schedulingPattern, start.getTime());
}
/**
* It builds a predictor with the given scheduling pattern and the current system time as the prediction start time.
* @param schedulingPattern The pattern on which the prediction will be based.
* @throws InvalidPatternException In the given scheduling pattern isn't valid.
*/
public PastPredictor(String schedulingPattern) throws InvalidPatternException
{
this(schedulingPattern, System.currentTimeMillis());
}
/**
* It builds a predictor with the given scheduling pattern and start time.
* @param schedulingPattern The pattern on which the prediction will be based.
* @param start The start time of the prediction.
* @since 2.0
*/
public PastPredictor(SchedulingPattern schedulingPattern, long start)
{
_schedulingPattern = schedulingPattern;
_time = (start / (1000 * 60)) * 1000 * 60;
}
/**
* It builds a predictor with the given scheduling pattern and start time.
* @param schedulingPattern The pattern on which the prediction will be based.
* @param start The start time of the prediction.
* @since 2.0
*/
public PastPredictor(SchedulingPattern schedulingPattern, Date start)
{
this(schedulingPattern, start.getTime());
}
/**
* It builds a predictor with the given scheduling pattern and the current system time as the prediction start time.
* @param schedulingPattern The pattern on which the prediction will be based.
* @since 2.0
*/
public PastPredictor(SchedulingPattern schedulingPattern)
{
this(schedulingPattern, System.currentTimeMillis());
}
/**
* Sets the time zone for predictions.
* @param timeZone The time zone for predictions.
* @since 2.2.5
*/
public void setTimeZone(TimeZone timeZone)
{
_timeZone = timeZone;
}
/**
* It returns the previous matching moment as a millis value.
* @return The previous matching moment as a millis value.
*/
public synchronized long prevMatchingTime()
{
// Go a minute back.
_time -= 60000;
// Is it matching?
if (_schedulingPattern.match(_time))
{
return _time;
}
// Go through the matcher groups.
int size = _schedulingPattern.matcherSize;
long[] times = new long[size];
for (int k = 0; k < size; k++)
{
// Ok, split the time!
GregorianCalendar c = new GregorianCalendar();
c.setTimeInMillis(_time);
c.setTimeZone(_timeZone);
int minute = c.get(Calendar.MINUTE);
int hour = c.get(Calendar.HOUR_OF_DAY);
int dayOfMonth = c.get(Calendar.DAY_OF_MONTH);
int month = c.get(Calendar.MONTH);
int year = c.get(Calendar.YEAR);
// Gets the matchers.
ValueMatcher minuteMatcher = _schedulingPattern.minuteMatchers.get(k);
ValueMatcher hourMatcher = _schedulingPattern.hourMatchers.get(k);
ValueMatcher dayOfMonthMatcher = _schedulingPattern.dayOfMonthMatchers.get(k);
ValueMatcher dayOfWeekMatcher = _schedulingPattern.dayOfWeekMatchers.get(k);
ValueMatcher monthMatcher = _schedulingPattern.monthMatchers.get(k);
for (;;)
{ // day of week
for (;;)
{ // month
for (;;)
{ // day of month
for (;;)
{ // hour
for (;;)
{ // minutes
if (minuteMatcher.match(minute))
{
break;
}
minute--;
if (minute < 0)
{
minute = 59;
hour--;
}
}
if (hour < 0)
{
hour = 23;
dayOfMonth--;
}
if (hourMatcher.match(hour))
{
break;
}
hour--;
minute = 59;
}
if (dayOfMonth < 1)
{
dayOfMonth = 31;
month--;
}
if (month < Calendar.JANUARY)
{
month = Calendar.DECEMBER;
year--;
}
if (dayOfMonthMatcher instanceof DayOfMonthValueMatcher)
{
DayOfMonthValueMatcher aux = (DayOfMonthValueMatcher) dayOfMonthMatcher;
if (aux.match(dayOfMonth, month + 1, c.isLeapYear(year)))
{
break;
}
dayOfMonth--;
hour = 23;
minute = 59;
}
else if (dayOfMonthMatcher.match(dayOfMonth))
{
break;
}
else
{
dayOfMonth--;
hour = 23;
minute = 59;
}
}
if (monthMatcher.match(month + 1))
{
break;
}
month--;
dayOfMonth = 31;
hour = 23;
minute = 59;
}
// Is this ok?
c = new GregorianCalendar();
c.setTimeZone(_timeZone);
c.set(Calendar.MINUTE, minute);
c.set(Calendar.HOUR_OF_DAY, hour);
c.set(Calendar.DAY_OF_MONTH, dayOfMonth);
c.set(Calendar.MONTH, month);
c.set(Calendar.YEAR, year);
// Day-of-month/month/year compatibility check.
int oldDayOfMonth = dayOfMonth;
int oldMonth = month;
int oldYear = year;
dayOfMonth = c.get(Calendar.DAY_OF_MONTH);
month = c.get(Calendar.MONTH);
year = c.get(Calendar.YEAR);
if ((month != oldMonth) || (dayOfMonth != oldDayOfMonth) || (year != oldYear))
{
do
{
dayOfMonth = oldDayOfMonth - 1;
month = oldMonth;
year = oldYear;
c = new GregorianCalendar();
c.setTimeZone(_timeZone);
c.set(Calendar.MINUTE, minute);
c.set(Calendar.HOUR_OF_DAY, hour);
c.set(Calendar.DAY_OF_MONTH, dayOfMonth);
c.set(Calendar.MONTH, month);
c.set(Calendar.YEAR, year);
// Day-of-month/month/year compatibility check.
oldDayOfMonth = dayOfMonth;
oldMonth = month;
oldYear = year;
dayOfMonth = c.get(Calendar.DAY_OF_MONTH);
month = c.get(Calendar.MONTH);
year = c.get(Calendar.YEAR);
}
while ((month != oldMonth) || (dayOfMonth != oldDayOfMonth) || (year != oldYear));
// Take another spin!
continue;
}
// Day of week.
int dayOfWeek = c.get(Calendar.DAY_OF_WEEK);
if (dayOfWeekMatcher.match(dayOfWeek - 1))
{
break;
}
dayOfMonth--;
hour = 23;
minute = 59;
if (dayOfMonth < 1)
{
dayOfMonth = 31;
month--;
if (month < Calendar.JANUARY)
{
month = Calendar.DECEMBER;
year--;
}
}
}
// Seems it matches!
times[k] = (c.getTimeInMillis() / (1000 * 60)) * 1000 * 60;
}
// Which one?
long min = Long.MAX_VALUE;
for (int k = 0; k < size; k++)
{
if (times[k] < min)
{
min = times[k];
}
}
// Updates the object current time value.
_time = min;
// Here it is.
return _time;
}
/**
* It returns the previous matching moment as a {@link Date} object.
* @return The previous matching moment as a {@link Date} object.
*/
public synchronized Date prevMatchingDate()
{
return new Date(prevMatchingTime());
}
}

View File

@@ -0,0 +1,312 @@
/*
* cron4j - A pure Java cron-like scheduler
*
* Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version
* 2.1, as published by the Free Software Foundation.
*
* 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 Lesser General Public License 2.1 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License version 2.1 along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util.cron4j;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
/**
* <p>
* A predictor is able to predict when a scheduling pattern will be matched.
* </p>
* <p>
* Suppose you want to know when the scheduler will execute a task scheduled with the pattern <em>0 3 * jan-jun,sep-dec mon-fri</em>. You can predict the next <em>n</em> execution of the task using a Predictor instance:
* </p>
*
* <pre>
* String pattern = &quot;0 3 * jan-jun,sep-dec mon-fri&quot;;
* Predictor p = new Predictor(pattern);
* for (int i = 0; i &lt; n; i++)
* {
* System.out.println(p.nextMatchingDate());
* }
* </pre>
*
* @author Carlo Pelliccia
* @since 1.1
*/
public class Predictor
{
/**
* The scheduling pattern on which the predictor works.
*/
private final SchedulingPattern schedulingPattern;
/**
* The start time for the next prediction.
*/
private long time;
/**
* The time zone for the prediction.
*/
private TimeZone timeZone = TimeZone.getDefault();
/**
* It builds a predictor with the given scheduling pattern and start time.
* @param schedulingPattern The pattern on which the prediction will be based.
* @param start The start time of the prediction.
* @throws InvalidPatternException In the given scheduling pattern isn't valid.
*/
public Predictor(String schedulingPattern, long start) throws InvalidPatternException
{
this.schedulingPattern = new SchedulingPattern(schedulingPattern);
this.time = (start / (1000 * 60)) * 1000 * 60;
}
/**
* It builds a predictor with the given scheduling pattern and start time.
* @param schedulingPattern The pattern on which the prediction will be based.
* @param start The start time of the prediction.
* @throws InvalidPatternException In the given scheduling pattern isn't valid.
*/
public Predictor(String schedulingPattern, Date start) throws InvalidPatternException
{
this(schedulingPattern, start.getTime());
}
/**
* It builds a predictor with the given scheduling pattern and the current system time as the prediction start time.
* @param schedulingPattern The pattern on which the prediction will be based.
* @throws InvalidPatternException In the given scheduling pattern isn't valid.
*/
public Predictor(String schedulingPattern) throws InvalidPatternException
{
this(schedulingPattern, System.currentTimeMillis());
}
/**
* It builds a predictor with the given scheduling pattern and start time.
* @param schedulingPattern The pattern on which the prediction will be based.
* @param start The start time of the prediction.
* @since 2.0
*/
public Predictor(SchedulingPattern schedulingPattern, long start)
{
this.schedulingPattern = schedulingPattern;
this.time = (start / (1000 * 60)) * 1000 * 60;
}
/**
* It builds a predictor with the given scheduling pattern and start time.
* @param schedulingPattern The pattern on which the prediction will be based.
* @param start The start time of the prediction.
* @since 2.0
*/
public Predictor(SchedulingPattern schedulingPattern, Date start)
{
this(schedulingPattern, start.getTime());
}
/**
* It builds a predictor with the given scheduling pattern and the current system time as the prediction start time.
* @param schedulingPattern The pattern on which the prediction will be based.
* @since 2.0
*/
public Predictor(SchedulingPattern schedulingPattern)
{
this(schedulingPattern, System.currentTimeMillis());
}
/**
* Sets the time zone for predictions.
* @param timeZone The time zone for predictions.
* @since 2.2.5
*/
public void setTimeZone(TimeZone timeZone)
{
this.timeZone = timeZone;
}
/**
* It returns the next matching moment as a millis value.
* @return The next matching moment as a millis value.
*/
public synchronized long nextMatchingTime()
{
// Go a minute ahead.
time += 60000;
// Is it matching?
if (schedulingPattern.match(time))
{
return time;
}
// Go through the matcher groups.
int size = schedulingPattern.matcherSize;
long[] times = new long[size];
for (int k = 0; k < size; k++)
{
// Ok, split the time!
GregorianCalendar c = new GregorianCalendar();
c.setTimeInMillis(time);
c.setTimeZone(timeZone);
int minute = c.get(Calendar.MINUTE);
int hour = c.get(Calendar.HOUR_OF_DAY);
int dayOfMonth = c.get(Calendar.DAY_OF_MONTH);
int month = c.get(Calendar.MONTH);
int year = c.get(Calendar.YEAR);
// Gets the matchers.
ValueMatcher minuteMatcher = schedulingPattern.minuteMatchers.get(k);
ValueMatcher hourMatcher = schedulingPattern.hourMatchers.get(k);
ValueMatcher dayOfMonthMatcher = schedulingPattern.dayOfMonthMatchers.get(k);
ValueMatcher dayOfWeekMatcher = schedulingPattern.dayOfWeekMatchers.get(k);
ValueMatcher monthMatcher = schedulingPattern.monthMatchers.get(k);
for (;;)
{ // day of week
for (;;)
{ // month
for (;;)
{ // day of month
for (;;)
{ // hour
for (;;)
{ // minutes
if (minuteMatcher.match(minute))
{
break;
}
minute++;
if (minute > 59)
{
minute = 0;
hour++;
}
}
if (hour > 23)
{
hour = 0;
dayOfMonth++;
}
if (hourMatcher.match(hour))
{
break;
}
hour++;
minute = 0;
}
if (dayOfMonth > 31)
{
dayOfMonth = 1;
month++;
}
if (month > Calendar.DECEMBER)
{
month = Calendar.JANUARY;
year++;
}
if (dayOfMonthMatcher instanceof DayOfMonthValueMatcher)
{
DayOfMonthValueMatcher aux = (DayOfMonthValueMatcher) dayOfMonthMatcher;
if (aux.match(dayOfMonth, month + 1, c.isLeapYear(year)))
{
break;
}
dayOfMonth++;
hour = 0;
minute = 0;
}
else if (dayOfMonthMatcher.match(dayOfMonth))
{
break;
}
else
{
dayOfMonth++;
hour = 0;
minute = 0;
}
}
if (monthMatcher.match(month + 1))
{
break;
}
month++;
dayOfMonth = 1;
hour = 0;
minute = 0;
}
// Is this ok?
c = new GregorianCalendar();
c.setTimeZone(timeZone);
c.set(Calendar.MINUTE, minute);
c.set(Calendar.HOUR_OF_DAY, hour);
c.set(Calendar.DAY_OF_MONTH, dayOfMonth);
c.set(Calendar.MONTH, month);
c.set(Calendar.YEAR, year);
// Day-of-month/month/year compatibility check.
int oldDayOfMonth = dayOfMonth;
int oldMonth = month;
int oldYear = year;
dayOfMonth = c.get(Calendar.DAY_OF_MONTH);
month = c.get(Calendar.MONTH);
year = c.get(Calendar.YEAR);
if ((month != oldMonth) || (dayOfMonth != oldDayOfMonth) || (year != oldYear))
{
// Take another spin!
continue;
}
// Day of week.
int dayOfWeek = c.get(Calendar.DAY_OF_WEEK);
if (dayOfWeekMatcher.match(dayOfWeek - 1))
{
break;
}
dayOfMonth++;
hour = 0;
minute = 0;
if (dayOfMonth > 31)
{
dayOfMonth = 1;
month++;
if (month > Calendar.DECEMBER)
{
month = Calendar.JANUARY;
year++;
}
}
}
// Seems it matches!
times[k] = (c.getTimeInMillis() / (1000 * 60)) * 1000 * 60;
}
// Which one?
long min = Long.MAX_VALUE;
for (int k = 0; k < size; k++)
{
if (times[k] < min)
{
min = times[k];
}
}
// Updates the object current time value.
time = min;
// Here it is.
return time;
}
/**
* It returns the next matching moment as a {@link Date} object.
* @return The next matching moment as a {@link Date} object.
*/
public synchronized Date nextMatchingDate()
{
return new Date(nextMatchingTime());
}
}

View File

@@ -0,0 +1,420 @@
/*
* cron4j - A pure Java cron-like scheduler
*
* Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version
* 2.1, as published by the Free Software Foundation.
*
* 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 Lesser General Public License 2.1 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License version 2.1 along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util.cron4j;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* <p>
* A built-in {@link Task} implementation which can be used to run an external process.
* </p>
* @author Carlo Pelliccia
* @since 2.1
*/
public class ProcessTask extends Task
{
/**
* The command to launch.
*/
private String[] command;
/**
* Environment variables for the spawned process, in the form <em>name=value</em>. If null the process will inherit the current JVM environment variables.
*/
private String[] envs;
/**
* Working directory for the spawned process. If null the process will inherit the current JVM working directory.
*/
private File directory;
/**
* Standard input file (optional).
*/
private File stdinFile = null;
/**
* Standard output file (optional).
*/
private File stdoutFile = null;
/**
* Standard error file (optional).
*/
private File stderrFile = null;
/**
* Creates the task.
* @param command The command to launch and its arguments.
* @param envs Environment variables for the spawned process, in the form <em>name=value</em>. If null the process will inherit the current JVM environment variables.
* @param directory Working directory for the spawned process. If null the process will inherit the current JVM working directory.
*/
public ProcessTask(String[] command, String[] envs, File directory)
{
this.command = command;
this.envs = envs;
this.directory = directory;
}
/**
* Creates the task.
* @param command The command to launch and its arguments.
* @param envs Environment variables for the spawned process, in the form <em>name=value</em>. If null the process will inherit the current JVM environment variables.
*/
public ProcessTask(String[] command, String[] envs)
{
this(command, envs, null);
}
/**
* Creates the task.
* @param command The command to launch and its arguments.
*/
public ProcessTask(String[] command)
{
this(command, null, null);
}
/**
* Creates the task.
* @param command The command to launch.
*/
public ProcessTask(String command)
{
this(new String[]
{
command
}, null, null);
}
/**
* Returns true.
*/
@Override
public boolean canBeStopped()
{
return true;
}
/**
* Returns the command executed by this task.
* @return The command executed by this task.
*/
public String[] getCommand()
{
return command;
}
/**
* Sets the command executed by this task.
* @param command The command executed by this task.
*/
public void setCommand(String[] command)
{
this.command = command;
}
/**
* Returns the environment variables, in the <em>name=value</em> form, used by the task to run its process. If null the process will inherit the current JVM environment variables.
* @return The environment variables, in the <em>name=value</em> form, used by the task to run its process. If null the process will inherit the current JVM environment variables.
*/
public String[] getEnvs()
{
return envs;
}
/**
* Sets the environment variables, in the <em>name=value</em> form, used by the task to run its process. If null the process will inherit the current JVM environment variables.
* @param envs The environment variables, in the <em>name=value</em> form, used by the task to run its process. If null the process will inherit the current JVM environment variables.
*/
public void setEnvs(String[] envs)
{
this.envs = envs;
}
/**
* Resturns the working directory for the spawned process. If null the process will inherit the current JVM working directory.
* @return The working directory for the spawned process. If null the process will inherit the current JVM working directory.
*/
public File getDirectory()
{
return directory;
}
/**
* Sets the working directory for the spawned process. If null the process will inherit the current JVM working directory.
* @param directory The working directory for the spawned process. If null the process will inherit the current JVM working directory.
*/
public void setDirectory(File directory)
{
this.directory = directory;
}
/**
* Returns the standard input file (optional). If supplied, the standard input channel of the spawned process will be read from the given file.
* @return The standard input file (optional).
*/
public File getStdinFile()
{
return stdinFile;
}
/**
* Sets the standard input file (optional). If supplied, the standard input channel of the spawned process will be read from the given file.
* @param stdinFile The standard input file (optional).
*/
public void setStdinFile(File stdinFile)
{
this.stdinFile = stdinFile;
}
/**
* Sets the standard output file (optional). If supplied, the standard output channel of the spawned process will be written in the given file.
* @param stdoutFile The standard output file (optional).
*/
public void setStdoutFile(File stdoutFile)
{
this.stdoutFile = stdoutFile;
}
/**
* Returns the standard output file (optional). If supplied, the standard output channel of the spawned process will be written in the given file.
* @return The standard output file (optional).
*/
public File getStdoutFile()
{
return stdoutFile;
}
/**
* Sets the standard error file (optional). If supplied, the standard error channel of the spawned process will be written in the given file.
* @param stderrFile The standard error file (optional).
*/
public void setStderrFile(File stderrFile)
{
this.stderrFile = stderrFile;
}
/**
* Returns the standard error file (optional). If supplied, the standard error channel of the spawned process will be written in the given file.
* @return The standard error file (optional).
*/
public File getStderrFile()
{
return stderrFile;
}
/**
* Implements {@link Task#execute(TaskExecutionContext)}. Runs the given command as a separate process and waits for its end.
*/
@Override
public void execute(TaskExecutionContext context) throws RuntimeException
{
Process p;
try
{
p = exec();
}
catch (IOException e)
{
throw new RuntimeException(toString() + " cannot be started", e);
}
InputStream in = buildInputStream(stdinFile);
OutputStream out = buildOutputStream(stdoutFile);
OutputStream err = buildOutputStream(stderrFile);
if (in != null)
{
StreamBridge b = new StreamBridge(in, p.getOutputStream());
b.start();
}
if (out != null)
{
StreamBridge b = new StreamBridge(p.getInputStream(), out);
b.start();
}
if (err != null)
{
StreamBridge b = new StreamBridge(p.getErrorStream(), err);
b.start();
}
int r;
try
{
r = p.waitFor();
}
catch (InterruptedException e)
{
throw new RuntimeException(toString() + " has been interrupted");
}
finally
{
if (in != null)
{
try
{
in.close();
}
catch (Throwable e)
{
}
}
if (out != null)
{
try
{
out.close();
}
catch (Throwable e)
{
}
}
if (err != null)
{
try
{
err.close();
}
catch (Throwable e)
{
}
}
p.destroy();
}
if (r != 0)
{
throw new RuntimeException(toString() + " returns with error code " + r);
}
}
/**
* Executes the command.
* @return The launched Process.
* @throws IOException If an I/O error occurs.
*/
private Process exec() throws IOException
{
Runtime rt = Runtime.getRuntime();
Process p;
try
{
// java 1.3+
p = rt.exec(command, envs, directory);
}
catch (NoSuchMethodError e)
{
// java 1.2
p = rt.exec(command, envs);
}
return p;
}
/**
* Prepares an {@link InputStream} on a file and returns it.
* @param file The file.
* @return The stream, or null if the file is not found.
*/
private InputStream buildInputStream(File file)
{
if (file != null)
{
try
{
return new FileInputStream(file);
}
catch (FileNotFoundException e)
{
e.printStackTrace();
return null;
}
}
return null;
}
/**
* Prepares an {@link OutputStream} on a file and returns it.
* @param file The file.
* @return The stream, or null if the file is not found.
*/
private OutputStream buildOutputStream(File file)
{
if (file != null)
{
try
{
return new FileOutputStream(file);
}
catch (FileNotFoundException e)
{
e.printStackTrace();
return null;
}
}
return null;
}
/**
* Prints in the returned string the elements contained in the given string array.
* @param arr The array.
* @return A string representing the supplied array contents.
*/
private static String listStrings(String[] arr)
{
if (arr == null)
{
return "null";
}
StringBuffer b = new StringBuffer();
b.append('[');
for (int i = 0; i < arr.length; i++)
{
if (i > 0)
{
b.append(", ");
}
b.append(arr[i]);
}
b.append(']');
return b.toString();
}
/**
* Overrides {@link Object#toString()}.
*/
@Override
public String toString()
{
StringBuffer b = new StringBuffer();
b.append("Task[");
b.append("cmd=");
b.append(ProcessTask.listStrings(command));
b.append(", env=");
b.append(ProcessTask.listStrings(envs));
b.append(", ");
b.append("dir=");
b.append(directory);
b.append("]");
return b.toString();
}
}

View File

@@ -0,0 +1,76 @@
/*
* cron4j - A pure Java cron-like scheduler
*
* Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version
* 2.1, as published by the Free Software Foundation.
*
* 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 Lesser General Public License 2.1 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License version 2.1 along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util.cron4j;
/**
* <p>
* A {@link Task} implementation acting as a wrapper around a {@link Runnable} object.
* </p>
* @author Carlo Pelliccia
* @since 2.0
*/
class RunnableTask extends Task
{
/**
* The wrapped runnable object.
*/
private final Runnable runnable;
/**
* Builds the task.
* @param runnable The wrapped Runnable object.
* @throws InvalidPatternException If the supplied pattern is not valid.
*/
public RunnableTask(Runnable runnable) throws InvalidPatternException
{
this.runnable = runnable;
}
/**
* Returns the wrapped Runnable object.
* @return The wrapped Runnable object.
*/
public Runnable getRunnable()
{
return runnable;
}
/**
* Implements {@link Task#execute(TaskExecutionContext)}, launching the {@link Runnable#run()} method on the wrapped object.
*/
@Override
public void execute(TaskExecutionContext context)
{
runnable.run();
}
/**
* Overrides {@link Object#toString()}.
*/
@Override
public String toString()
{
StringBuffer b = new StringBuffer();
b.append("Task[");
b.append("runnable=");
b.append(runnable);
b.append("]");
return b.toString();
}
}

View File

@@ -0,0 +1,728 @@
/*
* cron4j - A pure Java cron-like scheduler
*
* Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version
* 2.1, as published by the Free Software Foundation.
*
* 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 Lesser General Public License 2.1 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License version 2.1 along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util.cron4j;
import java.io.File;
import java.util.ArrayList;
import java.util.TimeZone;
/**
* <p>
* The cron4j scheduler.
* </p>
* @author Carlo Pelliccia
*/
public class Scheduler
{
/**
* A GUID for this scheduler.
*/
private final String guid = GUIDGenerator.generate();
/**
* The time zone applied by the scheduler.
*/
private TimeZone timezone = null;
/**
* The daemon flag. If true the scheduler and its spawned threads acts like daemons.
*/
private boolean daemon = false;
/**
* The state flag. If true the scheduler is started and running, otherwise it is paused and no task is launched.
*/
private boolean started = false;
/**
* Registered {@link TaskCollector}s list.
*/
private final ArrayList<TaskCollector> collectors = new ArrayList<>();
/**
* The {@link MemoryTaskCollector} used for memory stored tasks. Represented here for convenience, it is also the first element in the {@link Scheduler#collectors} list.
*/
private final MemoryTaskCollector memoryTaskCollector = new MemoryTaskCollector();
/**
* The {@link FileTaskCollector} used for reading tasks from files. Represented here for convenience, it is also the second element in the {@link Scheduler#collectors} list.
*/
private final FileTaskCollector fileTaskCollector = new FileTaskCollector();
/**
* Registered {@link SchedulerListener}s list.
*/
private final ArrayList<SchedulerListener> listeners = new ArrayList<>();
/**
* The thread checking the clock and requesting the spawning of launcher threads.
*/
private TimerThread timer = null;
/**
* Currently running {@link LauncherThread} instances.
*/
private ArrayList<LauncherThread> launchers = null;
/**
* Currently running {@link TaskExecutor} instances.
*/
private ArrayList<TaskExecutor> executors = null;
/**
* Internal lock, used to synchronize status-aware operations.
*/
private final Object lock = new Object();
/**
* It builds and prepares a brand new Scheduler instance.
*/
public Scheduler()
{
collectors.add(memoryTaskCollector);
collectors.add(fileTaskCollector);
}
/**
* It returns the GUID for this scheduler.
* @return The GUID for this scheduler.
*/
public Object getGuid()
{
return guid;
}
/**
* <p>
* Sets the time zone applied by the scheduler.
* </p>
* <p>
* Current system time is adapted to the supplied time zone before comparing it with registered scheduling patterns. The result is that any supplied scheduling pattern is treated according to the specified time zone. In example, suppose:
* </p>
* <ul>
* <li>System time: 10:00</li>
* <li>System time zone: GMT+1</li>
* <li>Scheduler time zone: GMT+3</li>
* </ul>
* <p>
* The scheduler, before comparing system time with patterns, translates 10:00 from GMT+1 to GMT+3. It means that 10:00 becomes 12:00. The resulted time is then used by the scheduler to activate tasks. So, in the given configuration at the given moment, any task scheduled as <em>0 12 * * *</em>
* will be executed, while any <em>0 10 * * *</em> will not.
* </p>
* @param timezone The time zone applied by the scheduler.
*/
public void setTimeZone(TimeZone timezone)
{
this.timezone = timezone;
}
/**
* Returns the time zone applied by the scheduler.
* @return The time zone applied by the scheduler.
*/
public TimeZone getTimeZone()
{
return timezone != null ? timezone : TimeZone.getDefault();
}
/**
* Tests whether this scheduler is a daemon scheduler.
* @return true if this scheduler is a daemon scheduler; false otherwise.
*/
public boolean isDaemon()
{
return daemon;
}
/**
* Marks this scheduler daemon flag. When a scheduler is marked as a daemon scheduler it spawns only daemon threads. The Java Virtual Machine exits when the only threads running are all daemon threads. This method must be called before the scheduler is started.
* @param on If true, the scheduler will spawn only daemon threads.
* @throws IllegalStateException If the scheduler is started.
*/
public void setDaemon(boolean on) throws IllegalStateException
{
synchronized (lock)
{
if (started)
{
throw new IllegalStateException("Scheduler already started");
}
this.daemon = on;
}
}
/**
* Tests if this scheduler is started.
* @return true if the scheduler is started, false if it is stopped.
*/
public boolean isStarted()
{
synchronized (lock)
{
return started;
}
}
/**
* Adds a {@link File} instance to the scheduler. Every minute the file will be parsed. The scheduler will execute any declared task whose scheduling pattern matches the current system time. See {@link CronParser} documentation for informations about the file contents syntax.
* @param file The {@link File} instance.
*/
public void scheduleFile(File file)
{
fileTaskCollector.addFile(file);
}
/**
* Removes a {@link File} instance previously scheduled with the {@link Scheduler#scheduleFile(File)} method.
* @param file The {@link File} instance.
*/
public void descheduleFile(File file)
{
fileTaskCollector.removeFile(file);
}
/**
* Returns an array containing any {@link File} previously scheduled with the {@link Scheduler#scheduleFile(File)} method.
* @return An array containing any {@link File} previously scheduled with the {@link Scheduler#scheduleFile(File)} method.
*/
public File[] getScheduledFiles()
{
return fileTaskCollector.getFiles();
}
/**
* Adds a custom {@link TaskCollector} instance to the scheduler. The supplied object, once added to the scheduler, will be query every minute for its task list. The scheduler will execute any of the returned tasks whose scheduling pattern matches the current system time.
* @param collector The custom {@link TaskCollector} instance.
*/
public void addTaskCollector(TaskCollector collector)
{
synchronized (collectors)
{
collectors.add(collector);
}
}
/**
* Removes a previously registered custom {@link TaskCollector} instance.
* @param collector The custom {@link TaskCollector} instance.
*/
public void removeTaskCollector(TaskCollector collector)
{
synchronized (collectors)
{
collectors.remove(collector);
}
}
/**
* Returns an array containing any custom {@link TaskCollector} instance previously registered in the scheduler with the {@link Scheduler#addTaskCollector(TaskCollector)} method.
* @return An array containing any custom {@link TaskCollector} instance previously registered in the scheduler with the {@link Scheduler#addTaskCollector(TaskCollector)} method.
*/
public TaskCollector[] getTaskCollectors()
{
synchronized (collectors)
{
// Discard the first 2 elements in the list.
int size = collectors.size() - 2;
TaskCollector[] ret = new TaskCollector[size];
for (int i = 0; i < size; i++)
{
ret[i] = collectors.get(i + 2);
}
return ret;
}
}
/**
* Adds a {@link SchedulerListener} to the scheduler. A {@link SchedulerListener} is notified every time a task is launching, has succeeded or has failed.
* @param listener The listener.
*/
public void addSchedulerListener(SchedulerListener listener)
{
synchronized (listeners)
{
listeners.add(listener);
}
}
/**
* Removes a {@link SchedulerListener} previously registered with the {@link Scheduler#addSchedulerListener(SchedulerListener)} method.
* @param listener The listener.
*/
public void removeSchedulerListener(SchedulerListener listener)
{
synchronized (listeners)
{
listeners.remove(listener);
}
}
/**
* Returns an array containing any {@link SchedulerListener} previously registered with the {@link Scheduler#addSchedulerListener(SchedulerListener)} method.
* @return An array containing any {@link SchedulerListener} previously registered with the {@link Scheduler#addSchedulerListener(SchedulerListener)} method.
*/
public SchedulerListener[] getSchedulerListeners()
{
synchronized (listeners)
{
int size = listeners.size();
SchedulerListener[] ret = new SchedulerListener[size];
for (int i = 0; i < size; i++)
{
ret[i] = listeners.get(i);
}
return ret;
}
}
/**
* Returns an array containing any currently executing task, in the form of {@link TaskExecutor} objects. Each running task is executed by a different thread. A {@link TaskExecutor} object allows the control of the running task. The inner {@link Task} representation could be retrieved, the
* status of the task could be detected and the thread could be interrupted using any standard {@link Thread} method ( {@link Thread#interrupt()}, {@link Thread#isAlive() etc}.
* @return An array containing any currently executing task, in the form of {@link TaskExecutor} objects.
*/
public TaskExecutor[] getExecutingTasks()
{
synchronized (executors)
{
int size = executors.size();
TaskExecutor[] ret = new TaskExecutor[size];
for (int i = 0; i < size; i++)
{
ret[i] = executors.get(i);
}
return ret;
}
}
/**
* This method schedules a task execution.
* @param schedulingPattern The scheduling pattern for the task.
* @param task The task, as a plain Runnable object.
* @return The task auto-generated ID assigned by the scheduler. This ID can be used later to reschedule and deschedule the task, and also to retrieve informations about it.
* @throws InvalidPatternException If the supplied pattern is not valid.
*/
public String schedule(String schedulingPattern, Runnable task) throws InvalidPatternException
{
return schedule(schedulingPattern, new RunnableTask(task));
}
/**
* This method schedules a task execution.
* @param schedulingPattern The scheduling pattern for the task.
* @param task The task, as a plain Runnable object.
* @return The task auto-generated ID assigned by the scheduler. This ID can be used later to reschedule and deschedule the task, and also to retrieve informations about it.
* @throws InvalidPatternException If the supplied pattern is not valid.
* @since 2.0
*/
public String schedule(String schedulingPattern, Task task) throws InvalidPatternException
{
return schedule(new SchedulingPattern(schedulingPattern), task);
}
/**
* This method schedules a task execution.
* @param schedulingPattern The scheduling pattern for the task.
* @param task The task, as a plain Runnable object.
* @return The task auto-generated ID assigned by the scheduler. This ID can be used later to reschedule and deschedule the task, and also to retrieve informations about it.
* @since 2.0
*/
public String schedule(SchedulingPattern schedulingPattern, Task task)
{
return memoryTaskCollector.add(schedulingPattern, task);
}
/**
* This method changes the scheduling pattern of a task.
* @param id The ID assigned to the previously scheduled task.
* @param schedulingPattern The new scheduling pattern for the task.
* @throws InvalidPatternException If the supplied pattern is not valid.
* @deprecated Use {@link Scheduler#reschedule(String, String)}.
*/
@Deprecated
public void reschedule(Object id, String schedulingPattern) throws InvalidPatternException
{
reschedule((String) id, new SchedulingPattern(schedulingPattern));
}
/**
* This method changes the scheduling pattern of a task.
* @param id The ID assigned to the previously scheduled task.
* @param schedulingPattern The new scheduling pattern for the task.
* @throws InvalidPatternException If the supplied pattern is not valid.
*/
public void reschedule(String id, String schedulingPattern) throws InvalidPatternException
{
reschedule(id, new SchedulingPattern(schedulingPattern));
}
/**
* This method changes the scheduling pattern of a task.
* @param id The ID assigned to the previously scheduled task.
* @param schedulingPattern The new scheduling pattern for the task.
* @since 2.0
*/
public void reschedule(String id, SchedulingPattern schedulingPattern)
{
memoryTaskCollector.update(id, schedulingPattern);
}
/**
* This methods cancels the scheduling of a task.
* @param id The ID of the task.
* @deprecated Use {@link Scheduler#deschedule(String)}.
*/
@Deprecated
public void deschedule(Object id)
{
deschedule((String) id);
}
/**
* This methods cancels the scheduling of a task.
* @param id The ID of the task.
*/
public void deschedule(String id)
{
memoryTaskCollector.remove(id);
}
/**
* This method retrieves a previously scheduled task.
* @param id The task ID.
* @return The requested task, or null if the task was not found.
* @since 2.0
*/
public Task getTask(String id)
{
return memoryTaskCollector.getTask(id);
}
/**
* This method retrieves a previously scheduled task scheduling pattern.
* @param id The task ID.
* @return The requested scheduling pattern, or null if the task was not found.
* @since 2.0
*/
public SchedulingPattern getSchedulingPattern(String id)
{
return memoryTaskCollector.getSchedulingPattern(id);
}
/**
* This method retrieves the Runnable object of a previously scheduled task.
* @param id The task ID.
* @return The Runnable object of the task, or null if the task was not found.
* @deprecated Use {@link Scheduler#getTask(String)}.
*/
@Deprecated
public Runnable getTaskRunnable(Object id)
{
Task task = getTask((String) id);
if (task instanceof RunnableTask)
{
RunnableTask rt = (RunnableTask) task;
return rt.getRunnable();
}
return null;
}
/**
* This method retrieves the scheduling pattern of a previously scheduled task.
* @param id The task ID.
* @return The scheduling pattern of the task, or null if the task was not found.
* @deprecated Use {@link Scheduler#getSchedulingPattern(String)}.
*/
@Deprecated
public String getTaskSchedulingPattern(Object id)
{
return getSchedulingPattern((String) id).toString();
}
/**
* Executes immediately a task, without scheduling it.
* @param task The task.
* @return The {@link TaskExecutor} executing the given task.
* @throws IllegalStateException If the scheduler is not started.
*/
public TaskExecutor launch(Task task)
{
synchronized (lock)
{
if (!started)
{
throw new IllegalStateException("Scheduler not started");
}
return spawnExecutor(task);
}
}
/**
* This method starts the scheduler. When the scheduled is started the supplied tasks are executed at the given moment.
* @throws IllegalStateException Thrown if this scheduler is already started.
*/
public void start() throws IllegalStateException
{
synchronized (lock)
{
if (started)
{
throw new IllegalStateException("Scheduler already started");
}
// Initializes required lists.
launchers = new ArrayList<>();
executors = new ArrayList<>();
// Starts the timer thread.
timer = new TimerThread(this);
timer.setDaemon(daemon);
timer.start();
// Change the state of the scheduler.
started = true;
}
}
/**
* This method stops the scheduler execution. Before returning, it waits the end of all the running tasks previously launched. Once the scheduler has been stopped it can be started again with a start() call.
* @throws IllegalStateException Thrown if this scheduler is not started.
*/
public void stop() throws IllegalStateException
{
synchronized (lock)
{
if (!started)
{
throw new IllegalStateException("Scheduler not started");
}
// Interrupts the timer and waits for its death.
timer.interrupt();
tillThreadDies(timer);
timer = null;
// Interrupts any running launcher and waits for its death.
for (;;)
{
LauncherThread launcher = null;
synchronized (launchers)
{
if (launchers.size() == 0)
{
break;
}
launcher = launchers.remove(0);
}
launcher.interrupt();
tillThreadDies(launcher);
}
launchers = null;
// Interrupts any running executor and waits for its death.
// Before exiting wait for all the active tasks end.
for (;;)
{
TaskExecutor executor = null;
synchronized (executors)
{
if (executors.size() == 0)
{
break;
}
executor = executors.remove(0);
}
if (executor.canBeStopped())
{
executor.stop();
}
tillExecutorDies(executor);
}
executors = null;
// Change the state of the object.
started = false;
}
}
// -- PACKAGE RESERVED METHODS --------------------------------------------
/**
* Starts a launcher thread.
* @param referenceTimeInMillis Reference time in millis for the launcher.
* @return The spawned launcher.
*/
LauncherThread spawnLauncher(long referenceTimeInMillis)
{
TaskCollector[] nowCollectors;
synchronized (collectors)
{
int size = collectors.size();
nowCollectors = new TaskCollector[size];
for (int i = 0; i < size; i++)
{
nowCollectors[i] = collectors.get(i);
}
}
LauncherThread l = new LauncherThread(this, nowCollectors, referenceTimeInMillis);
synchronized (launchers)
{
launchers.add(l);
}
l.setDaemon(daemon);
l.start();
return l;
}
/**
* Starts the given task within a task executor.
* @param task The task.
* @return The spawned task executor.
*/
TaskExecutor spawnExecutor(Task task)
{
TaskExecutor e = new TaskExecutor(this, task);
synchronized (executors)
{
executors.add(e);
}
e.start(daemon);
return e;
}
/**
* This method is called by a launcher thread to notify that the execution is completed.
* @param launcher The launcher which has completed its task.
*/
void notifyLauncherCompleted(LauncherThread launcher)
{
synchronized (launchers)
{
launchers.remove(launcher);
}
}
/**
* This method is called by a task executor to notify that the execution is completed.
* @param executor The executor which has completed its task.
*/
void notifyExecutorCompleted(TaskExecutor executor)
{
synchronized (executors)
{
executors.remove(executor);
}
}
/**
* Notifies every registered listener that a task is going to be launched.
* @param executor The task executor.
*/
void notifyTaskLaunching(TaskExecutor executor)
{
synchronized (listeners)
{
int size = listeners.size();
for (int i = 0; i < size; i++)
{
SchedulerListener l = listeners.get(i);
l.taskLaunching(executor);
}
}
}
/**
* Notifies every registered listener that a task execution has successfully completed.
* @param executor The task executor.
*/
void notifyTaskSucceeded(TaskExecutor executor)
{
synchronized (listeners)
{
int size = listeners.size();
for (int i = 0; i < size; i++)
{
SchedulerListener l = listeners.get(i);
l.taskSucceeded(executor);
}
}
}
/**
* Notifies every registered listener that a task execution has failed due to an uncaught exception.
* @param executor The task executor.
* @param exception The exception.
*/
void notifyTaskFailed(TaskExecutor executor, Throwable exception)
{
synchronized (listeners)
{
int size = listeners.size();
if (size > 0)
{
for (int i = 0; i < size; i++)
{
SchedulerListener l = listeners.get(i);
l.taskFailed(executor, exception);
}
}
else
{
// Logs on console if no one has been notified about it.
exception.printStackTrace();
}
}
}
// -- PRIVATE METHODS -----------------------------------------------------
/**
* It waits until the given thread is dead. It is similar to {@link Thread#join()}, but this one avoids {@link InterruptedException} instances.
* @param thread The thread.
*/
private void tillThreadDies(Thread thread)
{
boolean dead = false;
do
{
try
{
thread.join();
dead = true;
}
catch (InterruptedException e)
{
}
}
while (!dead);
}
/**
* It waits until the given task executor is dead. It is similar to {@link TaskExecutor#join()}, but this one avoids {@link InterruptedException} instances.
* @param executor The task executor.
*/
private void tillExecutorDies(TaskExecutor executor)
{
boolean dead = false;
do
{
try
{
executor.join();
dead = true;
}
catch (InterruptedException e)
{
}
}
while (!dead);
}
}

View File

@@ -0,0 +1,48 @@
/*
* cron4j - A pure Java cron-like scheduler
*
* Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version
* 2.1, as published by the Free Software Foundation.
*
* 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 Lesser General Public License 2.1 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License version 2.1 along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util.cron4j;
/**
* <p>
* Implement this interface and register your instance with the {@link Scheduler#addSchedulerListener(SchedulerListener)} method to receive notifications about scheduled task executions.
* </p>
* @author Carlo Pelliccia
* @since 2.0
*/
public interface SchedulerListener
{
/**
* This one is called by the scheduler when a task execution is starting.
* @param executor The task executor.
*/
public void taskLaunching(TaskExecutor executor);
/**
* This one is called by the scheduler to notify that a task execution has been successfully completed.
* @param executor The task executor.
*/
public void taskSucceeded(TaskExecutor executor);
/**
* This one is called by the scheduler to notify that a task execution has failed.
* @param executor The task executor.
* @param exception The exception representing the failure notification.
*/
public void taskFailed(TaskExecutor executor, Throwable exception);
}

View File

@@ -0,0 +1,743 @@
/*
* cron4j - A pure Java cron-like scheduler
*
* Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version
* 2.1, as published by the Free Software Foundation.
*
* 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 Lesser General Public License 2.1 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License version 2.1 along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util.cron4j;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.StringTokenizer;
import java.util.TimeZone;
/**
* <p>
* A UNIX crontab-like pattern is a string split in five space separated parts. Each part is intented as:
* </p>
* <ol>
* <li><strong>Minutes sub-pattern</strong>. During which minutes of the hour should the task been launched? The values range is from 0 to 59.</li>
* <li><strong>Hours sub-pattern</strong>. During which hours of the day should the task been launched? The values range is from 0 to 23.</li>
* <li><strong>Days of month sub-pattern</strong>. During which days of the month should the task been launched? The values range is from 1 to 31. The special value L can be used to recognize the last day of month.</li>
* <li><strong>Months sub-pattern</strong>. During which months of the year should the task been launched? The values range is from 1 (January) to 12 (December), otherwise this sub-pattern allows the aliases &quot;jan&quot;, &quot;feb&quot;, &quot;mar&quot;, &quot;apr&quot;, &quot;may&quot;,
* &quot;jun&quot;, &quot;jul&quot;, &quot;aug&quot;, &quot;sep&quot;, &quot;oct&quot;, &quot;nov&quot; and &quot;dec&quot;.</li>
* <li><strong>Days of week sub-pattern</strong>. During which days of the week should the task been launched? The values range is from 0 (Sunday) to 6 (Saturday), otherwise this sub-pattern allows the aliases &quot;sun&quot;, &quot;mon&quot;, &quot;tue&quot;, &quot;wed&quot;, &quot;thu&quot;,
* &quot;fri&quot; and &quot;sat&quot;.</li>
* </ol>
* <p>
* The star wildcard character is also admitted, indicating &quot;every minute of the hour&quot;, &quot;every hour of the day&quot;, &quot;every day of the month&quot;, &quot;every month of the year&quot; and &quot;every day of the week&quot;, according to the sub-pattern in which it is used.
* </p>
* <p>
* Once the scheduler is started, a task will be launched when the five parts in its scheduling pattern will be true at the same time.
* </p>
* <p>
* Some examples:
* </p>
* <p>
* <strong>5 * * * *</strong><br />
* This pattern causes a task to be launched once every hour, at the begin of the fifth minute (00:05, 01:05, 02:05 etc.).
* </p>
* <p>
* <strong>* * * * *</strong><br />
* This pattern causes a task to be launched every minute.
* </p>
* <p>
* <strong>* 12 * * Mon</strong><br />
* This pattern causes a task to be launched every minute during the 12th hour of Monday.
* </p>
* <p>
* <strong>* 12 16 * Mon</strong><br />
* This pattern causes a task to be launched every minute during the 12th hour of Monday, 16th, but only if the day is the 16th of the month.
* </p>
* <p>
* Every sub-pattern can contain two or more comma separated values.
* </p>
* <p>
* <strong>59 11 * * 1,2,3,4,5</strong><br />
* This pattern causes a task to be launched at 11:59AM on Monday, Tuesday, Wednesday, Thursday and Friday.
* </p>
* <p>
* Values intervals are admitted and defined using the minus character.
* </p>
* <p>
* <strong>59 11 * * 1-5</strong><br />
* This pattern is equivalent to the previous one.
* </p>
* <p>
* The slash character can be used to identify step values within a range. It can be used both in the form <em>*&#47;c</em> and <em>a-b/c</em>. The subpattern is matched every <em>c</em> values of the range <em>0,maxvalue</em> or <em>a-b</em>.
* </p>
* <p>
* <strong>*&#47;5 * * * *</strong><br />
* This pattern causes a task to be launched every 5 minutes (0:00, 0:05, 0:10, 0:15 and so on).
* </p>
* <p>
* <strong>3-18&#47;5 * * * *</strong><br />
* This pattern causes a task to be launched every 5 minutes starting from the third minute of the hour, up to the 18th (0:03, 0:08, 0:13, 0:18, 1:03, 1:08 and so on).
* </p>
* <p>
* <strong>*&#47;15 9-17 * * *</strong><br />
* This pattern causes a task to be launched every 15 minutes between the 9th and 17th hour of the day (9:00, 9:15, 9:30, 9:45 and so on... note that the last execution will be at 17:45).
* </p>
* <p>
* All the fresh described syntax rules can be used together.
* </p>
* <p>
* <strong>* 12 10-16&#47;2 * *</strong><br />
* This pattern causes a task to be launched every minute during the 12th hour of the day, but only if the day is the 10th, the 12th, the 14th or the 16th of the month.
* </p>
* <p>
* <strong>* 12 1-15,17,20-25 * *</strong><br />
* This pattern causes a task to be launched every minute during the 12th hour of the day, but the day of the month must be between the 1st and the 15th, the 20th and the 25, or at least it must be the 17th.
* </p>
* <p>
* Finally cron4j lets you combine more scheduling patterns into one, with the pipe character:
* </p>
* <p>
* <strong>0 5 * * *|8 10 * * *|22 17 * * *</strong><br />
* This pattern causes a task to be launched every day at 05:00, 10:08 and 17:22.
* </p>
* @author Carlo Pelliccia
* @since 2.0
*/
public class SchedulingPattern
{
/**
* The parser for the minute values.
*/
private static final ValueParser MINUTE_VALUE_PARSER = new MinuteValueParser();
/**
* The parser for the hour values.
*/
private static final ValueParser HOUR_VALUE_PARSER = new HourValueParser();
/**
* The parser for the day of month values.
*/
private static final ValueParser DAY_OF_MONTH_VALUE_PARSER = new DayOfMonthValueParser();
/**
* The parser for the month values.
*/
private static final ValueParser MONTH_VALUE_PARSER = new MonthValueParser();
/**
* The parser for the day of week values.
*/
private static final ValueParser DAY_OF_WEEK_VALUE_PARSER = new DayOfWeekValueParser();
/**
* Validates a string as a scheduling pattern.
* @param schedulingPattern The pattern to validate.
* @return true if the given string represents a valid scheduling pattern; false otherwise.
*/
public static boolean validate(String schedulingPattern)
{
try
{
new SchedulingPattern(schedulingPattern);
}
catch (InvalidPatternException e)
{
return false;
}
return true;
}
/**
* The pattern as a string.
*/
private final String asString;
/**
* The ValueMatcher list for the "minute" field.
*/
protected ArrayList<ValueMatcher> minuteMatchers = new ArrayList<>();
/**
* The ValueMatcher list for the "hour" field.
*/
protected ArrayList<ValueMatcher> hourMatchers = new ArrayList<>();
/**
* The ValueMatcher list for the "day of month" field.
*/
protected ArrayList<ValueMatcher> dayOfMonthMatchers = new ArrayList<>();
/**
* The ValueMatcher list for the "month" field.
*/
protected ArrayList<ValueMatcher> monthMatchers = new ArrayList<>();
/**
* The ValueMatcher list for the "day of week" field.
*/
protected ArrayList<ValueMatcher> dayOfWeekMatchers = new ArrayList<>();
/**
* How many matcher groups in this pattern?
*/
protected int matcherSize = 0;
/**
* Builds a SchedulingPattern parsing it from a string.
* @param pattern The pattern as a crontab-like string.
* @throws InvalidPatternException If the supplied string is not a valid pattern.
*/
public SchedulingPattern(String pattern) throws InvalidPatternException
{
this.asString = pattern;
StringTokenizer st1 = new StringTokenizer(pattern, "|");
if (st1.countTokens() < 1)
{
throw new InvalidPatternException("invalid pattern: \"" + pattern + "\"");
}
while (st1.hasMoreTokens())
{
String localPattern = st1.nextToken();
StringTokenizer st2 = new StringTokenizer(localPattern, " \t");
if (st2.countTokens() != 5)
{
throw new InvalidPatternException("invalid pattern: \"" + localPattern + "\"");
}
try
{
minuteMatchers.add(buildValueMatcher(st2.nextToken(), MINUTE_VALUE_PARSER));
}
catch (Exception e)
{
throw new InvalidPatternException("invalid pattern \"" + localPattern + "\". Error parsing minutes field: " + e.getMessage() + ".");
}
try
{
hourMatchers.add(buildValueMatcher(st2.nextToken(), HOUR_VALUE_PARSER));
}
catch (Exception e)
{
throw new InvalidPatternException("invalid pattern \"" + localPattern + "\". Error parsing hours field: " + e.getMessage() + ".");
}
try
{
dayOfMonthMatchers.add(buildValueMatcher(st2.nextToken(), DAY_OF_MONTH_VALUE_PARSER));
}
catch (Exception e)
{
throw new InvalidPatternException("invalid pattern \"" + localPattern + "\". Error parsing days of month field: " + e.getMessage() + ".");
}
try
{
monthMatchers.add(buildValueMatcher(st2.nextToken(), MONTH_VALUE_PARSER));
}
catch (Exception e)
{
throw new InvalidPatternException("invalid pattern \"" + localPattern + "\". Error parsing months field: " + e.getMessage() + ".");
}
try
{
dayOfWeekMatchers.add(buildValueMatcher(st2.nextToken(), DAY_OF_WEEK_VALUE_PARSER));
}
catch (Exception e)
{
throw new InvalidPatternException("invalid pattern \"" + localPattern + "\". Error parsing days of week field: " + e.getMessage() + ".");
}
matcherSize++;
}
}
/**
* A ValueMatcher utility builder.
* @param str The pattern part for the ValueMatcher creation.
* @param parser The parser used to parse the values.
* @return The requested ValueMatcher.
* @throws Exception If the supplied pattern part is not valid.
*/
private ValueMatcher buildValueMatcher(String str, ValueParser parser) throws Exception
{
if ((str.length() == 1) && str.equals("*"))
{
return new AlwaysTrueValueMatcher();
}
ArrayList<Object> values = new ArrayList<>();
StringTokenizer st = new StringTokenizer(str, ",");
while (st.hasMoreTokens())
{
String element = st.nextToken();
ArrayList<Integer> local;
try
{
local = parseListElement(element, parser);
}
catch (Exception e)
{
throw new Exception("invalid field \"" + str + "\", invalid element \"" + element + "\", " + e.getMessage());
}
for (Integer integer : local)
{
Object value = integer;
if (!values.contains(value))
{
values.add(value);
}
}
}
if (values.size() == 0)
{
throw new Exception("invalid field \"" + str + "\"");
}
if (parser == DAY_OF_MONTH_VALUE_PARSER)
{
return new DayOfMonthValueMatcher(values);
}
return new IntArrayValueMatcher(values);
}
/**
* Parses an element of a list of values of the pattern.
* @param str The element string.
* @param parser The parser used to parse the values.
* @return A list of integers representing the allowed values.
* @throws Exception If the supplied pattern part is not valid.
*/
private ArrayList<Integer> parseListElement(String str, ValueParser parser) throws Exception
{
StringTokenizer st = new StringTokenizer(str, "/");
int size = st.countTokens();
if ((size < 1) || (size > 2))
{
throw new Exception("syntax error");
}
ArrayList<Integer> values;
try
{
values = parseRange(st.nextToken(), parser);
}
catch (Exception e)
{
throw new Exception("invalid range, " + e.getMessage());
}
if (size == 2)
{
String dStr = st.nextToken();
int div;
try
{
div = Integer.parseInt(dStr);
}
catch (NumberFormatException e)
{
throw new Exception("invalid divisor \"" + dStr + "\"");
}
if (div < 1)
{
throw new Exception("non positive divisor \"" + div + "\"");
}
ArrayList<Integer> values2 = new ArrayList<>();
for (int i = 0; i < values.size(); i += div)
{
values2.add(values.get(i));
}
return values2;
}
return values;
}
/**
* Parses a range of values.
* @param str The range string.
* @param parser The parser used to parse the values.
* @return A list of integers representing the allowed values.
* @throws Exception If the supplied pattern part is not valid.
*/
private ArrayList<Integer> parseRange(String str, ValueParser parser) throws Exception
{
if (str.equals("*"))
{
int min = parser.getMinValue();
int max = parser.getMaxValue();
ArrayList<Integer> values = new ArrayList<>();
for (int i = min; i <= max; i++)
{
values.add(new Integer(i));
}
return values;
}
StringTokenizer st = new StringTokenizer(str, "-");
int size = st.countTokens();
if ((size < 1) || (size > 2))
{
throw new Exception("syntax error");
}
String v1Str = st.nextToken();
int v1;
try
{
v1 = parser.parse(v1Str);
}
catch (Exception e)
{
throw new Exception("invalid value \"" + v1Str + "\", " + e.getMessage());
}
if (size == 1)
{
ArrayList<Integer> values = new ArrayList<>();
values.add(new Integer(v1));
return values;
}
String v2Str = st.nextToken();
int v2;
try
{
v2 = parser.parse(v2Str);
}
catch (Exception e)
{
throw new Exception("invalid value \"" + v2Str + "\", " + e.getMessage());
}
ArrayList<Integer> values = new ArrayList<>();
if (v1 < v2)
{
for (int i = v1; i <= v2; i++)
{
values.add(new Integer(i));
}
}
else if (v1 > v2)
{
int min = parser.getMinValue();
int max = parser.getMaxValue();
for (int i = v1; i <= max; i++)
{
values.add(new Integer(i));
}
for (int i = min; i <= v2; i++)
{
values.add(new Integer(i));
}
}
else
{
// v1 == v2
values.add(new Integer(v1));
}
return values;
}
/**
* This methods returns true if the given timestamp (expressed as a UNIX-era millis value) matches the pattern, according to the given time zone.
* @param timezone A time zone.
* @param millis The timestamp, as a UNIX-era millis value.
* @return true if the given timestamp matches the pattern.
*/
public boolean match(TimeZone timezone, long millis)
{
GregorianCalendar gc = new GregorianCalendar();
gc.setTimeInMillis(millis);
gc.setTimeZone(timezone);
int minute = gc.get(Calendar.MINUTE);
int hour = gc.get(Calendar.HOUR_OF_DAY);
int dayOfMonth = gc.get(Calendar.DAY_OF_MONTH);
int month = gc.get(Calendar.MONTH) + 1;
int dayOfWeek = gc.get(Calendar.DAY_OF_WEEK) - 1;
int year = gc.get(Calendar.YEAR);
for (int i = 0; i < matcherSize; i++)
{
ValueMatcher minuteMatcher = minuteMatchers.get(i);
ValueMatcher hourMatcher = hourMatchers.get(i);
ValueMatcher dayOfMonthMatcher = dayOfMonthMatchers.get(i);
ValueMatcher monthMatcher = monthMatchers.get(i);
ValueMatcher dayOfWeekMatcher = dayOfWeekMatchers.get(i);
boolean eval = minuteMatcher.match(minute) && hourMatcher.match(hour) && ((dayOfMonthMatcher instanceof DayOfMonthValueMatcher) ? ((DayOfMonthValueMatcher) dayOfMonthMatcher).match(dayOfMonth, month, gc.isLeapYear(year)) : dayOfMonthMatcher.match(dayOfMonth)) && monthMatcher.match(month) && dayOfWeekMatcher.match(dayOfWeek);
if (eval)
{
return true;
}
}
return false;
}
/**
* This methods returns true if the given timestamp (expressed as a UNIX-era millis value) matches the pattern, according to the system default time zone.
* @param millis The timestamp, as a UNIX-era millis value.
* @return true if the given timestamp matches the pattern.
*/
public boolean match(long millis)
{
return match(TimeZone.getDefault(), millis);
}
/**
* Returns the pattern as a string.
* @return The pattern as a string.
*/
@Override
public String toString()
{
return asString;
}
/**
* This utility method changes an alias to an int value.
* @param value The value.
* @param aliases The aliases list.
* @param offset The offset appplied to the aliases list indices.
* @return The parsed value.
* @throws Exception If the expressed values doesn't match any alias.
*/
static int parseAlias(String value, String[] aliases, int offset) throws Exception
{
for (int i = 0; i < aliases.length; i++)
{
if (aliases[i].equalsIgnoreCase(value))
{
return offset + i;
}
}
throw new Exception("invalid alias \"" + value + "\"");
}
/**
* Definition for a value parser.
*/
private static interface ValueParser
{
/**
* Attempts to parse a value.
* @param value The value.
* @return The parsed value.
* @throws Exception If the value can't be parsed.
*/
public int parse(String value) throws Exception;
/**
* Returns the minimum value accepred by the parser.
* @return The minimum value accepred by the parser.
*/
public int getMinValue();
/**
* Returns the maximum value accepred by the parser.
* @return The maximum value accepred by the parser.
*/
public int getMaxValue();
}
/**
* A simple value parser.
*/
private static class SimpleValueParser implements ValueParser
{
/**
* The minimum allowed value.
*/
protected int minValue;
/**
* The maximum allowed value.
*/
protected int maxValue;
/**
* Builds the value parser.
* @param minValue The minimum allowed value.
* @param maxValue The maximum allowed value.
*/
public SimpleValueParser(int minValue, int maxValue)
{
this.minValue = minValue;
this.maxValue = maxValue;
}
@Override
public int parse(String value) throws Exception
{
int i;
try
{
i = Integer.parseInt(value);
}
catch (NumberFormatException e)
{
throw new Exception("invalid integer value");
}
if ((i < minValue) || (i > maxValue))
{
throw new Exception("value out of range");
}
return i;
}
@Override
public int getMinValue()
{
return minValue;
}
@Override
public int getMaxValue()
{
return maxValue;
}
}
/**
* The minutes value parser.
*/
private static class MinuteValueParser extends SimpleValueParser
{
/**
* Builds the value parser.
*/
public MinuteValueParser()
{
super(0, 59);
}
}
/**
* The hours value parser.
*/
private static class HourValueParser extends SimpleValueParser
{
/**
* Builds the value parser.
*/
public HourValueParser()
{
super(0, 23);
}
}
/**
* The days of month value parser.
*/
private static class DayOfMonthValueParser extends SimpleValueParser
{
/**
* Builds the value parser.
*/
public DayOfMonthValueParser()
{
super(1, 31);
}
/**
* Added to support last-day-of-month.
* @param value The value to be parsed
* @return the integer day of the month or 32 for last day of the month
* @throws Exception if the input value is invalid
*/
@Override
public int parse(String value) throws Exception
{
if (value.equalsIgnoreCase("L"))
{
return 32;
}
return super.parse(value);
}
}
/**
* The value parser for the months field.
*/
private static class MonthValueParser extends SimpleValueParser
{
/**
* Months aliases.
*/
private static String[] ALIASES =
{
"jan",
"feb",
"mar",
"apr",
"may",
"jun",
"jul",
"aug",
"sep",
"oct",
"nov",
"dec"
};
/**
* Builds the months value parser.
*/
public MonthValueParser()
{
super(1, 12);
}
@Override
public int parse(String value) throws Exception
{
try
{
// try as a simple value
return super.parse(value);
}
catch (Exception e)
{
// try as an alias
return parseAlias(value, ALIASES, 1);
}
}
}
/**
* The value parser for the months field.
*/
private static class DayOfWeekValueParser extends SimpleValueParser
{
/**
* Days of week aliases.
*/
private static String[] ALIASES =
{
"sun",
"mon",
"tue",
"wed",
"thu",
"fri",
"sat"
};
/**
* Builds the months value parser.
*/
public DayOfWeekValueParser()
{
super(0, 7);
}
@Override
public int parse(String value) throws Exception
{
try
{
// try as a simple value
return super.parse(value) % 7;
}
catch (Exception e)
{
// try as an alias
return parseAlias(value, ALIASES, 0);
}
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* cron4j - A pure Java cron-like scheduler
*
* Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version
* 2.1, as published by the Free Software Foundation.
*
* 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 Lesser General Public License 2.1 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License version 2.1 along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util.cron4j;
/**
* <p>
* A scheduling patterns validator.
* </p>
* <p>
* The class lets you validate a scheduling pattern before/without using it with a {@link Scheduler} instance. Simply call:
* </p>
*
* <pre>
* boolean valid = SchedulingPatternValidator.validate(thePattern);
* </pre>
* <p>
* It is useful in validating user-entered patterns.
* </p>
* @author Carlo Pelliccia
* @deprecated Use {@link SchedulingPattern#validate(String)}.
*/
@Deprecated
public class SchedulingPatternValidator
{
/**
* Validates a string as a scheduling pattern.
* @param schedulingPattern The pattern to validate.
* @return true if the given string represents a valid scheduling pattern; false otherwise.
* @deprecated Use {@link SchedulingPattern#validate(String)}.
*/
@Deprecated
public static boolean validate(String schedulingPattern)
{
return SchedulingPattern.validate(schedulingPattern);
}
}

View File

@@ -0,0 +1,107 @@
/*
* cron4j - A pure Java cron-like scheduler
*
* Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version
* 2.1, as published by the Free Software Foundation.
*
* 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 Lesser General Public License 2.1 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License version 2.1 along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util.cron4j;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* This kind of task can be used to invoke a static method of a Java class. The specified method must accept an array of strings as its sole argument.
* @author Carlo Pelliccia
* @since 2.2
*/
class StaticMethodTask extends Task
{
/**
* The Java class name.
*/
private final String className;
/**
* The name of the static method of the class that has to be launched.
*/
private final String methodName;
/**
* Arguments for the static method. The array can be empty, but it can't be null.
*/
private final String[] args;
/**
* Builds the task.
* @param className The Java class name.
* @param methodName The name of the static method of the class that has to be launched.
* @param args Arguments for the static method. The array can be empty, but it can't be null.
*/
public StaticMethodTask(String className, String methodName, String[] args)
{
this.className = className;
this.methodName = methodName;
this.args = args;
}
/**
* Implements {@link Task#execute(TaskExecutionContext)}. It uses Java reflection to load the given class and call the given static method with the supplied arguments.
*/
@Override
public void execute(TaskExecutionContext context) throws RuntimeException
{
// Loads the class.
Class<?> classObject;
try
{
classObject = Class.forName(className);
}
catch (ClassNotFoundException e)
{
throw new RuntimeException("Cannot load class " + className, e);
}
// Finds the method.
Method methodObject;
try
{
Class<?>[] argTypes = new Class[]
{
String[].class
};
methodObject = classObject.getMethod(methodName, argTypes);
}
catch (NoSuchMethodException e)
{
throw new RuntimeException("Cannot find a " + methodName + "(String[]) method in class " + className, e);
}
int modifiers = methodObject.getModifiers();
if (!Modifier.isStatic(modifiers))
{
throw new RuntimeException("The method " + methodName + "(String[]) of the class " + className + " is not static");
}
// Invokes the method.
try
{
methodObject.invoke(null, new Object[]
{
args
});
}
catch (Exception e)
{
throw new RuntimeException("Failed to invoke the static method " + methodName + "(String[]) of the class " + className);
}
}
}

View File

@@ -0,0 +1,213 @@
/*
* cron4j - A pure Java cron-like scheduler
*
* Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version
* 2.1, as published by the Free Software Foundation.
*
* 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 Lesser General Public License 2.1 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License version 2.1 along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util.cron4j;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
/**
* <p>
* A package-reserved utility class. It spawns a secondary thread in which the supplied {@link InputStream} instance is read, and the incoming contents are written in the supplied {@link OutputStream}.
* </p>
* @author Carlo Pelliccia
* @since 2.0
*/
class StreamBridge
{
/**
* Used to trace alive instances.
*/
static ArrayList<StreamBridge> traced = new ArrayList<>();
/**
* A self-referece, for inner classes.
*/
final StreamBridge myself = this;
/**
* The thread executing the job.
*/
private final Thread thread;
/**
* The stream from which the data is read.
*/
final InputStream in;
/**
* The stream in which the data is written.
*/
final OutputStream out;
/**
* Builds the instance.
* @param in The stream from which the data is read.
* @param out The stream in which the data is written.
*/
public StreamBridge(InputStream in, OutputStream out)
{
this.in = in;
this.out = out;
this.thread = new Thread(new Runner());
synchronized (traced)
{
traced.add(this);
}
}
/**
* Starts the bridge job.
*/
public void start()
{
thread.start();
}
/**
* Aborts the ongoing job.
*/
public void abort()
{
thread.interrupt();
try
{
out.close();
}
catch (Throwable t)
{
}
try
{
in.close();
}
catch (Throwable t)
{
}
}
/**
* Waits for this job to die.
* @throws InterruptedException If another thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.
*/
public void join() throws InterruptedException
{
thread.join();
}
/**
* Waits at most <code>millis</code> milliseconds for this thread to die. A timeout of <code>0</code> means to wait forever.
* @param millis the time to wait in milliseconds.
* @throws InterruptedException If another thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.
*/
public void join(long millis) throws InterruptedException
{
thread.join(millis);
}
/**
* @param millis the time to wait in milliseconds.
* @param nanos 0-999999 additional nanoseconds to wait.
* @throws IllegalArgumentException if the value of millis is negative the value of nanos is not in the range 0-999999.
* @throws InterruptedException If another thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.
*/
public void join(long millis, int nanos) throws IllegalArgumentException, InterruptedException
{
thread.join(millis, nanos);
}
/**
* Tests if this bridge is alive. A job is alive if it has been started and has not yet completed.
* @return <code>true</code> if this thread is alive; <code>false</code> otherwise.
*/
public boolean isAlive()
{
return thread.isAlive();
}
/**
* Contains the routine doing the job in the secondary thread.
*/
private class Runner implements Runnable
{
public Runner()
{
}
@Override
public void run()
{
boolean skipout = false;
for (;;)
{
int b;
try
{
b = in.read();
}
catch (IOException e)
{
if (!Thread.interrupted())
{
e.printStackTrace();
}
break;
}
if (b == -1)
{
break;
}
if (!skipout)
{
try
{
out.write(b);
}
catch (IOException e)
{
if (!Thread.interrupted())
{
e.printStackTrace();
}
skipout = true;
}
}
}
try
{
out.close();
}
catch (Throwable t)
{
}
try
{
in.close();
}
catch (Throwable t)
{
}
synchronized (traced)
{
traced.remove(myself);
}
}
}
}

View File

@@ -0,0 +1,144 @@
/*
* cron4j - A pure Java cron-like scheduler
*
* Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version
* 2.1, as published by the Free Software Foundation.
*
* 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 Lesser General Public License 2.1 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License version 2.1 along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util.cron4j;
/**
* <p>
* Abstract base representation of a cron4j task.
* </p>
* <p>
* Developers can extends this abstract class to build their own tasks.
* </p>
* <p>
* Extending Task means, above all, implementing the {@link Task#execute(TaskExecutionContext)} method. Within this method the task must perform its operation. If the <em>execute()</em> method returns regularly then the execution is considered to be successfully completed. If <em>execute()</em> dies
* throwing a {@link RuntimeException} then the task execution is considered to be failed. The supplied parameter, which is a {@link TaskExecutionContext} instance, helps the developer in integrating his task with the scheduler executor. Through the context the developer can check if the execution
* has been paused or stopped, and he can also push back some status informations by calling {@link TaskExecutionContext#setCompleteness(double)} and {@link TaskExecutionContext#setStatusMessage(String)}.
* </p>
* <p>
* If the custom task supports pausing, stopping and/or tracking, that should be notified by overriding {@link Task#canBePaused()}, {@link Task#canBeStopped()}, {@link Task#supportsCompletenessTracking()} and/or {@link Task#supportsStatusTracking()}.
* </p>
* @author Carlo Pelliccia
* @since 2.0
*/
public abstract class Task
{
/**
* The ID for this task. Also used as an instance synchronization lock.
*/
private final Object id = GUIDGenerator.generate();
/**
* Empty constructor, does nothing.
*/
public Task()
{
}
/**
* It returns the ID for this task.
* @return The ID for this task.
*/
Object getId()
{
return id;
}
/**
* <p>
* Checks whether this task supports pause requests.
* </p>
* <p>
* Default implementation returns <em>false</em>.
* </p>
* <p>
* Task developers can override this method to let it return a <em>true</em> value, and at the same time they have to implement the {@link Task#execute(TaskExecutionContext)} method, so that pause requests are really handled. This can be done calling regularly the
* {@link TaskExecutionContext#pauseIfRequested()} method during the task execution.
* </p>
* @return true if this task can be paused; false otherwise.
*/
public boolean canBePaused()
{
return false;
}
/**
* <p>
* Checks whether this task supports stop requests.
* </p>
* <p>
* Default implementation returns <em>false</em>.
* </p>
* <p>
* Task developers can override this method to let it return a <em>true</em> value, and at the same time they have to implement the {@link Task#execute(TaskExecutionContext)} method, so that stop requests are really handled. This can be done checking regularly the
* {@link TaskExecutionContext#isStopped()} method during the task execution.
* </p>
* @return true if this task can be stopped; false otherwise.
*/
public boolean canBeStopped()
{
return false;
}
/**
* <p>
* Tests whether this task supports status tracking.
* </p>
* <p>
* Default implementation returns <em>false</em>.
* </p>
* <p>
* The task developer can override this method and returns <em>true</em>, having care to regularly calling the {@link TaskExecutionContext#setStatusMessage(String)} method during the task execution.
* </p>
* @return true if this task, during its execution, provides status message regularly.
*/
public boolean supportsStatusTracking()
{
return false;
}
/**
* <p>
* Tests whether this task supports completeness tracking.
* </p>
* <p>
* Default implementation returns <em>false</em>.
* </p>
* <p>
* The task developer can override this method and returns <em>true</em>, having care to regularly calling the {@link TaskExecutionContext#setCompleteness(double)} method during the task execution.
* </p>
* @return true if this task, during its execution, provides a completeness value regularly.
*/
public boolean supportsCompletenessTracking()
{
return false;
}
/**
* <p>
* This method is called to require a task execution, and should contain the core routine of any scheduled task.
* </p>
* <p>
* If the <em>execute()</em> method ends regularly the scheduler will consider the execution successfully completed, and this will be communicated to any {@link SchedulerListener} interested in it. If the <em>execute()</em> method dies throwing a {@link RuntimeException} the scheduler will
* consider it as a failure notification. Any {@link SchedulerListener} will be notified about the occurred exception.
* </p>
* @param context The execution context.
* @throws RuntimeException Task execution has somehow failed.
*/
public abstract void execute(TaskExecutionContext context) throws RuntimeException;
}

View File

@@ -0,0 +1,37 @@
/*
* cron4j - A pure Java cron-like scheduler
*
* Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version
* 2.1, as published by the Free Software Foundation.
*
* 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 Lesser General Public License 2.1 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License version 2.1 along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util.cron4j;
/**
* <p>
* This interface describes a task collector. Task collectors can be registered in a {@link Scheduler} instance with the {@link Scheduler#addTaskCollector(TaskCollector)} method. Any registered task collector is queried by the scheduler once a minute. The developer has to implement the
* {@link TaskCollector#getTasks()} method, returning a {@link TaskTable} whose elements has been collected with a custom logic. In example the list can be extracted from a database.
* </p>
* @author Carlo Pelliccia
* @since 2.0
*/
public interface TaskCollector
{
/**
* Once the instance has been registered on a {@link Scheduler} instance, with the {@link Scheduler#addTaskCollector(TaskCollector)} method, this method will be queried once a minute. It should return a custom {@link TaskTable} object. The scheduler instance will automatically iterate over the
* returned table elements, executing any task whose scheduling pattern is matching the current system time.
* @return The task table that will be automatically injected in the scheduler.
*/
public TaskTable getTasks();
}

View File

@@ -0,0 +1,67 @@
/*
* cron4j - A pure Java cron-like scheduler
*
* Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version
* 2.1, as published by the Free Software Foundation.
*
* 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 Lesser General Public License 2.1 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License version 2.1 along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util.cron4j;
/**
* <p>
* A TaskExecutionContext object provides support methods for the execution of a task. An instance of this class is always passed to the task when its {@link Task#execute(TaskExecutionContext)} method is called. The task, while executing, can use the received context to exchange informations with
* its own executor. If the task declares to supports pausing, stopping, completeness tracking and/or status tracking, it has to use its context methods to perform any declared operation (checks pause and stop requests, sends back tracking informations).
* </p>
* @author Carlo Pelliccia
* @since 2.0
*/
public interface TaskExecutionContext
{
/**
* Returns the scheduler.
* @return The scheduler.
*/
public Scheduler getScheduler();
/**
* Returns the task executor.
* @return The task executor.
*/
public TaskExecutor getTaskExecutor();
/**
* Sets the current status tracking message, that has to be something about what the task is doing at the moment.
* @param message A message representing the current execution status. Null messages will be blanked.
*/
public void setStatusMessage(String message);
/**
* Sets the completeness tracking value, that has to be between 0 and 1.
* @param completeness A completeness value, between 0 and 1. Values out of range will be ignored.
*/
public void setCompleteness(double completeness);
/**
* If the task execution has been paused, stops until the operation is resumed. It can also returns because of a stop operation without any previous resuming. Due to this the task developer should always check the {@link TaskExecutionContext#isStopped()} value after any
* <em>pauseIfRequested()</em> call. Note that a task execution can be paused only if the task {@link Task#canBePaused()} method returns <em>true</em>.
*/
public void pauseIfRequested();
/**
* Checks whether the task execution has been demanded to be stopped. If the returned value is <em>true</em>, the task developer must shut down gracefully its task execution, as soon as possible. Note that a task execution can be stopped only if the task {@link Task#canBePaused()} method returns
* <em>true</em>.
* @return <em>true</em> if the current task execution has been demanded to be stopped; <em>false</em> otherwise.
*/
public boolean isStopped();
}

View File

@@ -0,0 +1,612 @@
/*
* cron4j - A pure Java cron-like scheduler
*
* Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version
* 2.1, as published by the Free Software Foundation.
*
* 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 Lesser General Public License 2.1 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License version 2.1 along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util.cron4j;
import java.util.ArrayList;
/**
* <p>
* Represents a task executor, which is something similar to threads.
* </p>
* <p>
* Each time a task is launched, a new executor is spawned, executing and watching the task
* </p>
* <p>
* Alive task executors can be retrieved with the {@link Scheduler#getExecutingTasks()} method, and they expose method to control the ongoing execution.
* </p>
* @see Scheduler#getExecutingTasks()
* @author Carlo Pelliccia
* @since 2.0
*/
public class TaskExecutor
{
/**
* The scheduler whose this executor belongs to.
*/
final Scheduler scheduler;
/**
* The executed task.
*/
final Task task;
/**
* A task execution context.
*/
final MyContext context;
/**
* A unique ID for this executor (used also as a lock object).
*/
private final String guid = GUIDGenerator.generate();
/**
* An alternative to this (inner classes need it).
*/
final TaskExecutor myself = this;
/**
* A list of {@link TaskExecutorListener} instances.
*/
private final ArrayList<TaskExecutorListener> listeners = new ArrayList<>();
/**
* A time stamp reporting the start time of this thread.
*/
long startTime = -1;
/**
* The thread actually executing the task.
*/
private Thread thread;
/**
* Is this executor paused now?
*/
boolean paused = false;
/**
* Has been this executor stopped?
*/
boolean stopped = false;
/**
* A lock object, for synchronization purposes.
*/
final Object lock = new Object();
/**
* Builds the executor.
* @param scheduler The scheduler whose this executor belongs to.
* @param task The task that has to be executed.
*/
TaskExecutor(Scheduler scheduler, Task task)
{
this.scheduler = scheduler;
this.task = task;
this.context = new MyContext();
}
/**
* Adds a listener to the executor.
* @param listener The listener.
*/
public void addTaskExecutorListener(TaskExecutorListener listener)
{
synchronized (listeners)
{
listeners.add(listener);
}
}
/**
* Removes a listener from the executor.
* @param listener The listener.
*/
public void removeTaskExecutorListener(TaskExecutorListener listener)
{
synchronized (listeners)
{
listeners.remove(listener);
}
}
/**
* Returns an array containing any {@link TaskExecutorListener} previously registered with the {@link TaskExecutor#addTaskExecutorListener(TaskExecutorListener)} method.
* @return An array containing any {@link TaskExecutorListener} previously registered with the {@link TaskExecutor#addTaskExecutorListener(TaskExecutorListener)} method.
*/
public TaskExecutorListener[] getTaskExecutorListeners()
{
synchronized (listeners)
{
int size = listeners.size();
TaskExecutorListener[] ret = new TaskExecutorListener[size];
for (int i = 0; i < size; i++)
{
ret[i] = listeners.get(i);
}
return ret;
}
}
/**
* Returns a GUID for this executor.
* @return A GUID for this executor.
*/
public String getGuid()
{
return guid;
}
/**
* Returns the {@link Scheduler} instance whose this executor belongs to.
* @return The scheduler.
*/
public Scheduler getScheduler()
{
return scheduler;
}
/**
* Returns the representation of the executed task.
* @return The executing/executed task.
*/
public Task getTask()
{
return task;
}
/**
* Returns a time stamp reporting the start time of this executor, or a value less than 0 if this executor has not been yet started.
* @return A time stamp reporting the start time of this executor, or a value less than 0 if this executor has not been yet started.
*/
public long getStartTime()
{
return startTime;
}
/**
* Checks whether this executor supports pausing.
* @return true if this executor supports pausing.
*/
public boolean canBePaused()
{
return task.canBePaused();
}
/**
* Checks whether this executor supports stopping.
* @return true if this executor supports stopping.
*/
public boolean canBeStopped()
{
return task.canBeStopped();
}
/**
* Checks whether this executor provides completeness tracking informations.
* @return true if this executor provides completeness tracking informations.
*/
public boolean supportsCompletenessTracking()
{
return task.supportsCompletenessTracking();
}
/**
* Checks whether this executor provides status tracking messages.
* @return true if this executor provides status tracking messages.
*/
public boolean supportsStatusTracking()
{
return task.supportsStatusTracking();
}
/**
* Starts executing the task (spawns a secondary thread).
* @param daemon true to spawn a daemon thread; false otherwise.
*/
void start(boolean daemon)
{
synchronized (lock)
{
startTime = System.currentTimeMillis();
String name = "cron4j::scheduler[" + scheduler.getGuid() + "]::executor[" + guid + "]";
thread = new Thread(new Runner());
thread.setDaemon(daemon);
thread.setName(name);
thread.start();
}
}
/**
* Pauses the ongoing execution.
* @throws UnsupportedOperationException The operation is not supported if {@link TaskExecutor#canBePaused()} returns <em>false</em>.
*/
public void pause() throws UnsupportedOperationException
{
if (!canBePaused())
{
throw new UnsupportedOperationException("Pause not supported");
}
synchronized (lock)
{
if ((thread != null) && !paused)
{
notifyExecutionPausing();
paused = true;
}
}
}
/**
* Resumes the execution after it has been paused.
*/
public void resume()
{
synchronized (lock)
{
if ((thread != null) && paused)
{
notifyExecutionResuming();
paused = false;
lock.notifyAll();
}
}
}
/**
* Stops the ongoing execution.
* @throws UnsupportedOperationException The operation is not supported if {@link TaskExecutor#canBeStopped()} returns <em>false</em>.
*/
public void stop() throws UnsupportedOperationException
{
if (!canBeStopped())
{
throw new UnsupportedOperationException("Stop not supported");
}
boolean joinit = false;
synchronized (lock)
{
if ((thread != null) && !stopped)
{
stopped = true;
if (paused)
{
resume();
}
notifyExecutionStopping();
thread.interrupt();
joinit = true;
}
}
if (joinit)
{
do
{
try
{
thread.join();
break;
}
catch (InterruptedException e)
{
continue;
}
}
while (true);
thread = null;
}
}
/**
* Waits for this executor to die.
* @throws InterruptedException If any thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.
*/
public void join() throws InterruptedException
{
if (thread != null)
{
thread.join();
}
}
/**
* Tests if this executor is alive. An executor is alive if it has been started and has not yet died.
* @return true if this executor is alive; false otherwise.
*/
public boolean isAlive()
{
if (thread != null)
{
return thread.isAlive();
}
return false;
}
/**
* Returns the current status message.
* @return The current status message.
* @throws UnsupportedOperationException The operation is not supported if {@link TaskExecutor#supportsStatusTracking()} returns <em>false</em>.
*/
public String getStatusMessage() throws UnsupportedOperationException
{
if (!supportsStatusTracking())
{
throw new UnsupportedOperationException("Status tracking not supported");
}
return context.getStatusMessage();
}
/**
* Returns the current completeness value, which is a value between 0 and 1.
* @return The current completeness value, which is a value between 0 and 1.
* @throws UnsupportedOperationException The operation is not supported if {@link TaskExecutor#supportsCompletenessTracking()} returns <em>false</em>.
*/
public double getCompleteness() throws UnsupportedOperationException
{
if (!supportsCompletenessTracking())
{
throw new UnsupportedOperationException("Completeness tracking not supported");
}
return context.getCompleteness();
}
/**
* Tests whether this executor has been paused.
* @return true if this executor is paused; false otherwise.
*/
public boolean isPaused()
{
return paused;
}
/**
* Tests whether this executor has been stopped.
* @return true if this executor is stopped; false otherwise.
*/
public boolean isStopped()
{
return stopped;
}
/**
* Notify registered listeners the execution has been paused.
*/
private void notifyExecutionPausing()
{
synchronized (listeners)
{
for (TaskExecutorListener taskExecutorListener : listeners)
{
TaskExecutorListener l = taskExecutorListener;
l.executionPausing(this);
}
}
}
/**
* Notify registered listeners the execution has been resumed.
*/
private void notifyExecutionResuming()
{
synchronized (listeners)
{
for (TaskExecutorListener taskExecutorListener : listeners)
{
TaskExecutorListener l = taskExecutorListener;
l.executionResuming(this);
}
}
}
/**
* Notify registered listeners the executor is stopping.
*/
private void notifyExecutionStopping()
{
synchronized (listeners)
{
for (TaskExecutorListener taskExecutorListener : listeners)
{
TaskExecutorListener l = taskExecutorListener;
l.executionStopping(this);
}
}
}
/**
* Notify registered listeners the execution has been terminated.
* @param exception If the execution has been terminated due to an error, this is the encountered exception; otherwise the parameter is null.
*/
void notifyExecutionTerminated(Throwable exception)
{
synchronized (listeners)
{
for (TaskExecutorListener taskExecutorListener : listeners)
{
TaskExecutorListener l = taskExecutorListener;
l.executionTerminated(this, exception);
}
}
}
/**
* Notify registered listeners the execution status message has changed.
* @param statusMessage The new status message.
*/
void notifyStatusMessageChanged(String statusMessage)
{
synchronized (listeners)
{
for (TaskExecutorListener taskExecutorListener : listeners)
{
TaskExecutorListener l = taskExecutorListener;
l.statusMessageChanged(this, statusMessage);
}
}
}
/**
* Notify registered listeners the execution completeness value has changed.
* @param completenessValue The new completeness value.
*/
void notifyCompletenessValueChanged(double completenessValue)
{
synchronized (listeners)
{
for (TaskExecutorListener taskExecutorListener : listeners)
{
TaskExecutorListener l = taskExecutorListener;
l.completenessValueChanged(this, completenessValue);
}
}
}
/**
* Inner Runnable class.
*/
private class Runner implements Runnable
{
public Runner()
{
}
/**
* It implements {@link Thread#run()}, executing the wrapped task.
*/
@Override
public void run()
{
Throwable error = null;
startTime = System.currentTimeMillis();
try
{
// Notify.
scheduler.notifyTaskLaunching(myself);
// Task execution.
task.execute(context);
// Succeeded.
scheduler.notifyTaskSucceeded(myself);
}
catch (Throwable exception)
{
// Failed.
error = exception;
scheduler.notifyTaskFailed(myself, exception);
}
finally
{
// Notify.
notifyExecutionTerminated(error);
scheduler.notifyExecutorCompleted(myself);
}
}
}
/**
* Inner TaskExecutionHelper implementation.
*/
private class MyContext implements TaskExecutionContext
{
/**
* Status message.
*/
private String message = "";
/**
* Completeness value.
*/
private double completeness = 0D;
public MyContext()
{
}
@Override
public Scheduler getScheduler()
{
return scheduler;
}
@Override
public TaskExecutor getTaskExecutor()
{
return myself;
}
@Override
public boolean isStopped()
{
return stopped;
}
@Override
public void pauseIfRequested()
{
synchronized (lock)
{
if (paused)
{
try
{
lock.wait();
}
catch (InterruptedException e)
{
}
}
}
}
@Override
public void setCompleteness(double completeness)
{
if ((completeness >= 0D) && (completeness <= 1D))
{
this.completeness = completeness;
notifyCompletenessValueChanged(completeness);
}
}
@Override
public void setStatusMessage(String message)
{
this.message = message != null ? message : "";
notifyStatusMessageChanged(message);
}
/**
* Returns the current status message.
* @return The current status message.
*/
public String getStatusMessage()
{
return message;
}
/**
* Returns the current completeness value, which is a value between 0 and 1.
* @return The current completeness value, which is a value between 0 and 1.
*/
public double getCompleteness()
{
return completeness;
}
}
}

View File

@@ -0,0 +1,67 @@
/*
* cron4j - A pure Java cron-like scheduler
*
* Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version
* 2.1, as published by the Free Software Foundation.
*
* 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 Lesser General Public License 2.1 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License version 2.1 along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util.cron4j;
/**
* A TaskExecutorListener is notified with events from a {@link TaskExecutor}. You can add listeners to a TaskExecutor by calling its {@link TaskExecutor#addTaskExecutorListener(TaskExecutorListener)} method.
* @see TaskExecutor
* @author Carlo Pelliccia
* @since 2.0
*/
public interface TaskExecutorListener
{
/**
* Called when the execution has been requested to be paused.
* @param executor The source executor.
*/
public void executionPausing(TaskExecutor executor);
/**
* Called when the execution has been requested to be resumed.
* @param executor The source executor.
*/
public void executionResuming(TaskExecutor executor);
/**
* Called when the executor has been requested to be stopped.
* @param executor The source executor.
*/
public void executionStopping(TaskExecutor executor);
/**
* Called at execution end. If the execution has failed due to an error, the encountered exception is reported.
* @param executor The source executor.
* @param exception If the execution has been terminated due to an error, this is the encountered exception; otherwise the parameter is null.
*/
public void executionTerminated(TaskExecutor executor, Throwable exception);
/**
* Called every time the execution status message changes.
* @param executor The source executor.
* @param statusMessage The new status message.
*/
public void statusMessageChanged(TaskExecutor executor, String statusMessage);
/**
* Called every time the execution completeness value changes.
* @param executor The source executor.
* @param completenessValue The new completeness value.
*/
public void completenessValueChanged(TaskExecutor executor, double completenessValue);
}

View File

@@ -0,0 +1,102 @@
/*
* cron4j - A pure Java cron-like scheduler
*
* Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version
* 2.1, as published by the Free Software Foundation.
*
* 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 Lesser General Public License 2.1 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License version 2.1 along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util.cron4j;
import java.util.ArrayList;
/**
* <p>
* A table coupling tasks with scheduling patterns.
* </p>
* @author Carlo Pelliccia
* @since 2.0
*/
public class TaskTable
{
/**
* Table size.
*/
private int size = 0;
/**
* Pattern list.
*/
private final ArrayList<SchedulingPattern> patterns = new ArrayList<>();
/**
* Task list.
*/
private final ArrayList<Task> tasks = new ArrayList<>();
/**
* Adds a task and an associated scheduling pattern to the table.
* @param pattern The associated scheduling pattern.
* @param task The task.
*/
public void add(SchedulingPattern pattern, Task task)
{
patterns.add(pattern);
tasks.add(task);
size++;
}
/**
* Returns the size of the table, representing the number of the elements stored in it.
* @return The table size.
*/
public int size()
{
return size;
}
/**
* Returns the task at the specified position. Valid positions are between <em>0</em> to <em>{@link TaskTable#size()} - 1</em>.
* @param index The index.
* @return The task at the specified position.
* @throws IndexOutOfBoundsException If the supplied index is out of range.
*/
public Task getTask(int index) throws IndexOutOfBoundsException
{
return tasks.get(index);
}
/**
* Returns the scheduling pattern at the specified position. Valid positions are between <em>0</em> to <em>{@link TaskTable#size()} - 1</em>.
* @param index The index.
* @return The scheduling pattern at the specified position.
* @throws IndexOutOfBoundsException If the supplied index is out of range.
*/
public SchedulingPattern getSchedulingPattern(int index) throws IndexOutOfBoundsException
{
return patterns.get(index);
}
/**
* Remove a task from the table.
* @param index The index of the task to remove.
* @throws IndexOutOfBoundsException If the supplied index is not valid.
* @since 2.1
*/
public void remove(int index) throws IndexOutOfBoundsException
{
tasks.remove(index);
patterns.remove(index);
size--;
}
}

View File

@@ -0,0 +1,117 @@
/*
* cron4j - A pure Java cron-like scheduler
*
* Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version
* 2.1, as published by the Free Software Foundation.
*
* 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 Lesser General Public License 2.1 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License version 2.1 along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util.cron4j;
/**
* <p>
* TimeThreads are used by {@link Scheduler} instances. A TimerThread spends most of the time sleeping. It wakes up every minute and it requests to the scheduler the spawning of a {@link LauncherThread}.
* </p>
* @author Carlo Pelliccia
* @since 2.0
*/
class TimerThread extends Thread
{
/**
* A GUID for this object.
*/
private final String guid = GUIDGenerator.generate();
/**
* The owner scheduler.
*/
private Scheduler scheduler;
/**
* Builds the timer thread.
* @param scheduler The owner scheduler.
*/
public TimerThread(Scheduler scheduler)
{
this.scheduler = scheduler;
// Thread name.
String name = "cron4j::scheduler[" + scheduler.getGuid() + "]::timer[" + guid + "]";
setName(name);
}
/**
* Returns the GUID for this object.
* @return The GUID for this object.
*/
public Object getGuid()
{
return guid;
}
/**
* It has been reported that the {@link Thread#sleep(long)} method sometimes exits before the requested time has passed. This one offers an alternative that sometimes could sleep a few millis more than requested, but never less.
* @param millis The length of time to sleep in milliseconds.
* @throws InterruptedException If another thread has interrupted the current thread. The <i>interrupted status</i> of the current thread is cleared when this exception is thrown.
* @see Thread#sleep(long)
*/
private void safeSleep(long millis) throws InterruptedException
{
long done = 0;
do
{
long before = System.currentTimeMillis();
sleep(millis - done);
long after = System.currentTimeMillis();
done += (after - before);
}
while (done < millis);
}
/**
* Overrides {@link Thread#run()}.
*/
@Override
public void run()
{
// What time is it?
long millis = System.currentTimeMillis();
// Calculating next minute.
long nextMinute = ((millis / 60000) + 1) * 60000;
// Work until the scheduler is started.
for (;;)
{
// Coffee break 'till next minute comes!
long sleepTime = (nextMinute - System.currentTimeMillis());
if (sleepTime > 0)
{
try
{
safeSleep(sleepTime);
}
catch (InterruptedException e)
{
// Must exit!
break;
}
}
// What time is it?
millis = System.currentTimeMillis();
// Launching the launching thread!
scheduler.spawnLauncher(millis);
// Calculating next minute.
nextMinute = ((millis / 60000) + 1) * 60000;
}
// Discard scheduler reference.
scheduler = null;
}
}

View File

@@ -0,0 +1,35 @@
/*
* cron4j - A pure Java cron-like scheduler
*
* Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version
* 2.1, as published by the Free Software Foundation.
*
* 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 Lesser General Public License 2.1 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License version 2.1 along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.util.cron4j;
/**
* <p>
* This interface describes the ValueMatcher behavior. A ValueMatcher is an object that validate an integer value against a set of rules.
* </p>
* @author Carlo Pelliccia
*/
interface ValueMatcher
{
/**
* Validate the given integer value against a set of rules.
* @param value The value.
* @return true if the given value matches the rules of the ValueMatcher, false otherwise.
*/
public boolean match(int value);
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2014 Frank Asseg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.l2jmobius.gameserver.util.exp4j;
/**
* Represents an argument separator in functions i.e: ','
*/
class ArgumentSeparatorToken extends Token
{
/**
* Create a new instance
*/
ArgumentSeparatorToken()
{
super(Token.TOKEN_SEPARATOR);
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2015 Federico Vera
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.l2jmobius.gameserver.util.exp4j;
import java.util.EmptyStackException;
/**
* Simple double stack using a double array as data storage
* @author Federico Vera (dktcoding [at] gmail)
*/
class ArrayStack
{
private double[] data;
private int idx;
ArrayStack()
{
this(5);
}
ArrayStack(int initialCapacity)
{
if (initialCapacity <= 0)
{
throw new IllegalArgumentException("Stack's capacity must be positive");
}
data = new double[initialCapacity];
idx = -1;
}
void push(double value)
{
if ((idx + 1) == data.length)
{
double[] temp = new double[(int) (data.length * 1.2) + 1];
System.arraycopy(data, 0, temp, 0, data.length);
data = temp;
}
data[++idx] = value;
}
double peek()
{
if (idx == -1)
{
throw new EmptyStackException();
}
return data[idx];
}
double pop()
{
if (idx == -1)
{
throw new EmptyStackException();
}
return data[idx--];
}
boolean isEmpty()
{
return idx == -1;
}
int size()
{
return idx + 1;
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2014 Frank Asseg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.l2jmobius.gameserver.util.exp4j;
/**
* represents closed parentheses
*/
class CloseParenthesesToken extends Token
{
/**
* Creare a new instance
*/
CloseParenthesesToken()
{
super(Token.TOKEN_PARENTHESES_CLOSE);
}
}

View File

@@ -0,0 +1,254 @@
/*
* Copyright 2014 Frank Asseg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.l2jmobius.gameserver.util.exp4j;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
public class Expression
{
private final Token[] tokens;
private final Map<String, Double> variables;
private final Set<String> userFunctionNames;
private static Map<String, Double> createDefaultVariables()
{
final Map<String, Double> vars = new HashMap<>(4);
vars.put("pi", Math.PI);
vars.put("Ο€", Math.PI);
vars.put("Ο†", 1.61803398874d);
vars.put("e", Math.E);
return vars;
}
/**
* Creates a new expression that is a copy of the existing one.
* @param existing the expression to copy
*/
public Expression(final Expression existing)
{
tokens = Arrays.copyOf(existing.tokens, existing.tokens.length);
variables = new HashMap<>();
variables.putAll(existing.variables);
userFunctionNames = new HashSet<>(existing.userFunctionNames);
}
Expression(final Token[] tokens)
{
this.tokens = tokens;
variables = createDefaultVariables();
userFunctionNames = Collections.<String> emptySet();
}
Expression(final Token[] tokens, Set<String> userFunctionNames)
{
this.tokens = tokens;
variables = createDefaultVariables();
this.userFunctionNames = userFunctionNames;
}
public Expression setVariable(final String name, final double value)
{
checkVariableName(name);
variables.put(name, Double.valueOf(value));
return this;
}
private void checkVariableName(String name)
{
if (userFunctionNames.contains(name) || (Functions.getBuiltinFunction(name) != null))
{
throw new IllegalArgumentException("The variable name '" + name + "' is invalid. Since there exists a function with the same name");
}
}
public Expression setVariables(Map<String, Double> variables)
{
for (Map.Entry<String, Double> v : variables.entrySet())
{
setVariable(v.getKey(), v.getValue());
}
return this;
}
public Set<String> getVariableNames()
{
final Set<String> variables = new HashSet<>();
for (final Token t : tokens)
{
if (t.getType() == Token.TOKEN_VARIABLE)
{
variables.add(((VariableToken) t).getName());
}
}
return variables;
}
public ValidationResult validate(boolean checkVariablesSet)
{
final List<String> errors = new ArrayList<>(0);
if (checkVariablesSet)
{
/* check that all vars have a value set */
for (final Token t : tokens)
{
if (t.getType() == Token.TOKEN_VARIABLE)
{
final String var = ((VariableToken) t).getName();
if (!variables.containsKey(var))
{
errors.add("The setVariable '" + var + "' has not been set");
}
}
}
}
/*
* Check if the number of operands, functions and operators match. The idea is to increment a counter for operands and decrease it for operators. When a function occurs the number of available arguments has to be greater than or equals to the function's expected number of arguments. The
* count has to be larger than 1 at all times and exactly 1 after all tokens have been processed
*/
int count = 0;
for (Token tok : tokens)
{
switch (tok.getType())
{
case Token.TOKEN_NUMBER:
case Token.TOKEN_VARIABLE:
count++;
break;
case Token.TOKEN_FUNCTION:
final Function func = ((FunctionToken) tok).getFunction();
final int argsNum = func.getNumArguments();
if (argsNum > count)
{
errors.add("Not enough arguments for '" + func.getName() + "'");
}
if (argsNum > 1)
{
count -= argsNum - 1;
}
else if (argsNum == 0)
{
// see https://github.com/fasseg/exp4j/issues/59
count++;
}
break;
case Token.TOKEN_OPERATOR:
Operator op = ((OperatorToken) tok).getOperator();
if (op.getNumOperands() == 2)
{
count--;
}
break;
}
if (count < 1)
{
errors.add("Too many operators");
return new ValidationResult(false, errors);
}
}
if (count > 1)
{
errors.add("Too many operands");
}
return errors.size() == 0 ? ValidationResult.SUCCESS : new ValidationResult(false, errors);
}
public ValidationResult validate()
{
return validate(true);
}
public Future<Double> evaluateAsync(ExecutorService executor)
{
return executor.submit(() -> evaluate());
}
public double evaluate()
{
final ArrayStack output = new ArrayStack();
for (Token t : tokens)
{
if (t.getType() == Token.TOKEN_NUMBER)
{
output.push(((NumberToken) t).getValue());
}
else if (t.getType() == Token.TOKEN_VARIABLE)
{
final String name = ((VariableToken) t).getName();
final Double value = variables.get(name);
if (value == null)
{
throw new IllegalArgumentException("No value has been set for the setVariable '" + name + "'.");
}
output.push(value);
}
else if (t.getType() == Token.TOKEN_OPERATOR)
{
OperatorToken op = (OperatorToken) t;
if (output.size() < op.getOperator().getNumOperands())
{
throw new IllegalArgumentException("Invalid number of operands available for '" + op.getOperator().getSymbol() + "' operator");
}
if (op.getOperator().getNumOperands() == 2)
{
/* pop the operands and push the result of the operation */
double rightArg = output.pop();
double leftArg = output.pop();
output.push(op.getOperator().apply(leftArg, rightArg));
}
else if (op.getOperator().getNumOperands() == 1)
{
/* pop the operand and push the result of the operation */
double arg = output.pop();
output.push(op.getOperator().apply(arg));
}
}
else if (t.getType() == Token.TOKEN_FUNCTION)
{
FunctionToken func = (FunctionToken) t;
final int numArguments = func.getFunction().getNumArguments();
if (output.size() < numArguments)
{
throw new IllegalArgumentException("Invalid number of arguments available for '" + func.getFunction().getName() + "' function");
}
/* collect the arguments from the stack */
double[] args = new double[numArguments];
for (int j = numArguments - 1; j >= 0; j--)
{
args[j] = output.pop();
}
output.push(func.getFunction().apply(args));
}
}
if (output.size() > 1)
{
throw new IllegalArgumentException("Invalid number of items on the output queue. Might be caused by an invalid number of arguments for a function.");
}
return output.pop();
}
}

View File

@@ -0,0 +1,213 @@
/*
* Copyright 2014 Frank Asseg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.l2jmobius.gameserver.util.exp4j;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Factory class for {@link Expression} instances. This class is the main API entrypoint. Users should create new {@link Expression} instances using this factory class.
*/
public class ExpressionBuilder
{
private final String expression;
private final Map<String, Function> userFunctions;
private final Map<String, Operator> userOperators;
private final Set<String> variableNames;
private boolean implicitMultiplication = true;
/**
* Create a new ExpressionBuilder instance and initialize it with a given expression string.
* @param expression the expression to be parsed
*/
public ExpressionBuilder(String expression)
{
if ((expression == null) || (expression.trim().length() == 0))
{
throw new IllegalArgumentException("Expression can not be empty");
}
this.expression = expression;
userOperators = new HashMap<>(4);
userFunctions = new HashMap<>(4);
variableNames = new HashSet<>(4);
}
/**
* Add a {@link com.l2jmobius.gameserver.util.exp4j.Function} implementation available for use in the expression
* @param function the custom {@link com.l2jmobius.gameserver.util.exp4j.Function} implementation that should be available for use in the expression.
* @return the ExpressionBuilder instance
*/
public ExpressionBuilder function(Function function)
{
userFunctions.put(function.getName(), function);
return this;
}
/**
* Add multiple {@link com.l2jmobius.gameserver.util.exp4j.Function} implementations available for use in the expression
* @param functions the custom {@link com.l2jmobius.gameserver.util.exp4j.Function} implementations
* @return the ExpressionBuilder instance
*/
public ExpressionBuilder functions(Function... functions)
{
for (Function f : functions)
{
userFunctions.put(f.getName(), f);
}
return this;
}
/**
* Add multiple {@link com.l2jmobius.gameserver.util.exp4j.Function} implementations available for use in the expression
* @param functions A {@link java.util.List} of custom {@link com.l2jmobius.gameserver.util.exp4j.Function} implementations
* @return the ExpressionBuilder instance
*/
public ExpressionBuilder functions(List<Function> functions)
{
for (Function f : functions)
{
userFunctions.put(f.getName(), f);
}
return this;
}
/**
* Declare variable names used in the expression
* @param variableNames the variables used in the expression
* @return the ExpressionBuilder instance
*/
public ExpressionBuilder variables(Set<String> variableNames)
{
this.variableNames.addAll(variableNames);
return this;
}
/**
* Declare variable names used in the expression
* @param variableNames the variables used in the expression
* @return the ExpressionBuilder instance
*/
public ExpressionBuilder variables(String... variableNames)
{
Collections.addAll(this.variableNames, variableNames);
return this;
}
/**
* Declare a variable used in the expression
* @param variableName the variable used in the expression
* @return the ExpressionBuilder instance
*/
public ExpressionBuilder variable(String variableName)
{
variableNames.add(variableName);
return this;
}
public ExpressionBuilder implicitMultiplication(boolean enabled)
{
implicitMultiplication = enabled;
return this;
}
/**
* Add an {@link com.l2jmobius.gameserver.util.exp4j.Operator} which should be available for use in the expression
* @param operator the custom {@link com.l2jmobius.gameserver.util.exp4j.Operator} to add
* @return the ExpressionBuilder instance
*/
public ExpressionBuilder operator(Operator operator)
{
checkOperatorSymbol(operator);
userOperators.put(operator.getSymbol(), operator);
return this;
}
private void checkOperatorSymbol(Operator op)
{
String name = op.getSymbol();
for (char ch : name.toCharArray())
{
if (!Operator.isAllowedOperatorChar(ch))
{
throw new IllegalArgumentException("The operator symbol '" + name + "' is invalid");
}
}
}
/**
* Add multiple {@link com.l2jmobius.gameserver.util.exp4j.Operator} implementations which should be available for use in the expression
* @param operators the set of custom {@link com.l2jmobius.gameserver.util.exp4j.Operator} implementations to add
* @return the ExpressionBuilder instance
*/
public ExpressionBuilder operator(Operator... operators)
{
for (Operator o : operators)
{
this.operator(o);
}
return this;
}
/**
* Add multiple {@link com.l2jmobius.gameserver.util.exp4j.Operator} implementations which should be available for use in the expression
* @param operators the {@link java.util.List} of custom {@link com.l2jmobius.gameserver.util.exp4j.Operator} implementations to add
* @return the ExpressionBuilder instance
*/
public ExpressionBuilder operator(List<Operator> operators)
{
for (Operator o : operators)
{
this.operator(o);
}
return this;
}
/**
* Build the {@link Expression} instance using the custom operators and functions set.
* @return an {@link Expression} instance which can be used to evaluate the result of the expression
*/
public Expression build()
{
if (expression.length() == 0)
{
throw new IllegalArgumentException("The expression can not be empty");
}
/* set the contants' varibale names */
variableNames.add("pi");
variableNames.add("Ο€");
variableNames.add("e");
variableNames.add("Ο†");
/* Check if there are duplicate vars/functions */
for (String var : variableNames)
{
if ((Functions.getBuiltinFunction(var) != null) || userFunctions.containsKey(var))
{
throw new IllegalArgumentException("A variable can not have the same name as a function [" + var + "]");
}
}
return new Expression(ShuntingYard.convertToRPN(expression, userFunctions, userOperators, variableNames, implicitMultiplication), userFunctions.keySet());
}
}

View File

@@ -0,0 +1,132 @@
/*
* Copyright 2014 Frank Asseg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.l2jmobius.gameserver.util.exp4j;
/**
* A class representing a Function which can be used in an expression
*/
public abstract class Function
{
protected final String name;
protected final int numArguments;
/**
* Create a new Function with a given name and number of arguments
* @param name the name of the Function
* @param numArguments the number of arguments the function takes
*/
public Function(String name, int numArguments)
{
if (numArguments < 0)
{
throw new IllegalArgumentException("The number of function arguments can not be less than 0 for '" + name + "'");
}
if (!isValidFunctionName(name))
{
throw new IllegalArgumentException("The function name '" + name + "' is invalid");
}
this.name = name;
this.numArguments = numArguments;
}
/**
* Create a new Function with a given name that takes a single argument
* @param name the name of the Function
*/
public Function(String name)
{
this(name, 1);
}
/**
* Get the name of the Function
* @return the name
*/
public String getName()
{
return name;
}
/**
* Get the number of arguments for this function
* @return the number of arguments
*/
public int getNumArguments()
{
return numArguments;
}
/**
* Method that does the actual calculation of the function value given the arguments
* @param args the set of arguments used for calculating the function
* @return the result of the function evaluation
*/
public abstract double apply(double... args);
/**
* Get the set of characters which are allowed for use in Function names.
* @return the set of characters allowed
* @deprecated since 0.4.5 All unicode letters are allowed to be used in function names since 0.4.3. This API Function can be safely ignored. Checks for function name validity can be done using Character.isLetter() et al.
*/
@Deprecated
public static char[] getAllowedFunctionCharacters()
{
char[] chars = new char[53];
int count = 0;
for (int i = 65; i < 91; i++)
{
chars[count++] = (char) i;
}
for (int i = 97; i < 123; i++)
{
chars[count++] = (char) i;
}
chars[count] = '_';
return chars;
}
public static boolean isValidFunctionName(final String name)
{
if (name == null)
{
return false;
}
final int size = name.length();
if (size == 0)
{
return false;
}
for (int i = 0; i < size; i++)
{
final char c = name.charAt(i);
if (Character.isLetter(c) || (c == '_'))
{
continue;
}
else if (Character.isDigit(c) && (i > 0))
{
continue;
}
return false;
}
return true;
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2014 Frank Asseg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.l2jmobius.gameserver.util.exp4j;
public class FunctionToken extends Token
{
private final Function function;
public FunctionToken(final Function function)
{
super(Token.TOKEN_FUNCTION);
this.function = function;
}
public Function getFunction()
{
return function;
}
}

View File

@@ -0,0 +1,356 @@
/*
* Copyright 2014 Frank Asseg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.l2jmobius.gameserver.util.exp4j;
/**
* Class representing the builtin functions available for use in expressions
*/
public class Functions
{
private static final int INDEX_SIN = 0;
private static final int INDEX_COS = 1;
private static final int INDEX_TAN = 2;
private static final int INDEX_COT = 3;
private static final int INDEX_LOG = 4;
private static final int INDEX_LOG1P = 5;
private static final int INDEX_ABS = 6;
private static final int INDEX_ACOS = 7;
private static final int INDEX_ASIN = 8;
private static final int INDEX_ATAN = 9;
private static final int INDEX_CBRT = 10;
private static final int INDEX_CEIL = 11;
private static final int INDEX_FLOOR = 12;
private static final int INDEX_SINH = 13;
private static final int INDEX_SQRT = 14;
private static final int INDEX_TANH = 15;
private static final int INDEX_COSH = 16;
private static final int INDEX_POW = 17;
private static final int INDEX_EXP = 18;
private static final int INDEX_EXPM1 = 19;
private static final int INDEX_LOG10 = 20;
private static final int INDEX_LOG2 = 21;
private static final int INDEX_SGN = 22;
private static final Function[] builtinFunctions = new Function[23];
static
{
builtinFunctions[INDEX_SIN] = new Function("sin")
{
@Override
public double apply(double... args)
{
return Math.sin(args[0]);
}
};
builtinFunctions[INDEX_COS] = new Function("cos")
{
@Override
public double apply(double... args)
{
return Math.cos(args[0]);
}
};
builtinFunctions[INDEX_TAN] = new Function("tan")
{
@Override
public double apply(double... args)
{
return Math.tan(args[0]);
}
};
builtinFunctions[INDEX_COT] = new Function("cot")
{
@Override
public double apply(double... args)
{
double tan = Math.tan(args[0]);
if (tan == 0d)
{
throw new ArithmeticException("Division by zero in cotangent!");
}
return 1d / Math.tan(args[0]);
}
};
builtinFunctions[INDEX_LOG] = new Function("log")
{
@Override
public double apply(double... args)
{
return Math.log(args[0]);
}
};
builtinFunctions[INDEX_LOG2] = new Function("log2")
{
@Override
public double apply(double... args)
{
return Math.log(args[0]) / Math.log(2d);
}
};
builtinFunctions[INDEX_LOG10] = new Function("log10")
{
@Override
public double apply(double... args)
{
return Math.log10(args[0]);
}
};
builtinFunctions[INDEX_LOG1P] = new Function("log1p")
{
@Override
public double apply(double... args)
{
return Math.log1p(args[0]);
}
};
builtinFunctions[INDEX_ABS] = new Function("abs")
{
@Override
public double apply(double... args)
{
return Math.abs(args[0]);
}
};
builtinFunctions[INDEX_ACOS] = new Function("acos")
{
@Override
public double apply(double... args)
{
return Math.acos(args[0]);
}
};
builtinFunctions[INDEX_ASIN] = new Function("asin")
{
@Override
public double apply(double... args)
{
return Math.asin(args[0]);
}
};
builtinFunctions[INDEX_ATAN] = new Function("atan")
{
@Override
public double apply(double... args)
{
return Math.atan(args[0]);
}
};
builtinFunctions[INDEX_CBRT] = new Function("cbrt")
{
@Override
public double apply(double... args)
{
return Math.cbrt(args[0]);
}
};
builtinFunctions[INDEX_FLOOR] = new Function("floor")
{
@Override
public double apply(double... args)
{
return Math.floor(args[0]);
}
};
builtinFunctions[INDEX_SINH] = new Function("sinh")
{
@Override
public double apply(double... args)
{
return Math.sinh(args[0]);
}
};
builtinFunctions[INDEX_SQRT] = new Function("sqrt")
{
@Override
public double apply(double... args)
{
return Math.sqrt(args[0]);
}
};
builtinFunctions[INDEX_TANH] = new Function("tanh")
{
@Override
public double apply(double... args)
{
return Math.tanh(args[0]);
}
};
builtinFunctions[INDEX_COSH] = new Function("cosh")
{
@Override
public double apply(double... args)
{
return Math.cosh(args[0]);
}
};
builtinFunctions[INDEX_CEIL] = new Function("ceil")
{
@Override
public double apply(double... args)
{
return Math.ceil(args[0]);
}
};
builtinFunctions[INDEX_POW] = new Function("pow", 2)
{
@Override
public double apply(double... args)
{
return Math.pow(args[0], args[1]);
}
};
builtinFunctions[INDEX_EXP] = new Function("exp", 1)
{
@Override
public double apply(double... args)
{
return Math.exp(args[0]);
}
};
builtinFunctions[INDEX_EXPM1] = new Function("expm1", 1)
{
@Override
public double apply(double... args)
{
return Math.expm1(args[0]);
}
};
builtinFunctions[INDEX_SGN] = new Function("signum", 1)
{
@Override
public double apply(double... args)
{
if (args[0] > 0)
{
return 1;
}
else if (args[0] < 0)
{
return -1;
}
else
{
return 0;
}
}
};
}
/**
* Get the builtin function for a given name
* @param name te name of the function
* @return a Function instance
*/
public static Function getBuiltinFunction(final String name)
{
if (name.equals("sin"))
{
return builtinFunctions[INDEX_SIN];
}
else if (name.equals("cos"))
{
return builtinFunctions[INDEX_COS];
}
else if (name.equals("tan"))
{
return builtinFunctions[INDEX_TAN];
}
else if (name.equals("cot"))
{
return builtinFunctions[INDEX_COT];
}
else if (name.equals("asin"))
{
return builtinFunctions[INDEX_ASIN];
}
else if (name.equals("acos"))
{
return builtinFunctions[INDEX_ACOS];
}
else if (name.equals("atan"))
{
return builtinFunctions[INDEX_ATAN];
}
else if (name.equals("sinh"))
{
return builtinFunctions[INDEX_SINH];
}
else if (name.equals("cosh"))
{
return builtinFunctions[INDEX_COSH];
}
else if (name.equals("tanh"))
{
return builtinFunctions[INDEX_TANH];
}
else if (name.equals("abs"))
{
return builtinFunctions[INDEX_ABS];
}
else if (name.equals("log"))
{
return builtinFunctions[INDEX_LOG];
}
else if (name.equals("log10"))
{
return builtinFunctions[INDEX_LOG10];
}
else if (name.equals("log2"))
{
return builtinFunctions[INDEX_LOG2];
}
else if (name.equals("log1p"))
{
return builtinFunctions[INDEX_LOG1P];
}
else if (name.equals("ceil"))
{
return builtinFunctions[INDEX_CEIL];
}
else if (name.equals("floor"))
{
return builtinFunctions[INDEX_FLOOR];
}
else if (name.equals("sqrt"))
{
return builtinFunctions[INDEX_SQRT];
}
else if (name.equals("cbrt"))
{
return builtinFunctions[INDEX_CBRT];
}
else if (name.equals("pow"))
{
return builtinFunctions[INDEX_POW];
}
else if (name.equals("exp"))
{
return builtinFunctions[INDEX_EXP];
}
else if (name.equals("expm1"))
{
return builtinFunctions[INDEX_EXPM1];
}
else if (name.equals("signum"))
{
return builtinFunctions[INDEX_SGN];
}
else
{
return null;
}
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2014 Frank Asseg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.l2jmobius.gameserver.util.exp4j;
/**
* Represents a number in the expression
*/
public final class NumberToken extends Token
{
private final double value;
/**
* Create a new instance
* @param value the value of the number
*/
public NumberToken(double value)
{
super(TOKEN_NUMBER);
this.value = value;
}
NumberToken(final char[] expression, final int offset, final int len)
{
this(Double.parseDouble(String.valueOf(expression, offset, len)));
}
/**
* Get the value of the number
* @return the value
*/
public double getValue()
{
return value;
}
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright 2014 Frank Asseg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.l2jmobius.gameserver.util.exp4j;
class OpenParenthesesToken extends Token
{
OpenParenthesesToken()
{
super(TOKEN_PARENTHESES_OPEN);
}
}

View File

@@ -0,0 +1,161 @@
/*
* Copyright 2014 Frank Asseg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.l2jmobius.gameserver.util.exp4j;
/**
* Class representing operators that can be used in an expression
*/
public abstract class Operator
{
/**
* The precedence value for the addition operation
*/
public static final int PRECEDENCE_ADDITION = 500;
/**
* The precedence value for the subtraction operation
*/
public static final int PRECEDENCE_SUBTRACTION = PRECEDENCE_ADDITION;
/**
* The precedence value for the multiplication operation
*/
public static final int PRECEDENCE_MULTIPLICATION = 1000;
/**
* The precedence value for the division operation
*/
public static final int PRECEDENCE_DIVISION = PRECEDENCE_MULTIPLICATION;
/**
* The precedence value for the modulo operation
*/
public static final int PRECEDENCE_MODULO = PRECEDENCE_DIVISION;
/**
* The precedence value for the power operation
*/
public static final int PRECEDENCE_POWER = 10000;
/**
* The precedence value for the unary minus operation
*/
public static final int PRECEDENCE_UNARY_MINUS = 5000;
/**
* The precedence value for the unary plus operation
*/
public static final int PRECEDENCE_UNARY_PLUS = PRECEDENCE_UNARY_MINUS;
/**
* The set of allowed operator chars
*/
public static final char[] ALLOWED_OPERATOR_CHARS =
{
'+',
'-',
'*',
'/',
'%',
'^',
'!',
'#',
'§',
'$',
'&',
';',
':',
'~',
'<',
'>',
'|',
'='
};
protected final int numOperands;
protected final boolean leftAssociative;
protected final String symbol;
protected final int precedence;
/**
* Create a new operator for use in expressions
* @param symbol the symbol of the operator
* @param numberOfOperands the number of operands the operator takes (1 or 2)
* @param leftAssociative set to true if the operator is left associative, false if it is right associative
* @param precedence the precedence value of the operator
*/
public Operator(String symbol, int numberOfOperands, boolean leftAssociative, int precedence)
{
super();
this.numOperands = numberOfOperands;
this.leftAssociative = leftAssociative;
this.symbol = symbol;
this.precedence = precedence;
}
/**
* Check if a character is an allowed operator char
* @param ch the char to check
* @return true if the char is allowed an an operator symbol, false otherwise
*/
public static boolean isAllowedOperatorChar(char ch)
{
for (char allowed : ALLOWED_OPERATOR_CHARS)
{
if (ch == allowed)
{
return true;
}
}
return false;
}
/**
* Check if the operator is left associative
* @return true os the operator is left associative, false otherwise
*/
public boolean isLeftAssociative()
{
return leftAssociative;
}
/**
* Check the precedence value for the operator
* @return the precedence value
*/
public int getPrecedence()
{
return precedence;
}
/**
* Apply the operation on the given operands
* @param args the operands for the operation
* @return the calculated result of the operation
*/
public abstract double apply(double... args);
/**
* Get the operator symbol
* @return the symbol
*/
public String getSymbol()
{
return symbol;
}
/**
* Get the number of operands
* @return the number of operands
*/
public int getNumOperands()
{
return numOperands;
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2014 Frank Asseg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.l2jmobius.gameserver.util.exp4j;
/**
* Represents an operator used in expressions
*/
public class OperatorToken extends Token
{
private final Operator operator;
/**
* Create a new instance
* @param op the operator
*/
public OperatorToken(Operator op)
{
super(Token.TOKEN_OPERATOR);
if (op == null)
{
throw new IllegalArgumentException("Operator is unknown for token.");
}
this.operator = op;
}
/**
* Get the operator for that token
* @return the operator
*/
public Operator getOperator()
{
return operator;
}
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright 2014 Frank Asseg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.l2jmobius.gameserver.util.exp4j;
public abstract class Operators
{
private static final int INDEX_ADDITION = 0;
private static final int INDEX_SUBTRACTION = 1;
private static final int INDEX_MUTLIPLICATION = 2;
private static final int INDEX_DIVISION = 3;
private static final int INDEX_POWER = 4;
private static final int INDEX_MODULO = 5;
private static final int INDEX_UNARYMINUS = 6;
private static final int INDEX_UNARYPLUS = 7;
private static final Operator[] builtinOperators = new Operator[8];
static
{
builtinOperators[INDEX_ADDITION] = new Operator("+", 2, true, Operator.PRECEDENCE_ADDITION)
{
@Override
public double apply(final double... args)
{
return args[0] + args[1];
}
};
builtinOperators[INDEX_SUBTRACTION] = new Operator("-", 2, true, Operator.PRECEDENCE_ADDITION)
{
@Override
public double apply(final double... args)
{
return args[0] - args[1];
}
};
builtinOperators[INDEX_UNARYMINUS] = new Operator("-", 1, false, Operator.PRECEDENCE_UNARY_MINUS)
{
@Override
public double apply(final double... args)
{
return -args[0];
}
};
builtinOperators[INDEX_UNARYPLUS] = new Operator("+", 1, false, Operator.PRECEDENCE_UNARY_PLUS)
{
@Override
public double apply(final double... args)
{
return args[0];
}
};
builtinOperators[INDEX_MUTLIPLICATION] = new Operator("*", 2, true, Operator.PRECEDENCE_MULTIPLICATION)
{
@Override
public double apply(final double... args)
{
return args[0] * args[1];
}
};
builtinOperators[INDEX_DIVISION] = new Operator("/", 2, true, Operator.PRECEDENCE_DIVISION)
{
@Override
public double apply(final double... args)
{
if (args[1] == 0d)
{
throw new ArithmeticException("Division by zero!");
}
return args[0] / args[1];
}
};
builtinOperators[INDEX_POWER] = new Operator("^", 2, false, Operator.PRECEDENCE_POWER)
{
@Override
public double apply(final double... args)
{
return Math.pow(args[0], args[1]);
}
};
builtinOperators[INDEX_MODULO] = new Operator("%", 2, true, Operator.PRECEDENCE_MODULO)
{
@Override
public double apply(final double... args)
{
if (args[1] == 0d)
{
throw new ArithmeticException("Division by zero!");
}
return args[0] % args[1];
}
};
}
public static Operator getBuiltinOperator(final char symbol, final int numArguments)
{
switch (symbol)
{
case '+':
if (numArguments != 1)
{
return builtinOperators[INDEX_ADDITION];
}
return builtinOperators[INDEX_UNARYPLUS];
case '-':
if (numArguments != 1)
{
return builtinOperators[INDEX_SUBTRACTION];
}
return builtinOperators[INDEX_UNARYMINUS];
case '*':
return builtinOperators[INDEX_MUTLIPLICATION];
case '/':
return builtinOperators[INDEX_DIVISION];
case '^':
return builtinOperators[INDEX_POWER];
case '%':
return builtinOperators[INDEX_MODULO];
default:
return null;
}
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2014 Frank Asseg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.l2jmobius.gameserver.util.exp4j;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
/**
* Shunting yard implementation to convert infix to reverse polish notation
*/
public class ShuntingYard
{
/**
* Convert a Set of tokens from infix to reverse polish notation
* @param expression the expression to convert
* @param userFunctions the custom functions used
* @param userOperators the custom operators used
* @param variableNames the variable names used in the expression
* @param implicitMultiplication set to fasle to turn off implicit multiplication
* @return a {@link com.l2jmobius.gameserver.util.exp4j.Token} array containing the result
*/
public static Token[] convertToRPN(final String expression, final Map<String, Function> userFunctions, final Map<String, Operator> userOperators, final Set<String> variableNames, final boolean implicitMultiplication)
{
final Stack<Token> stack = new Stack<>();
final List<Token> output = new ArrayList<>();
final Tokenizer tokenizer = new Tokenizer(expression, userFunctions, userOperators, variableNames, implicitMultiplication);
while (tokenizer.hasNext())
{
Token token = tokenizer.nextToken();
switch (token.getType())
{
case Token.TOKEN_NUMBER:
case Token.TOKEN_VARIABLE:
output.add(token);
break;
case Token.TOKEN_FUNCTION:
stack.add(token);
break;
case Token.TOKEN_SEPARATOR:
while (!stack.empty() && (stack.peek().getType() != Token.TOKEN_PARENTHESES_OPEN))
{
output.add(stack.pop());
}
if (stack.empty() || (stack.peek().getType() != Token.TOKEN_PARENTHESES_OPEN))
{
throw new IllegalArgumentException("Misplaced function separator ',' or mismatched parentheses");
}
break;
case Token.TOKEN_OPERATOR:
while (!stack.empty() && (stack.peek().getType() == Token.TOKEN_OPERATOR))
{
OperatorToken o1 = (OperatorToken) token;
OperatorToken o2 = (OperatorToken) stack.peek();
if ((o1.getOperator().getNumOperands() == 1) && (o2.getOperator().getNumOperands() == 2))
{
break;
}
else if ((o1.getOperator().isLeftAssociative() && (o1.getOperator().getPrecedence() <= o2.getOperator().getPrecedence())) || (o1.getOperator().getPrecedence() < o2.getOperator().getPrecedence()))
{
output.add(stack.pop());
}
else
{
break;
}
}
stack.push(token);
break;
case Token.TOKEN_PARENTHESES_OPEN:
stack.push(token);
break;
case Token.TOKEN_PARENTHESES_CLOSE:
while (stack.peek().getType() != Token.TOKEN_PARENTHESES_OPEN)
{
output.add(stack.pop());
}
stack.pop();
if (!stack.isEmpty() && (stack.peek().getType() == Token.TOKEN_FUNCTION))
{
output.add(stack.pop());
}
break;
default:
throw new IllegalArgumentException("Unknown Token type encountered. This should not happen");
}
}
while (!stack.empty())
{
Token t = stack.pop();
if ((t.getType() == Token.TOKEN_PARENTHESES_CLOSE) || (t.getType() == Token.TOKEN_PARENTHESES_OPEN))
{
throw new IllegalArgumentException("Mismatched parentheses detected. Please check the expression");
}
output.add(t);
}
return output.toArray(new Token[output.size()]);
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2014 Frank Asseg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.l2jmobius.gameserver.util.exp4j;
/**
* Abstract class for tokens used by exp4j to tokenize expressions
*/
public abstract class Token
{
public static final short TOKEN_NUMBER = 1;
public static final short TOKEN_OPERATOR = 2;
public static final short TOKEN_FUNCTION = 3;
public static final short TOKEN_PARENTHESES_OPEN = 4;
public static final short TOKEN_PARENTHESES_CLOSE = 5;
public static final short TOKEN_VARIABLE = 6;
public static final short TOKEN_SEPARATOR = 7;
protected final int type;
Token(int type)
{
this.type = type;
}
public int getType()
{
return type;
}
}

View File

@@ -0,0 +1,329 @@
/*
* Copyright 2014 Frank Asseg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.l2jmobius.gameserver.util.exp4j;
import java.util.Map;
import java.util.Set;
public class Tokenizer
{
private final char[] expression;
private final int expressionLength;
private final Map<String, Function> userFunctions;
private final Map<String, Operator> userOperators;
private final Set<String> variableNames;
private final boolean implicitMultiplication;
private int pos = 0;
private Token lastToken;
public Tokenizer(String expression, final Map<String, Function> userFunctions, final Map<String, Operator> userOperators, final Set<String> variableNames, final boolean implicitMultiplication)
{
this.expression = expression.trim().toCharArray();
this.expressionLength = this.expression.length;
this.userFunctions = userFunctions;
this.userOperators = userOperators;
this.variableNames = variableNames;
this.implicitMultiplication = implicitMultiplication;
}
public Tokenizer(String expression, final Map<String, Function> userFunctions, final Map<String, Operator> userOperators, final Set<String> variableNames)
{
this.expression = expression.trim().toCharArray();
this.expressionLength = this.expression.length;
this.userFunctions = userFunctions;
this.userOperators = userOperators;
this.variableNames = variableNames;
this.implicitMultiplication = true;
}
public boolean hasNext()
{
return this.expression.length > pos;
}
public Token nextToken()
{
char ch = expression[pos];
while (Character.isWhitespace(ch))
{
ch = expression[++pos];
}
if (Character.isDigit(ch) || (ch == '.'))
{
if (lastToken != null)
{
if (lastToken.getType() == Token.TOKEN_NUMBER)
{
throw new IllegalArgumentException("Unable to parse char '" + ch + "' (Code:" + (int) ch + ") at [" + pos + "]");
}
else if (implicitMultiplication && ((lastToken.getType() != Token.TOKEN_OPERATOR) && (lastToken.getType() != Token.TOKEN_PARENTHESES_OPEN) && (lastToken.getType() != Token.TOKEN_FUNCTION) && (lastToken.getType() != Token.TOKEN_SEPARATOR)))
{
// insert an implicit multiplication token
lastToken = new OperatorToken(Operators.getBuiltinOperator('*', 2));
return lastToken;
}
}
return parseNumberToken(ch);
}
else if (isArgumentSeparator(ch))
{
return parseArgumentSeparatorToken(ch);
}
else if (isOpenParentheses(ch))
{
if ((lastToken != null) && implicitMultiplication && ((lastToken.getType() != Token.TOKEN_OPERATOR) && (lastToken.getType() != Token.TOKEN_PARENTHESES_OPEN) && (lastToken.getType() != Token.TOKEN_FUNCTION) && (lastToken.getType() != Token.TOKEN_SEPARATOR)))
{
// insert an implicit multiplication token
lastToken = new OperatorToken(Operators.getBuiltinOperator('*', 2));
return lastToken;
}
return parseParentheses(true);
}
else if (isCloseParentheses(ch))
{
return parseParentheses(false);
}
else if (Operator.isAllowedOperatorChar(ch))
{
return parseOperatorToken(ch);
}
else if (isAlphabetic(ch) || (ch == '_'))
{
// parse the name which can be a setVariable or a function
if ((lastToken != null) && implicitMultiplication && ((lastToken.getType() != Token.TOKEN_OPERATOR) && (lastToken.getType() != Token.TOKEN_PARENTHESES_OPEN) && (lastToken.getType() != Token.TOKEN_FUNCTION) && (lastToken.getType() != Token.TOKEN_SEPARATOR)))
{
// insert an implicit multiplication token
lastToken = new OperatorToken(Operators.getBuiltinOperator('*', 2));
return lastToken;
}
return parseFunctionOrVariable();
}
throw new IllegalArgumentException("Unable to parse char '" + ch + "' (Code:" + (int) ch + ") at [" + pos + "]");
}
private Token parseArgumentSeparatorToken(char ch)
{
this.pos++;
this.lastToken = new ArgumentSeparatorToken();
return lastToken;
}
private boolean isArgumentSeparator(char ch)
{
return ch == ',';
}
private Token parseParentheses(final boolean open)
{
if (open)
{
this.lastToken = new OpenParenthesesToken();
}
else
{
this.lastToken = new CloseParenthesesToken();
}
this.pos++;
return lastToken;
}
private boolean isOpenParentheses(char ch)
{
return (ch == '(') || (ch == '{') || (ch == '[');
}
private boolean isCloseParentheses(char ch)
{
return (ch == ')') || (ch == '}') || (ch == ']');
}
private Token parseFunctionOrVariable()
{
final int offset = this.pos;
int testPos;
int lastValidLen = 1;
Token lastValidToken = null;
int len = 1;
if (isEndOfExpression(offset))
{
this.pos++;
}
testPos = (offset + len) - 1;
while (!isEndOfExpression(testPos) && isVariableOrFunctionCharacter(expression[testPos]))
{
String name = new String(expression, offset, len);
if ((variableNames != null) && variableNames.contains(name))
{
lastValidLen = len;
lastValidToken = new VariableToken(name);
}
else
{
final Function f = getFunction(name);
if (f != null)
{
lastValidLen = len;
lastValidToken = new FunctionToken(f);
}
}
len++;
testPos = (offset + len) - 1;
}
if (lastValidToken == null)
{
throw new UnknownFunctionOrVariableException(new String(expression), pos, len);
}
pos += lastValidLen;
lastToken = lastValidToken;
return lastToken;
}
private Function getFunction(String name)
{
Function f = null;
if (this.userFunctions != null)
{
f = this.userFunctions.get(name);
}
if (f == null)
{
f = Functions.getBuiltinFunction(name);
}
return f;
}
private Token parseOperatorToken(char firstChar)
{
final int offset = this.pos;
int len = 1;
final StringBuilder symbol = new StringBuilder();
Operator lastValid = null;
symbol.append(firstChar);
while (!isEndOfExpression(offset + len) && Operator.isAllowedOperatorChar(expression[offset + len]))
{
symbol.append(expression[offset + len++]);
}
while (symbol.length() > 0)
{
Operator op = this.getOperator(symbol.toString());
if (op == null)
{
symbol.setLength(symbol.length() - 1);
}
else
{
lastValid = op;
break;
}
}
pos += symbol.length();
lastToken = new OperatorToken(lastValid);
return lastToken;
}
private Operator getOperator(String symbol)
{
Operator op = null;
if (this.userOperators != null)
{
op = this.userOperators.get(symbol);
}
if ((op == null) && (symbol.length() == 1))
{
int argc = 2;
if (lastToken == null)
{
argc = 1;
}
else
{
int lastTokenType = lastToken.getType();
if ((lastTokenType == Token.TOKEN_PARENTHESES_OPEN) || (lastTokenType == Token.TOKEN_SEPARATOR))
{
argc = 1;
}
else if (lastTokenType == Token.TOKEN_OPERATOR)
{
final Operator lastOp = ((OperatorToken) lastToken).getOperator();
if ((lastOp.getNumOperands() == 2) || ((lastOp.getNumOperands() == 1) && !lastOp.isLeftAssociative()))
{
argc = 1;
}
}
}
op = Operators.getBuiltinOperator(symbol.charAt(0), argc);
}
return op;
}
private Token parseNumberToken(final char firstChar)
{
final int offset = this.pos;
int len = 1;
this.pos++;
if (isEndOfExpression(offset + len))
{
lastToken = new NumberToken(Double.parseDouble(String.valueOf(firstChar)));
return lastToken;
}
while (!isEndOfExpression(offset + len) && isNumeric(expression[offset + len], (expression[(offset + len) - 1] == 'e') || (expression[(offset + len) - 1] == 'E')))
{
len++;
this.pos++;
}
// check if the e is at the end
if ((expression[(offset + len) - 1] == 'e') || (expression[(offset + len) - 1] == 'E'))
{
// since the e is at the end it's not part of the number and a rollback is necessary
len--;
pos--;
}
lastToken = new NumberToken(expression, offset, len);
return lastToken;
}
private static boolean isNumeric(char ch, boolean lastCharE)
{
return Character.isDigit(ch) || (ch == '.') || (ch == 'e') || (ch == 'E') || (lastCharE && ((ch == '-') || (ch == '+')));
}
public static boolean isAlphabetic(int codePoint)
{
return Character.isLetter(codePoint);
}
public static boolean isVariableOrFunctionCharacter(int codePoint)
{
return isAlphabetic(codePoint) || Character.isDigit(codePoint) || (codePoint == '_') || (codePoint == '.');
}
private boolean isEndOfExpression(int offset)
{
return this.expressionLength <= offset;
}
}

View File

@@ -0,0 +1,65 @@
package com.l2jmobius.gameserver.util.exp4j;
/**
* This exception is being thrown whenever {@link Tokenizer} finds unknown function or variable.
* @author Bartosz Firyn (sarxos)
*/
public class UnknownFunctionOrVariableException extends IllegalArgumentException
{
private final String message;
private final String expression;
private final String token;
private final int position;
public UnknownFunctionOrVariableException(String expression, int position, int length)
{
this.expression = expression;
this.token = token(expression, position, length);
this.position = position;
this.message = "Unknown function or variable '" + token + "' at pos " + position + " in expression '" + expression + "'";
}
private static String token(String expression, int position, int length)
{
int len = expression.length();
int end = (position + length) - 1;
if (len < end)
{
end = len;
}
return expression.substring(position, end);
}
@Override
public String getMessage()
{
return message;
}
/**
* @return Expression which contains unknown function or variable
*/
public String getExpression()
{
return expression;
}
/**
* @return The name of unknown function or variable
*/
public String getToken()
{
return token;
}
/**
* @return The position of unknown function or variable
*/
public int getPosition()
{
return position;
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2014 Frank Asseg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.l2jmobius.gameserver.util.exp4j;
import java.util.List;
/**
* Contains the validation result for a given {@link Expression}
*/
public class ValidationResult
{
private final boolean valid;
private final List<String> errors;
/**
* Create a new instance
* @param valid Whether the validation of the expression was successful
* @param errors The list of errors returned if the validation was unsuccessful
*/
public ValidationResult(boolean valid, List<String> errors)
{
this.valid = valid;
this.errors = errors;
}
/**
* Check if an expression has been validated successfully
* @return true if the validation was successful, false otherwise
*/
public boolean isValid()
{
return valid;
}
/**
* Get the list of errors describing the issues while validating the expression
* @return The List of errors
*/
public List<String> getErrors()
{
return errors;
}
/**
* A static class representing a successful validation result
*/
public static final ValidationResult SUCCESS = new ValidationResult(true, null);
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2014 Frank Asseg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.l2jmobius.gameserver.util.exp4j;
/**
* represents a setVariable used in an expression
*/
public class VariableToken extends Token
{
private final String name;
/**
* Get the name of the setVariable
* @return the name
*/
public String getName()
{
return name;
}
/**
* Create a new instance
* @param name the name of the setVariable
*/
public VariableToken(String name)
{
super(TOKEN_VARIABLE);
this.name = name;
}
}