Java tutorial
/* * Copyright 2008 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.google.gwt.gears.sample.gwtnote.client; import com.google.gwt.core.client.GWT; import com.google.gwt.gears.sample.gwtnote.client.local.GearsHelper; import com.google.gwt.gears.sample.gwtnote.client.rpc.Note; import com.google.gwt.gears.sample.gwtnote.client.rpc.NoteService; import com.google.gwt.gears.sample.gwtnote.client.rpc.NoteServiceAsync; import com.google.gwt.gears.sample.gwtnote.client.ui.RichTextWidget; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.ServiceDefTarget; import com.google.gwt.user.client.ui.ChangeListener; import com.google.gwt.user.client.ui.Widget; import java.util.HashMap; import java.util.Iterator; /** * A controller class that manages UI and data-synchronization events. This is * the core component of the GWT GearsNote application. */ public class AppController { protected static final String REPLACE_CONF_TEXT = "Someone has changed the note you are viewing. Keep your copy?"; private static final int TICKS_PER_RPC = 100; protected boolean ready = false; protected boolean localDirty = false; private RichTextWidget rtw = null; private GearsHelper gears = null; private NoteServiceAsync rpc = null; private HashMap<String, Note> noteData = new HashMap<String, Note>(); private int rpcCntdown = TICKS_PER_RPC; private Object lastData = ""; /** * Default constructor. */ public AppController() { // set up RPC handles rpc = GWT.create(NoteService.class); String rpcUrl = GWT.getModuleBaseURL() + "rpc"; ((ServiceDefTarget) rpc).setServiceEntryPoint(rpcUrl); // create a database helper gears = new GearsHelper(); } /** * Tells the controller which widget instance to use to monitor and update for * UI changes. * * @param mainPanel * the widget to use */ public void setWidget(RichTextWidget mainPanel) { this.rtw = mainPanel; rtw.addNameChangeListener(new ChangeListener() { public void onChange(Widget sender) { String newName = rtw.getName(); if (noteData.containsKey(newName)) { Note n = noteData.get(newName); if (!rtw.getHTML().equals(n.getText())) { rtw.setHTML(n.getText()); } } } }); } /** * Kicks off the main processing loop. That loop is a timer that fires every * 10 milliseconds, forever. It calls {@link #update()} which implements the * sync logic, and may or may not actually do anything for a give timer tick. * (For instance, it may be stalled waiting on an asynchronous RPC call to * return.) */ public void startMainLoop() { init(); Timer t = new Timer() { @Override public void run() { if (!ready) { return; } update(); } }; t.scheduleRepeating(10); } /** * General initialization. */ protected void init() { ready = false; noteData.clear(); // ditto syncFromGears(true); syncToServer(true); // init the dirty-testing code localDirty = false; Note def = noteData.get("default"); if (def != null) { lastData = def.getText(); } } /** * Fallback-mode initialization. This method is called when the attempt to * fetch data from the RPC server on startup fails; this fallback method * fetches similar data from the local database. * * @param isInit * instructs this method that the attempt to init over RPC previously * failed, which causes this method to take additional actions to * prevent data loss when the server comes back online */ protected void syncFromGears(boolean isInit) { if (!gears.gearsEnabled()) { return; } Note[] notes = gears.getNotes(); Note n = null; if (notes != null && notes.length > 0) { for (int i = 0; i < notes.length; ++i) { n = notes[i]; if (!noteData.containsKey(n.getName())) { n = notes[i]; noteData.put(n.getName(), n); } } } if (isInit) { n = noteData.get("default"); if (n != null) { rtw.setHTML(n.getText()); } } } /** * Synchronizes the local application's state with the server's data. The * <code>gearsFallback</code> parameter should generally only be set to true * during first-time initialization. * * @param isInit * if true, indicates that a failure to contact server should result * in an attempt to sync from the Gears database */ protected void syncFromServer(final boolean isInit) { rpc.getNotes(new AsyncCallback<Note[]>() { public void onFailure(Throwable caught) { ready = true; } public void onSuccess(Note[] notes) { if (notes == null) { ready = true; return; } // process the results and figure out if we need to update our state Note n = null; for (int i = 0; i < notes.length; ++i) { n = notes[i]; // server sent us a totally new record -- just store it if (!noteData.containsKey(n.getName())) { noteData.put(n.getName(), n); gears.updateNote(n); continue; } // record exists -- check if server version is more recent & handle it Note current = noteData.get(n.getName()); if (!current.getVersion().equals(n.getVersion())) { current.setVersion(n.getVersion()); if (current.getText().equals(n.getText())) { // versions don't match but text is same anyway gears.updateNote(current); // to update version... localDirty = false; lastData = current.getText(); } else if (current.getName().equals(rtw.getName()) && localDirty && Window.confirm(REPLACE_CONF_TEXT)) { // if versions don't match, ask user for permission to override gears.updateNote(current); // to update version... // we are proceeding w/ local data, so don't touch UI } else { // user rejected override, or else was not current note current.setText(n.getText()); gears.updateNote(current); localDirty = false; lastData = current.getText(); // don't forget to update UI state... if (rtw.getName().equals(current.getName())) { rtw.setHTML(current.getText()); } } continue; } } // in the special case of startup, check for default value if (isInit) { Note def = noteData.get("default"); if (def != null) { rtw.setHTML(def.getText()); } } ready = true; } }); } /** * Uploads the current set of dirty (modified) notes to the server, and upon * acknowledgment of that, fetches the server's set of data. */ protected void syncToServer(final boolean isInit) { // temporarily stall the main timer loop until we're done ready = false; // convert our Map of notes into an array Note[] notes = new Note[noteData.size()]; Iterator<Note> it = noteData.values().iterator(); for (int i = 0; it.hasNext(); ++i) { notes[i] = it.next(); } // upload our current data rpc.setNotes(notes, new AsyncCallback<Void>() { public void onFailure(Throwable caught) { // next call is also likely to fail, so don't bother to try if (isInit) { Note def = noteData.get("default"); if (def != null) { rtw.setHeight(def.getText()); } } ready = true; } public void onSuccess(Void result) { // it worked: now request server's data syncFromServer(isInit); // releases 'ready' later } }); } /** * Core execution routine. Called once per timer tick. */ protected void update() { syncFromGears(false); if (rpcCntdown == 0) { rpcCntdown = TICKS_PER_RPC; syncToServer(false); } rpcCntdown -= 1; updateUIState(); } /** * Synchronizes the user interface state with the in-memory data model. Note * that this goes both ways: it updates the UI if new data is present, and * also updates the data if the user has entered or changed data. */ protected void updateUIState() { // extract data from the UI String curName = rtw.getName(); String curData = rtw.getHTML(); curData = (curData == null) ? "" : curData; curName = (curName == null) ? "" : curName; if (!curData.equals(lastData)) { localDirty = true; lastData = curData; } if (noteData.containsKey(curName)) { // fetch the latest data for the note the user is trying to look at Note n = noteData.get(curName); if (!n.getText().equals(curData)) { // if the UI doesn't currently show latest data, update it n.setText(curData); gears.updateNote(n); } } else { // if the user has created a new record (unknown name) just store it Note n = new Note(curName, "1", curData); noteData.put(curName, n); gears.updateNote(n); } // add all the notes to the options list Iterator<String> it = noteData.keySet().iterator(); String[] names = new String[noteData.size()]; for (int i = 0; it.hasNext(); ++i) { names[i] = it.next(); } rtw.setNameOptions(names); } }