001package jmri.managers;
002
003import javax.annotation.Nonnull;
004
005import jmri.*;
006
007/**
008 * Implementation of a Manager that can serves as a proxy for multiple
009 * system-specific implementations.
010 * <p>
011 * Automatically includes an Internal system, which need not be separately added
012 * any more.
013 * <p>
014 * Encapsulates access to the "Primary" manager, used by default, which is the
015 * first one provided.
016 * <p>
017 * Internally, this is done by using an ordered list of all non-Internal
018 * managers, plus a separate reference to the internal manager and default
019 * manager.
020 *
021 * @param <E> the supported type of NamedBean
022 * @author Bob Jacobsen      Copyright (C) 2003, 2010, 2018
023 * @author Daniel Bergqvist  Copyright (C) 2020
024 */
025abstract public class AbstractProvidingProxyManager<E extends NamedBean> extends AbstractProxyManager<E> implements ProvidingManager<E> {
026
027    /**
028     * Locate via user name, then system name if needed. If that fails, create a
029     * new NamedBean: If the name is a valid system name, it will be used for
030     * the new NamedBean. Otherwise, the makeSystemName method will attempt to
031     * turn it into a valid system name. Subclasses use this to create provider methods such as
032     * getSensor or getTurnout via casts.
033     *
034     * @param name the user name or system name of the bean
035     * @return an existing or new NamedBean
036     * @throws IllegalArgumentException if name is not usable in a bean
037     */
038    @Nonnull
039    protected E provideNamedBean(String name) throws IllegalArgumentException {
040        // make sure internal present
041        initInternal();
042
043        E t = getNamedBean(name);
044        if (t != null) {
045            return t;
046        }
047        // Doesn't exist. If the systemName was specified, find that system
048        Manager<E> manager = getManager(name);
049        log.trace("{} doesn't exist, make with {}", name, manager);
050        if (manager != null) {
051            return makeBean(manager, name, null);
052        }
053        log.debug("provideNamedBean did not find manager for name {}, defer to default", name); // NOI18N
054        return makeBean(getDefaultManager(), getDefaultManager().makeSystemName(name), null);
055    }
056
057    /**
058     * Return an instance with the specified user or system name. 
059     * <p>
060     * Lookup by UserName, then provide by System Name.
061     * <p>
062     * Note that
063     * two calls with the same arguments will get the same instance; there is
064     * i.e. only one Sensor object representing a given physical sensor and
065     * therefore only one with a specific system or user name.
066     * <p>
067     * This will always return a valid object reference for a valid request; a
068     * new object will be created if necessary. In that case:
069     * <ul>
070     * <li>If a null reference is given for user name, no user name will be
071     * associated with the NamedBean object created; a valid system name must be
072     * provided
073     * <li>If both names are provided, the system name defines the hardware
074     * access of the desired turnout, and the user address is associated with
075     * it.
076     * <li>If a matching UserName is located, that will be returned.
077     * <li>Else If a matching SystemName is located, that will be returned.
078     * <li>Else A New Bean will be created with the given System Name.
079     * The UserName will be added to the New Bean if no existing.
080     * </ul>
081     * Note that it is possible to make an inconsistent request if both
082     * addresses are provided, but the given values are associated with
083     * different objects. This is a problem, and we don't have a good solution
084     * except to issue warnings. This will mostly happen if you're creating
085     * NamedBean when you should be looking them up.
086     * <p>
087     * If the System Name contains the start of a specified Manager, that will be used,
088     * else the default manager will be used.
089     * @see #getManager(java.lang.String)
090     *
091     * @param systemName the system name
092     * @param userName   the user name
093     * @return requested NamedBean object (never null)
094     */
095    @Nonnull
096    public E newNamedBean(@Nonnull String systemName, String userName) throws IllegalArgumentException {
097        // make sure internal present
098        initInternal();
099
100        // if the systemName is specified, find that system
101        Manager<E> m = getManager(systemName);
102        if (m != null) {
103            return makeBean(m, systemName, userName);
104        }
105
106        // did not find a manager, allow it to default to the primary
107        log.debug("Did not find manager for system name {}, delegate to primary", systemName); // NOI18N
108        return makeBean(getDefaultManager(), systemName, userName);
109    }
110
111    /**
112     * Defer creation of the proper type to the subclass.
113     *
114     * @param manager    the manager to invoke
115     * @param systemName the system name
116     * @param userName   the user name
117     * @throws IllegalArgumentException if unable to make.
118     * @return a bean
119     */
120    @Nonnull
121    abstract protected E makeBean(Manager<E> manager,@Nonnull String systemName, String userName) throws IllegalArgumentException;
122
123    // initialize logging
124    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractProvidingProxyManager.class);
125
126}