001package jmri.jmrit.roster;
002
003import java.beans.PropertyChangeEvent;
004import java.io.File;
005import java.io.FileNotFoundException;
006import java.util.HashMap;
007import java.util.Locale;
008import java.util.Set;
009import java.util.prefs.BackingStoreException;
010import java.util.prefs.Preferences;
011import javax.annotation.CheckForNull;
012import javax.annotation.Nonnull;
013import jmri.implementation.FileLocationsPreferences;
014import jmri.profile.Profile;
015import jmri.profile.ProfileManager;
016import jmri.profile.ProfileUtils;
017import jmri.spi.PreferencesManager;
018import jmri.util.FileUtil;
019import jmri.util.prefs.AbstractPreferencesManager;
020import jmri.util.prefs.InitializationException;
021import org.openide.util.lookup.ServiceProvider;
022import org.slf4j.Logger;
023import org.slf4j.LoggerFactory;
024
025/**
026 * Load and store the Roster configuration.
027 *
028 * This only configures the Roster when initialized so that configuration
029 * changes made by users do not affect the running instance of JMRI, but only
030 * take effect after restarting JMRI.
031 *
032 * @author Randall Wood (C) 2015
033 */
034@ServiceProvider(service = PreferencesManager.class)
035public class RosterConfigManager extends AbstractPreferencesManager {
036
037    private final HashMap<Profile, String> directories = new HashMap<>();
038    private final HashMap<Profile, String> defaultOwners = new HashMap<>();
039    private final HashMap<Profile, Roster> rosters = new HashMap<>();
040
041    public static final String DIRECTORY = "directory";
042    public static final String DEFAULT_OWNER = "defaultOwner";
043    private static final Logger log = LoggerFactory.getLogger(RosterConfigManager.class);
044
045    public RosterConfigManager() {
046        log.debug("Roster is {}", this.directories);
047        FileUtil.getDefault().addPropertyChangeListener(FileUtil.PREFERENCES, (PropertyChangeEvent evt) -> {
048            FileUtil.Property oldValue = (FileUtil.Property) evt.getOldValue();
049            FileUtil.Property newValue = (FileUtil.Property) evt.getNewValue();
050            Profile project = oldValue.getKey();
051            log.debug("UserFiles changed from {} to {}", evt.getOldValue(), evt.getNewValue());
052            if (RosterConfigManager.this.getDirectory(project).equals(oldValue.getValue())) {
053                RosterConfigManager.this.setDirectory(project, newValue.getValue());
054            }
055        });
056    }
057
058    @Override
059    public void initialize(Profile profile) throws InitializationException {
060        if (!this.isInitialized(profile)) {
061            Preferences preferences = ProfileUtils.getPreferences(profile, this.getClass(), true);
062            this.setDefaultOwner(profile, preferences.get(DEFAULT_OWNER, this.getDefaultOwner(profile)));
063            try {
064                this.setDirectory(profile, preferences.get(DIRECTORY, this.getDirectory()));
065            } catch (IllegalArgumentException ex) {
066                this.setInitialized(profile, true);
067                String unavailablePath = preferences.get(DIRECTORY, this.getDirectory());
068                log.warn("Roster location unavailable: {}", unavailablePath);
069                throw new RosterLocationUnavailableException(
070                        Bundle.getMessage(Locale.ENGLISH, "IllegalRosterLocation", unavailablePath),
071                        ex.getMessage(),
072                        unavailablePath,
073                        ex);
074            }
075            getRoster(profile).setRosterLocation(this.getDirectory());
076            this.setInitialized(profile, true);
077        }
078    }
079
080    @Override
081    public void savePreferences(Profile profile) {
082        Preferences preferences = ProfileUtils.getPreferences(profile, this.getClass(), true);
083        preferences.put(DIRECTORY, FileUtil.getPortableFilename(this.getDirectory()));
084        preferences.put(DEFAULT_OWNER, this.getDefaultOwner(profile));
085        try {
086            preferences.sync();
087        } catch (BackingStoreException ex) {
088            log.error("Unable to save preferences", ex);
089        }
090    }
091
092    @Override
093    @Nonnull
094    public Set<Class<? extends PreferencesManager>> getRequires() {
095        Set<Class<? extends PreferencesManager>> requires = super.getRequires();
096        requires.add(FileLocationsPreferences.class);
097        return requires;
098    }
099
100    /**
101     * Get the default owner for the active profile.
102     * 
103     * @return the default owner
104     */
105    @Nonnull
106    public String getDefaultOwner() {
107        return getDefaultOwner(ProfileManager.getDefault().getActiveProfile());
108    }
109
110    /**
111     * Get the default owner for the specified profile.
112     * 
113     * @param profile the profile to get the default owner for
114     * @return the default owner
115     */
116    @Nonnull
117    public String getDefaultOwner(@CheckForNull Profile profile) {
118        String owner = defaultOwners.get(profile);
119        // defaultOwner should never be null, but check anyway to ensure its not
120        if (owner == null) {
121            owner = ""; // NOI18N
122            defaultOwners.put(profile, owner);
123        }
124        return owner;
125    }
126
127    /**
128     * Set the default owner for the specified profile.
129     * 
130     * @param profile      the profile to set the default owner for
131     * @param defaultOwner the default owner to set
132     */
133    public void setDefaultOwner(@CheckForNull Profile profile, @CheckForNull String defaultOwner) {
134        if (defaultOwner == null) {
135            defaultOwner = "";
136        }
137        String oldDefaultOwner = this.defaultOwners.get(profile);
138        this.defaultOwners.put(profile, defaultOwner);
139        firePropertyChange(DEFAULT_OWNER, oldDefaultOwner, defaultOwner);
140    }
141
142    /**
143     * Get the roster directory for the active profile.
144     * 
145     * @return the directory
146     */
147    @Nonnull
148    public String getDirectory() {
149        return getDirectory(ProfileManager.getDefault().getActiveProfile());
150    }
151
152    /**
153     * Get the roster directory for the specified profile.
154     * 
155     * @param profile the profile to get the directory for
156     * @return the directory
157     */
158    @Nonnull
159    public String getDirectory(@CheckForNull Profile profile) {
160        String directory = directories.get(profile);
161        if (directory == null) {
162            directory = FileUtil.PREFERENCES;
163        }
164        if (FileUtil.PREFERENCES.equals(directory)) {
165            return FileUtil.getUserFilesPath();
166        }
167        return directory;
168    }
169
170    /**
171     * Set the roster directory for the specified profile.
172     * 
173     * @param profile   the profile to set the directory for
174     * @param directory the directory to set
175     */
176    public void setDirectory(@CheckForNull Profile profile, @CheckForNull String directory) {
177        if (directory == null || directory.isEmpty()) {
178            directory = FileUtil.PREFERENCES;
179        }
180        String oldDirectory = this.directories.get(profile);
181        try {
182            if (!FileUtil.getFile(directory).isDirectory()) {
183                throw new IllegalArgumentException(Bundle.getMessage("IllegalRosterLocation", directory)); // NOI18N
184            }
185        } catch (FileNotFoundException ex) { // thrown by getFile() if directory does not exist
186            throw new IllegalArgumentException(Bundle.getMessage("IllegalRosterLocation", directory)); // NOI18N
187        }
188        if (!directory.equals(FileUtil.PREFERENCES)) {
189            directory = FileUtil.getAbsoluteFilename(directory);
190            if (!directory.endsWith(File.separator)) {
191                directory = directory + File.separator;
192            }
193        }
194        this.directories.put(profile, directory);
195        log.debug("Roster changed from {} to {}", oldDirectory, this.directories);
196        firePropertyChange(DIRECTORY, oldDirectory, directory);
197    }
198
199    /**
200     * Get the roster for the profile.
201     * 
202     * @param profile the profile to get the roster for
203     * @return the roster for the profile
204     */
205    @Nonnull
206    public Roster getRoster(@CheckForNull Profile profile) {
207        Roster roster = rosters.get(profile);
208        if (roster == null) {
209            roster = new Roster();
210            rosters.put(profile, roster);
211        }
212        return roster;
213    }
214
215    /**
216     * Set the roster for the profile.
217     * 
218     * @param profile the profile to set the roster for
219     * @param roster the roster for the profile
220     * @return the roster just set, so this method can be used in a chain
221     */
222    @Nonnull
223    public Roster setRoster(@CheckForNull Profile profile, @Nonnull Roster roster) {
224        rosters.put(profile, roster);
225        return getRoster(profile);
226    }
227}