1 /**
2  * Jin - a chess client for internet chess servers.
3  * More information is available at http://www.jinchess.com/.
4  * Copyright (C) 2006 Alexander Maryanovsky.
5  * All rights reserved.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
20  */
21 
22 package free.jin;
23 
24 import java.applet.Applet;
25 import java.awt.*;
26 import java.awt.event.ActionEvent;
27 import java.awt.event.ActionListener;
28 import java.io.*;
29 import java.net.MalformedURLException;
30 import java.net.URL;
31 import java.net.URLConnection;
32 import java.util.*;
33 import java.util.zip.ZipEntry;
34 import java.util.zip.ZipInputStream;
35 
36 import free.chess.BoardImageBoardPainter;
37 import free.chess.ImagePiecePainter;
38 import free.chess.SquareImagesBoardPainter;
39 import free.jin.action.ActionInfo;
40 import free.jin.plugin.Plugin;
41 import free.jin.plugin.PluginInfo;
42 import free.util.*;
43 import free.util.audio.AppletContextAudioPlayer;
44 
45 
46 /**
47  * A <code>JinContext</code> implementation for running Jin as an applet.
48  */
49 
50 public class JinApplet extends Applet implements JinContext{
51 
52 
53 
54   /**
55    * The <code>Locale</code> for this instance of Jin.
56    */
57 
58   private Locale locale;
59 
60 
61 
62   /**
63    * The <code>Localization</code> for this class.
64    */
65 
66   private Localization l10n;
67 
68 
69 
70   /**
71    * Autologin parameters for Jin. They override any applet parameters in
72    * <code>getParameter</code>.
73    */
74 
75   private final Properties autologinParams = new Properties();
76 
77 
78 
79   /**
80    * The server we're connecting to.
81    */
82 
83   private Server server;
84 
85 
86 
87   /**
88    * The actions we'll be using.
89    */
90 
91   private ActionInfo [] actions;
92 
93 
94 
95   /**
96    * The plugins we'll be using.
97    */
98 
99   private PluginInfo [] plugins;
100 
101 
102 
103   /**
104    * The preferences, created after authenticating the user.
105    */
106 
107   private Preferences prefs;
108 
109 
110 
111   /**
112    * The list of known accounts, created after authenticating the user.
113    */
114 
115   private User [] users;
116 
117 
118 
119   /**
120    * The username with which we authenticated the user.
121    */
122 
123   private String username;
124 
125 
126 
127   /**
128    * The password with which we authenticated the user.
129    */
130 
131   private String password;
132 
133 
134 
135   /**
136    * Initializes the applet.
137    */
138 
init()139   public void init(){
140     try{
141       // Determine the locale
142       locale = determineLocale();
143 
144       // Configure the libraries the applet depends on
145       configureLibraries();
146 
147       // Load localization
148       l10n = Localization.load(JinApplet.class);
149 
150       // Load the server we'll be connecting to
151       server = loadServer();
152 
153       // Load the actions we'll be using.
154       actions = loadActions();
155 
156       // Load the plugins we'll be running.
157       plugins = loadPlugins();
158 
159 
160       // Set the background color
161       String bgColorString = getParameter("bgcolor");
162       if (bgColorString != null)
163         setBackground(new Color(0xff000000 | Integer.parseInt(bgColorString, 16)));
164 
165       setLayout(new FlowLayout());
166       add(new UserAuthPanel());
167     } catch (Throwable t){
168         createErrorUI(t);
169       }
170   }
171 
172 
173 
174   /**
175    * Restarts JinApplet. This is called when the user closes Jin and is supposed
176    * to bring the applet to its initial state - ready to accept a new username
177    * and password.
178    */
179 
restart()180   private void restart(){
181     try{
182       username = null;
183       password = null;
184 
185       removeAll();
186       autologinParams.clear();
187 
188       // The server needs to be recreated because the user might use a different
189       // account after restarting Jin, which means a different guest User.
190       server = loadServer();
191 
192       add(new UserAuthPanel());
193       validate();
194     } catch (Throwable t){
195         createErrorUI(t);
196       }
197   }
198 
199 
200 
201   /**
202    * Starts Jin with the specified application <code>Preferences</code> and list
203    * of known users. Autologin parameters are set to login with the specified
204    * username and password.
205    */
206 
start(Preferences prefs, User guestUser, User [] users, String username, String password)207   private void start(Preferences prefs, User guestUser, User [] users, String username, String password){
208     this.username = username;
209     this.password = password;
210     this.prefs = prefs;
211     this.users = users;
212 
213     server.setGuestUser(guestUser);
214 
215     autologinParams.put("login.username", username);
216     autologinParams.put("login.password", password);
217     autologinParams.put("autologin", "true");
218 
219     Jin.createInstance(this);
220     Jin.getInstance().start();
221   }
222 
223 
224 
225   /**
226    * Starts Jin and logs on as a guest.
227    */
228 
startAsGuest()229   private void startAsGuest(){
230     this.username = null;
231     this.password = null;
232     this.prefs = Preferences.createNew();
233     this.users = new User[0];
234 
235     server.setGuestUser(null);
236 
237     autologinParams.put("login.guest", "true");
238     autologinParams.put("autologin", "true");
239 
240     Jin.createInstance(this);
241     Jin.getInstance().start();
242   }
243 
244 
245 
246   /**
247    * Determines the locale for this instance of Jin.
248    */
249 
determineLocale()250   private Locale determineLocale(){
251     String language = getParameter("locale.language");
252     String country = getParameter("locale.country");
253     String variant = getParameter("locale.variant");
254 
255     language = language == null ? "" : language;
256     country = country == null ? "" : country;
257     variant = variant == null ? "" : variant;
258 
259     return new Locale(language, country, variant);
260   }
261 
262 
263 
264   /**
265    * Configures various libraries the applet uses.
266    */
267 
configureLibraries()268   private void configureLibraries(){
269     Localization.setAppLocale(locale);
270     BrowserControl.setAppletContext(getAppletContext());
271     AppletContextAudioPlayer.setAppletContext(getAppletContext());
272     ImagePiecePainter.setAsyncImageLoad(true);
273     BoardImageBoardPainter.setAsyncImageLoad(true);
274     SquareImagesBoardPainter.setAsyncImageLoad(true);
275   }
276 
277 
278 
279   /**
280    * Creates and returns the <code>Server</code> object for the server we'll be
281    * connecting to.
282    */
283 
loadServer()284   private Server loadServer() throws ClassNotFoundException,
285       InstantiationException, IllegalAccessException{
286 
287     String className = getParameter("server.classname");
288     if (className == null)
289       throw new IllegalStateException("No server.classname parameter specified");
290 
291     Server server = (Server)Class.forName(className).newInstance();
292 
293     server.setHost(getDocumentBase().getHost());
294 
295     String portString = getParameter("port");
296     if (portString != null)
297       server.setPort(Integer.parseInt(portString));
298 
299     return server;
300   }
301 
302 
303 
304   /**
305    * Loads the actions we'll be using.
306    */
307 
loadActions()308   private ActionInfo [] loadActions() throws IOException, ClassNotFoundException{
309     String actionsCount = getParameter("actions.count");
310     if (actionsCount == null)
311       throw new IllegalStateException("No actions.count parameter specified");
312 
313     ActionInfo [] actions = new ActionInfo[Integer.parseInt(actionsCount)];
314 
315     for (int i = 0; i < actions.length; i++){
316       String className = getParameter("actions." + i + ".classname");
317       if (className == null)
318         throw new IllegalStateException("Missing classname for action No. " + i);
319 
320       // See the long comment about the definition file in loadPlugins
321 
322       Class actionClass = Class.forName(className);
323 
324       InputStream actionPrefsIn = actionClass.getResourceAsStream("preferences");
325       Preferences actionPrefs = (actionPrefsIn == null ? Preferences.createNew() : Preferences.load(actionPrefsIn));
326 
327       if (actionPrefsIn != null)
328         actionPrefsIn.close();
329 
330       actions[i] = new ActionInfo(actionClass, actionPrefs);
331     }
332 
333     return actions;
334   }
335 
336 
337 
338   /**
339    * Loads the plugins we'll be using.
340    */
341 
loadPlugins()342   private PluginInfo [] loadPlugins() throws IOException, ClassNotFoundException{
343     String pluginsCount = getParameter("plugins.count");
344     if (pluginsCount == null)
345       throw new IllegalStateException("No plugins.count parameter specified");
346 
347     PluginInfo [] plugins = new PluginInfo[Integer.parseInt(pluginsCount)];
348 
349     for (int i = 0; i < plugins.length; i++){
350       String className = getParameter("plugins." + i + ".classname");
351       if (className == null)
352         throw new IllegalStateException("Missing classname for plugin No. " + i);
353 
354       // We should actually read the definition file here.
355       // Currently (30.07.2004) the definition file only contains the class name
356       // of the plugin, so we don't really need it, but it might contain
357       // additional information in the future, which we might need to read.
358       // It seems that we can't access the definition file when running as an
359       // applet because Class.getResourceAsStream("/definition") will return
360       // the first "definition" file it sees on the applet classpath (as
361       // specified by the ARCHIVE tag.
362 
363       Class pluginClass = Class.forName(className);
364 
365       InputStream pluginPrefsIn = pluginClass.getResourceAsStream("preferences");
366       Preferences pluginPrefs = (pluginPrefsIn == null ? Preferences.createNew() : Preferences.load(pluginPrefsIn));
367 
368       if (pluginPrefsIn != null)
369         pluginPrefsIn.close();
370 
371       plugins[i] = new PluginInfo(pluginClass, pluginPrefs);
372     }
373 
374     return plugins;
375   }
376 
377 
378 
379   /**
380    * Returns the locale for this instance of Jin.
381    */
382 
getLocale()383   public Locale getLocale(){
384     return locale;
385   }
386 
387 
388 
389   /**
390    * Overrides the <code>getParameter</code> method to allow us to add our own
391    * parameters, via the props Properties object.
392    */
393 
getParameter(String paramName)394   public String getParameter(String paramName){
395     String paramValue = autologinParams.getProperty(paramName);
396 
397     return paramValue == null ? super.getParameter(paramName) : paramValue;
398   }
399 
400 
401 
402   /**
403    * Returns the application-wide preferences.
404    */
405 
getPrefs()406   public Preferences getPrefs(){
407     return prefs;
408   }
409 
410 
411 
412   /**
413    * Returns all the resources of the specified type.
414    * See {@link JinContext#getResources(String, Plugin)} for more information.
415    */
416 
getResources(String resourceType, Plugin plugin)417   public Map getResources(String resourceType, Plugin plugin){
418     Map resourceMap = new HashMap();
419 
420     try{
421       URL codeBase = getCodeBase();
422       URL definitionsFileURL = new URL(codeBase, "resources/" + resourceType + "/definitions.zip");
423       if (!IOUtilities.isURLCached(definitionsFileURL))
424         IOUtilities.cacheURL(definitionsFileURL);
425       ZipInputStream zip = new ZipInputStream(IOUtilities.inputStreamForURL(definitionsFileURL, true));
426       ZipEntry entry;
427       while ((entry = zip.getNextEntry()) != null){
428         String entryName = entry.getName();
429         if (!entryName.endsWith("definition") || entry.isDirectory())
430           continue;
431         String resourcePath = entryName.substring(0, entryName.length() - "/definition".length());
432         byte [] data = IOUtilities.readToEnd(zip);
433         URL resourceURL = new URL(codeBase, "resources/" + resourceType + "/" + resourcePath + "/");
434         IOUtilities.cacheData(new URL(resourceURL, "definition"), data);
435         Resource resource = loadResource(resourceURL, plugin);
436         if (resource != null)
437           resourceMap.put(resource.getId(), resource);
438       }
439     } catch (IOException e){
440         e.printStackTrace();
441       }
442 
443     return resourceMap;
444   }
445 
446 
447 
448   /**
449    * Returns the resource with the specified type and id, or <code>null</code>
450    * if the resource can't be loaded.
451    */
452 
getResource(String type, String id, Plugin plugin)453   public Resource getResource(String type, String id, Plugin plugin){
454     try{
455       URL resourceURL = new URL(getCodeBase(), "resources/" + type + "/" + id + "/");
456       return loadResource(resourceURL, plugin);
457     } catch (IOException e){
458         e.printStackTrace();
459         return null;
460       }
461   }
462 
463 
464 
465 
466   /**
467    * Loads a single resource from the specified URL. Returns <code>null</code>
468    * if unsuccessful.
469    */
470 
loadResource(URL url, Plugin plugin)471   private Resource loadResource(URL url, Plugin plugin) throws IOException{
472     URL defURL = new URL(url, "definition");
473     if (!IOUtilities.isURLCached(defURL))
474       IOUtilities.cacheURL(defURL);
475 
476     Properties def = IOUtilities.loadProperties(defURL, true);
477     String classname = def.getProperty("classname");
478     if (classname == null)
479       return null;
480 
481     try{
482       // We need to load it with the plugin's classloader because the
483       // resource may be of a type which is a part of the plugin.
484       Class resourceClass = plugin.getClass().getClassLoader().loadClass(classname);
485       Resource resource = (Resource)resourceClass.newInstance();
486       if (resource.load(url, plugin))
487         return resource;
488       else
489         return null;
490     } catch (ClassNotFoundException e){e.printStackTrace(); return null;}
491       catch (InstantiationException e){e.printStackTrace(); return null;}
492       catch (IllegalAccessException e){e.printStackTrace(); return null;}
493   }
494 
495 
496 
497   /**
498    * The thread that uploads the user settings. <code>null</code> when none.
499    */
500 
501   private Thread settingsUploadThread = null;
502 
503 
504 
505   /**
506    * The dialog displayed to the user while settings are uploaded.
507    */
508 
509   private Dialog settingsUploadDialog = null;
510 
511 
512 
513   /**
514    * Uploads preferences and reinitializes the applet, so that it's ready to
515    * go again.
516    */
517 
shutdown()518   public void shutdown(){
519     if (username != null){ // Not logged in as guest
520 
521       settingsUploadThread = new Thread(){
522         public void run(){
523           try{
524             String result = null;
525             while (!"OK".equals(result = uploadSettings())){
526               // Hackish, I know
527               if (result.toLowerCase().indexOf("password") != -1){
528                 if (!showPasswordDialog(result))
529                   break;
530               }
531               else
532                 showErrorDialog(result);
533             }
534           } catch (IOException e){
535               ByteArrayOutputStream buf = new ByteArrayOutputStream();
536               e.printStackTrace(new PrintStream(buf));
537               showErrorDialog(buf.toString());
538             }
539             finally{
540               synchronized(JinApplet.this){
541                 if (settingsUploadThread == Thread.currentThread()){
542                   settingsUploadThread = null;
543                   if (settingsUploadDialog != null){
544                     settingsUploadDialog.dispose();
545                     settingsUploadDialog = null;
546                   }
547                 }
548               }
549             }
550         }
551 
552         private void showErrorDialog(String message){
553           Dialog errorDialog =
554             new SettingsUploadErrorDialog(AWTUtilities.frameForComponent(JinApplet.this), message);
555           AWTUtilities.centerWindow(errorDialog, JinApplet.this);
556           errorDialog.setVisible(true);
557         }
558 
559         /**
560          * Shows a password dialog and returns whether preferences upload should
561          * be retried.
562          */
563 
564         private boolean showPasswordDialog(String message){
565           PasswordDialog passDialog =
566             new PasswordDialog(AWTUtilities.frameForComponent(JinApplet.this), message, username);
567           AWTUtilities.centerWindow(passDialog, JinApplet.this);
568 
569           boolean result = passDialog.shouldRetry();
570 
571           if (result){
572             username = passDialog.getUsername();
573             password = passDialog.getPassword();
574           }
575           return result;
576         }
577       };
578 
579       settingsUploadDialog = new SettingsUploadDialog(AWTUtilities.frameForComponent(this)){
580         public void addNotify(){
581           super.addNotify();
582           if ((settingsUploadThread != null) && !settingsUploadThread.isAlive())
583             settingsUploadThread.start();
584         }
585         public void canceled(){
586           synchronized(JinApplet.this){
587             this.dispose();
588           }
589         }
590       };
591 
592       AWTUtilities.centerWindow(settingsUploadDialog, this);
593       settingsUploadDialog.setVisible(true);
594     }
595 
596     restart();
597   }
598 
599 
600 
601 
602   /**
603    * Stores the user settings.
604    */
605 
uploadSettings()606   private String uploadSettings() throws IOException{
607     URL savePrefsUrl = getPrefsUploadUrl();
608     URLConnection conn = savePrefsUrl.openConnection();
609     conn.setDoOutput(true);
610     conn.setRequestProperty("Content-type", "application/binary");
611 
612     DataOutputStream out = new DataOutputStream(new BufferedOutputStream(conn.getOutputStream()));
613     out.writeBytes(username + "\n");
614     out.writeBytes(password + "\n");
615 
616 
617     // Write application-wide prefs
618     ByteArrayOutputStream buf = new ByteArrayOutputStream();
619     prefs.save(buf);
620     out.writeInt(buf.size());
621     buf.writeTo(out);
622 
623     // Write guest
624     writeUser(out, server.getGuest());
625 
626     // Write users
627     out.writeInt(users.length);
628     for (int i = 0; i < users.length; i++)
629       writeUser(out, users[i]);
630 
631     out.writeBytes("PREFS_UPLOAD_END");
632 
633     out.close();
634 
635     conn.connect();
636 
637     buf.reset();
638     IOUtilities.pump(conn.getInputStream(), buf);
639     String result = new String(buf.toByteArray());
640     return result;
641   }
642 
643 
644 
645   /**
646    * Writes (for storage purposes) the information about the specified user into
647    * the specified output stream.
648    */
649 
writeUser(DataOutputStream out, User user)650   private void writeUser(DataOutputStream out, User user) throws IOException{
651     out.writeUTF(user.getUsername());
652 
653     ByteArrayOutputStream buf = new ByteArrayOutputStream();
654     user.getPrefs().save(buf);
655     out.writeInt(buf.size());
656     buf.writeTo(out);
657 
658     // Todo: add storing user files
659   }
660 
661 
662 
663   /**
664    * Returns an array containing the server we're connecting to.
665    */
666 
getServers()667   public Server [] getServers(){
668     return new Server[]{server};
669   }
670 
671 
672 
673   /**
674    * Returns the list of known user's accounts on the server.
675    */
676 
getUsers()677   public User [] getUsers(){
678     return users;
679   }
680 
681 
682 
683   /**
684    * Sets the list of known user accounts, so that they can be uploaded as a
685    * part of the preferences.
686    */
687 
setUsers(User [] users)688   public void setUsers(User [] users){
689     this.users = users;
690   }
691 
692 
693 
694   /**
695    * Returns the descriptions of actions for the specified server.
696    */
697 
getActions(Server server)698   public ActionInfo [] getActions(Server server){
699     if (server != this.server)
700       throw new IllegalArgumentException("Unknown server: " + server);
701 
702     return actions;
703   }
704 
705 
706 
707   /**
708    * Returns the descriptions of plugins for the specified server.
709    */
710 
getPlugins(Server server)711   public PluginInfo [] getPlugins(Server server){
712     if (server != this.server)
713       throw new IllegalArgumentException("Unknown server: " + server);
714 
715     return plugins;
716   }
717 
718 
719 
720   /**
721    * Returns <code>true</code>.
722    */
723 
isSavePrefsCapable()724   public boolean isSavePrefsCapable(){
725     return true;
726   }
727 
728 
729 
730   /**
731    * Returns text warning the user about saving his password and asking him to
732    * confirm it.
733    */
734 
getPasswordSaveWarning()735   public String getPasswordSaveWarning(){
736     try{
737       boolean isSecure = getPrefsDownloadUrl().getProtocol().equals("https") &&
738                          getPrefsUploadUrl().getProtocol().equals("https");
739 
740       if (isSecure)
741         return null;
742       else
743         return l10n.getString("passwordSaveWarning");
744     } catch (MalformedURLException e){
745         e.printStackTrace();
746         return "Error: Your password will not be stored due to a configuration problem.";
747       }
748   }
749 
750 
751 
752   /**
753    * Returns <code>false</code>.
754    */
755 
isUserExtensible()756   public boolean isUserExtensible(){
757     return false;
758   }
759 
760 
761 
762   /**
763    * Creates UI which informs the user that the specified error has occurred.
764    */
765 
createErrorUI(Throwable t)766   private void createErrorUI(Throwable t){
767     removeAll();
768 
769     setLayout(new BorderLayout());
770 
771     add(new Label(l10n.getString("errorLabel.text")), BorderLayout.NORTH);
772 
773     ByteArrayOutputStream buf = new ByteArrayOutputStream();
774     t.printStackTrace(new PrintStream(buf));
775     TextArea stackTraceArea = new TextArea(buf.toString());
776 
777     add(stackTraceArea, BorderLayout.CENTER);
778     doLayout();
779   }
780 
781 
782 
783   /**
784    * Returns the URL from which we download user settings.
785    */
786 
getPrefsDownloadUrl()787   private URL getPrefsDownloadUrl() throws MalformedURLException{
788     URL url = new URL(getDocumentBase(), getParameter("loadPrefsURL"));
789     return new URL(getParameter("prefsProtocol"), url.getHost(), url.getPort(), url.getFile());
790   }
791 
792 
793 
794   /**
795    * Returns the URL to which we upload user settings.
796    */
797 
getPrefsUploadUrl()798   private URL getPrefsUploadUrl() throws MalformedURLException{
799     URL url = new URL(getDocumentBase(), getParameter("savePrefsURL"));
800     return new URL(getParameter("prefsProtocol"), url.getHost(), url.getPort(), url.getFile());
801   }
802 
803 
804 
805   /**
806    * A panel which asks the user to specify his username and password. When
807    * the user submits those, the settings for that user are retrieved and
808    * the <code>start(Preferences, User, User [], String, String)</code> method
809    * of <code>JinApplet</code> is invoked. If the user chooses to continue as
810    * guest, the <code>startAsGuest</code> method of <code>JinApplet</code> is
811    * invoked.
812    */
813 
814   private class UserAuthPanel extends Panel implements Runnable{
815 
816 
817 
818     /**
819      * The username text field.
820      */
821 
822     private final TextField usernameField;
823 
824 
825 
826     /**
827      * The password field.
828      */
829 
830     private final TextField passwordField;
831 
832 
833 
834     /**
835      * The status label.
836      */
837 
838     private final Label statusLabel;
839 
840 
841 
842     /**
843      * The login button.
844      */
845 
846     private Button loginButton;
847 
848 
849 
850     /**
851      * The "login as guest" button.
852      */
853 
854     private Button guestButton;
855 
856 
857 
858     /**
859      * The thread authenticating the user and retrieving his settings.
860      */
861 
862     private Thread authThread = null;
863 
864 
865 
866     /**
867      * Creates a new <code>UserAuthPanel</code>.
868      */
869 
UserAuthPanel()870     public UserAuthPanel(){
871       usernameField = new TextField(20);
872       passwordField = new TextField(20);
873       statusLabel = new Label();
874 
875       passwordField.setEchoChar('*');
876 
877       createUI();
878     }
879 
880 
881 
882     /**
883      * Sets the status to the specified value (sets the status label).
884      */
885 
setStatus(String status, Color color)886     private void setStatus(String status, Color color){
887       statusLabel.setForeground(color);
888       statusLabel.setText(status);
889     }
890 
891 
892 
893     /**
894      * Builds the ui of this panel.
895      */
896 
createUI()897     private void createUI(){
898       this.setLayout(new BorderLayout(15, 15));
899       this.add(BorderLayout.NORTH,
900         new Label(l10n.getString("instructionsLabel.text")));
901 
902       Panel userInfoPanel = new Panel(new BorderLayout());
903       Panel labelsPanel = new Panel(new GridLayout(2, 1, 10, 10));
904       Panel textFieldsPanel = new Panel(new GridLayout(2, 1, 10, 10));
905 
906       labelsPanel.add(new Label(l10n.getString("usernameLabel.text")));
907       textFieldsPanel.add(usernameField);
908 
909       labelsPanel.add(new Label(l10n.getString("passwordLabel.text")));
910       textFieldsPanel.add(passwordField);
911 
912       userInfoPanel.add(BorderLayout.WEST, labelsPanel);
913 
914       Panel textFieldsWrapperPanel = new Panel(new BorderLayout());
915       textFieldsWrapperPanel.add(BorderLayout.WEST, textFieldsPanel);
916       userInfoPanel.add(BorderLayout.CENTER, textFieldsWrapperPanel);
917 
918       this.add(BorderLayout.CENTER, userInfoPanel);
919 
920       Panel statusAndButtonsPanel = new Panel(new GridLayout(2, 1));
921 
922       statusAndButtonsPanel.add(statusLabel);
923 
924       loginButton = new Button(l10n.getString("loginButton.text"));
925       guestButton = new Button(l10n.getString("loginAsGuestButton.text"));
926 
927       Panel buttonsPanel = new Panel(new FlowLayout(FlowLayout.CENTER));
928       buttonsPanel.add(loginButton);
929       buttonsPanel.add(guestButton);
930       statusAndButtonsPanel.add(buttonsPanel);
931 
932       this.add(BorderLayout.SOUTH, statusAndButtonsPanel);
933 
934       ActionListener loginListener = new ActionListener(){
935         public void actionPerformed(ActionEvent evt){
936           String username = usernameField.getText();
937           String password = passwordField.getText();
938 
939           if ((username == null) || "".equals(username)){
940             setStatus(l10n.getString("usernameUnspecifiedError"), Color.red);
941             usernameField.requestFocus();
942           }
943           else if ((password == null) || "".equals(password)){
944             setStatus(l10n.getString("passwordUnspecifiedError"), Color.red);
945             passwordField.requestFocus();
946           }
947           else{
948             loginButton.setEnabled(false);
949             guestButton.setEnabled(false);
950 
951             retrievePrefs();
952           }
953         }
954       };
955       loginButton.addActionListener(loginListener);
956       // We do this to imitate a "default" button
957       usernameField.addActionListener(loginListener);
958       passwordField.addActionListener(loginListener);
959 
960       guestButton.addActionListener(new ActionListener(){
961         public void actionPerformed(ActionEvent evt){
962           setStatus(l10n.getString("startingJinStatus"), Color.black);
963           loginButton.setEnabled(false);
964           guestButton.setEnabled(false);
965           startAsGuest();
966           setStatus(l10n.getString("stayOnPageStatus"), Color.black);
967         }
968       });
969     }
970 
971 
972 
973     /**
974      * Have we been painted already?
975      */
976 
977     private boolean isPainted = false;
978 
979 
980 
981     /**
982      * Set focus to the username field on the first paint.
983      */
984 
paint(Graphics g)985     public void paint(Graphics g){
986       super.paint(g);
987 
988       if (!isPainted){
989         isPainted = true;
990         usernameField.requestFocus();
991       }
992     }
993 
994 
995 
996     /**
997      * Authenticates the user and retrieves the preferences from the server.
998      */
999 
retrievePrefs()1000     private synchronized void retrievePrefs(){
1001       if (authThread == null){
1002         authThread = new Thread(this);
1003         authThread.start();
1004       }
1005     }
1006 
1007 
1008 
1009     /**
1010      * Connects to the server and retrieves the preferences.
1011      */
1012 
run()1013     public void run(){
1014       try{
1015         String username = usernameField.getText();
1016         String password = passwordField.getText();
1017 
1018         setStatus(l10n.getString("connectingStatus"), Color.black);
1019 
1020         URL loadPrefsUrl = getPrefsDownloadUrl();
1021         URLConnection conn = loadPrefsUrl.openConnection();
1022         conn.setDoOutput(true);
1023         conn.setRequestProperty("Content-type", "application/binary");
1024         DataOutputStream out = new DataOutputStream(conn.getOutputStream());
1025         out.writeBytes(username + "\n");
1026         out.writeBytes(password + "\n");
1027 
1028         out.close();
1029 
1030         conn.connect();
1031 
1032         setStatus(l10n.getString("authenticatingStatus"), Color.black);
1033 
1034         InputStream in = new BufferedInputStream(conn.getInputStream());
1035         DataInputStream dataIn = new DataInputStream(in);
1036 
1037         Preferences prefs;
1038         User guest;
1039         User [] users;
1040 
1041         // Read return code
1042         String returnCode = dataIn.readLine();
1043 
1044         if ("OK".equals(returnCode)){
1045           setStatus(l10n.getString("retrievingPreferencesStatus"), Color.black);
1046 
1047           // Read application preferences
1048           int appPrefsLength = dataIn.readInt();
1049           prefs = Preferences.load(new ByteArrayInputStream(IOUtilities.read(in, appPrefsLength)));
1050 
1051           // Read guest user
1052           guest = loadUser(dataIn);
1053 
1054           // Number of known users
1055           int usersCount = dataIn.readInt();
1056           users = new User[usersCount];
1057 
1058           for (int i = 0; i < users.length; i++)
1059             users[i] = loadUser(dataIn);
1060         } else if ("NOPREFS".equals(returnCode)){ // A new user
1061           prefs = Preferences.createNew();
1062           guest = null;
1063           users = new User[0];
1064         }
1065         else{ // An error
1066           setStatus(returnCode, Color.red);
1067           System.out.println(returnCode);
1068           IOUtilities.pump(in, System.out);
1069 
1070           loginButton.setEnabled(true);
1071           guestButton.setEnabled(true);
1072           return;
1073         }
1074 
1075         setStatus(l10n.getString("startingJinStatus"), Color.black);
1076 
1077         start(prefs, guest, users, username, password);
1078 
1079         setStatus(l10n.getString("stayOnPageStatus"), Color.black);
1080       } catch (IOException e){
1081           e.printStackTrace();
1082           createErrorUI(e);
1083         }
1084         catch (RuntimeException e){
1085           e.printStackTrace();
1086           createErrorUI(e);
1087         }
1088         finally{
1089           synchronized(this){
1090             authThread = null;
1091           }
1092         }
1093     }
1094 
1095 
1096 
1097     /**
1098      * Creates a User from the specified <code>InputStream</code>.
1099      */
1100 
loadUser(DataInputStream in)1101     private User loadUser(DataInputStream in) throws IOException{
1102       String username = in.readUTF();
1103       int prefsLength = in.readInt();
1104       Preferences prefs = Preferences.load(new ByteArrayInputStream(IOUtilities.read(in, prefsLength)));
1105       Hashtable files = new Hashtable(); // TODO: Add loading/saving files
1106 
1107       return new User(server, username, prefs, files);
1108     }
1109 
1110 
1111   }
1112 
1113 
1114 
1115   /**
1116    * A dialog which is displayed while the user settings are uploaded.
1117    */
1118 
1119   private abstract class SettingsUploadDialog extends Dialog{
1120 
1121 
1122 
1123     /**
1124      * Creates a new <code>SettingsUploadDialog</code> with the specified
1125      * parent Frame.
1126      */
1127 
SettingsUploadDialog(Frame parent)1128     public SettingsUploadDialog(Frame parent){
1129       super(parent, "", true);
1130 
1131       setTitle(l10n.getString("prefsUploadDialog.title"));
1132 
1133       this.setLayout(new GridLayout(2, 1));
1134 
1135       this.add(new Label(l10n.getString("prefsUploadDialog.message")));
1136 
1137       Button button = new Button(l10n.getString("prefsUploadDialog.cancelButton.text"));
1138       Panel buttonPanel = new Panel(new FlowLayout());
1139       buttonPanel.add(button);
1140       this.add(buttonPanel);
1141 
1142       button.addActionListener(new ActionListener(){
1143         public void actionPerformed(ActionEvent evt){
1144           canceled();
1145         }
1146       });
1147     }
1148 
1149 
1150 
1151     /**
1152      * Gets called when the user pressed the "cancel" button.
1153      */
1154 
canceled()1155     public abstract void canceled();
1156 
1157 
1158   }
1159 
1160 
1161 
1162   /**
1163    * A dialog for displaying the error that occurred while uploading user
1164    * settings.
1165    */
1166 
1167   private class SettingsUploadErrorDialog extends Dialog{
1168 
1169 
1170 
1171     /**
1172      * Creates a new <code>SettingsUploadErrorDialog</code> with the specified
1173      * parent frame and error text.
1174      */
1175 
SettingsUploadErrorDialog(Frame parent, String errorMessage)1176     public SettingsUploadErrorDialog(Frame parent, String errorMessage){
1177       super(parent, l10n.getString("prefsUploadErrorDialog.title"), true);
1178 
1179       createUI(errorMessage);
1180     }
1181 
1182 
1183 
1184     /**
1185      * Creates the UI of this dialog.
1186      */
1187 
createUI(String errorMessage)1188     private void createUI(String errorMessage){
1189       this.setLayout(new BorderLayout(5, 5));
1190 
1191       this.add(BorderLayout.NORTH, new Label(l10n.getString("prefsUploadErrorDialog.message")));
1192 
1193       TextArea errorArea = new TextArea(errorMessage, 3, 40);
1194 
1195       errorArea.setEditable(false);
1196       this.add(BorderLayout.CENTER, errorArea);
1197 
1198       Button closeButton = new Button(l10n.getString("prefsUploadErrorDialog.closeButton.text"));
1199       Panel buttonPanel = new Panel(new FlowLayout());
1200       buttonPanel.add(closeButton);
1201       this.add(BorderLayout.SOUTH, buttonPanel);
1202 
1203       closeButton.addActionListener(new ActionListener(){
1204         public void actionPerformed(ActionEvent evt){
1205           dispose();
1206         }
1207       });
1208     }
1209 
1210 
1211 
1212   }
1213 
1214 
1215 
1216   /**
1217    * A dialog which asks the user to input his password and retry uploading the
1218    * preferences.
1219    */
1220 
1221   private class PasswordDialog extends Dialog{
1222 
1223 
1224     /**
1225      * The username field.
1226      */
1227 
1228     private final TextField usernameField;
1229 
1230 
1231 
1232     /**
1233      * The password field.
1234      */
1235 
1236     private final TextField passwordField;
1237 
1238 
1239 
1240     /**
1241      * Whether prefs upload should be retried.
1242      */
1243 
1244     private boolean shouldRetry;
1245 
1246 
1247 
1248     /**
1249      * Creates a new <code>PasswordDialog</code> with the specified
1250      * parent frame, error text and current username.
1251      */
1252 
PasswordDialog(Frame parent, String errorMessage, String username)1253     public PasswordDialog(Frame parent, String errorMessage, String username){
1254       super(parent, l10n.getString("passwordPrefsUploadErrorDialog.title"), true);
1255 
1256       usernameField = new TextField(username);
1257       passwordField = new TextField();
1258       passwordField.setEchoChar('*');
1259 
1260       createUI(errorMessage);
1261     }
1262 
1263 
1264 
1265     /**
1266      * Creates the UI of this dialog.
1267      */
1268 
createUI(String errorMessage)1269     private void createUI(String errorMessage){
1270       this.setLayout(new BorderLayout(5, 5));
1271 
1272       this.add(BorderLayout.NORTH, new Label(l10n.getString("passwordPrefsUploadErrorDialog.message")));
1273 
1274       Panel centerPanel = new Panel(new BorderLayout(5, 5));
1275 
1276       TextArea errorArea = new TextArea(errorMessage, 3, 40);
1277       errorArea.setEditable(false);
1278       centerPanel.add(BorderLayout.CENTER, errorArea);
1279 
1280       Panel userInfoPanel = new Panel(new BorderLayout());
1281       Panel labelsPanel = new Panel(new GridLayout(2, 1, 10, 10));
1282       Panel textFieldsPanel = new Panel(new GridLayout(2, 1, 10, 10));
1283 
1284       labelsPanel.add(new Label(l10n.getString("usernameLabel.text")));
1285       textFieldsPanel.add(usernameField);
1286 
1287       labelsPanel.add(new Label(l10n.getString("passwordLabel.text")));
1288       textFieldsPanel.add(passwordField);
1289 
1290       userInfoPanel.add(BorderLayout.WEST, labelsPanel);
1291       userInfoPanel.add(BorderLayout.CENTER, textFieldsPanel);
1292       centerPanel.add(BorderLayout.SOUTH, userInfoPanel);
1293       this.add(BorderLayout.CENTER, centerPanel);
1294 
1295 
1296       Button retryButton = new Button(l10n.getString("passwordPrefsUploadErrorDialog.retryButton.text"));
1297       Button closeButton = new Button(l10n.getString("passwordPrefsUploadErrorDialog.closeButton.text"));
1298 
1299       Panel buttonPanel = new Panel(new FlowLayout());
1300       buttonPanel.add(retryButton);
1301       buttonPanel.add(closeButton);
1302       this.add(BorderLayout.SOUTH, buttonPanel);
1303 
1304       retryButton.addActionListener(new ActionListener(){
1305         public void actionPerformed(ActionEvent evt){
1306           shouldRetry = true;
1307           dispose();
1308         }
1309       });
1310 
1311       closeButton.addActionListener(new ActionListener(){
1312         public void actionPerformed(ActionEvent evt){
1313           shouldRetry = false;
1314           dispose();
1315         }
1316       });
1317     }
1318 
1319 
1320 
1321     /**
1322      * Displays the dialog and returns whether the user asked to retry
1323      * prefs upload.
1324      */
1325 
shouldRetry()1326     public boolean shouldRetry(){
1327       this.setVisible(true);
1328       return shouldRetry;
1329     }
1330 
1331 
1332 
1333     /**
1334      * Returns the username specified by the user.
1335      */
1336 
getUsername()1337     public String getUsername(){
1338       return usernameField.getText();
1339     }
1340 
1341 
1342 
1343     /**
1344      * Returns the password specified by the user.
1345      */
1346 
getPassword()1347     public String getPassword(){
1348       return passwordField.getText();
1349     }
1350 
1351 
1352   }
1353 
1354 }