/* * SegmentBuilder.java February 2008 * * Copyright (C) 2008, Niall Gallagher <niallg@users.sf.net> * * 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 org.simpleframework.transport; import java.io.IOException; import java.util.PriorityQueue; /** * The <code>SegmentBuilder</code> object is used to build segments such that * they represent segments of data from the the packets that are provided to the * builder. This enables packets to be compacted into each other so that packets * can be freed when they are no longer needed. This enables the transport as a * whole to perform better as it ensures the packet pool is not exhausted when * there is sufficient space in other queued packets. Also this will copy shared * packets in to allocated space if requested, ensuring that the writing thread * does not need to block. * * @author Niall Gallagher * * @see org.simpleframework.transport.Writer */ class SegmentBuilder { /** * This is a compact queue which contains the compact packets. */ private final Queue compact; /** * This is the packet queue that is used to queue packets. */ private final Queue ready; /** * This is the maximum size a packet can be duplicated as. */ private final int limit; /** * Constructor for the <code>SegmentBuilder</code> object. This is used to * create a queue of packets such that each packet is of a minimum size. To * ensure packets are of a minimum size this aggregates them by moving bytes * between packets. */ public SegmentBuilder() { this(20480); } /** * Constructor for the <code>SegmentBuilder</code> object. This is used to * create a queue of packets such that each packet is of a minimum size. To * ensure packets are of a minimum size this aggregates them by moving bytes * between packets. * * @param limit * this is the threshold for asynchronous buffers */ public SegmentBuilder(int limit) { this.compact = new Queue(); this.ready = new Queue(); this.limit = limit; } /** * This is used to determine if the builder contains any references. If the * segment builder contains any reference packets it forces the * <code>Writer</code> to block. Blocking is required so that a race * condition is avoided where the writing thread and the flushing thread to * not confuse each other. * * @return true if there are any referenced buffers in the builder */ public boolean isReference() { for (Packet packet : this.ready) { if (packet.isReference()) return true; } return false; } /** * This will aggregate the queued packets in to a packet that is at least * the minimum required size. If there are none in the queue then this will * return null. Also if the queued packets are of zero length this will * return null. * * @return this returns a packet from the queue of packets */ public Segment build() throws IOException { Packet packet = this.ready.peek(); if (packet == null) return null; return this.create(packet); } /** * This will aggregate the queued packets in to a packet that is at least * the minimum required size. If there are none in the queue then this will * return null. Also if the queued packets are of zero length this will * return null. * * @param packet * this is the packet to wrap within a closer packet * * @return this returns a packet from the queue of packets */ private Segment create(Packet packet) throws IOException { int length = packet.length(); if (length <= 0) { packet.close(); this.ready.poll(); return this.build(); } return new Segment(packet, this.ready); } /** * This will aggregate the queued packets in to a packet that is at least * the minimum required size. If there are none in the queue then this will * return null. Also if the queued packets are of zero length this will * return null. * * @param packet * this is a new packet to be added to the packet queue * * @return this returns a packet from the queue of packets */ public Segment build(Packet packet) throws IOException { boolean update = this.ready.offer(packet); long sequence = packet.sequence(); if (!update) throw new PacketException("Could not add packet " + sequence); return this.build(); } /** * This method is used to compact the packets within the builder such that * it duplicates any shared packets and closes them. Duplicating and closing * shared packets is done so that the writing thread does not need to block. * Duplication of shared packets only occurs if the remaining length is less * that than the maximum duplication size specified. */ public void compact() throws IOException { Packet packet = this.ready.peek(); while (packet != null) { packet = this.ready.poll(); if (packet != null) { this.compact.offer(packet); } } this.extract(); } /** * This is used to take all packets queued in to the compact queue and * determine whether they need to be extracted in to separate private * packets. Extracting shared packets ensures that they do not suffer from * race conditions when the writing thread is released. This increases the * concurrency capability. */ private void extract() throws IOException { int count = this.limit; for (Packet packet : this.compact) { int length = packet.length(); if (length <= count) { packet = packet.extract(); count -= length; } if (packet != null) { this.ready.offer(packet); } } this.compact.clear(); } /** * This returns the total length of all packets within the queue. This can * be used to determine if any packets can be created using the * <code>aggregate</code> method. If the length is zero there are no packets * waiting to be aggregated. * * @return this returns the total length of all queued packets */ public int length() throws IOException { int count = 0; for (Packet packet : this.ready) { count += packet.length(); } return count; } /** * This is used to close all packets within the builder. This is done when * there is an error or the client has closed the connection from their * size. This is important as it releases any resources occupied by the * queued packets. */ public void close() throws IOException { for (Packet packet : this.ready) { packet.close(); } this.ready.clear(); } /** * The <code>Queue</code> object is used to create a queue of packets that * represent the order the packets have been added to the builder. This * order ensures that the packets can be reassembled on the client size as a * complete resource. */ private class Queue extends PriorityQueue<Packet> { /** * Constructor for the <code>SegmentBuilder</code> object. This is used * to create a queue to order the packets that are to be aggregated and * delivered to a client. */ public Queue() { super(); } } }