View Javadoc
1   package fr.ifremer.quadrige2.ui.swing.common;
2   
3   /*-
4    * #%L
5    * Quadrige2 Core :: Quadrige2 UI Common
6    * $Id:$
7    * $HeadURL:$
8    * %%
9    * Copyright (C) 2017 Ifremer
10   * %%
11   * This program is free software: you can redistribute it and/or modify
12   * it under the terms of the GNU Affero General Public License as published by
13   * the Free Software Foundation, either version 3 of the License, or
14   * (at your option) any later version.
15   * 
16   * This program is distributed in the hope that it will be useful,
17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19   * GNU General Public License for more details.
20   * 
21   * You should have received a copy of the GNU Affero General Public License
22   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23   * #L%
24   */
25  
26  import com.google.common.base.Preconditions;
27  import com.google.common.collect.Sets;
28  import fr.ifremer.quadrige2.ui.swing.common.table.state.ExtendedState;
29  import jaxx.runtime.swing.session.State;
30  import org.apache.commons.collections4.MapUtils;
31  import org.apache.commons.io.FileUtils;
32  import org.apache.commons.lang3.StringUtils;
33  import org.apache.commons.logging.Log;
34  import org.apache.commons.logging.LogFactory;
35  
36  import java.awt.Component;
37  import java.io.File;
38  import java.io.IOException;
39  import java.util.Collection;
40  import java.util.Map;
41  import java.util.Set;
42  import java.util.regex.Pattern;
43  
44  /**
45   * Swing session that extends standard swing session to handle components states save/restore but handles a set of component to avoid save and restore action
46   *
47   * @author Ludovic Pecquot <ludovic.pecquot@e-is.pro>
48   */
49  public class SwingSession extends jaxx.runtime.swing.session.SwingSession {
50  
51      private static final Log LOG = LogFactory.getLog(SwingSession.class);
52  
53      protected final Set<Component> unsavedComponents = Sets.newHashSet();
54  
55      protected final Set<String> unsavedRootComponentNames = Sets.newHashSet();
56  
57      private boolean partialSave;
58  
59      /**
60       * <p>newSwingSession.</p>
61       *
62       * @param file             a {@link File} object.
63       * @param autoSave         a boolean.
64       * @param additionalStates a {@link Map} object.
65       * @return a {@link SwingSession} object.
66       */
67      public static SwingSession newSwingSession(File file, boolean autoSave, Map<Class, State> additionalStates) {
68          Preconditions.checkNotNull(file);
69          try {
70              return new SwingSession(file, autoSave, additionalStates);
71          } catch (IOException e) {
72              // reset file
73              if (LOG.isErrorEnabled()) {
74                  LOG.error("Could not read swing session file: " + file, e);
75              }
76              if (file.exists()) {
77  
78                  // try to delete it
79                  try {
80                      FileUtils.forceDelete(file);
81                  } catch (IOException e1) {
82                      throw new RuntimeException("Could not delete file: " + file, e1);
83                  }
84              }
85  
86              try {
87                  return new SwingSession(file, autoSave, additionalStates);
88              } catch (IOException e1) {
89                  throw new RuntimeException("Could create swing session from empty file: " + file, e1);
90              }
91          }
92      }
93  
94      private SwingSession(File file, boolean autoSave, Map<Class, State> additionalStates) throws IOException {
95          super(file, autoSave, additionalStates);
96      }
97  
98      /**
99       * Restore the state for the specified component
100      *
101      * @param component the component to restore
102      */
103     public void restoreState(Component component) {
104         walkThrowComponent("", registeredComponent, new ExtendedRestoreStateAction(component.getName()));
105     }
106 
107     /**
108      * Update the state for the specified component
109      *
110      * @param component    the component to update
111      * @param stateContext a {@link String} object.
112      */
113     public void updateState(Component component, String stateContext) {
114         walkThrowComponent("", registeredComponent, new ExtendedSaveStateAction(component.getName(), stateContext));
115     }
116 
117     /**
118      * Allow a partial save. When save() is called, updateState() is called to update all registered components before write xml file.
119      * With partial save, updateState() will not be called. It allows user to update (and save) some components only to xml file.
120      * for ex:
121      * swingSession.updateState(componentToSave);
122      * swingSession.setPartialSave(true);
123      * swingSession.save();
124      * swingSession.setPartialSave(false);
125      *
126      * @param partialSave a boolean.
127      */
128     public void setPartialSave(boolean partialSave) {
129         this.partialSave = partialSave;
130     }
131 
132     /**
133      * {@inheritDoc}
134      */
135     @Override
136     public void updateState() {
137         if (partialSave) {
138             return;
139         }
140 
141         super.updateState();
142     }
143 
144     /**
145      * {@inheritDoc}
146      * <p>
147      * Make sure we use the correct save and restore actions
148      */
149     @Override
150     protected void walkThrowComponent(String path, Collection<Component> roots, Action action) {
151         if (action instanceof SaveStateAction && !(action instanceof ExtendedSaveStateAction)) {
152             action = new ExtendedSaveStateAction();
153         }
154         if (action instanceof RestoreStateAction && !(action instanceof ExtendedRestoreStateAction)) {
155             action = new ExtendedRestoreStateAction();
156         }
157         super.walkThrowComponent(path, roots, action);
158     }
159 
160     @Override
161     protected String getComponentName(Component c) {
162         String name = c.getName();
163         if (name == null) {
164             int n = c.getParent() == null ? -1 : c.getParent().getComponentZOrder(c);
165             if (n >= 0) {
166                 Class clazz = c.getClass();
167                 name = clazz.getSimpleName();
168                 if (StringUtils.isEmpty(name)) {
169                     name = "Anonymous" + clazz.getSuperclass().getSimpleName();
170                 }
171                 name += n;
172             } else {
173                 LOG.warn("Couldn't compute pathname for " + c);
174             }
175         }
176         return name;
177     }
178 
179     /**
180      * <p>addUnsavedComponent.</p>
181      *
182      * @param unsavedComponent a {@link Component} object.
183      */
184     public void addUnsavedComponent(Component unsavedComponent) {
185         this.unsavedComponents.add(unsavedComponent);
186     }
187 
188     /**
189      * <p>addUnsavedComponents.</p>
190      *
191      * @param unsavedComponents a {@link Set} object.
192      */
193     public void addUnsavedComponents(Set<Component> unsavedComponents) {
194         this.unsavedComponents.addAll(unsavedComponents);
195     }
196 
197     /**
198      * <p>addUnsavedRootComponentByName.</p>
199      *
200      * @param unsavedRootComponentName a {@link String} object.
201      */
202     public void addUnsavedRootComponentByName(String unsavedRootComponentName) {
203         this.unsavedRootComponentNames.add("/" + unsavedRootComponentName);
204     }
205 
206     /**
207      * <p>isComponentToSave.</p>
208      *
209      * @param path a {@link String} object.
210      * @param c    a {@link Component} object.
211      * @return a boolean.
212      */
213     public boolean isComponentToSave(String path, Component c) {
214         if (!unsavedRootComponentNames.isEmpty()) {
215             for (String rootName : unsavedRootComponentNames) {
216                 if (path.startsWith(rootName)) {
217                     return false;
218                 }
219             }
220         }
221         return unsavedComponents.isEmpty() || !unsavedComponents.contains(c);
222     }
223 
224     /**
225      * Return the state of the component by name matching the regex
226      *
227      * @param componentNameRegEx
228      * @return
229      */
230     public State findStateByComponentName(String componentNameRegEx) {
231 
232         if (MapUtils.isNotEmpty(states)) {
233             Pattern p = Pattern.compile(componentNameRegEx);
234             for (String componentPath : states.keySet()) {
235                 if (p.matcher(componentPath).matches()) {
236                     return states.get(componentPath);
237                 }
238             }
239         }
240         return null;
241     }
242 
243     class ExtendedSaveStateAction extends SaveStateAction {
244 
245         private final String componentName;
246         private final String stateContext;
247 
248         public ExtendedSaveStateAction() {
249             this(null);
250         }
251 
252         public ExtendedSaveStateAction(String componentName) {
253             this(componentName, null);
254         }
255 
256         public ExtendedSaveStateAction(String componentName, String stateContext) {
257             this.componentName = componentName;
258             this.stateContext = stateContext;
259         }
260 
261         @Override
262         public void doAction(jaxx.runtime.swing.session.SwingSession session, String path, Component c) {
263             if (session instanceof SwingSession && !((SwingSession) session).isComponentToSave(path, c)) {
264                 // don't save state of specified components
265                 return;
266             }
267 
268             if (componentName != null && !path.endsWith(componentName)) {
269                 // if componentName is specified, save only this component
270                 return;
271             }
272 
273             // don't call super method
274             State manager = session.getStateManager(c.getClass());
275             State state;
276             if (manager instanceof ExtendedState) {
277                 // call getState with the previous state stored in session
278                 state = ((ExtendedState) manager).getState(c, session.getStates(path), stateContext);
279             } else {
280                 state = manager.getState(c);
281             }
282             session.setStates(path, state);
283         }
284     }
285 
286     class ExtendedRestoreStateAction extends RestoreStateAction {
287 
288         private final String componentName;
289 
290         public ExtendedRestoreStateAction(String componentName) {
291             this.componentName = componentName;
292         }
293 
294         public ExtendedRestoreStateAction() {
295             this(null);
296         }
297 
298         @Override
299         public void doAction(jaxx.runtime.swing.session.SwingSession session, String path, Component c) {
300             if (session instanceof SwingSession && !((SwingSession) session).isComponentToSave(path, c)) {
301                 // don't restore state of specified components
302                 return;
303             }
304 
305             if (componentName != null && !path.endsWith(componentName)) {
306                 // if componentName is specified, restore only this component
307                 return;
308             }
309 
310             super.doAction(session, path, c);
311         }
312     }
313 }
314 
315