1.1 --- a/src/org/sonews/daemon/NNTPConnection.java Sun Aug 29 17:28:58 2010 +0200
1.2 +++ b/src/org/sonews/daemon/NNTPConnection.java Sun Aug 29 23:23:15 2010 +0200
1.3 @@ -46,383 +46,341 @@
1.4 public final class NNTPConnection
1.5 {
1.6
1.7 - public static final String NEWLINE = "\r\n"; // RFC defines this as newline
1.8 - public static final String MESSAGE_ID_PATTERN = "<[^>]+>";
1.9 -
1.10 - private static final Timer cancelTimer = new Timer(true); // Thread-safe? True for run as daemon
1.11 -
1.12 - /** SocketChannel is generally thread-safe */
1.13 - private SocketChannel channel = null;
1.14 - private Charset charset = Charset.forName("UTF-8");
1.15 - private Command command = null;
1.16 - private Article currentArticle = null;
1.17 - private Channel currentGroup = null;
1.18 - private volatile long lastActivity = System.currentTimeMillis();
1.19 - private ChannelLineBuffers lineBuffers = new ChannelLineBuffers();
1.20 - private int readLock = 0;
1.21 - private final Object readLockGate = new Object();
1.22 - private SelectionKey writeSelKey = null;
1.23 -
1.24 - public NNTPConnection(final SocketChannel channel)
1.25 - throws IOException
1.26 - {
1.27 - if(channel == null)
1.28 - {
1.29 - throw new IllegalArgumentException("channel is null");
1.30 - }
1.31 + public static final String NEWLINE = "\r\n"; // RFC defines this as newline
1.32 + public static final String MESSAGE_ID_PATTERN = "<[^>]+>";
1.33 + private static final Timer cancelTimer = new Timer(true); // Thread-safe? True for run as daemon
1.34 + /** SocketChannel is generally thread-safe */
1.35 + private SocketChannel channel = null;
1.36 + private Charset charset = Charset.forName("UTF-8");
1.37 + private Command command = null;
1.38 + private Article currentArticle = null;
1.39 + private Channel currentGroup = null;
1.40 + private volatile long lastActivity = System.currentTimeMillis();
1.41 + private ChannelLineBuffers lineBuffers = new ChannelLineBuffers();
1.42 + private int readLock = 0;
1.43 + private final Object readLockGate = new Object();
1.44 + private SelectionKey writeSelKey = null;
1.45
1.46 - this.channel = channel;
1.47 - Stats.getInstance().clientConnect();
1.48 - }
1.49 -
1.50 - /**
1.51 - * Tries to get the read lock for this NNTPConnection. This method is Thread-
1.52 - * safe and returns true of the read lock was successfully set. If the lock
1.53 - * is still hold by another Thread the method returns false.
1.54 - */
1.55 - boolean tryReadLock()
1.56 - {
1.57 - // As synchronizing simple types may cause deadlocks,
1.58 - // we use a gate object.
1.59 - synchronized(readLockGate)
1.60 - {
1.61 - if(readLock != 0)
1.62 - {
1.63 - return false;
1.64 - }
1.65 - else
1.66 - {
1.67 - readLock = Thread.currentThread().hashCode();
1.68 - return true;
1.69 - }
1.70 - }
1.71 - }
1.72 -
1.73 - /**
1.74 - * Releases the read lock in a Thread-safe way.
1.75 - * @throws IllegalMonitorStateException if a Thread not holding the lock
1.76 - * tries to release it.
1.77 - */
1.78 - void unlockReadLock()
1.79 - {
1.80 - synchronized(readLockGate)
1.81 - {
1.82 - if(readLock == Thread.currentThread().hashCode())
1.83 - {
1.84 - readLock = 0;
1.85 - }
1.86 - else
1.87 - {
1.88 - throw new IllegalMonitorStateException();
1.89 - }
1.90 - }
1.91 - }
1.92 -
1.93 - /**
1.94 - * @return Current input buffer of this NNTPConnection instance.
1.95 - */
1.96 - public ByteBuffer getInputBuffer()
1.97 - {
1.98 - return this.lineBuffers.getInputBuffer();
1.99 - }
1.100 -
1.101 - /**
1.102 - * @return Output buffer of this NNTPConnection which has at least one byte
1.103 - * free storage.
1.104 - */
1.105 - public ByteBuffer getOutputBuffer()
1.106 - {
1.107 - return this.lineBuffers.getOutputBuffer();
1.108 - }
1.109 -
1.110 - /**
1.111 - * @return ChannelLineBuffers instance associated with this NNTPConnection.
1.112 - */
1.113 - public ChannelLineBuffers getBuffers()
1.114 - {
1.115 - return this.lineBuffers;
1.116 - }
1.117 -
1.118 - /**
1.119 - * @return true if this connection comes from a local remote address.
1.120 - */
1.121 - public boolean isLocalConnection()
1.122 - {
1.123 - return ((InetSocketAddress)this.channel.socket().getRemoteSocketAddress())
1.124 - .getHostName().equalsIgnoreCase("localhost");
1.125 - }
1.126 + public NNTPConnection(final SocketChannel channel)
1.127 + throws IOException
1.128 + {
1.129 + if (channel == null) {
1.130 + throw new IllegalArgumentException("channel is null");
1.131 + }
1.132
1.133 - void setWriteSelectionKey(SelectionKey selKey)
1.134 - {
1.135 - this.writeSelKey = selKey;
1.136 - }
1.137 + this.channel = channel;
1.138 + Stats.getInstance().clientConnect();
1.139 + }
1.140
1.141 - public void shutdownInput()
1.142 - {
1.143 - try
1.144 - {
1.145 - // Closes the input line of the channel's socket, so no new data
1.146 - // will be received and a timeout can be triggered.
1.147 - this.channel.socket().shutdownInput();
1.148 - }
1.149 - catch(IOException ex)
1.150 - {
1.151 - Log.get().warning("Exception in NNTPConnection.shutdownInput(): " + ex);
1.152 - }
1.153 - }
1.154 -
1.155 - public void shutdownOutput()
1.156 - {
1.157 - cancelTimer.schedule(new TimerTask()
1.158 - {
1.159 - @Override
1.160 - public void run()
1.161 - {
1.162 - try
1.163 - {
1.164 - // Closes the output line of the channel's socket.
1.165 - channel.socket().shutdownOutput();
1.166 - channel.close();
1.167 - }
1.168 - catch(SocketException ex)
1.169 - {
1.170 - // Socket was already disconnected
1.171 - Log.get().info("NNTPConnection.shutdownOutput(): " + ex);
1.172 - }
1.173 - catch(Exception ex)
1.174 - {
1.175 - Log.get().warning("NNTPConnection.shutdownOutput(): " + ex);
1.176 - }
1.177 - }
1.178 - }, 3000);
1.179 - }
1.180 -
1.181 - public SocketChannel getSocketChannel()
1.182 - {
1.183 - return this.channel;
1.184 - }
1.185 -
1.186 - public Article getCurrentArticle()
1.187 - {
1.188 - return this.currentArticle;
1.189 - }
1.190 -
1.191 - public Charset getCurrentCharset()
1.192 - {
1.193 - return this.charset;
1.194 - }
1.195 + /**
1.196 + * Tries to get the read lock for this NNTPConnection. This method is Thread-
1.197 + * safe and returns true of the read lock was successfully set. If the lock
1.198 + * is still hold by another Thread the method returns false.
1.199 + */
1.200 + boolean tryReadLock()
1.201 + {
1.202 + // As synchronizing simple types may cause deadlocks,
1.203 + // we use a gate object.
1.204 + synchronized (readLockGate) {
1.205 + if (readLock != 0) {
1.206 + return false;
1.207 + } else {
1.208 + readLock = Thread.currentThread().hashCode();
1.209 + return true;
1.210 + }
1.211 + }
1.212 + }
1.213
1.214 - /**
1.215 - * @return The currently selected communication channel (not SocketChannel)
1.216 - */
1.217 - public Channel getCurrentChannel()
1.218 - {
1.219 - return this.currentGroup;
1.220 - }
1.221 -
1.222 - public void setCurrentArticle(final Article article)
1.223 - {
1.224 - this.currentArticle = article;
1.225 - }
1.226 -
1.227 - public void setCurrentGroup(final Channel group)
1.228 - {
1.229 - this.currentGroup = group;
1.230 - }
1.231 -
1.232 - public long getLastActivity()
1.233 - {
1.234 - return this.lastActivity;
1.235 - }
1.236 -
1.237 - /**
1.238 - * Due to the readLockGate there is no need to synchronize this method.
1.239 - * @param raw
1.240 - * @throws IllegalArgumentException if raw is null.
1.241 - * @throws IllegalStateException if calling thread does not own the readLock.
1.242 - */
1.243 - void lineReceived(byte[] raw)
1.244 - {
1.245 - if(raw == null)
1.246 - {
1.247 - throw new IllegalArgumentException("raw is null");
1.248 - }
1.249 -
1.250 - if(readLock == 0 || readLock != Thread.currentThread().hashCode())
1.251 - {
1.252 - throw new IllegalStateException("readLock not properly set");
1.253 - }
1.254 + /**
1.255 + * Releases the read lock in a Thread-safe way.
1.256 + * @throws IllegalMonitorStateException if a Thread not holding the lock
1.257 + * tries to release it.
1.258 + */
1.259 + void unlockReadLock()
1.260 + {
1.261 + synchronized (readLockGate) {
1.262 + if (readLock == Thread.currentThread().hashCode()) {
1.263 + readLock = 0;
1.264 + } else {
1.265 + throw new IllegalMonitorStateException();
1.266 + }
1.267 + }
1.268 + }
1.269
1.270 - this.lastActivity = System.currentTimeMillis();
1.271 -
1.272 - String line = new String(raw, this.charset);
1.273 -
1.274 - // There might be a trailing \r, but trim() is a bad idea
1.275 - // as it removes also leading spaces from long header lines.
1.276 - if(line.endsWith("\r"))
1.277 - {
1.278 - line = line.substring(0, line.length() - 1);
1.279 - raw = Arrays.copyOf(raw, raw.length - 1);
1.280 - }
1.281 -
1.282 - Log.get().fine("<< " + line);
1.283 -
1.284 - if(command == null)
1.285 - {
1.286 - command = parseCommandLine(line);
1.287 - assert command != null;
1.288 - }
1.289 + /**
1.290 + * @return Current input buffer of this NNTPConnection instance.
1.291 + */
1.292 + public ByteBuffer getInputBuffer()
1.293 + {
1.294 + return this.lineBuffers.getInputBuffer();
1.295 + }
1.296
1.297 - try
1.298 - {
1.299 - // The command object will process the line we just received
1.300 - try
1.301 - {
1.302 - command.processLine(this, line, raw);
1.303 - }
1.304 - catch(StorageBackendException ex)
1.305 - {
1.306 - Log.get().info("Retry command processing after StorageBackendException");
1.307 + /**
1.308 + * @return Output buffer of this NNTPConnection which has at least one byte
1.309 + * free storage.
1.310 + */
1.311 + public ByteBuffer getOutputBuffer()
1.312 + {
1.313 + return this.lineBuffers.getOutputBuffer();
1.314 + }
1.315
1.316 - // Try it a second time, so that the backend has time to recover
1.317 - command.processLine(this, line, raw);
1.318 - }
1.319 - }
1.320 - catch(ClosedChannelException ex0)
1.321 - {
1.322 - try
1.323 - {
1.324 - Log.get().info("Connection to " + channel.socket().getRemoteSocketAddress()
1.325 - + " closed: " + ex0);
1.326 - }
1.327 - catch(Exception ex0a)
1.328 - {
1.329 - ex0a.printStackTrace();
1.330 - }
1.331 - }
1.332 - catch(Exception ex1) // This will catch a second StorageBackendException
1.333 - {
1.334 - try
1.335 - {
1.336 - command = null;
1.337 - ex1.printStackTrace();
1.338 - println("500 Internal server error");
1.339 - }
1.340 - catch(Exception ex2)
1.341 - {
1.342 - ex2.printStackTrace();
1.343 - }
1.344 - }
1.345 + /**
1.346 + * @return ChannelLineBuffers instance associated with this NNTPConnection.
1.347 + */
1.348 + public ChannelLineBuffers getBuffers()
1.349 + {
1.350 + return this.lineBuffers;
1.351 + }
1.352
1.353 - if(command == null || command.hasFinished())
1.354 - {
1.355 - command = null;
1.356 - charset = Charset.forName("UTF-8"); // Reset to default
1.357 - }
1.358 - }
1.359 -
1.360 - /**
1.361 - * This method determines the fitting command processing class.
1.362 - * @param line
1.363 - * @return
1.364 - */
1.365 - private Command parseCommandLine(String line)
1.366 - {
1.367 - String cmdStr = line.split(" ")[0];
1.368 - return CommandSelector.getInstance().get(cmdStr);
1.369 - }
1.370 -
1.371 - /**
1.372 - * Puts the given line into the output buffer, adds a newline character
1.373 - * and returns. The method returns immediately and does not block until
1.374 - * the line was sent. If line is longer than 510 octets it is split up in
1.375 - * several lines. Each line is terminated by \r\n (NNTPConnection.NEWLINE).
1.376 - * @param line
1.377 - */
1.378 - public void println(final CharSequence line, final Charset charset)
1.379 - throws IOException
1.380 - {
1.381 - writeToChannel(CharBuffer.wrap(line), charset, line);
1.382 - writeToChannel(CharBuffer.wrap(NEWLINE), charset, null);
1.383 - }
1.384 + /**
1.385 + * @return true if this connection comes from a local remote address.
1.386 + */
1.387 + public boolean isLocalConnection()
1.388 + {
1.389 + return ((InetSocketAddress) this.channel.socket().getRemoteSocketAddress()).getHostName().equalsIgnoreCase("localhost");
1.390 + }
1.391
1.392 - /**
1.393 - * Writes the given raw lines to the output buffers and finishes with
1.394 - * a newline character (\r\n).
1.395 - * @param rawLines
1.396 - */
1.397 - public void println(final byte[] rawLines)
1.398 - throws IOException
1.399 - {
1.400 - this.lineBuffers.addOutputBuffer(ByteBuffer.wrap(rawLines));
1.401 - writeToChannel(CharBuffer.wrap(NEWLINE), charset, null);
1.402 - }
1.403 -
1.404 - /**
1.405 - * Encodes the given CharBuffer using the given Charset to a bunch of
1.406 - * ByteBuffers (each 512 bytes large) and enqueues them for writing at the
1.407 - * connected SocketChannel.
1.408 - * @throws java.io.IOException
1.409 - */
1.410 - private void writeToChannel(CharBuffer characters, final Charset charset,
1.411 - CharSequence debugLine)
1.412 - throws IOException
1.413 - {
1.414 - if(!charset.canEncode())
1.415 - {
1.416 - Log.get().severe("FATAL: Charset " + charset + " cannot encode!");
1.417 - return;
1.418 - }
1.419 -
1.420 - // Write characters to output buffers
1.421 - LineEncoder lenc = new LineEncoder(characters, charset);
1.422 - lenc.encode(lineBuffers);
1.423 -
1.424 - enableWriteEvents(debugLine);
1.425 - }
1.426 + void setWriteSelectionKey(SelectionKey selKey)
1.427 + {
1.428 + this.writeSelKey = selKey;
1.429 + }
1.430
1.431 - private void enableWriteEvents(CharSequence debugLine)
1.432 - {
1.433 - // Enable OP_WRITE events so that the buffers are processed
1.434 - try
1.435 - {
1.436 - this.writeSelKey.interestOps(SelectionKey.OP_WRITE);
1.437 - ChannelWriter.getInstance().getSelector().wakeup();
1.438 - }
1.439 - catch(Exception ex) // CancelledKeyException and ChannelCloseException
1.440 - {
1.441 - Log.get().warning("NNTPConnection.writeToChannel(): " + ex);
1.442 - return;
1.443 - }
1.444 + public void shutdownInput()
1.445 + {
1.446 + try {
1.447 + // Closes the input line of the channel's socket, so no new data
1.448 + // will be received and a timeout can be triggered.
1.449 + this.channel.socket().shutdownInput();
1.450 + } catch (IOException ex) {
1.451 + Log.get().warning("Exception in NNTPConnection.shutdownInput(): " + ex);
1.452 + }
1.453 + }
1.454
1.455 - // Update last activity timestamp
1.456 - this.lastActivity = System.currentTimeMillis();
1.457 - if(debugLine != null)
1.458 - {
1.459 - Log.get().fine(">> " + debugLine);
1.460 - }
1.461 - }
1.462 -
1.463 - public void println(final CharSequence line)
1.464 - throws IOException
1.465 - {
1.466 - println(line, charset);
1.467 - }
1.468 -
1.469 - public void print(final String line)
1.470 - throws IOException
1.471 - {
1.472 - writeToChannel(CharBuffer.wrap(line), charset, line);
1.473 - }
1.474 -
1.475 - public void setCurrentCharset(final Charset charset)
1.476 - {
1.477 - this.charset = charset;
1.478 - }
1.479 + public void shutdownOutput()
1.480 + {
1.481 + cancelTimer.schedule(new TimerTask()
1.482 + {
1.483
1.484 - void setLastActivity(long timestamp)
1.485 - {
1.486 - this.lastActivity = timestamp;
1.487 - }
1.488 -
1.489 + @Override
1.490 + public void run()
1.491 + {
1.492 + try {
1.493 + // Closes the output line of the channel's socket.
1.494 + channel.socket().shutdownOutput();
1.495 + channel.close();
1.496 + } catch (SocketException ex) {
1.497 + // Socket was already disconnected
1.498 + Log.get().info("NNTPConnection.shutdownOutput(): " + ex);
1.499 + } catch (Exception ex) {
1.500 + Log.get().warning("NNTPConnection.shutdownOutput(): " + ex);
1.501 + }
1.502 + }
1.503 + }, 3000);
1.504 + }
1.505 +
1.506 + public SocketChannel getSocketChannel()
1.507 + {
1.508 + return this.channel;
1.509 + }
1.510 +
1.511 + public Article getCurrentArticle()
1.512 + {
1.513 + return this.currentArticle;
1.514 + }
1.515 +
1.516 + public Charset getCurrentCharset()
1.517 + {
1.518 + return this.charset;
1.519 + }
1.520 +
1.521 + /**
1.522 + * @return The currently selected communication channel (not SocketChannel)
1.523 + */
1.524 + public Channel getCurrentChannel()
1.525 + {
1.526 + return this.currentGroup;
1.527 + }
1.528 +
1.529 + public void setCurrentArticle(final Article article)
1.530 + {
1.531 + this.currentArticle = article;
1.532 + }
1.533 +
1.534 + public void setCurrentGroup(final Channel group)
1.535 + {
1.536 + this.currentGroup = group;
1.537 + }
1.538 +
1.539 + public long getLastActivity()
1.540 + {
1.541 + return this.lastActivity;
1.542 + }
1.543 +
1.544 + /**
1.545 + * Due to the readLockGate there is no need to synchronize this method.
1.546 + * @param raw
1.547 + * @throws IllegalArgumentException if raw is null.
1.548 + * @throws IllegalStateException if calling thread does not own the readLock.
1.549 + */
1.550 + void lineReceived(byte[] raw)
1.551 + {
1.552 + if (raw == null) {
1.553 + throw new IllegalArgumentException("raw is null");
1.554 + }
1.555 +
1.556 + if (readLock == 0 || readLock != Thread.currentThread().hashCode()) {
1.557 + throw new IllegalStateException("readLock not properly set");
1.558 + }
1.559 +
1.560 + this.lastActivity = System.currentTimeMillis();
1.561 +
1.562 + String line = new String(raw, this.charset);
1.563 +
1.564 + // There might be a trailing \r, but trim() is a bad idea
1.565 + // as it removes also leading spaces from long header lines.
1.566 + if (line.endsWith("\r")) {
1.567 + line = line.substring(0, line.length() - 1);
1.568 + raw = Arrays.copyOf(raw, raw.length - 1);
1.569 + }
1.570 +
1.571 + Log.get().fine("<< " + line);
1.572 +
1.573 + if (command == null) {
1.574 + command = parseCommandLine(line);
1.575 + assert command != null;
1.576 + }
1.577 +
1.578 + try {
1.579 + // The command object will process the line we just received
1.580 + try {
1.581 + command.processLine(this, line, raw);
1.582 + } catch (StorageBackendException ex) {
1.583 + Log.get().info("Retry command processing after StorageBackendException");
1.584 +
1.585 + // Try it a second time, so that the backend has time to recover
1.586 + command.processLine(this, line, raw);
1.587 + }
1.588 + } catch (ClosedChannelException ex0) {
1.589 + try {
1.590 + Log.get().info("Connection to " + channel.socket().getRemoteSocketAddress()
1.591 + + " closed: " + ex0);
1.592 + } catch (Exception ex0a) {
1.593 + ex0a.printStackTrace();
1.594 + }
1.595 + } catch (Exception ex1) // This will catch a second StorageBackendException
1.596 + {
1.597 + try {
1.598 + command = null;
1.599 + ex1.printStackTrace();
1.600 + println("500 Internal server error");
1.601 + } catch (Exception ex2) {
1.602 + ex2.printStackTrace();
1.603 + }
1.604 + }
1.605 +
1.606 + if (command == null || command.hasFinished()) {
1.607 + command = null;
1.608 + charset = Charset.forName("UTF-8"); // Reset to default
1.609 + }
1.610 + }
1.611 +
1.612 + /**
1.613 + * This method determines the fitting command processing class.
1.614 + * @param line
1.615 + * @return
1.616 + */
1.617 + private Command parseCommandLine(String line)
1.618 + {
1.619 + String cmdStr = line.split(" ")[0];
1.620 + return CommandSelector.getInstance().get(cmdStr);
1.621 + }
1.622 +
1.623 + /**
1.624 + * Puts the given line into the output buffer, adds a newline character
1.625 + * and returns. The method returns immediately and does not block until
1.626 + * the line was sent. If line is longer than 510 octets it is split up in
1.627 + * several lines. Each line is terminated by \r\n (NNTPConnection.NEWLINE).
1.628 + * @param line
1.629 + */
1.630 + public void println(final CharSequence line, final Charset charset)
1.631 + throws IOException
1.632 + {
1.633 + writeToChannel(CharBuffer.wrap(line), charset, line);
1.634 + writeToChannel(CharBuffer.wrap(NEWLINE), charset, null);
1.635 + }
1.636 +
1.637 + /**
1.638 + * Writes the given raw lines to the output buffers and finishes with
1.639 + * a newline character (\r\n).
1.640 + * @param rawLines
1.641 + */
1.642 + public void println(final byte[] rawLines)
1.643 + throws IOException
1.644 + {
1.645 + this.lineBuffers.addOutputBuffer(ByteBuffer.wrap(rawLines));
1.646 + writeToChannel(CharBuffer.wrap(NEWLINE), charset, null);
1.647 + }
1.648 +
1.649 + /**
1.650 + * Encodes the given CharBuffer using the given Charset to a bunch of
1.651 + * ByteBuffers (each 512 bytes large) and enqueues them for writing at the
1.652 + * connected SocketChannel.
1.653 + * @throws java.io.IOException
1.654 + */
1.655 + private void writeToChannel(CharBuffer characters, final Charset charset,
1.656 + CharSequence debugLine)
1.657 + throws IOException
1.658 + {
1.659 + if (!charset.canEncode()) {
1.660 + Log.get().severe("FATAL: Charset " + charset + " cannot encode!");
1.661 + return;
1.662 + }
1.663 +
1.664 + // Write characters to output buffers
1.665 + LineEncoder lenc = new LineEncoder(characters, charset);
1.666 + lenc.encode(lineBuffers);
1.667 +
1.668 + enableWriteEvents(debugLine);
1.669 + }
1.670 +
1.671 + private void enableWriteEvents(CharSequence debugLine)
1.672 + {
1.673 + // Enable OP_WRITE events so that the buffers are processed
1.674 + try {
1.675 + this.writeSelKey.interestOps(SelectionKey.OP_WRITE);
1.676 + ChannelWriter.getInstance().getSelector().wakeup();
1.677 + } catch (Exception ex) // CancelledKeyException and ChannelCloseException
1.678 + {
1.679 + Log.get().warning("NNTPConnection.writeToChannel(): " + ex);
1.680 + return;
1.681 + }
1.682 +
1.683 + // Update last activity timestamp
1.684 + this.lastActivity = System.currentTimeMillis();
1.685 + if (debugLine != null) {
1.686 + Log.get().fine(">> " + debugLine);
1.687 + }
1.688 + }
1.689 +
1.690 + public void println(final CharSequence line)
1.691 + throws IOException
1.692 + {
1.693 + println(line, charset);
1.694 + }
1.695 +
1.696 + public void print(final String line)
1.697 + throws IOException
1.698 + {
1.699 + writeToChannel(CharBuffer.wrap(line), charset, line);
1.700 + }
1.701 +
1.702 + public void setCurrentCharset(final Charset charset)
1.703 + {
1.704 + this.charset = charset;
1.705 + }
1.706 +
1.707 + void setLastActivity(long timestamp)
1.708 + {
1.709 + this.lastActivity = timestamp;
1.710 + }
1.711 }