/* * 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 java.util.HashMap; import java.util.HashSet; import java.util.Locale; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This interface represents {@code protocolInfo} profile name attributes like * {@code DLNA.ORG_PN} or {@code SONY.COM_PN}. * * @author Nadahar */ public interface ProfileName extends ProtocolInfoAttribute { /** * An abstract factory for creating, caching and retrieving * {@link ProfileName} instances. * * @param <E> the instance type */ public abstract static class AbstractProfileNameFactory<E extends ProfileName> { /** The logger. */ private static final Logger LOGGER = LoggerFactory.getLogger(AbstractProfileNameFactory.class); /** The instance cache lock. */ protected final ReentrantReadWriteLock instanceCacheLock = new ReentrantReadWriteLock(); /** The instance cache. */ protected final HashSet<E> instanceCache = new HashSet<>(); /** * Gets the static {@code NONE} instance used for representing * blank/empty values. * * @return the {@code NONE} instance. */ protected abstract E getNoneInstance(); /** * Searches through the predefined "known" instances, if any. Returns * the instance if a match is found, otherwise {@code null}. * * @param value the value to search for. * @return The found {@link ProfileName} or {@code null}. */ protected abstract E searchKnownInstances(String value); /** * Creates a new instance of the concrete type {@link E}. * * @param value the value to use for the new instance. * @return The new instance of {@link E}. */ protected abstract E getNewInstance(String value); /** * Retrieves an instance of {@link E} representing the profile name * {@code value} from the predefined instances or the cache. If no such * {@link E} instance exists, {@code null} is returned. * * @param value the profile name value. * @return The profile name instance of {@link E} or {@code null}. */ public E getProfileName(String value) { // Check for static instances if (isBlank(value)) { return getNoneInstance(); } value = value.trim().toUpperCase(Locale.ROOT); // Check for known instances E instance = searchKnownInstances(value); if (instance != null) { return instance; } // Check for cached instances instanceCacheLock.readLock().lock(); try { for (E cachedAttribute : instanceCache) { if (value.equals(cachedAttribute.getValue())) { return cachedAttribute; } } } finally { instanceCacheLock.readLock().unlock(); } return null; } /** * Creates or retrieves an instance of {@link E} representing the * profile name {@code value}. Any existing predefined or cached * instances are first checked, and if none are found for {@code value} * a new instance is created. * * @param value the profile name value. * @return The profile name instance of {@link E}. */ public E createProfileName(String value) { // Check if an instance for this value already exists E instance = getProfileName(value); if (instance != null) { return instance; } // Null values will already have been handled above value = value.trim().toUpperCase(Locale.ROOT); // Prepare to create a new instance instanceCacheLock.writeLock().lock(); try { // Check cache again as it could have been added while the // lock was released for (E cachedAttribute : instanceCache) { if (value.equals(cachedAttribute.getValue())) { return cachedAttribute; } } // None was found, create the instance instance = getNewInstance(value); instanceCache.add(instance); LOGGER.trace("{} added unknown profile \"{}\"", getClass().getSimpleName(), value); return instance; } finally { instanceCacheLock.writeLock().unlock(); } } } /** * An abstract, immutable class of a default profile name implementation. * Default profile name classes are used by the {@code ProfileNameFactories} * when creating new instances not handled by any other class implementing * that interface. */ public abstract static class AbstractDefaultProfileName implements ProfileName { private static final long serialVersionUID = 1L; /** The profile name value. */ protected final String value; /** * Instantiates a new {@link AbstractDefaultProfileName}. For use by * subclasses only. * * @param value the profile name value. */ protected AbstractDefaultProfileName(String value) { this.value = value; } @Override public String getValue() { return value; } @Override public String toString() { return getName() + " = " + value; } @Override public abstract ProtocolInfoAttributeName getName(); @Override public String getNameString() { return getName() == null ? "" : getName().getName(); } @Override public String getAttributeString() { return isBlank(getNameString()) || isBlank(value) ? "" : getNameString() + "=" + value; } } /** * A factory for creating, caching and retrieving * {@link DefaultGenericProfileName} instances. */ public static class DefaultGenericProfileNameFactory { /** The logger. */ private static final Logger LOGGER = LoggerFactory.getLogger(AbstractProfileNameFactory.class); /** The instance cache lock. */ protected final ReentrantReadWriteLock instanceCacheLock = new ReentrantReadWriteLock(); /** The instance cache. */ protected final HashMap<ProtocolInfoAttributeName, HashSet<DefaultGenericProfileName>> instanceCache = new HashMap<>(); /** * For internal use only, use {@link DefaultGenericProfileName#FACTORY} * instead. */ protected DefaultGenericProfileNameFactory() { } /** * Retrieves an instance of {@link DefaultGenericProfileName} * representing attribute name {@code name} and profile name * {@code value} from the cache. If no such * {@link DefaultGenericProfileName} instance exists, {@code null} is * returned. * * @param name the attribute name. * @param value the profile name value. * @return The {@link DefaultGenericProfileName} instance or * {@code null}. */ public DefaultGenericProfileName getProfileName(String name, String value) { return getProfileName(ProtocolInfoAttributeName.FACTORY.createAttributeName(name), value); } /** * Retrieves an instance of {@link DefaultGenericProfileName} * representing attribute name {@code name} and profile name * {@code value} from the cache. If no such * {@link DefaultGenericProfileName} instance exists, {@code null} is * returned. * * @param name the {@link ProtocolInfoAttributeName}. * @param value the profile name value. * @return The {@link DefaultGenericProfileName} instance or * {@code null}. */ public DefaultGenericProfileName getProfileName(ProtocolInfoAttributeName name, String value) { // Make all blank values the same if (isBlank(value)) { value = ""; } else { value = value.trim().toUpperCase(Locale.ROOT); } // Check for cached instances instanceCacheLock.readLock().lock(); try { HashSet<DefaultGenericProfileName> set = instanceCache.get(name); if (set != null) { for (DefaultGenericProfileName cachedProfileName : set) { if (value.equals(cachedProfileName.getValue())) { return cachedProfileName; } } } } finally { instanceCacheLock.readLock().unlock(); } return null; } /** * Creates or retrieves an instance of {@link DefaultGenericProfileName} * representing the profile name {@code value}. All cached instances are * checked first, and if none are found for {@code name} and * {@code value} a new instance is created and cached. * * @param name the attribute name. * @param value the profile name value. * @return The {@link DefaultGenericProfileName} instance. */ public DefaultGenericProfileName createProfileName(String name, String value) { return createProfileName(ProtocolInfoAttributeName.FACTORY.createAttributeName(name), value); } /** * Creates or retrieves an instance of {@link DefaultGenericProfileName} * representing the profile name {@code value}. All cached instances are * checked first, and if none are found for {@code name} and * {@code value} a new instance is created and cached. * * @param name the {@link ProtocolInfoAttributeName}. * @param value the profile name value. * @return The {@link DefaultGenericProfileName} instance. */ public DefaultGenericProfileName createProfileName(ProtocolInfoAttributeName name, String value) { // Check if an instance for this value already exists DefaultGenericProfileName instance = getProfileName(name, value); if (instance != null) { return instance; } // Make all blank values the same if (isBlank(value)) { value = ""; } else { value = value.trim().toUpperCase(Locale.ROOT); } // Prepare to create a new instance instanceCacheLock.writeLock().lock(); try { // Check cache again as it could have been added while the // lock was released HashSet<DefaultGenericProfileName> set = instanceCache.get(name); if (set != null) { for (DefaultGenericProfileName cachedProfileName : set) { if (value.equals(cachedProfileName.getValue())) { return cachedProfileName; } } } else { // Store this set set = new HashSet<>(); instanceCache.put(name, set); } // None was found, create the instance instance = new DefaultGenericProfileName(name, value); set.add(instance); LOGGER.trace("Adding unknown generic \"{}\" profile name \"{}\"", name, value); return instance; } finally { instanceCacheLock.writeLock().unlock(); } } } /** * This is the default, immutable class implementing generic * {@link ProfileName}, that is {@link ProfileName} types that doesn't have * a specific implementation. At this time, that is any {@code _PN} * attributes except {@code DLNA.ORG_PN}. * <p> * {@link DefaultGenericProfileNameFactory} creates and caches instances of * this class. */ public static class DefaultGenericProfileName extends AbstractDefaultProfileName { private static final long serialVersionUID = 1L; /** * The static factory singleton instance used to create and retrieve * {@link DefaultGenericProfileName} instances. */ public static final DefaultGenericProfileNameFactory FACTORY = new DefaultGenericProfileNameFactory(); /** The attribute name */ protected final ProtocolInfoAttributeName name; /** * For internal use only, use {@link #FACTORY} to create new instances. * * @param name the {@link ProtocolInfoAttributeName}. * @param value the profile name. */ protected DefaultGenericProfileName(ProtocolInfoAttributeName name, String value) { super(value); this.name = name; } @Override public ProtocolInfoAttributeName getName() { return name; } } }