/*
* Copyright (C) 2011 Google Inc.
*
* 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.ros.internal.transport;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import org.apache.commons.logging.Log;
import org.jboss.netty.buffer.ChannelBuffer;
import org.ros.exception.RosRuntimeException;
import org.ros.internal.message.MessageBuffers;
import org.ros.log.RosLogFactory;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
/**
* @author damonkohler@google.com (Damon Kohler)
*/
public class ConnectionHeader {
private static final Log log = RosLogFactory.getLog(ConnectionHeader.class);
private final Map<String, String> fields;
/**
* Decodes a header that came over the wire into a {@link Map} of fields and
* values.
*
* @param buffer
* the incoming {@link ChannelBuffer} containing the header
* @return a {@link Map} of header fields and values
*/
public static ConnectionHeader decode(ChannelBuffer buffer) {
Map<String, String> fields = Maps.newHashMap();
int position = 0;
int readableBytes = buffer.readableBytes();
while (position < readableBytes) {
int fieldSize = buffer.readInt();
position += 4;
if (fieldSize == 0) {
throw new IllegalStateException("Invalid 0 length handshake header field.");
}
if (position + fieldSize > readableBytes) {
throw new IllegalStateException("Invalid line length handshake header field.");
}
String field = decodeAsciiString(buffer, fieldSize);
position += field.length();
Preconditions.checkState(field.indexOf("=") > 0,
String.format("Invalid field in handshake header: \"%s\"", field));
String[] keyAndValue = field.split("=");
if (keyAndValue.length == 1) {
fields.put(keyAndValue[0], "");
} else {
fields.put(keyAndValue[0], keyAndValue[1]);
}
}
if (log.isDebugEnabled()) {
log.debug("Decoded header: " + fields);
}
ConnectionHeader connectionHeader = new ConnectionHeader();
connectionHeader.mergeFields(fields);
return connectionHeader;
}
private static String decodeAsciiString(ChannelBuffer buffer, int length) {
return buffer.readBytes(length).toString(Charset.forName("US-ASCII"));
}
public ConnectionHeader() {
this.fields = Maps.newConcurrentMap();
}
/**
* Encodes this {@link ConnectionHeader} for transmission over the wire.
*
* @return a {@link ChannelBuffer} containing the encoded header for wire
* transmission
*/
public ChannelBuffer encode() {
ChannelBuffer buffer = MessageBuffers.dynamicBuffer();
for (Entry<String, String> entry : fields.entrySet()) {
String field = entry.getKey() + "=" + entry.getValue();
buffer.writeInt(field.length());
buffer.writeBytes(field.getBytes(Charset.forName("US-ASCII")));
}
return buffer;
}
public void merge(ConnectionHeader other) {
Map<String, String> otherFields = other.getFields();
mergeFields(otherFields);
}
public void mergeFields(Map<String, String> other) {
for (Entry<String, String> field : other.entrySet()) {
String name = field.getKey();
String value = field.getValue();
addField(name, value);
}
}
public void addField(String name, String value) {
if (!fields.containsKey(name) || fields.get(name).equals(value)) {
fields.put(name, value);
} else {
throw new RosRuntimeException(String.format("Unable to merge field %s: %s != %s", name,
value, fields.get(name)));
}
}
public Map<String, String> getFields() {
return Collections.unmodifiableMap(fields);
}
public boolean hasField(String name) {
return fields.containsKey(name);
}
public String getField(String name) {
return fields.get(name);
}
@Override
public String toString() {
return String.format("ConnectionHeader <%s>", fields.toString());
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((fields == null) ? 0 : fields.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ConnectionHeader other = (ConnectionHeader) obj;
if (fields == null) {
if (other.fields != null)
return false;
} else if (!fields.equals(other.fields))
return false;
return true;
}
}