/*
* Universal Media Server, for streaming any media to DLNA
* compatible renderers based on the http://www.ps3mediaserver.org.
* Copyright (C) 2012 UMS developers.
*
* This program is a 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; version 2
* of the License only.
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package net.pms.dlna.protocolinfo;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.TreeMap;
import org.fourthline.cling.support.model.Protocol;
import org.fourthline.cling.support.model.dlna.DLNAAttribute;
import org.fourthline.cling.support.model.dlna.DLNAProfiles;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.pms.dlna.protocolinfo.ProtocolInfoAttributeName.KnownProtocolInfoAttributeName;
import net.pms.util.ParseException;
/**
* This immutable class represents a {@code protocolInfo} element.
*
* @author Nadahar
*/
public class ProtocolInfo implements Comparable<ProtocolInfo>, Serializable {
private static final long serialVersionUID = 1L;
private static final Logger LOGGER = LoggerFactory.getLogger(ProtocolInfo.class);
/** The wildcard character {@code "*"} */
public static final String WILDCARD = "*";
/** A static instance of an empty attribute map */
public static final SortedMap<ProtocolInfoAttributeName, ProtocolInfoAttribute> EMPTYMAP =
Collections.unmodifiableSortedMap(createEmptyAttributesMap());
/** The protocol (first field) of {@code protocolInfo} */
protected final Protocol protocol;
/** The network (second field) of {@code protocolInfo} */
protected final String network;
/** The contentType (third field) of {@code protocolInfo} */
protected final MimeType mimeType;
/** The {@code additionalInfo} (fourth field) of {@code protocolInfo} */
protected final String additionalInfo;
/** The attributes parsed from {@code additionalInfo}. */
protected final SortedMap<ProtocolInfoAttributeName, ProtocolInfoAttribute> attributes;
/** The cached string representation of {@link #attributes}. */
protected final String attributesString;
/** The cached string representation. */
protected final String stringValue;
/**
* Creates a new instance by parsing a {@code ProtocolInfo} string.
*
* @param protocolInfoString the {@link String} to parse.
* @throws ParseException If {@code protocolInfoString} can't be parsed.
*/
public ProtocolInfo(String protocolInfoString) throws ParseException {
String tmpNetwork = WILDCARD;
MimeType tmpMimeType = MimeType.ANYANY;
String tmpAdditionalInfo = WILDCARD;
if (isBlank(protocolInfoString)) {
protocol = Protocol.ALL;
} else {
protocolInfoString = protocolInfoString.trim();
String[] elements = protocolInfoString.split("\\s*:\\s*");
protocol = Protocol.value(elements[0]);
if (elements.length > 1) {
tmpNetwork = elements[1];
}
if (elements.length > 2) {
tmpMimeType = createMimeType(elements[2]);
}
if (elements.length > 3) {
tmpAdditionalInfo = elements[3];
}
if (elements.length > 4) {
throw new ParseException("Invalid protocolInfo string \"" + protocolInfoString + "\"");
}
}
network = tmpNetwork;
mimeType = tmpMimeType;
additionalInfo = tmpAdditionalInfo;
attributes = Collections.unmodifiableSortedMap(parseAdditionalInfo());
attributesString = generateAttributesString();
stringValue = generateStringValue();
}
/**
* Creates a new instance using the provided information.
*
* @param protocol the {@link Protocol} for the new instance. Use
* {@code null} for "any".
* @param network the network for the new instance. Use {@code null} or
* blank for "any".
* @param contentFormat the content format for the new instance. Use
* {@code null} or blank for "any".
* @param additionalInfo the additional information for the new instance.
*/
public ProtocolInfo(Protocol protocol, String network, String contentFormat, String additionalInfo) {
this.protocol = protocol == null ? Protocol.ALL : protocol;
this.network = isBlank(network) ? WILDCARD : network;
this.mimeType = createMimeType(contentFormat);
this.additionalInfo = isBlank(additionalInfo) ? WILDCARD : additionalInfo;
this.attributes = Collections.unmodifiableSortedMap(parseAdditionalInfo());
this.attributesString = generateAttributesString();
this.stringValue = generateStringValue();
}
/**
* Creates a new instance using the provided information.
*
* @param protocol the {@link Protocol} for the new instance. Use
* {@code null} for "any".
* @param network the network for the new instance. Use {@code null} or
* blank for "any".
* @param mimeType the mime-type for the new instance. Use {@code null} or
* {@link MimeType#ANYANY} for "any".
* @param additionalInfo the additional information for the new instance.
*/
public ProtocolInfo(Protocol protocol, String network, MimeType mimeType, String additionalInfo) {
this.protocol = protocol == null ? Protocol.ALL : protocol;
this.network = isBlank(network) ? WILDCARD : network;
this.mimeType = mimeType == null ? MimeType.ANYANY : mimeType;
this.additionalInfo = isBlank(additionalInfo) ? WILDCARD : additionalInfo;
this.attributes = Collections.unmodifiableSortedMap(parseAdditionalInfo());
this.attributesString = generateAttributesString();
this.stringValue = generateStringValue();
}
/**
* Creates a new instance using the provided information.
*
* @param protocol the {@link Protocol} for the new instance. Use
* {@code null} for "any".
* @param network the network for the new instance. Use {@code null} or
* blank for "any".
* @param contentFormat the content format for the new instance. Use
* {@code null} or blank for "any".
* @param attributes a {@link Map} of {@link ProtocolInfoAttributeName} and
* {@link ProtocolInfoAttribute} pairs for the new instance.
*/
public ProtocolInfo(
Protocol protocol,
String network,
String contentFormat,
Map<ProtocolInfoAttributeName, ProtocolInfoAttribute> attributes
) {
this.protocol = protocol == null ? Protocol.ALL : protocol;
this.network = isBlank(network) ? WILDCARD : network;
this.mimeType = createMimeType(contentFormat);
TreeMap<ProtocolInfoAttributeName, ProtocolInfoAttribute> tmpAttributes = createEmptyAttributesMap();
tmpAttributes.putAll(attributes);
this.attributes = Collections.unmodifiableSortedMap(tmpAttributes);
this.attributesString = generateAttributesString();
this.additionalInfo = this.attributesString;
this.stringValue = generateStringValue();
}
/**
* Creates a new instance using the provided information.
*
* @param protocol the {@link Protocol} for the new instance. Use
* {@code null} for "any".
* @param network the network for the new instance. Use {@code null} or
* blank for "any".
* @param mimeType the mime-type for the new instance. Use {@code null} or
* {@link MimeType#ANYANY} for "any".
* @param attributes a {@link Map} of {@link ProtocolInfoAttributeName} and
* {@link ProtocolInfoAttribute} pairs for the new instance.
*/
public ProtocolInfo(
Protocol protocol,
String network,
MimeType mimeType,
Map<ProtocolInfoAttributeName, ProtocolInfoAttribute> attributes
) {
this.protocol = protocol == null ? Protocol.ALL : protocol;
this.network = isBlank(network) ? WILDCARD : network;
this.mimeType = mimeType == null ? MimeType.ANYANY : mimeType;
TreeMap<ProtocolInfoAttributeName, ProtocolInfoAttribute> tmpAttributes = createEmptyAttributesMap();
tmpAttributes.putAll(attributes);
this.attributes = Collections.unmodifiableSortedMap(tmpAttributes);
this.attributesString = generateAttributesString();
this.additionalInfo = this.attributesString;
this.stringValue = generateStringValue();
}
/**
* Creates a new instance using the provided information.
*
* @param protocol the {@link Protocol} for the new instance. Use
* {@code null} for "any".
* @param network the network for the new instance. Use {@code null} or
* blank for "any".
* @param contentFormat the content format for the new instance. Use
* {@code null} or blank for "any".
* @param attributes an {@link EnumMap} with {@link DLNAAttribute}s the new
* instance.
*/
public ProtocolInfo(
Protocol protocol,
String network,
String contentFormat,
EnumMap<DLNAAttribute.Type, DLNAAttribute<?>> attributes
) {
this.protocol = protocol == null ? Protocol.ALL : protocol;
this.network = isBlank(network) ? WILDCARD : network;
this.mimeType = createMimeType(contentFormat);
this.attributes = Collections.unmodifiableSortedMap(dlnaAttributesToAttributes(attributes));
this.attributesString = generateAttributesString();
this.additionalInfo = this.attributesString;
this.stringValue = generateStringValue();
}
/**
* Creates a new instance using the provided information.
*
* @param protocol the {@link Protocol} for the new instance. Use
* {@code null} for "any".
* @param network the network for the new instance. Use {@code null} or
* blank for "any".
* @param mimeType the mime-type for the new instance. Use {@code null} or
* {@link MimeType#ANYANY} for "any".
* @param attributes an {@link EnumMap} with {@link DLNAAttribute}s for the
* new instance.
*/
public ProtocolInfo(
Protocol protocol,
String network,
MimeType mimeType,
EnumMap<DLNAAttribute.Type, DLNAAttribute<?>> attributes
) {
this.protocol = protocol == null ? Protocol.ALL : protocol;
this.network = isBlank(network) ? WILDCARD : network;
this.mimeType = mimeType == null ? MimeType.ANYANY : mimeType;
this.attributes = Collections.unmodifiableSortedMap(dlnaAttributesToAttributes(attributes));
this.attributesString = generateAttributesString();
this.additionalInfo = this.attributesString;
this.stringValue = generateStringValue();
}
/**
* Creates a new instance based on a {@link DLNAProfiles} profile.
*
* @param protocol the {@link Protocol} for the new instance.
* @param profile the {@link DLNAProfiles} profile for the new instance.
*/
public ProtocolInfo(Protocol protocol, DLNAProfiles profile) {
this.protocol = protocol == null ? Protocol.ALL : protocol;
this.network = WILDCARD;
this.mimeType = createMimeType(profile.getContentFormat());
SortedMap<ProtocolInfoAttributeName, ProtocolInfoAttribute> tmpAttributes = createEmptyAttributesMap();
DLNAOrgProfileName profileName = DLNAOrgProfileName.FACTORY.createProfileName(profile.getCode());
tmpAttributes.put(profileName.getName(), profileName);
this.attributes = Collections.unmodifiableSortedMap(tmpAttributes);
this.attributesString = generateAttributesString();
this.additionalInfo = this.attributesString;
this.stringValue = generateStringValue();
}
/**
* Creates a new instance based on a {@link DLNAProfiles} profile and
* additional {@link DLNAAttribute}s.
*
* @param protocol the {@link Protocol} for the new instance.
* @param profile the {@link DLNAProfiles} profile for the new instance.
* @param dlnaAttributes an {@link EnumMap} with {@link DLNAAttribute}s for
* the new instance.
*/
public ProtocolInfo(
Protocol protocol,
DLNAProfiles profile,
EnumMap<DLNAAttribute.Type, DLNAAttribute<?>> dlnaAttributes
) {
this.protocol = protocol == null ? Protocol.ALL : protocol;
this.network = WILDCARD;
this.mimeType = createMimeType(profile.getContentFormat());
TreeMap<ProtocolInfoAttributeName, ProtocolInfoAttribute> tmpAttributes =
dlnaAttributesToAttributes(dlnaAttributes);
DLNAOrgProfileName profileName = DLNAOrgProfileName.FACTORY.createProfileName(profile.getCode());
tmpAttributes.put(profileName.getName(), profileName);
this.attributes = Collections.unmodifiableSortedMap(tmpAttributes);
this.attributesString = generateAttributesString();
this.additionalInfo = this.attributesString;
this.stringValue = generateStringValue();
}
/**
* Creates a new instance from a {@link org.fourthline.cling.support.model.ProtocolInfo} instance.
*
* @param template the {@link org.fourthline.cling.support.model.ProtocolInfo} instance.
*/
public ProtocolInfo(org.fourthline.cling.support.model.ProtocolInfo template) {
this(template.getProtocol(),
template.getNetwork(),
template.getContentFormat(),
template.getAdditionalInfo()
);
}
/**
* @return The {@code DLNA.ORG_PN} (DLNA media format profile) for this
* {@link ProtocolInfo} or {@code null} if it isn't defined.
*/
public DLNAOrgProfileName getDLNAProfileName() {
ProtocolInfoAttribute pnAttribute =
attributes.get(KnownProtocolInfoAttributeName.DLNA_ORG_PN);
return pnAttribute instanceof DLNAOrgProfileName ?
(DLNAOrgProfileName) pnAttribute :
null;
}
/**
* @return The {@code DLNA.ORG_OP} of this {@link ProtocolInfo} or
* {@code null} if it isn't defined.
*/
public DLNAOrgOperations getDLNAOperations() {
ProtocolInfoAttribute operationsAttribute =
attributes.get(KnownProtocolInfoAttributeName.DLNA_ORG_OP);
return operationsAttribute instanceof DLNAOrgOperations ?
(DLNAOrgOperations) operationsAttribute :
null;
}
/**
* @return The {@code DLNA.ORG_PS} of this {@link ProtocolInfo} or
* {@code null} if it isn't defined.
*/
public DLNAOrgPlaySpeeds getDLNAPlaySpeeds() {
ProtocolInfoAttribute playSpeedsAttribute =
attributes.get(KnownProtocolInfoAttributeName.DLNA_ORG_PS);
return playSpeedsAttribute instanceof DLNAOrgPlaySpeeds ?
(DLNAOrgPlaySpeeds) playSpeedsAttribute :
null;
}
/**
* @return The {@code DLNA.ORG_CI} of this {@link ProtocolInfo} or
* {@code null} if it isn't defined.
*/
public DLNAOrgConversionIndicator getDLNAConversionIndicator() {
ProtocolInfoAttribute conversionIndicatorAttribute =
attributes.get(KnownProtocolInfoAttributeName.DLNA_ORG_CI);
return conversionIndicatorAttribute instanceof DLNAOrgConversionIndicator ?
(DLNAOrgConversionIndicator) conversionIndicatorAttribute :
null;
}
/**
* @return The {@code DLNA.ORG_FLAGS} of this {@link ProtocolInfo} or
* {@code null} if it isn't defined.
*/
public DLNAOrgFlags getFlags() {
ProtocolInfoAttribute flagsAttribute =
attributes.get(KnownProtocolInfoAttributeName.DLNA_ORG_FLAGS);
return flagsAttribute instanceof DLNAOrgFlags ?
(DLNAOrgFlags) flagsAttribute :
null;
}
/**
* Searches for a {@link ProfileName} (any attribute name ending with
* {@code "_PN"} among the attributes, and returns the first one found.
* There is supposed to be zero or one {@link ProfileName} for any given
* instance of {@link ProtocolInfo}. If none is found, {@code null} is
* returned.
* <p>
* <b>Note: This will return any {@link ProfileName}, not just
* {@link DLNAOrgProfileName}s</b>. If you're looking for a
* {@code DLNA.ORG_PN}, use {@link #getDLNAProfileName} instead.
*
* @return The {@code DLNA.ORG_PN} (DLNA media format profile) for this
* {@link ProtocolInfo} or {@code null} if it isn't defined.
*/
public ProfileName getProfileName() {
for (ProtocolInfoAttribute attribute : attributes.values()) {
if (attribute instanceof ProfileName) {
return (ProfileName) attribute;
}
}
return null;
}
/**
* Gets the cached {@link String} generated from the attributes map. This
* should be identical to {@link #getAdditionalInfo()}.
*
* @return The {@link String} representation of {@link #attributes}.
*/
public String getAttributesString() {
return attributesString;
}
/**
* For internal use only. Generates a {@link String} representation of the
* {@link ProtocolInfoAttribute}s in {@link #attributes}.
*
* @return The {@link String} representation.
*/
protected String generateAttributesString() {
if (attributes == null || attributes.isEmpty()) {
return "";
}
StringBuilder sb = new StringBuilder();
for (ProtocolInfoAttribute attribute : attributes.values()) {
String attributeString = attribute.getAttributeString();
if (isNotBlank(attributeString)) {
if (sb.length() > 0) {
sb.append(";");
}
sb.append(attributeString);
}
}
return sb.toString();
}
/**
* Gets the {@link MimeType} created from the content format of this
* {@link ProtocolInfo}.
*
* @return The {@link MimeType}.
*/
public MimeType getMimeType() {
return mimeType;
}
/**
* For internal use only, creates the {@link MimeType} that is stored in
* {@code this.mimeType}.
*
* @param contentFormat the {@code protocolInfo} {@code contentFormat} to
* parse.
* @return A new {@link MimeType} instance.
*/
protected MimeType createMimeType(String contentFormat) {
try {
return MimeType.valueOf(contentFormat);
} catch (ParseException e) {
LOGGER.error("Error parsing MimeType from \"{}\": {}", contentFormat, e.getMessage());
LOGGER.trace("", e);
}
return MimeType.ANYANY;
}
/**
* Creates a new {@link org.seamless.util.MimeType} from the
* {@link MimeType} of this {@link ProtocolInfo}. To get the
* {@link MimeType}, use {@link #getMimeType()} instead.
*
* @return The corresponding {@link org.seamless.util.MimeType}.
* @throws IllegalArgumentException if
* {@link org.seamless.util.MimeType#valueOf()} can't parse this
* {@link MimeType}.
* @see #getMimeType()
*/
public org.seamless.util.MimeType getSeamlessMimeType() throws IllegalArgumentException {
return org.seamless.util.MimeType.valueOf(mimeType.toString());
}
/**
* Parses {@code additionalInfo}.
*
* @return The {@link SortedMap} of parsed {@link ProtocolInfoAttribute}s.
*/
protected SortedMap<ProtocolInfoAttributeName, ProtocolInfoAttribute> parseAdditionalInfo() {
if (isBlank(additionalInfo) || WILDCARD.equals(additionalInfo.trim())) {
return EMPTYMAP;
}
TreeMap<ProtocolInfoAttributeName, ProtocolInfoAttribute> result = createEmptyAttributesMap();
String[] attributeStrings = additionalInfo.trim().toUpperCase(Locale.ROOT).split("\\s*;\\s*");
for (String attributeString : attributeStrings) {
if (isBlank(attributeString)) {
continue;
}
String[] attributeEntry = attributeString.split("\\s*=\\s*");
if (attributeEntry.length == 2) {
try {
ProtocolInfoAttribute attribute = ProtocolInfoAttribute.FACTORY.createAttribute(
attributeEntry[0],
attributeEntry[1],
protocol
);
if (attribute != null) {
result.put(attribute.getName(), attribute);
} else {
LOGGER.debug("Failed to parse attribute \"{}\"", attributeString);
}
} catch (ParseException e) {
LOGGER.debug("Failed to parse attribute \"{}\": {}", attributeString, e.getMessage());
LOGGER.trace("", e);
}
} else {
LOGGER.debug("Invalid ProtocolInfo attribute \"{}\"", attributeString);
}
}
return result;
}
/**
* @return the {@link Protocol} of this {@link ProtocolInfo}.
*/
public Protocol getProtocol() {
return protocol;
}
/**
* @return the {@code network} of this {@link ProtocolInfo}.
*/
public String getNetwork() {
return network;
}
/**
* @return the {@code contentFormat} of this {@link ProtocolInfo}.
*/
public String getContentFormat() {
return mimeType.toString();
}
/**
* @return the {@code additionalInfo} of this {@link ProtocolInfo}.
*/
public String getAdditionalInfo() {
return additionalInfo;
}
/**
* The attributes are the content of {@code additionalInfo} in parsed form.
*
* @return the attributes of this {@link ProtocolInfo}.
*/
public SortedMap<ProtocolInfoAttributeName, ProtocolInfoAttribute> getAttributes() {
return attributes;
}
/**
* Returns a debug string representation of this {@link ProtocolInfo}.
*
* @return The debug {@link String} representation.
*/
public String toDebugString() {
StringBuilder sb = new StringBuilder();
sb .append("Protocol: ").append(protocol)
.append(", Network: ").append(network)
.append(", ContentFormat/MimeType: ").append(mimeType);
if (isNotBlank(additionalInfo)) {
sb.append(", AdditionalInfo: ").append(additionalInfo);
}
if (!mimeType.toString().equals(mimeType.toStringWithoutParameters())) {
sb.append(", Simple MimeType: ").append(mimeType.toStringWithoutParameters());
}
if (mimeType.isDRM()) {
sb.append(", DRM");
}
if (!mimeType.getParameters().isEmpty()) {
sb.append(", MimeType Parameters: ").append(mimeType.getParameters());
}
if (attributes != null && !attributes.isEmpty()) {
sb.append(", Attributes: ").append(attributes);
}
return sb.toString();
}
@Override
public String toString() {
return stringValue;
}
/**
* For internal use only, generates the string representation of this
* {@link ProtocolInfo} for use for the cached {@link #toString()} value.
*
* @return The string representation.
*/
protected String generateStringValue() {
StringBuilder sb = new StringBuilder();
sb .append(protocol == null ? WILDCARD : protocol).append(":")
.append(isBlank(network) ? WILDCARD : network).append(":")
.append(mimeType == null ? MimeType.ANYANY : mimeType).append(":")
.append(isBlank(attributesString) ? WILDCARD : attributesString);
return sb.toString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((additionalInfo == null) ? 0 : additionalInfo.hashCode());
result = prime * result + ((attributesString == null) ? 0 : attributesString.hashCode());
result = prime * result + ((mimeType == null) ? 0 : mimeType.hashCode());
result = prime * result + ((network == null) ? 0 : network.hashCode());
result = prime * result + ((protocol == null) ? 0 : protocol.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof ProtocolInfo)) {
return false;
}
ProtocolInfo other = (ProtocolInfo) obj;
if (additionalInfo == null) {
if (other.additionalInfo != null) {
return false;
}
} else if (!additionalInfo.equals(other.additionalInfo)) {
return false;
}
if (attributesString == null) {
if (other.attributesString != null) {
return false;
}
} else if (!attributesString.equals(other.attributesString)) {
return false;
}
if (mimeType == null) {
if (other.mimeType != null) {
return false;
}
} else if (!mimeType.equals(other.mimeType)) {
return false;
}
if (network == null) {
if (other.network != null) {
return false;
}
} else if (!network.equals(other.network)) {
return false;
}
if (protocol != other.protocol) {
return false;
}
return true;
}
/**
* Converts an {@link EnumMap} of
* {@link org.fourthline.cling.support.model.dlna.DLNAAttribute}s to a
* {@link TreeMap} of {@link ProtocolInfoAttribute}s.
*
* @param dlnaAttributes the {@link EnumMap} of
* {@link org.fourthline.cling.support.model.dlna.DLNAAttribute}s
* to convert.
* @return A {@link TreeMap} containing the converted
* {@link ProtocolInfoAttribute}s.
*/
public static TreeMap<ProtocolInfoAttributeName, ProtocolInfoAttribute> dlnaAttributesToAttributes(
EnumMap<DLNAAttribute.Type,
DLNAAttribute<?>> dlnaAttributes
) {
TreeMap<ProtocolInfoAttributeName, ProtocolInfoAttribute> attributes = createEmptyAttributesMap();
for (Entry<DLNAAttribute.Type, DLNAAttribute<?>> entry: dlnaAttributes.entrySet()) {
try {
ProtocolInfoAttribute attribute = ProtocolInfoAttribute.FACTORY.createAttribute(
entry.getKey().getAttributeName(),
entry.getValue().getString(),
Protocol.HTTP_GET
);
if (attribute != null) {
attributes.put(attribute.getName(), attribute);
}
} catch (ParseException e) {
LOGGER.debug(
"Couldn't parse DLNAAttribute \"{}\" = \"{}\": {}",
entry.getKey().getAttributeName(),
entry.getValue().getString(), e.getMessage()
);
LOGGER.trace("", e);
}
}
return attributes;
}
/**
* A convenience method to create an empty {@link TreeMap} of
* {@link ProtocolInfoAttribute}s with the correct {@link Comparator}
* {@link AttributeComparator}.
*
* @return The empty attributes map.
*/
public static TreeMap<ProtocolInfoAttributeName, ProtocolInfoAttribute> createEmptyAttributesMap() {
return new TreeMap<>(new AttributeComparator());
}
/**
* DLNA requires {@code protocolInfo} attributes to appear in a certain
* order. Any {@link SortedMap} that is initialized with this
* {@link Comparator} will automatically sort its elements according to this
* custom order.
*
* It is vital that any {@link Map} used to store
* {@link ProtocolInfoAttribute}s in relation to {@link ProtocolInfo} use
* this class as it's {@link Comparator}.
*
* @author Nadahar
*/
public static class AttributeComparator implements Comparator<ProtocolInfoAttributeName>, Serializable {
private static final long serialVersionUID = 1L;
/** Defines the sort order for known attributes */
public static final List<ProtocolInfoAttributeName> DEFINED_ORDER =
Collections.unmodifiableList(Arrays.asList(new ProtocolInfoAttributeName[] {
KnownProtocolInfoAttributeName.DLNA_ORG_PN,
KnownProtocolInfoAttributeName.DLNA_ORG_OP,
KnownProtocolInfoAttributeName.DLNA_ORG_PS,
KnownProtocolInfoAttributeName.DLNA_ORG_CI,
KnownProtocolInfoAttributeName.DLNA_ORG_FLAGS,
KnownProtocolInfoAttributeName.ARIB_OR_JP_PN,
KnownProtocolInfoAttributeName.DTV_MVP_PN,
KnownProtocolInfoAttributeName.PANASONIC_COM_PN,
KnownProtocolInfoAttributeName.MICROSOFT_COM_PN,
KnownProtocolInfoAttributeName.SHARP_COM_PN,
KnownProtocolInfoAttributeName.SONY_COM_PN
}));
@Override
public int compare(ProtocolInfoAttributeName o1, ProtocolInfoAttributeName o2) {
if (o1 == null && o2 == null) {
return 0;
}
if (o1 == null) {
return 1;
}
if (o2 == null) {
return -1;
}
int o1Index = DEFINED_ORDER.indexOf(o1);
int o2Index = DEFINED_ORDER.indexOf(o2);
// Sort by defined order if both arguments are defined
if (o1Index >= 0 && o2Index >= 0) {
return o1Index - o2Index;
}
// Sort by string value if none of the arguments are defined
if (o1Index < 0 && o2Index < 0) {
return o1.getName().compareTo(o2.getName());
}
// Sort defined arguments before undefined arguments
if (o1Index < 0) {
return 1;
}
if (o2Index < 0) {
return -1;
}
// Sort alphabetically by name
String o1Name = o1.getName();
String o2Name = o2.getName();
if (o1Name == null && o2Name == null) {
return 0;
}
if (o1Name == null) {
return 1;
}
if (o2Name == null) {
return -1;
}
return o1Name.compareTo(o2Name);
}
}
/**
* Compares {@link ProtocolInfo} instances for sorting. Sorting is done in
* this order: {@code protocol}, {@code network}, {@code contentFormat} and
* {@code additionalInfo}.
*
* @param other the {@link ProtocolInfo} instance to compare to.
*/
@Override
public int compareTo(ProtocolInfo other) {
if (other == null) {
return -1;
}
// Protocol
if (protocol == null && other.protocol != null) {
return 1;
}
if (protocol != null && other.protocol == null) {
return -1;
}
int result;
if (protocol != null && other.protocol != null) {
result = protocol.compareTo(other.protocol);
if (result != 0) {
return result;
}
}
// Network
if (network == null && other.network != null) {
return 1;
}
if (network != null && other.network == null) {
return -1;
}
if (network != null && other.network != null) {
result = network.compareTo(other.network);
if (result != 0) {
return result;
}
}
// ContentFormat/MimeType
if (mimeType == null && other.mimeType != null) {
return 1;
}
if (mimeType != null && other.mimeType == null) {
return -1;
}
if (mimeType != null && other.mimeType != null) {
result = mimeType.compareTo(other.mimeType);
if (result != 0) {
return result;
}
}
// AdditionalInfo
if (additionalInfo == null && other.additionalInfo != null) {
return 1;
}
if (additionalInfo != null && other.additionalInfo == null) {
return -1;
}
if (additionalInfo != null && other.additionalInfo != null) {
return additionalInfo.compareTo(other.additionalInfo);
}
return 0;
}
}