Package de.uniluebeck.itm.ncoap.message

Source Code of de.uniluebeck.itm.ncoap.message.CoapMessage$LinkedHashSetSupplier

/**
* Copyright (c) 2012, Oliver Kleine, Institute of Telematics, University of Luebeck
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
* following conditions are met:
*
*  - Redistributions of source messageCode must retain the above copyright notice, this list of conditions and the following
*    disclaimer.
*
*  - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
*    following disclaimer in the documentation and/or other materials provided with the distribution.
*
*  - Neither the name of the University of Luebeck nor the names of its contributors may be used to endorse or promote
*    products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package de.uniluebeck.itm.ncoap.message;

import com.google.common.base.Supplier;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;
import com.google.common.primitives.Longs;
import de.uniluebeck.itm.ncoap.communication.dispatching.client.Token;
import de.uniluebeck.itm.ncoap.message.options.*;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.charset.Charset;
import java.util.*;


/**
* This class is the base class for inheriting subtypes, e.g. requests and responses. This abstract class provides the
* cut-set in terms of functionality of {@link CoapRequest} and {@link CoapResponse}.
*
* @author Oliver Kleine
*/
public abstract class CoapMessage {

    /**
     * The CoAP protocol version (1)
     */
    public static final int PROTOCOL_VERSION = 1;

    /**
     * The default character set for {@link CoapMessage}s (UTF-8)
     */
    public static final Charset CHARSET = Charset.forName("UTF-8");

    /**
     * Internal constant to indicate that the message ID was not yet set (-1)
     */
    public static final int UNDEFINED_MESSAGE_ID = -1;

    /**
     * The maximum length of the byte array that backs the {@link Token} of {@link CoapMessage} (8)
     */
    public static final int MAX_TOKEN_LENGTH = 8;

    private static Logger log = LoggerFactory.getLogger(CoapMessage.class.getName());

    private static final String WRONG_OPTION_TYPE = "Option no. %d is no option of type %s";
    private static final String OPTION_NOT_ALLOWED_WITH_MESSAGE_TYPE = "Option no. %d is not allowed with " +
            "message type %s";
    private static final String OPTION_ALREADY_SET = "Option no. %d is already set and is only allowed once per " +
            "message";

    private static final String DOES_NOT_ALLOW_CONTENT = "CoAP messages with code %s do not allow payload.";
    private static final String EXCLUDES = "Already contained option no. %d excludes option no. %d";
    private static final String OUT_OF_ALLOWED_RANGE = "Given value length (%d) is out of allowed range " +
            "for option no. %d (min: %d, max; %d).";

    private static final int ONCE       = 1;
    private static final int MULTIPLE   = 2;
   
    private static HashBasedTable<Integer, Integer, Integer> optionOccurenceConstraints = HashBasedTable.create();
    static{
        //Requests
        optionOccurenceConstraints.put(MessageCode.Name.GET.getNumber(),      OptionValue.Name.URI_HOST,           ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.GET.getNumber(),      OptionValue.Name.URI_PORT,           ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.GET.getNumber(),      OptionValue.Name.URI_PATH,           MULTIPLE);
        optionOccurenceConstraints.put(MessageCode.Name.GET.getNumber(),      OptionValue.Name.URI_QUERY,          MULTIPLE);
        optionOccurenceConstraints.put(MessageCode.Name.GET.getNumber(),      OptionValue.Name.PROXY_URI,          ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.GET.getNumber(),      OptionValue.Name.PROXY_SCHEME,       ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.GET.getNumber(),      OptionValue.Name.ACCEPT,             MULTIPLE);
        optionOccurenceConstraints.put(MessageCode.Name.GET.getNumber(),      OptionValue.Name.ETAG,               MULTIPLE);
        optionOccurenceConstraints.put(MessageCode.Name.GET.getNumber(),      OptionValue.Name.OBSERVE,            ONCE);

        optionOccurenceConstraints.put(MessageCode.Name.POST.getNumber(),     OptionValue.Name.URI_HOST,           ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.POST.getNumber(),     OptionValue.Name.URI_PORT,           ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.POST.getNumber(),     OptionValue.Name.URI_PATH,           MULTIPLE);
        optionOccurenceConstraints.put(MessageCode.Name.POST.getNumber(),     OptionValue.Name.URI_QUERY,          MULTIPLE);
        optionOccurenceConstraints.put(MessageCode.Name.POST.getNumber(),     OptionValue.Name.PROXY_URI,          ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.POST.getNumber(),     OptionValue.Name.PROXY_SCHEME,       ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.POST.getNumber(),     OptionValue.Name.CONTENT_FORMAT,     ONCE);

        optionOccurenceConstraints.put(MessageCode.Name.PUT.getNumber(),      OptionValue.Name.URI_HOST,           ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.PUT.getNumber(),      OptionValue.Name.URI_PORT,           ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.PUT.getNumber(),      OptionValue.Name.URI_PATH,           MULTIPLE);
        optionOccurenceConstraints.put(MessageCode.Name.PUT.getNumber(),      OptionValue.Name.URI_QUERY,          MULTIPLE);
        optionOccurenceConstraints.put(MessageCode.Name.PUT.getNumber(),      OptionValue.Name.PROXY_URI,          ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.PUT.getNumber(),      OptionValue.Name.PROXY_SCHEME,       ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.PUT.getNumber(),      OptionValue.Name.CONTENT_FORMAT,     ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.PUT.getNumber(),      OptionValue.Name.IF_MATCH,           ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.PUT.getNumber(),      OptionValue.Name.IF_NONE_MATCH,      ONCE);

        optionOccurenceConstraints.put(MessageCode.Name.DELETE.getNumber(),   OptionValue.Name.URI_HOST,           ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.DELETE.getNumber(),   OptionValue.Name.URI_PORT,           ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.DELETE.getNumber(),   OptionValue.Name.URI_PATH,           MULTIPLE);
        optionOccurenceConstraints.put(MessageCode.Name.DELETE.getNumber(),   OptionValue.Name.URI_QUERY,          MULTIPLE);
        optionOccurenceConstraints.put(MessageCode.Name.DELETE.getNumber(),   OptionValue.Name.PROXY_URI,          ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.DELETE.getNumber(),   OptionValue.Name.PROXY_SCHEME,       ONCE);

        //Response success (2.x)
        optionOccurenceConstraints.put(MessageCode.Name.CREATED_201.getNumber(),  OptionValue.Name.ETAG,               ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.CREATED_201.getNumber(),  OptionValue.Name.OBSERVE,            ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.CREATED_201.getNumber(),  OptionValue.Name.LOCATION_PATH,      MULTIPLE);
        optionOccurenceConstraints.put(MessageCode.Name.CREATED_201.getNumber(),  OptionValue.Name.LOCATION_QUERY,     MULTIPLE);
        optionOccurenceConstraints.put(MessageCode.Name.CREATED_201.getNumber(),  OptionValue.Name.CONTENT_FORMAT,     ONCE);

        optionOccurenceConstraints.put(MessageCode.Name.DELETED_202.getNumber(),  OptionValue.Name.CONTENT_FORMAT,     ONCE);

        optionOccurenceConstraints.put(MessageCode.Name.VALID_203.getNumber(),    OptionValue.Name.OBSERVE,            ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.VALID_203.getNumber(),    OptionValue.Name.ETAG,               ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.VALID_203.getNumber(),    OptionValue.Name.MAX_AGE,            ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.VALID_203.getNumber(),    OptionValue.Name.CONTENT_FORMAT,     ONCE);

        optionOccurenceConstraints.put(MessageCode.Name.CHANGED_204.getNumber(),  OptionValue.Name.CONTENT_FORMAT,     ONCE);

        optionOccurenceConstraints.put(MessageCode.Name.CONTENT_205.getNumber(),  OptionValue.Name.OBSERVE,            ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.CONTENT_205.getNumber(),  OptionValue.Name.CONTENT_FORMAT,     ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.CONTENT_205.getNumber(),  OptionValue.Name.MAX_AGE,            ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.CONTENT_205.getNumber(),  OptionValue.Name.ETAG,               ONCE);

        //Client errors (4.x)
        optionOccurenceConstraints.put(MessageCode.Name.BAD_REQUEST_400.getNumber(),                  OptionValue.Name.MAX_AGE,    ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.BAD_REQUEST_400.getNumber(),  OptionValue.Name.CONTENT_FORMAT,     ONCE);

        optionOccurenceConstraints.put(MessageCode.Name.UNAUTHORIZED_401.getNumber(),                 OptionValue.Name.MAX_AGE,    ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.UNAUTHORIZED_401.getNumber(),  OptionValue.Name.CONTENT_FORMAT,     ONCE);

        optionOccurenceConstraints.put(MessageCode.Name.BAD_OPTION_402.getNumber(),                   OptionValue.Name.MAX_AGE,    ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.BAD_OPTION_402.getNumber(),  OptionValue.Name.CONTENT_FORMAT,     ONCE);

        optionOccurenceConstraints.put(MessageCode.Name.FORBIDDEN_403.getNumber(),                    OptionValue.Name.MAX_AGE,    ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.FORBIDDEN_403.getNumber(),  OptionValue.Name.CONTENT_FORMAT,     ONCE);

        optionOccurenceConstraints.put(MessageCode.Name.NOT_FOUND_404.getNumber(),                    OptionValue.Name.MAX_AGE,    ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.NOT_FOUND_404.getNumber(),  OptionValue.Name.CONTENT_FORMAT,     ONCE);

        optionOccurenceConstraints.put(MessageCode.Name.METHOD_NOT_ALLOWED_405.getNumber(),           OptionValue.Name.MAX_AGE,    ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.METHOD_NOT_ALLOWED_405.getNumber(),  OptionValue.Name.CONTENT_FORMAT,     ONCE);

        optionOccurenceConstraints.put(MessageCode.Name.NOT_ACCEPTABLE_406.getNumber(),               OptionValue.Name.MAX_AGE,    ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.NOT_ACCEPTABLE_406.getNumber(),  OptionValue.Name.CONTENT_FORMAT,     ONCE);

        optionOccurenceConstraints.put(MessageCode.Name.PRECONDITION_FAILED_412.getNumber(),          OptionValue.Name.MAX_AGE,    ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.PRECONDITION_FAILED_412.getNumber(),  OptionValue.Name.CONTENT_FORMAT,     ONCE);

        optionOccurenceConstraints.put(MessageCode.Name.REQUEST_ENTITY_TOO_LARGE_413.getNumber(),     OptionValue.Name.MAX_AGE,    ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.REQUEST_ENTITY_TOO_LARGE_413.getNumber(),  OptionValue.Name.CONTENT_FORMAT,     ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.REQUEST_ENTITY_TOO_LARGE_413.getNumber(),     OptionValue.Name.SIZE_1,     ONCE);

        optionOccurenceConstraints.put(MessageCode.Name.UNSUPPORTED_CONTENT_FORMAT_415.getNumber(),   OptionValue.Name.MAX_AGE,    ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.UNSUPPORTED_CONTENT_FORMAT_415.getNumber(),  OptionValue.Name.CONTENT_FORMAT,     ONCE);

        //Server errors (5.x)
        optionOccurenceConstraints.put(MessageCode.Name.INTERNAL_SERVER_ERROR_500.getNumber(),    OptionValue.Name.MAX_AGE,   ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.INTERNAL_SERVER_ERROR_500.getNumber(),  OptionValue.Name.CONTENT_FORMAT,     ONCE);

        optionOccurenceConstraints.put(MessageCode.Name.NOT_IMPLEMENTED_501.getNumber(),          OptionValue.Name.MAX_AGE,   ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.NOT_IMPLEMENTED_501.getNumber(),  OptionValue.Name.CONTENT_FORMAT,     ONCE);

        optionOccurenceConstraints.put(MessageCode.Name.BAD_GATEWAY_502.getNumber(),              OptionValue.Name.MAX_AGE,   ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.BAD_GATEWAY_502.getNumber(),  OptionValue.Name.CONTENT_FORMAT,     ONCE);

        optionOccurenceConstraints.put(MessageCode.Name.GATEWAY_TIMEOUT_504.getNumber(),          OptionValue.Name.MAX_AGE,   ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.GATEWAY_TIMEOUT_504.getNumber(),  OptionValue.Name.CONTENT_FORMAT,     ONCE);

        optionOccurenceConstraints.put(MessageCode.Name.PROXYING_NOT_SUPPORTED_505.getNumber(),   OptionValue.Name.MAX_AGE,   ONCE);
        optionOccurenceConstraints.put(MessageCode.Name.PROXYING_NOT_SUPPORTED_505.getNumber(),  OptionValue.Name.CONTENT_FORMAT,     ONCE);
    }


    private int messageType;
    private int messageCode;
    private int messageID;
    private Token token;

    protected SetMultimap<Integer, OptionValue> options;
    private ChannelBuffer content;

    /**
     * Creates a new instance of {@link CoapMessage}.
     *
     * @param messageType the number representing the {@link MessageType} for this {@link CoapMessage}
     * @param messageCode the number representing the {@link MessageCode} for this {@link CoapMessage}
     * @param messageID  the message ID for this {@link CoapMessage}
     * @param token the {@link Token} for this {@link CoapMessage}
     *
     * @throws IllegalArgumentException if one of the given arguments is invalid
     */
    protected CoapMessage(int messageType, int messageCode, int messageID, Token token)
            throws IllegalArgumentException {

        if(!MessageType.Name.isMessageType(messageType))
            throw new IllegalArgumentException("No. " + messageType + " is not corresponding to any message type.");

        if(!MessageCode.Name.isMessageCode(messageCode))
            throw new IllegalArgumentException("No. " + messageCode + " is not corresponding to any message code.");

        this.setMessageType(messageType);
        this.setMessageCode(messageCode);

        log.debug("Set Message Code to {} ({}).", MessageCode.Name.getName(messageCode), messageCode);

        this.setMessageID(messageID);
        this.setToken(token);

        this.options = Multimaps.newSetMultimap(new TreeMap<Integer, Collection<OptionValue>>(),
                LinkedHashSetSupplier.getInstance());

        this.content = ChannelBuffers.EMPTY_BUFFER;

        log.debug("Created CoAP message: {}", this);
    }

    /**
     * Creates a new instance of {@link CoapMessage}. Invocation of this constructor has the same effect as
     * invocation of {@link #CoapMessage(int, int, int, de.uniluebeck.itm.ncoap.communication.dispatching.client.Token)} with
     * <ul>
     *     <li>
     *         message ID: {@link CoapMessage#UNDEFINED_MESSAGE_ID} (to be set automatically by the framework)
     *     </li>
     *     <li>
     *         token: {@link Token#Token(byte[])} with empty byte array.
     *     </li>
     * </ul>
     * @param messageType the number representing the {@link MessageType} for this {@link CoapMessage}
     * @param messageCode the number representing the {@link MessageCode} for this {@link CoapMessage}
     *
     * @throws IllegalArgumentException if one of the given arguments is invalid
     */
    protected CoapMessage(int messageType, int messageCode) throws IllegalArgumentException {
        this(messageType, messageCode, UNDEFINED_MESSAGE_ID, new Token(new byte[0]));
    }


    /**
     * Method to create an empty reset message which is strictly speaking neither a request nor a response
     *
     * @param messageID the message ID of the reset message.
     *
     * @return an instance of {@link CoapMessage} with {@link MessageType.Name#RST}
     *
     * @throws IllegalArgumentException if the given message ID is out of the allowed range
     */
    public static CoapMessage createEmptyReset(int messageID) throws IllegalArgumentException {
        return new CoapMessage(MessageType.Name.RST.getNumber(), MessageCode.Name.EMPTY.getNumber(), messageID,
                new Token(new byte[0])){};
    }


    /**
     * Method to create an empty acknowledgement message which is strictly speaking neither a request nor a response
     *
     * @param messageID the message ID of the acknowledgement message.
     *
     * @return an instance of {@link CoapMessage} with {@link MessageType.Name#ACK}
     *
     * @throws IllegalArgumentException if the given message ID is out of the allowed range
     */
    public static CoapMessage createEmptyAcknowledgement(int messageID) throws IllegalArgumentException {
        return new CoapMessage(MessageType.Name.ACK.getNumber(), MessageCode.Name.EMPTY.getNumber(), messageID,
                new Token(new byte[0])){};
    }


    /**
     * Method to create an empty confirmable message which is considered a PIMG message on application layer, i.e.
     * a message to check if a CoAP endpoints is alive (not only the host but also the CoAP application!).
     *
     * @param messageID the message ID of the acknowledgement message.
     *
     * @return an instance of {@link CoapMessage} with {@link MessageType.Name#CON}
     *
     * @throws IllegalArgumentException if the given message ID is out of the allowed range
     */
    public static CoapMessage createPing(int messageID) throws IllegalArgumentException{
        return new CoapMessage(MessageType.Name.CON.getNumber(), MessageCode.Name.EMPTY.getNumber(), messageID,
                new Token(new byte[0])){};
    }


    /**
     * Sets the message type of this {@link CoapMessage}. Usually there is no need to use this method as the value
     * is either set via constructor parameter (for requests) or automatically by the nCoAP framework (for responses).
     *
     * @param messageType the number representing the message type of this method
     *
     * @throws java.lang.IllegalArgumentException if the given message type is not supported.
     */
    public void setMessageType(int messageType) throws IllegalArgumentException {
        if(!MessageType.Name.isMessageType(messageType))
            throw new IllegalArgumentException("Invalid message type (" + messageType +
                    "). Only numbers 0-3 are allowed.");

        this.messageType = messageType;
    }


    /**
     * Adds an option to this {@link CoapMessage}. However, it is recommended to use the options specific methods
     * from {@link CoapRequest} and {@link CoapResponse} to add options. This method is intended for framework internal
     * use.
     *
     * @param optionNumber the number representing the option type
     * @param optionValue the {@link OptionValue} of this option
     *
     * @throws java.lang.IllegalArgumentException if the given option number is unknwon, or if the given value is
     * either the default value or exceeds the defined length limits for options with the given option number
     */
    public void addOption(int optionNumber, OptionValue optionValue) throws IllegalArgumentException {
        this.checkOptionPermission(optionNumber);

//        if(optionNumber == OptionValue.Name.OBSERVE && MessageCode.isRequest(this.getMessageCode())
//            && optionValue.getValue().length > 0){
//
//            throw new IllegalArgumentException(String.format(OUT_OF_ALLOWED_RANGE,
//                    optionValue.getValue().length, 6, 0, 0));
//        }

        for(int containedOption : options.keySet()){
            if(OptionValue.mutuallyExcludes(containedOption, optionNumber))
                throw new IllegalArgumentException(String.format(EXCLUDES, containedOption, optionNumber));
        }

        options.put(optionNumber, optionValue);

        log.debug("Added option (number: {}, value: {})", optionNumber, optionValue.toString());

    }

    /**
     * Adds an string option to this {@link CoapMessage}. However, it is recommended to use the options specific methods
     * from {@link CoapRequest} and {@link CoapResponse} to add options. This method is intended for framework internal
     * use.
     *
     * @param optionNumber the number representing the option type
     * @param value the value of this string option
     *
     * @throws java.lang.IllegalArgumentException if the given option number refers to an unknown option or if the
     * given {@link OptionValue} is not valid, e.g. to long
     */
    protected void addStringOption(int optionNumber, String value) throws IllegalArgumentException {

        if(!(OptionValue.getOptionType(optionNumber) == OptionValue.Type.STRING))
            throw new IllegalArgumentException(String.format(WRONG_OPTION_TYPE, optionNumber, OptionValue.Type.STRING));

        //Add new option to option list
        StringOptionValue option = new StringOptionValue(optionNumber, value);
        addOption(optionNumber, option);
    }

    /**
     * Adds an uint option to this {@link CoapMessage}. However, it is recommended to use the options specific methods
     * from {@link CoapRequest} and {@link CoapResponse} to add options. This method is intended for framework internal
     * use.
     *
     * @param optionNumber the number representing the option type
     * @param value the value of this uint option
     *
     * @throws java.lang.IllegalArgumentException
     */
    protected void addUintOption(int optionNumber, long value) throws IllegalArgumentException {

        if(!(OptionValue.getOptionType(optionNumber) == OptionValue.Type.UINT))
            throw new IllegalArgumentException(String.format(WRONG_OPTION_TYPE, optionNumber, OptionValue.Type.STRING));

        //Add new option to option list
        byte[] byteValue = Longs.toByteArray(value);
        int index = 0;
        while(index < byteValue.length && byteValue[index] == 0)
            index++;

        UintOptionValue option = new UintOptionValue(optionNumber, Arrays.copyOfRange(byteValue, index, byteValue.length));
        addOption(optionNumber, option);

    }

    /**
     * Adds an opaque option to this {@link CoapMessage}. However, it is recommended to use the options specific methods
     * from {@link CoapRequest} and {@link CoapResponse} to add options. This method is intended for framework internal
     * use.
     *
     * @param optionNumber the number representing the option type
     * @param value the value of this opaque option
     *
     * @throws java.lang.IllegalArgumentException
     */
    protected void addOpaqueOption(int optionNumber, byte[] value) throws IllegalArgumentException {

        if(!(OptionValue.getOptionType(optionNumber) == OptionValue.Type.OPAQUE))
            throw new IllegalArgumentException(String.format(WRONG_OPTION_TYPE, optionNumber, OptionValue.Type.OPAQUE));

        //Add new option to option list
        OpaqueOptionValue option = new OpaqueOptionValue(optionNumber, value);
        addOption(optionNumber, option);

    }

    /**
     * Adds an empty option to this {@link CoapMessage}. However, it is recommended to use the options specific methods
     * from {@link CoapRequest} and {@link CoapResponse} to add options. This method is intended for framework internal
     * use.
     *
     * @param optionNumber the number representing the option type
     *
     * @throws java.lang.IllegalArgumentException if the given option number refers to an unknown option or to
     * a not-empty option.
     */
    protected void addEmptyOption(int optionNumber) throws IllegalArgumentException {

        if(!(OptionValue.getOptionType(optionNumber) == OptionValue.Type.EMPTY))
            throw new IllegalArgumentException(String.format(WRONG_OPTION_TYPE, optionNumber, OptionValue.Type.EMPTY));

        //Add new option to option list
        options.put(optionNumber, new EmptyOptionValue(optionNumber));

        log.debug("Added empty option (number: {})", optionNumber);
    }

    /**
     * Removes all options with the given option number from this {@link CoapMessage} instance.
     *
     * @param optionNumber the option number to remove from this message
     *
     * @return the number of options that were removed, i.e. the count.
     */
    public int removeOptions(int optionNumber){
        int result = options.removeAll(optionNumber).size();
        log.debug("Removed {} options with number {}.", result, optionNumber);
        return result;
    }


    private void checkOptionPermission(int optionNumber) throws IllegalArgumentException {
        Integer allowedOccurence = optionOccurenceConstraints.get(this.messageCode, optionNumber);
        if(allowedOccurence == null)
            throw new IllegalArgumentException(String.format(OPTION_NOT_ALLOWED_WITH_MESSAGE_TYPE,
                    optionNumber, this.getMessageCodeName()));

        if(options.containsKey(optionNumber)){
            if(optionOccurenceConstraints.get(this.messageCode, optionNumber) == ONCE)
                throw new IllegalArgumentException(String.format(OPTION_ALREADY_SET, optionNumber));
        }
    }

    /**
     * Returns the CoAP protocol version used for this {@link CoapMessage}
     *
     * @return the CoAP protocol version used for this {@link CoapMessage}
     */
    public int getProtocolVersion() {
        return PROTOCOL_VERSION;
    }


    /**
     * Sets the message ID for this message. However, there is no need to set the message ID manually. It is set (or
     * overwritten) automatically by the nCoAP framework.
     *
     * @param messageID the message ID for the message
     */
    public void setMessageID(int messageID) throws IllegalArgumentException {

        if(messageID < -1 || messageID > 65535)
            throw new IllegalArgumentException("Message ID " + messageID + " is either negative or greater than 65535");

        this.messageID = messageID;
    }

    /**
     * Returns the message ID (or {@link CoapMessage#UNDEFINED_MESSAGE_ID} if not set)
     *
     * @return the message ID (or {@link CoapMessage#UNDEFINED_MESSAGE_ID} if not set)
     */
    public int getMessageID() {
        return this.messageID;
    }


    /**
     * Returns the number representing the {@link MessageType} of this {@link CoapMessage}
     *
     * @return the number representing the {@link MessageType} of this {@link CoapMessage}
     */
    public int getMessageType() {
        return this.messageType;
    }

    /**
     * Returns the {@link MessageType.Name} of this {@link CoapMessage}. Invocation of
     * {@link MessageType.Name#getNumber()} on the returned value returns the same value as {@link #getMessageType()}.
     *
     * @return the {@link MessageType.Name} of this {@link CoapMessage}
     */
    public MessageType.Name getMessageTypeName(){
        return MessageType.Name.getName(this.messageType);
    }

    /**
     * Returns the number representing the {@link MessageCode} of this {@link CoapMessage}
     *
     * @return the number representing the {@link MessageCode} of this {@link CoapMessage}
     */
    public int getMessageCode() {
        return this.messageCode;
    }

    /**
     * Returns the {@link MessageCode.Name} of this {@link CoapMessage}. Invocation of
     * {@link MessageCode.Name#getNumber()} on the returned value returns the same value as {@link #getMessageCode()}.
     *
     * @return the {@link MessageCode.Name} of this {@link CoapMessage}
     */
    public MessageCode.Name getMessageCodeName(){
        return MessageCode.Name.getName(this.messageCode);
    }

    /**
     * Sets a {@link Token} to this {@link CoapMessage}. However, there is no need to set the {@link Token} manually,
     * as it is set (or overwritten) automatically by the framework.
     *
     * @param token the {@link Token} for this {@link CoapMessage}
     */
    public void setToken(Token token){
        this.token = token;
    }


    /**
     * Returns the {@link Token} of this {@link CoapMessage}
     *
     * @return the {@link Token} of this {@link CoapMessage}
     */
    public Token getToken(){
        return this.token;
    }


    /**
     * Returns the number representing the format of the content or {@link ContentFormat#UNDEFINED} if no such
     * option is present in this {@link CoapMessage}. See {@link ContentFormat} for some constants for predefined
     * numbers (according to the CoAP specification).
     *
     * @return the number representing the format of the content or {@link ContentFormat#UNDEFINED} if no such option
     * is present in this {@link CoapMessage}.
     */
    public long getContentFormat(){
        if(options.containsKey(OptionValue.Name.CONTENT_FORMAT))
            return ((UintOptionValue) options.get(OptionValue.Name.CONTENT_FORMAT).iterator().next()).getDecodedValue();

        return ContentFormat.UNDEFINED;
    }


    /**
     * Sets the Max-Age option of this {@link CoapMessage}. If there was a Max-Age option set prior to the
     * invocation of this method, the previous value is overwritten.
     *
     * @param maxAge the value for the Max-Age option to be set
     *
     * @throws de.uniluebeck.itm.ncoap.communication.codec.OptionCodecException
     */
    public void setMaxAge(long maxAge)  {
        try{
            this.options.removeAll(OptionValue.Name.MAX_AGE);
            this.addUintOption(OptionValue.Name.MAX_AGE, maxAge);
        }
        catch (IllegalArgumentException e) {
            log.error("This should never happen.", e);
        }
    }

    /**
     * Returns the value of the Max-Age option of this {@link CoapMessage}. If no such option exists, this method
     * returns {@link de.uniluebeck.itm.ncoap.message.options.OptionValue#MAX_AGE_DEFAULT}.
     *
     * @return the value of the Max-Age option of this {@link CoapMessage}. If no such option exists, this method
     * returns {@link de.uniluebeck.itm.ncoap.message.options.OptionValue#MAX_AGE_DEFAULT}.
     */
    public long getMaxAge(){
        if(options.containsKey(OptionValue.Name.MAX_AGE))
            return ((UintOptionValue) options.get(OptionValue.Name.MAX_AGE).iterator().next()).getDecodedValue();
        else
            return OptionValue.MAX_AGE_DEFAULT;
    }


    /**
     * Sets the observing option in this {@link de.uniluebeck.itm.ncoap.message.CoapRequest} and returns
     * <code>true</code> if the option is set after method returns (may already have been set beforehand in a prior
     * method invocation) or <code>false</code if the option is not set, e.g. because that option has no meaning with
     * the message code of this {@link de.uniluebeck.itm.ncoap.message.CoapRequest}.
     *
     * @param value <code>true</code> if this {@link de.uniluebeck.itm.ncoap.message.CoapRequest} is supposed
     *              to register as an observer and <code>false</code> to deregister as observer, i.e. cancel
     *               an ongoing observation
     */
    public void setObserve(long value){
        try {
            this.removeOptions(OptionValue.Name.OBSERVE);
            value = value & 0xFFFFFF;
            this.addUintOption(OptionValue.Name.OBSERVE, value);
        }
        catch (IllegalArgumentException e){
            this.removeOptions(OptionValue.Name.OBSERVE);
            log.error("This should never happen.", e);
        }
    }


    /**
     * Returns the value of the observing option (no.6) or
     * {@link de.uniluebeck.itm.ncoap.message.options.UintOptionValue#UNDEFINED} if there is no such option present in
     * this {@link de.uniluebeck.itm.ncoap.message.CoapRequest}.
     *
     * @return he value of the observing option (no.6) or
     * {@link de.uniluebeck.itm.ncoap.message.options.UintOptionValue#UNDEFINED} if there is no such option present in
     * this {@link de.uniluebeck.itm.ncoap.message.CoapRequest}.
     */
    public long getObserve(){
        if(!options.containsKey(OptionValue.Name.OBSERVE))
            return UintOptionValue.UNDEFINED;

        return (long) options.get(OptionValue.Name.OBSERVE).iterator().next().getDecodedValue();
    }

    /**
     * Adds the content to the message. If this {@link CoapMessage} contained any content prior to the invocation of
     * method, the previous content is removed.
     *
     * @param content ChannelBuffer containing the message content
     *
     * @throws java.lang.IllegalArgumentException if the messages code does not allow content and for the given
     * {@link ChannelBuffer#readableBytes()} is greater then zero.
     */
    public void setContent(ChannelBuffer content) throws IllegalArgumentException {

        if(!(MessageCode.allowsContent(this.messageCode)) && content.readableBytes() > 0)
            throw new IllegalArgumentException(String.format(DOES_NOT_ALLOW_CONTENT, this.getMessageCodeName()));

        this.content = content;
    }

    /**
     * Sets the content (payload) of this {@link CoapMessage}.
     *
     * @param content {@link ChannelBuffer} containing the message content
     * @param contentFormat a long value representing the format of the content (see {@link ContentFormat} for some
     *                      predefined numbers (according to the CoAP specification)
     *
     * @throws java.lang.IllegalArgumentException if the messages code does not allow content and for the given
     * {@link ChannelBuffer#readableBytes()} is greater then zero.
     */
    public void setContent(ChannelBuffer content, long contentFormat) throws IllegalArgumentException {

        try {
            this.addUintOption(OptionValue.Name.CONTENT_FORMAT, contentFormat);
            setContent(content);
        }
        catch (IllegalArgumentException e) {
            this.content = ChannelBuffers.EMPTY_BUFFER;
            this.removeOptions(OptionValue.Name.CONTENT_FORMAT);
            throw e;
        }
    }


    /**
     * Adds the content to the message. If this {@link CoapMessage} contained any content prior to the invocation of
     * method, the previous content is removed.
     *
     * @param content ChannelBuffer containing the message content
     *
     * @throws java.lang.IllegalArgumentException if the messages code does not allow content and the given byte array
     * has a length more than zero.
     */
    public void setContent(byte[] content) throws IllegalArgumentException {

        setContent(ChannelBuffers.wrappedBuffer(content));

    }


    /**
     * Adds the content to the message. If this {@link CoapMessage} contained any content prior to the invocation of
     * method, the previous content is removed.
     *
     * @param content ChannelBuffer containing the message content
     * @param contentFormat a long value representing the format of the content
     *
     * @throws java.lang.IllegalArgumentException if the messages code does not allow content
     */
    public void setContent(byte[] content, long contentFormat) throws IllegalArgumentException {

        setContent(ChannelBuffers.wrappedBuffer(content), contentFormat);

    }

    /**
     * Returns the messages content. If the message does not contain any content, this method returns an empty
     * {@link ChannelBuffer} ({@link ChannelBuffers#EMPTY_BUFFER}).
     *
     * @return Returns the messages content.
     */
    public ChannelBuffer getContent(){
        return content;
    }


    /**
     * Returns a {@link Multimap} with the option numbers as keys and {@link de.uniluebeck.itm.ncoap.message.options.OptionValue}s as values.
     * The returned multimap does not contain options with default values.
     *
     * @return a {@link Multimap} with the option numbers as keys and {@link de.uniluebeck.itm.ncoap.message.options.OptionValue}s as values.
     */
    public SetMultimap<Integer, OptionValue> getAllOptions(){
        return this.options;
    }


    /**
     * Returns a {@link Set< de.uniluebeck.itm.ncoap.message.options.OptionValue >} containing the options that are explicitly set in this {@link CoapMessage}. The
     * returned set does not contain options with default values. If this {@link CoapMessage} does not contain any
     * options of the given option number, then the returned set is empty.
     *
     * @param optionNumber the option number
     *
     * @return a {@link Set< de.uniluebeck.itm.ncoap.message.options.OptionValue >} containing the options that are explicitly set in this {@link CoapMessage}.
     */
    public Set<OptionValue> getOptions(int optionNumber){
        return this.options.get(optionNumber);
    }


    @Override
    public int hashCode(){
        return toString().hashCode() + content.hashCode();
    }

    /**
     * Returns <code>true</code> if and only if the given object is an instance of {@link CoapMessage}
     * and if the header, the token, the options and the content of both instances equal.
     *
     * @param object another object to compare this {@link CoapMessage} with
     *
     * @return <code>true</code> if and only if the given object is an instance of {@link CoapMessage}
     * and if the header, the token, the options and the content of both instances equal.
     */
    @Override
    public boolean equals(Object object){

        if(!(object instanceof CoapMessage)){
            log.error("Different type");
            return false;
        }

        CoapMessage other = (CoapMessage) object;

        //Check header fields
        if(this.getProtocolVersion() != other.getProtocolVersion())
            return false;

        if(this.getMessageType() != other.getMessageType())
            return false;

        if(this.getMessageCode() != other.getMessageCode())
            return false;

        if(this.getMessageID() != other.getMessageID())
            return false;

        if(!this.getToken().equals(other.getToken()))
            return false;


        //Iterators iterate over the contained options
        Iterator<Map.Entry<Integer, OptionValue>> iterator1 = this.getAllOptions().entries().iterator();
        Iterator<Map.Entry<Integer, OptionValue>> iterator2 = other.getAllOptions().entries().iterator();

        //Check if both CoAP Messages contain the same options in the same order
        while(iterator1.hasNext()){

            //Check if iterator2 has no more options while iterator1 has at least one more
            if(!iterator2.hasNext())
                return false;

            Map.Entry<Integer, OptionValue> entry1 = iterator1.next();
            Map.Entry<Integer, OptionValue> entry2 = iterator2.next();

            if(!entry1.getKey().equals(entry2.getKey()))
                return false;

            if(!entry1.getValue().equals(entry2.getValue()))
                return false;
        }

        //Check if iterator2 has at least one more option while iterator1 has no more
        if(iterator2.hasNext())
            return false;

        //Check content
        return this.getContent().equals(other.getContent());
    }


    @Override
    public String toString(){

        StringBuffer result =  new StringBuffer();

        //Header + Token
        result.append("[Header: (V) " + getProtocolVersion() + ", (T) " + getMessageTypeName() + ", (TKL) "
            + token.getBytes().length + ", (C) " + getMessageCodeName() + ", (ID) " + getMessageID() + " | (Token) "
            + token + " | ");

        //Options
        result.append("Options:");
        for(int optionNumber : getAllOptions().keySet()){
            result.append(" (No. " + optionNumber + ") ");
            Iterator<OptionValue> iterator = this.getOptions(optionNumber).iterator();
            OptionValue optionValue = iterator.next();
            result.append(optionValue.toString());
            while(iterator.hasNext())
                result.append(" / " + iterator.next().toString());
        }
        result.append(" | ");

        //Content
        result.append("Content: ");
        long payloadLength = getContent().readableBytes();
        if(payloadLength == 0)
            result.append("<no content>]");
        else
            result.append(getContent().toString(0, Math.min(getContent().readableBytes(), 20), CoapMessage.CHARSET)
                + "... ( " + payloadLength + " bytes)]");

        return result.toString();

    }

    public void setMessageCode(int messageCode) throws IllegalArgumentException {
        if(!MessageCode.Name.isMessageCode(messageCode))
            throw new IllegalArgumentException("Invalid message code no. " + messageCode);

        this.messageCode = messageCode;
    }


    /**
     * This is the supplier to provide the {@link LinkedHashSet} to contain the {@link de.uniluebeck.itm.ncoap.message.options.OptionValue} instances. There
     * is one {@link LinkedHashSet} provided per option number. The order prevention of the values contained
     * in such a set is necessary to keep the order of multiple values for one option (e.g. URI path).
     */
    private final static class LinkedHashSetSupplier implements Supplier<LinkedHashSet<OptionValue>> {

        public static LinkedHashSetSupplier instance = new LinkedHashSetSupplier();

        private LinkedHashSetSupplier(){}

        public static LinkedHashSetSupplier getInstance(){
            return instance;
        }

        @Override
        public LinkedHashSet<OptionValue> get() {
            return new LinkedHashSet<>();
        }
    }
}
TOP

Related Classes of de.uniluebeck.itm.ncoap.message.CoapMessage$LinkedHashSetSupplier

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.