Java tutorial
/* * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * Copyright (C) 2012 Kristian S. Stangeland * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program; * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA */ package com.comphenix.protocol.async; import java.io.IOException; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Iterator; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import com.comphenix.protocol.PacketStream; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolLogger; import com.comphenix.protocol.events.NetworkMarker; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.injector.PrioritizedListener; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftVersion; import com.google.common.primitives.Longs; /** * Contains information about the packet that is being processed by asynchronous listeners. * <p> * Asynchronous listeners can use this to set packet timeout or transmission order. * * @author Kristian */ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> { /** * Generated by Eclipse. */ private static final long serialVersionUID = -2621498096616187384L; /** * Default number of milliseconds until a packet will rejected. */ public static final int DEFAULT_TIMEOUT_DELTA = 1800 * 1000; /** * Default number of packets to skip. */ public static final int DEFAULT_SENDING_DELTA = 0; /** * The packet stream responsible for transmitting the packet when it's done processing. */ private transient PacketStream packetStream; /** * Current list of async packet listeners. */ private transient Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal; // Timeout handling private long initialTime; private long timeout; // Packet order private long originalSendingIndex; private long newSendingIndex; // Used to determine if a packet must be reordered in the sending queue private Long queuedSendingIndex; // Whether or not the packet has been processed by the listeners private volatile boolean processed; // Whether or not the packet has been sent private volatile boolean transmitted; // Whether or not the asynchronous processing itself should be cancelled private volatile boolean asyncCancelled; // Whether or not to delay processing private AtomicInteger processingDelay = new AtomicInteger(); // Used to synchronize processing on the shared PacketEvent private Object processingLock = new Object(); // Used to identify the asynchronous worker private transient AsyncListenerHandler listenerHandler; private transient int workerID; // Determine if Minecraft processes this packet asynchronously private volatile static Method isMinecraftAsync; private volatile static boolean alwaysSync; /** * Create a container for asyncronous packets. * @param initialTime - the current time in milliseconds since 01.01.1970 00:00. */ AsyncMarker(PacketStream packetStream, long sendingIndex, long initialTime, long timeoutDelta) { if (packetStream == null) throw new IllegalArgumentException("packetStream cannot be NULL"); this.packetStream = packetStream; // Timeout this.initialTime = initialTime; this.timeout = initialTime + timeoutDelta; // Sending index this.originalSendingIndex = sendingIndex; this.newSendingIndex = sendingIndex; } /** * Retrieve the time the packet was initially queued for asynchronous processing. * @return The initial time in number of milliseconds since 01.01.1970 00:00. */ public long getInitialTime() { return initialTime; } /** * Retrieve the time the packet will be forcefully rejected. * @return The time to reject the packet, in milliseconds since 01.01.1970 00:00. */ public long getTimeout() { return timeout; } /** * Set the time the packet will be forcefully rejected. * @param timeout - time to reject the packet, in milliseconds since 01.01.1970 00:00. */ public void setTimeout(long timeout) { this.timeout = timeout; } /** * Retrieve the order the packet was originally transmitted. * @return The original packet index. */ public long getOriginalSendingIndex() { return originalSendingIndex; } /** * Retrieve the desired sending order after processing has completed. * <p> * Higher sending order means lower priority. * @return Desired sending order. */ public long getNewSendingIndex() { return newSendingIndex; } /** * Sets the desired sending order after processing has completed. * <p> * Higher sending order means lower priority. * @param newSendingIndex - new packet send index. */ public void setNewSendingIndex(long newSendingIndex) { this.newSendingIndex = newSendingIndex; } /** * Retrieve the packet stream responsible for transmitting this packet. * @return The packet stream. */ public PacketStream getPacketStream() { return packetStream; } /** * Sets the output packet stream responsible for transmitting this packet. * @param packetStream - new output packet stream. */ public void setPacketStream(PacketStream packetStream) { this.packetStream = packetStream; } /** * Retrieve whether or not this packet has been processed by the async listeners. * @return TRUE if it has been processed, FALSE otherwise. */ public boolean isProcessed() { return processed; } /** * Sets whether or not this packet has been processed by the async listeners. * @param processed - TRUE if it has, FALSE otherwise. */ void setProcessed(boolean processed) { this.processed = processed; } /** * Increment the number of times the current packet must be signalled as done before its transmitted. * <p> * This is useful if an asynchronous listener is waiting for further information before the * packet can be sent to the user. A packet listener <b>MUST</b> eventually call * {@link AsyncFilterManager#signalPacketTransmission(PacketEvent)}, * even if the packet is cancelled, after this method is called. * <p> * It is recommended that processing outside a packet listener is wrapped in a synchronized block * using the {@link #getProcessingLock()} method. * * @return The new processing delay. */ public int incrementProcessingDelay() { return processingDelay.incrementAndGet(); } /** * Decrement the number of times this packet must be signalled as done before it's transmitted. * @return The new processing delay. If zero, the packet should be sent. */ int decrementProcessingDelay() { return processingDelay.decrementAndGet(); } /** * Retrieve the number of times a packet must be signalled to be done before it's sent. * @return Number of processing delays. */ public int getProcessingDelay() { return processingDelay.get(); } /** * Whether or not this packet is or has been queued for processing. * @return TRUE if it has, FALSE otherwise. */ public boolean isQueued() { return queuedSendingIndex != null; } /** * Retrieve the sending index when the packet was queued. * @return Queued sending index. */ public long getQueuedSendingIndex() { return queuedSendingIndex != null ? queuedSendingIndex : 0; } /** * Set the sending index when the packet was queued. * @param queuedSendingIndex - sending index. */ void setQueuedSendingIndex(Long queuedSendingIndex) { this.queuedSendingIndex = queuedSendingIndex; } /** * Processing lock used to synchronize access to the parent PacketEvent and PacketContainer. * <p> * This lock is automatically acquired for every asynchronous packet listener. It should only be * used to synchronize access to a PacketEvent if it's processing has been delayed. * @return A processing lock. */ public Object getProcessingLock() { return processingLock; } public void setProcessingLock(Object processingLock) { this.processingLock = processingLock; } /** * Retrieve whether or not this packet has already been sent. * @return TRUE if it has been sent before, FALSE otherwise. */ public boolean isTransmitted() { return transmitted; } /** * Determine if this packet has expired. * @return TRUE if it has, FALSE otherwise. */ public boolean hasExpired() { return hasExpired(System.currentTimeMillis()); } /** * Determine if this packet has expired given this time. * @param currentTime - the current time in milliseconds since 01.01.1970 00:00. * @return TRUE if it has, FALSE otherwise. */ public boolean hasExpired(long currentTime) { return timeout < currentTime; } /** * Determine if the asynchronous handling should be cancelled. * @return TRUE if it should, FALSE otherwise. */ public boolean isAsyncCancelled() { return asyncCancelled; } /** * Set whether or not the asynchronous handling should be cancelled. * <p> * This is only relevant during the synchronous processing. Asynchronous * listeners should use the normal cancel-field to cancel a PacketEvent. * * @param asyncCancelled - TRUE to cancel it, FALSE otherwise. */ public void setAsyncCancelled(boolean asyncCancelled) { this.asyncCancelled = asyncCancelled; } /** * Retrieve the current asynchronous listener handler. * @return Asychronous listener handler, or NULL if this packet is not asynchronous. */ public AsyncListenerHandler getListenerHandler() { return listenerHandler; } /** * Set the current asynchronous listener handler. * <p> * Used by the worker to update the value. * @param listenerHandler - new listener handler. */ void setListenerHandler(AsyncListenerHandler listenerHandler) { this.listenerHandler = listenerHandler; } /** * Retrieve the current worker ID. * @return Current worker ID. */ public int getWorkerID() { return workerID; } /** * Set the current worker ID. * <p> * Used by the worker. * @param workerID - new worker ID. */ void setWorkerID(int workerID) { this.workerID = workerID; } /** * Retrieve iterator for the next listener in line. * @return Next async packet listener iterator. */ Iterator<PrioritizedListener<AsyncListenerHandler>> getListenerTraversal() { return listenerTraversal; } /** * Set the iterator for the next listener. * @param listenerTraversal - the new async packet listener iterator. */ void setListenerTraversal(Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal) { this.listenerTraversal = listenerTraversal; } /** * Transmit a given packet to the current packet stream. * @param event - the packet to send. * @throws IOException If the packet couldn't be sent. */ void sendPacket(PacketEvent event) throws IOException { try { if (event.isServerPacket()) { packetStream.sendServerPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event), false); } else { packetStream.recieveClientPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event), false); } transmitted = true; } catch (InvocationTargetException e) { throw new IOException("Cannot send packet", e); } catch (IllegalAccessException e) { throw new IOException("Cannot send packet", e); } } /** * Determine if Minecraft allows asynchronous processing of this packet. * @param event - packet event * @return TRUE if it does, FALSE otherwise. * @throws FieldAccessException If determining fails for some reasaon */ public boolean isMinecraftAsync(PacketEvent event) throws FieldAccessException { if (isMinecraftAsync == null && !alwaysSync) { try { isMinecraftAsync = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()) .getMethodByName("a_.*"); } catch (RuntimeException e) { // This will occur in 1.2.5 (or possibly in later versions) List<Method> methods = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()) .getMethodListByParameters(boolean.class, new Class[] {}); // Try to look for boolean methods if (methods.size() == 2) { isMinecraftAsync = methods.get(1); } else if (methods.size() == 1) { // We're in 1.2.5 alwaysSync = true; } else if (MinecraftVersion.getCurrentVersion().isAtLeast(MinecraftVersion.BOUNTIFUL_UPDATE)) { // The centralized async marker was removed in 1.8 // Incoming chat packets can be async if (event.getPacketType() == PacketType.Play.Client.CHAT) { String content = event.getPacket().getStrings().readSafely(0); if (content != null) { // Incoming chat packets are async only if they aren't commands return !content.startsWith("/"); } else { ProtocolLogger.log(Level.WARNING, "Failed to determine contents of incoming chat packet!"); alwaysSync = true; } } else if (event.getPacketType() == PacketType.Status.Server.SERVER_INFO) { return true; } else { // TODO: Find more cases of async packets return false; } } else { ProtocolLogger.log(Level.INFO, "Could not determine asynchronous state of packets (this can probably be ignored)"); alwaysSync = true; } } } if (alwaysSync) { return false; } else { try { // Wrap exceptions return (Boolean) isMinecraftAsync.invoke(event.getPacket().getHandle()); } catch (IllegalArgumentException e) { throw new FieldAccessException("Illegal argument", e); } catch (IllegalAccessException e) { throw new FieldAccessException("Unable to reflect method call 'a_', or: isAsyncPacket.", e); } catch (InvocationTargetException e) { throw new FieldAccessException("Minecraft error", e); } } } @Override public int compareTo(AsyncMarker o) { if (o == null) return 1; else return Longs.compare(getNewSendingIndex(), o.getNewSendingIndex()); } @Override public boolean equals(Object other) { // Standard equals if (other == this) return true; if (other instanceof AsyncMarker) return getNewSendingIndex() == ((AsyncMarker) other).getNewSendingIndex(); else return false; } @Override public int hashCode() { return Longs.hashCode(getNewSendingIndex()); } }