/* * 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.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.locks.ReentrantReadWriteLock; import net.pms.dlna.protocolinfo.ProtocolInfoAttributeName.KnownProtocolInfoAttributeName; import net.pms.util.Rational; /** * This class is immutable and represents {@code DLNA.ORG_PS} attributes. This * can be used for both DLNA and non-DLNA content. * * @author Nadahar */ public class DLNAOrgPlaySpeeds implements ProtocolInfoAttribute { private static final long serialVersionUID = 1L; /** The static {@code NONE} instance representing a blank/empty value */ public static final DLNAOrgPlaySpeeds NONE = new DLNAOrgPlaySpeeds(new Rational[0]); /** * The static factory used to create and retrieve {@link DLNAOrgPlaySpeeds} * instances. */ public static final DLNAOrgPlaySpeedsFactory FACTORY = new DLNAOrgPlaySpeedsFactory(); /** The static attribute name always used for this class */ public static final ProtocolInfoAttributeName NAME = KnownProtocolInfoAttributeName.DLNA_ORG_PS; /** The {@link Set} of {@link Rational} play speeds */ protected final SortedSet<Rational> speeds; /** The cached {@link String} representation. */ protected final String stringValue; /** The cached {@code hashCode} */ protected final int hashCode; /** * For internal use only, use {@link #FACTORY} to create new instances. * * @param speeds the play-speed values. */ private DLNAOrgPlaySpeeds(Rational... speeds) { TreeSet<Rational> speedSet = new TreeSet<>(); for (Rational speed : speeds) { if (speed != null && !Rational.ONE.equals(speed)) { speedSet.add(speed); } } this.speeds = Collections.unmodifiableSortedSet(speedSet); this.stringValue = generateStringValue(this.speeds); this.hashCode = calculateHashCode(); } /** * For internal use only, use {@link #FACTORY} to create new instances. * * @param speeds the play-speed values. */ private DLNAOrgPlaySpeeds(SortedSet<Rational> speedSet) { TreeSet<Rational> newSpeedsSet = new TreeSet<>(); for (Rational speed : speedSet) { if (speed != null && !Rational.ONE.equals(speed)) { newSpeedsSet.add(speed); } } speeds = Collections.unmodifiableSortedSet(newSpeedsSet); stringValue = generateStringValue(speeds); hashCode = calculateHashCode(); } /** * @return The unmodifiable {@link Set} or {@link Rational} play speeds for * this {@link DLNAOrgPlaySpeeds} instance. */ public SortedSet<Rational> getSpeeds() { return speeds; } @Override public ProtocolInfoAttributeName getName() { return NAME; } @Override public String getNameString() { return NAME.getName(); } @Override public String getValue() { return stringValue; } @Override public String getAttributeString() { return isBlank(stringValue) ? "" : NAME + "=" + stringValue; } @Override public String toString() { return stringValue; } @Override public int hashCode() { return hashCode; } @Override public boolean equals(Object object) { if (this == object) { return true; } if (object == null) { return false; } if (!(object instanceof DLNAOrgPlaySpeeds)) { return false; } DLNAOrgPlaySpeeds other = (DLNAOrgPlaySpeeds) object; if (speeds == null) { return other.speeds == null; } else if (speeds.size() != other.speeds.size()) { return false; } // Using the cached hashCode for performance return hashCode == other.hashCode; } /** * Generates a string representation of a {@link Set} of {@link Rational} * play speeds. Used for generating the cached string representation. * * @param speeds the {@link Set} of {@link Rational} play speeds. * @return The {@link String} representation. */ protected static String generateStringValue(SortedSet<Rational> speeds) { StringBuilder sb = new StringBuilder(); for (Rational speed : speeds) { if (speed != null && !Rational.ONE.equals(speed)) { if (sb.length() > 0) { sb.append(","); } sb.append(speed); } } return sb.toString(); } /** * Calculates the cached {@code hashCode}. * * @return the calculated {@code hashCode}. */ protected int calculateHashCode() { final int prime = 31; int result = 1; if (speeds == null) { result = prime * result; } else { for (Rational speed : speeds) { if (speed != null) { result = prime * result + speed.hashCode(); } } } return result; } /** * A factory for creating, caching and retrieving {@link DLNAOrgPlaySpeeds} * instances. */ public static class DLNAOrgPlaySpeedsFactory { /** The instance cache lock. */ protected final ReentrantReadWriteLock instanceCacheLock = new ReentrantReadWriteLock(); /** The instance cache. */ protected final HashSet<DLNAOrgPlaySpeeds> instanceCache = new HashSet<>(); /** * For internal use only, use {@link DLNAOrgPlaySpeeds#FACTORY} to get * the singleton instance. */ private DLNAOrgPlaySpeedsFactory() { } /** * Retrieves an instance of {@link DLNAOrgPlaySpeeds} representing the * comma separated list of play speeds in {@code values} from the * predefined instances or the cache. If no such * {@link DLNAOrgPlaySpeeds} instance exists, {@code null} is returned. * * @param values a comma separated list of play speeds in rational form. * @return The {@link DLNAOrgPlaySpeeds} instance or {@code null}. * @throws NumberFormatException If {@code values} cannot be parsed. */ public DLNAOrgPlaySpeeds getPlaySpeeds(String values) { // Check for static instances if (isBlank(values)) { return NONE; } // Parse the values values = values.trim(); String[] valueArray = values.split("\\s*,\\s*"); TreeSet<Rational> valueSet = new TreeSet<>(); for (String value : valueArray) { if (isNotBlank(value)) { valueSet.add(new Rational(value)); } } return getPlaySpeeds(valueSet); } /** * Retrieves an instance of {@link DLNAOrgPlaySpeeds} representing the * given {@link Rational} play speed values from the predefined * instances or the cache. If no such {@link DLNAOrgPlaySpeeds} instance * exists, {@code null} is returned. * * @param values the {@link Rational} play speed values. * @return The {@link DLNAOrgPlaySpeeds} instance or {@code null}. */ public DLNAOrgPlaySpeeds getPlaySpeeds(Rational... values) { // Check for static instances if (values == null || values.length == 0) { return NONE; } // Check for cached instances return getPlaySpeeds(new TreeSet<Rational>(Arrays.asList(values))); } /** * Retrieves an instance of {@link DLNAOrgPlaySpeeds} representing the * {@link SortedSet} of {@link Rational} play speeds in {@code speedSet} * from the predefined instances or the cache. If no such * {@link DLNAOrgPlaySpeeds} instance exists, {@code null} is returned. * * @param speedSet a {@link SortedSet} of {@link Rational} play speeds. * @return The {@link DLNAOrgPlaySpeeds} instance or {@code null}. */ public DLNAOrgPlaySpeeds getPlaySpeeds(SortedSet<Rational> speedSet) { // Check for static instances if (speedSet == null || speedSet.isEmpty()) { return NONE; } // Check for cached instances String stringValue = generateStringValue(speedSet); instanceCacheLock.readLock().lock(); try { for (DLNAOrgPlaySpeeds cachedAttribute : instanceCache) { if (stringValue.equals(cachedAttribute.getValue())) { return cachedAttribute; } } } finally { instanceCacheLock.readLock().unlock(); } return null; } /** * Creates or retrieves an instance of {@link DLNAOrgPlaySpeeds} * representing the comma separated list of play speeds in * {@code values}. Any existing predefined or cached instances are first * checked, and if none are found for {@code values} a new instance is * created. * * @param values a comma separated list of play speeds in rational form. * @return The {@link DLNAOrgPlaySpeeds} instance. * @throws NumberFormatException If {@code values} cannot be parsed. */ public DLNAOrgPlaySpeeds createPlaySpeeds(String values) { // Check for static instances if (isBlank(values)) { return NONE; } // Parse the values values = values.trim(); String[] valueArray = values.split("\\s*,\\s*"); TreeSet<Rational> valueSet = new TreeSet<>(); for (String value : valueArray) { if (isNotBlank(value)) { valueSet.add(new Rational(value)); } } return createPlaySpeeds(valueSet); } /** * Creates or retrieves an instance of {@link DLNAOrgPlaySpeeds} * representing the given {@link Rational} play speed values. Any * existing predefined or cached instances are first checked, and if * none are found for {@code values} a new instance is created. * * @param values the {@link Rational} play speed values. * @return The {@link DLNAOrgPlaySpeeds} instance. */ public DLNAOrgPlaySpeeds createPlaySpeeds(Rational... values) { // Check for static instances if (values == null || values.length == 0) { return NONE; } // Remove any "1" values from the set, they are prohibited by DLNA SortedSet<Rational> cleanedSpeedSet = new TreeSet<>(Arrays.asList(values)); if (cleanedSpeedSet.contains(Rational.ONE)) { cleanedSpeedSet.remove(Rational.ONE); } return createPlaySpeeds(cleanedSpeedSet); } /** * Creates or retrieves an instance of {@link DLNAOrgPlaySpeeds} * representing {@link SortedSet} of {@link Rational} play speeds in * {@code speedSet}. Any existing predefined or cached instances are * first checked, and if none are found for {@code speedSet} a new * instance is created. * * @param speedSet a {@link SortedSet} of {@link Rational} play speeds. * @return The {@link DLNAOrgPlaySpeeds} instance. */ public DLNAOrgPlaySpeeds createPlaySpeeds(SortedSet<Rational> speedSet) { // Check for static instances if (speedSet == null || speedSet.isEmpty()) { return NONE; } // Remove any "1" values from the set, they are prohibited by DLNA SortedSet<Rational> cleanedSpeedSet; if (speedSet.contains(Rational.ONE)) { // Don't modify the argument (we don't even know if it's mutable) cleanedSpeedSet = new TreeSet<>(speedSet); cleanedSpeedSet.remove(Rational.ONE); } else { cleanedSpeedSet = speedSet; } // Check if an instance for this value already exists DLNAOrgPlaySpeeds instance = getPlaySpeeds(cleanedSpeedSet); if (instance != null) { return instance; } // Prepare to create a new instance instanceCacheLock.writeLock().lock(); try { // Check cache again as it could have been added while the // lock was released instance = getPlaySpeeds(cleanedSpeedSet); if (instance != null) { return instance; } // None was found, create the instance instance = new DLNAOrgPlaySpeeds(cleanedSpeedSet); instanceCache.add(instance); return instance; } finally { instanceCacheLock.writeLock().unlock(); } } } }