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                throw new InitializationException(
068                        Bundle.getMessage(Locale.ENGLISH, "IllegalRosterLocation", preferences.get(DIRECTORY, this.getDirectory())),
069                        ex.getMessage(),
070                        ex);
071            }
072            getRoster(profile).setRosterLocation(this.getDirectory());
073            this.setInitialized(profile, true);
074        }
075    }
076
077    @Override
078    public void savePreferences(Profile profile) {
079        Preferences preferences = ProfileUtils.getPreferences(profile, this.getClass(), true);
080        preferences.put(DIRECTORY, FileUtil.getPortableFilename(this.getDirectory()));
081        preferences.put(DEFAULT_OWNER, this.getDefaultOwner(profile));
082        try {
083            preferences.sync();
084        } catch (BackingStoreException ex) {
085            log.error("Unable to save preferences", ex);
086        }
087    }
088
089    @Override
090    @Nonnull
091    public Set<Class<? extends PreferencesManager>> getRequires() {
092        Set<Class<? extends PreferencesManager>> requires = super.getRequires();
093        requires.add(FileLocationsPreferences.class);
094        return requires;
095    }
096
097    /**
098     * Get the default owner for the active profile.
099     * 
100     * @return the default owner
101     */
102    @Nonnull
103    public String getDefaultOwner() {
104        return getDefaultOwner(ProfileManager.getDefault().getActiveProfile());
105    }
106
107    /**
108     * Get the default owner for the specified profile.
109     * 
110     * @param profile the profile to get the default owner for
111     * @return the default owner
112     */
113    @Nonnull
114    public String getDefaultOwner(@CheckForNull Profile profile) {
115        String owner = defaultOwners.get(profile);
116        // defaultOwner should never be null, but check anyway to ensure its not
117        if (owner == null) {
118            owner = ""; // NOI18N
119            defaultOwners.put(profile, owner);
120        }
121        return owner;
122    }
123
124    /**
125     * Set the default owner for the specified profile.
126     * 
127     * @param profile      the profile to set the default owner for
128     * @param defaultOwner the default owner to set
129     */
130    public void setDefaultOwner(@CheckForNull Profile profile, @CheckForNull String defaultOwner) {
131        if (defaultOwner == null) {
132            defaultOwner = "";
133        }
134        String oldDefaultOwner = this.defaultOwners.get(profile);
135        this.defaultOwners.put(profile, defaultOwner);
136        firePropertyChange(DEFAULT_OWNER, oldDefaultOwner, defaultOwner);
137    }
138
139    /**
140     * Get the roster directory for the active profile.
141     * 
142     * @return the directory
143     */
144    @Nonnull
145    public String getDirectory() {
146        return getDirectory(ProfileManager.getDefault().getActiveProfile());
147    }
148
149    /**
150     * Get the roster directory for the specified profile.
151     * 
152     * @param profile the profile to get the directory for
153     * @return the directory
154     */
155    @Nonnull
156    public String getDirectory(@CheckForNull Profile profile) {
157        String directory = directories.get(profile);
158        if (directory == null) {
159            directory = FileUtil.PREFERENCES;
160        }
161        if (FileUtil.PREFERENCES.equals(directory)) {
162            return FileUtil.getUserFilesPath();
163        }
164        return directory;
165    }
166
167    /**
168     * Set the roster directory for the specified profile.
169     * 
170     * @param profile   the profile to set the directory for
171     * @param directory the directory to set
172     */
173    public void setDirectory(@CheckForNull Profile profile, @CheckForNull String directory) {
174        if (directory == null || directory.isEmpty()) {
175            directory = FileUtil.PREFERENCES;
176        }
177        String oldDirectory = this.directories.get(profile);
178        try {
179            if (!FileUtil.getFile(directory).isDirectory()) {
180                throw new IllegalArgumentException(Bundle.getMessage("IllegalRosterLocation", directory)); // NOI18N
181            }
182        } catch (FileNotFoundException ex) { // thrown by getFile() if directory does not exist
183            throw new IllegalArgumentException(Bundle.getMessage("IllegalRosterLocation", directory)); // NOI18N
184        }
185        if (!directory.equals(FileUtil.PREFERENCES)) {
186            directory = FileUtil.getAbsoluteFilename(directory);
187            if (!directory.endsWith(File.separator)) {
188                directory = directory + File.separator;
189            }
190        }
191        this.directories.put(profile, directory);
192        log.debug("Roster changed from {} to {}", oldDirectory, this.directories);
193        firePropertyChange(DIRECTORY, oldDirectory, directory);
194    }
195
196    /**
197     * Get the roster for the profile.
198     * 
199     * @param profile the profile to get the roster for
200     * @return the roster for the profile
201     */
202    @Nonnull
203    public Roster getRoster(@CheckForNull Profile profile) {
204        Roster roster = rosters.get(profile);
205        if (roster == null) {
206            roster = new Roster();
207            rosters.put(profile, roster);
208        }
209        return roster;
210    }
211
212    /**
213     * Set the roster for the profile.
214     * 
215     * @param profile the profile to set the roster for
216     * @param roster the roster for the profile
217     * @return the roster just set, so this method can be used in a chain
218     */
219    @Nonnull
220    public Roster setRoster(@CheckForNull Profile profile, @Nonnull Roster roster) {
221        rosters.put(profile, roster);
222        return getRoster(profile);
223    }
224}