View Javadoc

1   /*
2   Copyright (C) 2000 - 2007 Grid Systems, S.A.
3   
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License, version 2, as
6   published by the Free Software Foundation.
7   
8   This program is distributed in the hope that it will be useful,
9   but WITHOUT ANY WARRANTY; without even the implied warranty of
10  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  GNU General Public License for more details.
12  
13  You should have received a copy of the GNU General Public License
14  along with this program; if not, write to the Free Software
15  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16  */
17  
18  /*
19   * Project: KernelConfigurator
20   * Created on 03-mar-2004
21   *
22   * Copyright (c)2003 Grid Systems
23   */
24  package com.gridsystems.config.tools.console;
25  
26  import java.util.ArrayList;
27  import java.util.HashMap;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.ResourceBundle;
31  import java.util.MissingResourceException;
32  import java.text.MessageFormat;
33  
34  import com.gridsystems.config.Configurator;
35  import com.gridsystems.config.ConsoleConfiguratorView;
36  import com.gridsystems.config.ErrorListener;
37  import com.gridsystems.config.app.UI;
38  
39  /**
40   * Base implementation for console views.
41   *
42   * @author <a href="mailto:rruiz@gridsystems.com">Rodrigo Ruiz Aguayo</a>
43   * @version 1.0
44   */
45  public abstract class AbstractConsoleView implements ConsoleConfiguratorView,
46                                                       ErrorListener {
47    /**
48     * Default console width.
49     */
50    private static final int DEFAULT_CONSOLE_WIDTH = 70;
51  
52    /**
53     * Resource bundle to use for message I18N.
54     */
55    protected ResourceBundle bundle = null;
56  
57    /**
58     * Width of the console view.
59     */
60    protected int width;
61  
62    /**
63     * Form fields.
64     */
65    private Object[] form = null;
66  
67    /**
68     * Registered key mappings.
69     */
70    private Map<String, ConsoleViewAction> keyMappings
71      = new HashMap<String, ConsoleViewAction>();
72  
73    /**
74     * Message to show at the bottom line of the view.
75     */
76    private MessageField messages = new MessageField();
77  
78    /**
79     * The configurator this view is associated to.
80     */
81    private Configurator config;
82  
83    /**
84     * Creates a new instance with default with and the specified Configuration instance.
85     *
86     * @param config The configuration instance
87     */
88    public AbstractConsoleView(Configurator config) {
89      this(config, DEFAULT_CONSOLE_WIDTH);
90    }
91  
92    /**
93     * Creates a new instance.
94     *
95     * @param config The configuration to use
96     * @param width  The view width
97     */
98    public AbstractConsoleView(Configurator config, int width) {
99      this.bundle = config.getBundle();
100     this.width = width;
101     this.config = config;
102     this.config.setErrorListener(this);
103 
104     setApplyDiscardMappings(config);
105   }
106 
107   /**
108    * Registers the default apply and discard actions linked to the specified
109    * configuration instance.
110    *
111    * @param config The configuration instance which actions will affect to
112    */
113   public void setApplyDiscardMappings(Configurator config) {
114     addKeyMapping(new ApplyAction(config));
115     addKeyMapping(new DiscardAction(config));
116   }
117 
118   /**
119    * Gets the configurator this view is associated to.
120    *
121    * @return  The configurator
122    */
123   public Configurator getConfigurator() {
124     return config;
125   }
126 
127   /**
128    * @see com.gridsystems.config.ConsoleConfiguratorView#execute()
129    */
130   public void execute() {
131     Object[] localForm = buildForm();
132     String sPrompt = UI.getString("console.menu.prompt");
133 
134     boolean alive = true;
135     while (alive) {
136       try {
137         paintForm(localForm);
138         System.out.println();
139         String line = ConsoleTools.readLine(sPrompt).toUpperCase();
140         ConsoleViewAction action = (ConsoleViewAction)keyMappings.get(line);
141         if (action != null) {
142           alive = action.execute(this);
143         }
144       } catch (Exception e) {
145         e.printStackTrace();
146       }
147     }
148   }
149 
150   /**
151    * Displays the "view box".
152    *
153    * @param form The contents of the box
154    */
155   protected void paintForm(Object[] form) {
156     ConsoleTools.clear();
157     ConsoleTools.paintBox(getTitle(), form, width);
158 
159     // Resets the error line
160     messages.reset();
161   }
162 
163   /**
164    * Creates the contents of the "view box". It adds an Apply/Discard button bar
165    * to the bottom of the box after calling to {@link #getContents(java.util.List)}
166    *
167    * @return An array of objects conforming the "view box" contents, that can be
168    *         directly used as an argument by the painBox method in ConsoleTools.
169    */
170   protected Object[] buildForm() {
171     if (form == null) {
172       String sApplyText = UI.getString("console.apply.text");
173       String sDiscardText = UI.getString("console.discard.text");
174 
175       ArrayList<Object> items = new ArrayList<Object>();
176       getContents(items);
177       items.add("-");
178       items.add(" [" + sApplyText + "]   [" + sDiscardText + "]");
179 
180       // Place holders for error messages
181       items.add(null);
182       items.add(messages);
183 
184       form = new Object[items.size()];
185       items.toArray(form);
186     }
187     return form;
188   }
189 
190   /**
191    * Registers a string with a ConsoleViewAction instance. The action will
192    * be executed when the user types the key string.
193    *
194    * @param key    The associated command string
195    * @param action The action to execute when key is typed
196    */
197   public void addKeyMapping(String key, ConsoleViewAction action) {
198     keyMappings.put(key.toUpperCase(), action);
199   }
200 
201   /**
202    * Registers action with its own key mapping.
203    *
204    * @param action The action to execute
205    */
206   public void addKeyMapping(ConsoleViewAction action) {
207     addKeyMapping(action.getKeyMapping(), action);
208   }
209 
210   /**
211    * Registers the keymappings of all actions in the specified group field.
212    *
213    * @param group The group field.
214    */
215   public void addKeyMapping(GroupField group) {
216     ConsoleViewAction[] actions = group.getActions();
217     for (int i = 0; i < actions.length; i++) {
218       addKeyMapping(actions[i]);
219     }
220   }
221 
222   /**
223    * Removes the key mapping for the specified action.
224    *
225    * @param action  the action to remove
226    */
227   public void removeKeyMapping(ConsoleViewAction action) {
228     keyMappings.remove(action.getKeyMapping().toUpperCase());
229   }
230 
231   /**
232    * Removes all key mappings in the specified group field.
233    *
234    * @param group The group field
235    */
236   public void removeKeyMapping(GroupField group) {
237     ConsoleViewAction[] actions = group.getActions();
238     for (int i = 0; i < actions.length; i++) {
239       removeKeyMapping(actions[i]);
240     }
241   }
242 
243   /**
244    * Gets an array with the items conforming the contents of the "view box".
245    *
246    * @param list A list where to put the contents of the box
247    */
248   protected abstract void getContents(List<Object> list);
249 
250   /**
251    * Creates a ValueField that will share the same bundle as this instance.
252    *
253    * @param linePattern  The line pattern of the new field
254    * @param valuePattern The value pattern of the new field
255    * @return The field
256    */
257   protected ValueField createValueField(String linePattern, String valuePattern) {
258     return new ValueField(bundle, linePattern, valuePattern);
259   }
260 
261   /**
262    * Creates a SelectionField that will share the same bundle as this instance.
263    *
264    * @param linePattern a String with the line pattern of the new field.
265    * @param valuePattern a String with the value pattern of the new field.
266    * @param values a String[] with the list of valid values of the new field.
267    *
268    * @return the SelectionField created.
269    */
270   protected SelectionField createSelectionField(
271     String linePattern, String valuePattern, String[] values) {
272     return new SelectionField(bundle, linePattern, valuePattern, values);
273   }
274 
275   /**
276    * Creates a BoolField that will share the same bundle as this instance.
277    *
278    * @param linePattern The field line pattern
279    * @return The field
280    */
281   protected BoolField createBoolField(String linePattern) {
282     return new BoolField(bundle, linePattern);
283   }
284 
285   /**
286    * Creates a password field that will share the same bundle as this instance.
287    *
288    * @param linePattern The field line pattern
289    * @return The field
290    */
291   protected PasswordField createPasswordField(String linePattern) {
292     return new PasswordField(bundle, linePattern);
293   }
294 
295   /**
296    * Creates a password field that will share the same bundle as this instance.
297    *
298    * @param linePattern The field line pattern
299    * @param value the value
300    * @return The field
301    */
302   protected ConstantField createConstantField(String linePattern, String value) {
303     return new ConstantField(bundle, linePattern, value);
304   }
305   /**
306    * Gets a localized string, using the found string as a pattern to be used
307    * by a MessageFormat instance.
308    *
309    * @param key    The key for the localized pattern
310    * @param params The parameters for pattern substitution
311    * @return       The localized / formatter string
312    * @throws NullPointerException if the bundle is not set
313    */
314   public String getString(String key, Object[] params) {
315     if (bundle == null) {
316       throw new NullPointerException("Bundle not set");
317     }
318 
319     try {
320       String pattern = bundle.getString(key);
321       return MessageFormat.format(pattern, params);
322     } catch (MissingResourceException e) {
323       return key + "#not found";
324     }
325   }
326 
327   /**
328    * Gets a localized string, using the found string as a pattern to be used
329    * by a MessageFormat instance.
330    *
331    * @param key    The key for the localized pattern
332    * @return       The localized / formatter string
333    * @throws NullPointerException if the bundle is not set
334    */
335   public String getString(String key) {
336     return getString(key, null);
337   }
338 
339   /**
340    * Shows an error message in the bottom line of the view.
341    * <p>
342    * The message is removed on the next view refresh
343    *
344    * @param message The error message lines
345    */
346   public void showError(String[] message) {
347     int count = (message == null) ? 0 : message.length;
348     if (count == 0) {
349       // No message to show
350       messages.reset();
351     } else {
352       String title = UI.getString("messages.error");
353       StringBuffer sb = new StringBuffer();
354 
355       messages.addLine("-");
356       for (int i = 0; i < count; i++) {
357         sb.setLength(0);
358         if (i == 0) {
359           sb.append(title).append(": ");
360         } else {
361           ConsoleTools.fill(sb, ' ', title.length() + 2);
362         }
363         sb.append(message[i]);
364         messages.addLine(sb.toString());
365       }
366     }
367   }
368 
369   /**
370    * Map of current errors in the view.
371    */
372   Map<String, String> errors = new HashMap<String, String>();
373 
374   /**
375    * {@inheritDoc}
376    */
377   public void updateError(String id, String msg, boolean add) {
378     if (add) {
379       System.err.println("ERROR: " + msg);
380       errors.put(id, msg);
381     } else {
382       errors.remove(id);
383     }
384   }
385 
386   /**
387    * {@inheritDoc}
388    */
389   public void clearErrors() {
390     errors.clear();
391   }
392 
393   /**
394    * {@inheritDoc}
395    */
396   public boolean hasErrors() {
397     return this.errors.size() == 0;
398   }
399 }