/* Copyright (c) 2009 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.appengine.demos.sticky.client.model;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import com.google.gwt.core.client.GWT;
/**
* A client-side object representing a surface object.
*
* @author knorton@google.com (Kelly Norton)
*/
@SuppressWarnings("serial")
public class Surface implements Serializable {
/**
* A callback interface for observing updates on a particular instance of
* {@link Surface}.
*/
public static interface Observer {
void onUpdate(Surface surface);
}
private static String[] appendAuthorName(String[] original, String name) {
final int n = original.length;
final String[] names = new String[n + 1];
for (int i = 0; i < n; ++i) {
names[i] = original[i];
}
names[n] = name;
return names;
}
/**
* The primary key which is always assigned by the server.
*/
private String key;
/**
* The title for the surface.
*/
private String title;
/**
* A list of author nick names.
*/
private String[] authorNames;
/**
* The number of notes on this surface.
*/
private int noteCount;
/**
* A date that is managed by the server which indicates the last time this
* object was saved on the server.
*/
private Date lastUpdatedAt;
/**
* comma-separated list of author's. This value is a cache of the computation
* that is done in {@link Surface#computeAuthorNamesAsString(Model)}.
*/
private transient String authorNamesString;
/**
* The list of observers that are listening for updates to this object.
*/
private transient List<Observer> observers;
/**
* Used to create a new surface in client-side code.
*
* @param model
* the model to which this surface is bound
* @param title
* the title of the surface
*/
public Surface(Model model, String title) {
assert GWT.isClient();
this.title = title;
this.authorNames = new String[] { "You" };
initialize(model);
}
/**
* Constructs a new surface. This constructor can only be invoked on the
* server.
*
* @param key
* the primary key
* @param title
* the surface's title
* @param authorNames
* the names of all the authors
* @param noteCount
* the number of notes on this surface
* @param lastUpdatedAt
* the time the surface was last modified on the server
*/
public Surface(String key, String title, String[] authorNames, int noteCount,
Date lastUpdatedAt) {
assert !GWT.isClient();
this.key = key;
this.title = title;
this.authorNames = authorNames;
this.noteCount = noteCount;
this.lastUpdatedAt = lastUpdatedAt;
}
/**
* Needed for RPC serialization.
*/
@SuppressWarnings("unused")
private Surface() {
}
/**
* Subscribe an observer to receive update notifications.
*
* @param observer
*/
public void addObserver(Observer observer) {
observers.add(observer);
}
/**
* Returns the nick names of all authors on a surface.
*
* @return
*/
public String[] getAuthorNames() {
return authorNames;
}
/**
* Returns a comma-separated and human readable list of authors for this
* surface.
*
* @return
*/
public String getAuthorNamesAsString() {
return authorNamesString;
}
/**
* Returns the primary key for this surface.
*
* @return
*/
public String getKey() {
return key;
}
/**
* Returns the date corresponding to the time this object was last saved on
* the server.
*
* @return
*/
public Date getLastUpdatedAt() {
return lastUpdatedAt;
}
/**
* Returns the number of notes on this surface.
*
* @return
*/
public int getNoteCount() {
return noteCount;
}
/**
* Returns the title for the surface.
*
* @return
*/
public String getTitle() {
return title;
}
/**
* Indicates whether the surface has received a key from the server (objects
* receive keys after they are initially saved to the server).
*
* @return
*/
public boolean hasKey() {
return key != null;
}
/**
* Remove an existing observer.
*
* @param observer
*/
public void removeObserver(Observer observer) {
observers.remove(observer);
}
/**
* Returns a comma-separated, human-readable string containing all of the
* authors for this surface. This attempts to shorten author names to
* something more friendly. For instance, knorton@google.com would be
* shortened to knorton and Kelly Norton would become Kelly.
*
* @param model
* a model that is needed to determine the current user
* @return
*/
private String computeAuthorNamesAsString(Model model) {
final String currentAuthorName = model.getCurrentAuthor().getName();
final String[] names = authorNames;
final int n = names.length;
assert n > 0;
if (n == 1) {
return "Just You";
}
final StringBuffer buffer = new StringBuffer();
final int m = n - 1;
boolean foundYou = false;
for (int i = 0; i < n; ++i) {
String name = names[i];
if (!foundYou && currentAuthorName.equals(name)) {
name = "You";
foundYou = true;
}
if (i == m) {
buffer.append(" & ");
} else if (i != 0) {
buffer.append(", ");
}
buffer.append(Author.getShortName(name));
}
return buffer.toString();
}
private void notifyUpdate() {
assert observers != null;
for (int i = 0, n = observers.size(); i < n; ++i) {
observers.get(i).onUpdate(this);
}
}
/**
* Initializes some transient data in the object. This should be called by the
* {@link Model} when a surface is first introduced in the client application.
*
* @param model
* the model to which this object is bound
*/
void initialize(Model model) {
assert observers == null;
authorNamesString = computeAuthorNamesAsString(model);
observers = new ArrayList<Observer>();
}
Surface update(Model model, String authorName, Date updatedAt) {
assert updatedAt.after(lastUpdatedAt);
authorNames = appendAuthorName(authorNames, authorName);
lastUpdatedAt = updatedAt;
authorNamesString = computeAuthorNamesAsString(model);
notifyUpdate();
return this;
}
Surface update(Model model, Surface surface) {
if (!surface.getLastUpdatedAt().equals(lastUpdatedAt)) {
authorNames = surface.authorNames;
noteCount = surface.noteCount;
authorNamesString = computeAuthorNamesAsString(model);
notifyUpdate();
}
return this;
}
Surface update(String key, Date updatedAt) {
this.key = key;
this.lastUpdatedAt = updatedAt;
return this;
}
}