001package jmri.util.prefs; 002 003import java.util.ArrayList; 004import java.util.HashMap; 005import java.util.HashSet; 006import java.util.List; 007import java.util.Set; 008import java.util.stream.Collectors; 009import javax.annotation.Nonnull; 010import jmri.InstanceManager; 011import jmri.beans.Bean; 012import jmri.jmrix.ConnectionConfigManager; 013import jmri.profile.Profile; 014import jmri.spi.PreferencesManager; 015 016/** 017 * An abstract PreferencesManager that implements some of the boilerplate that 018 * PreferencesManager implementations would otherwise require. 019 * 020 * @author Randall Wood (C) 2015 021 */ 022public abstract class AbstractPreferencesManager extends Bean implements PreferencesManager { 023 024 private final HashMap<Profile, Boolean> initialized = new HashMap<>(); 025 private final HashMap<Profile, Boolean> initializing = new HashMap<>(); 026 private final HashMap<Profile, List<Exception>> exceptions = new HashMap<>(); 027 028 /** 029 * {@inheritDoc} 030 */ 031 @Override 032 public boolean isInitialized(Profile profile) { 033 return this.initialized.getOrDefault(profile, false) 034 && this.exceptions.getOrDefault(profile, new ArrayList<>()).isEmpty(); 035 } 036 037 /** 038 * {@inheritDoc} 039 */ 040 @Override 041 public boolean isInitializedWithExceptions(Profile profile) { 042 return this.initialized.getOrDefault(profile, false) 043 && !this.exceptions.getOrDefault(profile, new ArrayList<>()).isEmpty(); 044 } 045 046 /** 047 * {@inheritDoc} 048 */ 049 @Override 050 @Nonnull 051 public List<Exception> getInitializationExceptions(Profile profile) { 052 return new ArrayList<>(this.exceptions.getOrDefault(profile, new ArrayList<>())); 053 } 054 055 /** 056 * Test if the manager is being initialized. 057 * 058 * @param profile the profile against which the manager is being initialized 059 * or null if being initialized for this user regardless of 060 * profile 061 * @return true if being initialized; false otherwise 062 */ 063 protected boolean isInitializing(Profile profile) { 064 return !this.initialized.getOrDefault(profile, false) && this.initializing.getOrDefault(profile, false); 065 } 066 067 /** 068 * {@inheritDoc} 069 * <p> 070 * This implementation includes a default dependency on the 071 * {@link jmri.jmrix.ConnectionConfigManager}. 072 * 073 * @return An set of classes; if there are no dependencies, return an empty 074 * set instead of null; overriding implementations may add to this 075 * set directly 076 */ 077 @Override 078 @Nonnull 079 public Set<Class<? extends PreferencesManager>> getRequires() { 080 Set<Class<? extends PreferencesManager>> requires = new HashSet<>(); 081 requires.add(ConnectionConfigManager.class); 082 return requires; 083 } 084 085 /** 086 * {@inheritDoc} 087 * <p> 088 * This implementation returns the class of the object against which this 089 * method is called. 090 */ 091 @Override 092 @Nonnull 093 public Set<Class<?>> getProvides() { 094 Set<Class<?>> provides = new HashSet<>(); 095 provides.add(this.getClass()); 096 return provides; 097 } 098 099 /** 100 * Set the initialized state for the given profile. Sets 101 * {@link #isInitializing(jmri.profile.Profile)} to false if setting 102 * initialized to false. 103 * 104 * @param profile the profile to set initialized against 105 * @param initialized the initialized state to set 106 */ 107 protected void setInitialized(Profile profile, boolean initialized) { 108 this.initialized.put(profile, initialized); 109 if (initialized) { 110 this.setInitializing(profile, false); 111 } 112 } 113 114 /** 115 * Protect against circular attempts to initialize during initialization. 116 * 117 * @param profile the profile for which initializing is ongoing 118 * @param initializing the initializing state to set 119 */ 120 protected void setInitializing(Profile profile, boolean initializing) { 121 this.initializing.put(profile, initializing); 122 } 123 124 /** 125 * Add an error to the list of exceptions. 126 * 127 * @param profile the profile against which the manager is being 128 * initialized 129 * @param exception the exception to add 130 */ 131 protected void addInitializationException(Profile profile, @Nonnull Exception exception) { 132 if (this.exceptions.get(profile) == null) { 133 this.exceptions.put(profile, new ArrayList<>()); 134 } 135 this.exceptions.get(profile).add(exception); 136 } 137 138 /** 139 * Require that instances of the specified classes have initialized 140 * correctly. This method should only be called from within 141 * {@link #initialize(jmri.profile.Profile)}, generally immediately after 142 * the PreferencesManager verifies that it is not already initialized. If 143 * this method is within a try-catch block, the exception it generates 144 * should be re-thrown by initialize(profile). 145 * 146 * @param profile the profile against which the manager is being initialized 147 * @param classes the manager classes for which all calling 148 * {@link #isInitialized(jmri.profile.Profile)} must return 149 * true against all instances of 150 * @param message the localized message to display if an 151 * InitializationExcpetion is thrown 152 * @throws InitializationException if any instance of any class in classes 153 * returns false on isIntialized(profile) 154 * @throws IllegalArgumentException if any member of classes is not also in 155 * the set of classes returned by 156 * {@link #getRequires()} 157 */ 158 protected void requiresNoInitializedWithExceptions(Profile profile, 159 @Nonnull Set<Class<? extends PreferencesManager>> classes, @Nonnull String message) 160 throws InitializationException { 161 classes.stream().filter((clazz) -> (!this.getRequires().contains(clazz))).forEach((clazz) -> { 162 throw new IllegalArgumentException( 163 "Class " + clazz.getClass().getName() + " not marked as required by " + this.getClass().getName()); 164 }); 165 for (Class<? extends PreferencesManager> clazz : classes) { 166 for (PreferencesManager instance : InstanceManager.getList(clazz)) { 167 if (instance.isInitializedWithExceptions(profile)) { 168 InitializationException exception = new InitializationException("Refusing to initialize", message); 169 this.addInitializationException(profile, exception); 170 this.setInitialized(profile, true); 171 throw exception; 172 } 173 } 174 } 175 } 176 177 /** 178 * Require that instances of the specified classes have initialized 179 * correctly. This method should only be called from within 180 * {@link #initialize(jmri.profile.Profile)}, generally immediately after 181 * the PreferencesManager verifies that it is not already initialized. If 182 * this method is within a try-catch block, the exception it generates 183 * should be re-thrown by initialize(profile). This calls 184 * {@link #requiresNoInitializedWithExceptions(jmri.profile.Profile, java.util.Set, java.lang.String)} 185 * with the result of {@link #getRequires()} as the set of classes to 186 * require. 187 * 188 * @param profile the profile against which the manager is being initialized 189 * @param message the localized message to display if an 190 * InitializationExcpetion is thrown 191 * @throws InitializationException if any instance of any class in classes 192 * returns false on isIntialized(profile) 193 */ 194 protected void requiresNoInitializedWithExceptions(Profile profile, @Nonnull String message) 195 throws InitializationException { 196 this.requiresNoInitializedWithExceptions(profile, this.getRequires(), message); 197 } 198 199 /** 200 * Convenience method to allow a PreferencesManager to require all other 201 * PreferencesManager in an attempt to be the last PreferencesManager 202 * initialized. Use this method as the body of {@link #getRequires()}. 203 * <p> 204 * <strong>Note</strong> given a set of PreferencesManagers using this 205 * method as the body of {@link #getRequires()}, the order in which those 206 * PreferencesManagers are initialized is non-deterministic. 207 * 208 * @return a set of all PreferencesManagers registered with the 209 * InstanceManager except this one 210 */ 211 @Nonnull 212 protected Set<Class<? extends PreferencesManager>> requireAllOther() { 213 return InstanceManager.getList(PreferencesManager.class).stream() 214 .filter(pm -> !pm.equals(this)) 215 .map(pm -> pm.getClass()) 216 .collect(Collectors.toSet()); 217 } 218}