View Javadoc
1   package fr.ifremer.quadrige3.ui.swing;
2   
3   /*-
4    * #%L
5    * Quadrige3 Core :: Quadrige3 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.collect.Sets;
27  import fr.ifremer.quadrige3.core.dao.technical.Assert;
28  import fr.ifremer.quadrige3.core.exception.QuadrigeTechnicalException;
29  import fr.ifremer.quadrige3.ui.swing.table.state.ExtendedState;
30  import jaxx.runtime.swing.session.State;
31  import org.apache.commons.collections4.MapUtils;
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.nio.file.Files;
40  import java.util.Collection;
41  import java.util.List;
42  import java.util.Map;
43  import java.util.Set;
44  import java.util.regex.Pattern;
45  import java.util.stream.Collectors;
46  import java.util.stream.Stream;
47  
48  /**
49   * 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
50   *
51   * @author Ludovic Pecquot <ludovic.pecquot@e-is.pro>
52   */
53  public class SwingSession extends jaxx.runtime.swing.session.SwingSession {
54  
55      private static final Log LOG = LogFactory.getLog(SwingSession.class);
56  
57      private final Set<Component> unsavedComponents = Sets.newHashSet();
58  
59      private final Set<String> unsavedRootComponentNames = Sets.newHashSet();
60  
61      private boolean partialSave;
62  
63      /**
64       * <p>newSwingSession.</p>
65       *
66       * @param file             a {@link File} object.
67       * @param autoSave         a boolean.
68       * @param additionalStates a {@link Map} object.
69       * @return a {@link SwingSession} object.
70       */
71      public static SwingSession newSwingSession(File file, boolean autoSave, Map<Class, State> additionalStates) {
72          Assert.notNull(file);
73          patchFile(file);
74          return new SwingSession(file, autoSave, additionalStates);
75      }
76  
77      /**
78       * Patch file if some package rename have been done
79       *
80       * @param file
81       */
82      private static void patchFile(File file) {
83  
84          if (!file.exists())
85              return;
86  
87          try (Stream<String> lines = Files.lines(file.toPath())) {
88              List<String> replaced = lines
89                      .map(line-> line.replaceAll("fr.ifremer.quadrige3.ui.swing.common", "fr.ifremer.quadrige3.ui.swing"))
90                      .collect(Collectors.toList());
91              Files.write(file.toPath(), replaced);
92          } catch (IOException ioe) {
93              throw new QuadrigeTechnicalException("something goes wrong when patching file " + file, ioe);
94          }
95  
96      }
97  
98      private SwingSession(File file, boolean autoSave, Map<Class, State> additionalStates) {
99          super(file, autoSave, additionalStates);
100     }
101 
102     /**
103      * Restore the state for the specified component
104      *
105      * @param component the component to restore
106      */
107     public void restoreState(Component component) {
108         walkThrowComponent("", registeredComponent, new ExtendedRestoreStateAction(component.getName()));
109     }
110 
111     /**
112      * Update the state for the specified component
113      *
114      * @param component    the component to update
115      * @param stateContext a {@link String} object.
116      */
117     public void updateState(Component component, String stateContext) {
118         walkThrowComponent("", registeredComponent, new ExtendedSaveStateAction(component.getName(), stateContext));
119     }
120 
121     /**
122      * Allow a partial save. When save() is called, updateState() is called to update all registered components before write xml file.
123      * With partial save, updateState() will not be called. It allows user to update (and save) some components only to xml file.
124      * for ex:
125      * swingSession.updateState(componentToSave);
126      * swingSession.setPartialSave(true);
127      * swingSession.save();
128      * swingSession.setPartialSave(false);
129      *
130      * @param partialSave a boolean.
131      */
132     public void setPartialSave(boolean partialSave) {
133         this.partialSave = partialSave;
134     }
135 
136     /**
137      * {@inheritDoc}
138      */
139     @Override
140     public void updateState() {
141         if (partialSave) {
142             return;
143         }
144 
145         super.updateState();
146     }
147 
148     /**
149      * {@inheritDoc}
150      * <p>
151      * Make sure we use the correct save and restore actions
152      */
153     @Override
154     protected void walkThrowComponent(String path, Collection<Component> roots, Action action) {
155         if (action instanceof SaveStateAction && !(action instanceof ExtendedSaveStateAction)) {
156             action = new ExtendedSaveStateAction();
157         }
158         if (action instanceof RestoreStateAction && !(action instanceof ExtendedRestoreStateAction)) {
159             action = new ExtendedRestoreStateAction();
160         }
161         super.walkThrowComponent(path, roots, action);
162     }
163 
164     @Override
165     protected String getComponentName(Component c) {
166         String name = c.getName();
167         if (name == null) {
168             int n = c.getParent() == null ? -1 : c.getParent().getComponentZOrder(c);
169             if (n >= 0) {
170                 Class clazz = c.getClass();
171                 name = clazz.getSimpleName();
172                 if (StringUtils.isEmpty(name)) {
173                     name = "Anonymous" + clazz.getSuperclass().getSimpleName();
174                 }
175                 name += n;
176             } else {
177                 LOG.warn("Couldn't compute pathname for " + c);
178             }
179         }
180         return name;
181     }
182 
183     /**
184      * <p>addUnsavedComponent.</p>
185      *
186      * @param unsavedComponent a {@link Component} object.
187      */
188     public void addUnsavedComponent(Component unsavedComponent) {
189         this.unsavedComponents.add(unsavedComponent);
190     }
191 
192     /**
193      * <p>addUnsavedComponents.</p>
194      *
195      * @param unsavedComponents a {@link Set} object.
196      */
197     public void addUnsavedComponents(Set<Component> unsavedComponents) {
198         this.unsavedComponents.addAll(unsavedComponents);
199     }
200 
201     /**
202      * <p>addUnsavedRootComponentByName.</p>
203      *
204      * @param unsavedRootComponentName a {@link String} object.
205      */
206     public void addUnsavedRootComponentByName(String unsavedRootComponentName) {
207         this.unsavedRootComponentNames.add("/" + unsavedRootComponentName);
208     }
209 
210     /**
211      * <p>isComponentToSave.</p>
212      *
213      * @param path a {@link String} object.
214      * @param c    a {@link Component} object.
215      * @return a boolean.
216      */
217     public boolean isComponentToSave(String path, Component c) {
218         if (!unsavedRootComponentNames.isEmpty()) {
219             for (String rootName : unsavedRootComponentNames) {
220                 if (path.startsWith(rootName)) {
221                     return false;
222                 }
223             }
224         }
225         return unsavedComponents.isEmpty() || !unsavedComponents.contains(c);
226     }
227 
228     /**
229      * Return the state of the component by name matching the regex
230      *
231      * @param componentNameRegEx component name regex
232      * @return the found state
233      */
234     public State findStateByComponentName(String componentNameRegEx) {
235 
236         if (MapUtils.isNotEmpty(states)) {
237             Pattern p = Pattern.compile(componentNameRegEx);
238             for (String componentPath : states.keySet()) {
239                 if (p.matcher(componentPath).matches()) {
240                     return states.get(componentPath);
241                 }
242             }
243         }
244         return null;
245     }
246 
247     class ExtendedSaveStateAction extends SaveStateAction {
248 
249         private final String componentName;
250         private final String stateContext;
251 
252         public ExtendedSaveStateAction() {
253             this(null);
254         }
255 
256         public ExtendedSaveStateAction(String componentName) {
257             this(componentName, null);
258         }
259 
260         public ExtendedSaveStateAction(String componentName, String stateContext) {
261             this.componentName = componentName;
262             this.stateContext = stateContext;
263         }
264 
265         @Override
266         public void doAction(jaxx.runtime.swing.session.SwingSession session, String path, Component c) {
267             if (session instanceof SwingSession && !((SwingSession) session).isComponentToSave(path, c)) {
268                 // don't save state of specified components
269                 return;
270             }
271 
272             if (componentName != null && !path.endsWith(componentName)) {
273                 // if componentName is specified, save only this component
274                 return;
275             }
276 
277             // don't call super method
278             State manager = session.getStateManager(c.getClass());
279             State state;
280             if (manager instanceof ExtendedState) {
281                 // call getState with the previous state stored in session
282                 state = ((ExtendedState) manager).getState(c, session.getStates(path), stateContext);
283             } else {
284                 state = manager.getState(c);
285             }
286             session.setStates(path, state);
287         }
288     }
289 
290     class ExtendedRestoreStateAction extends RestoreStateAction {
291 
292         private final String componentName;
293 
294         public ExtendedRestoreStateAction(String componentName) {
295             this.componentName = componentName;
296         }
297 
298         public ExtendedRestoreStateAction() {
299             this(null);
300         }
301 
302         @Override
303         public void doAction(jaxx.runtime.swing.session.SwingSession session, String path, Component c) {
304             if (session instanceof SwingSession && !((SwingSession) session).isComponentToSave(path, c)) {
305                 // don't restore state of specified components
306                 return;
307             }
308 
309             if (componentName != null && !path.endsWith(componentName)) {
310                 // if componentName is specified, restore only this component
311                 return;
312             }
313 
314             super.doAction(session, path, c);
315         }
316     }
317 }
318 
319