/*
* Copyright 2010 kk-electronic a/s.
*
* This file is part of KKPortal.
*
* KKPortal is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KKPortal is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with KKPortal. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kk_electronic.kkportal.core.activity;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Hyperlink;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import com.kk_electronic.kkportal.core.event.LocationChangedEvent;
/**
* Class responsible for monitoring the History of the browser
* and firing event when it happens.
*
* @author Rasmus Carlsen
* @author Jes Andersen
*
*/
@Singleton
public class LocationManager implements ValueChangeHandler<String> {
private final LocationInfo locationInfo;
/**
* @return the locationInfo
*/
public LocationInfo getLocationInfo() {
return locationInfo;
}
private final EventBus eventBus;
private final String defaultplace;
private static final Map<String, String> tokenMap = new HashMap<String, String>();
private ArrayList<AbstractLink> linkList = new ArrayList<AbstractLink>();
@Inject
public LocationManager(@Named("DefaultHistoryToken") String defaultplace, EventBus eventBus, LocationInfo locationInfo) {
this.locationInfo = locationInfo;
this.eventBus = eventBus;
this.defaultplace = defaultplace;
History.addValueChangeHandler(this);
/*
* If we do not have anything in the history stack go to the default
* view TODO: Decide if we should redirect like here or just show it
*/
if ("".equals(History.getToken())) {
goHome();
} else {
/*
* If we have a history token we fire the change event which handles
* setup of the activity
*/
History.fireCurrentHistoryState();
}
}
public String getToken(String token) {
return tokenMap.get(token);
}
public String addTokens(String partialLink) {
tokenMap.entrySet();
return partialLink;
}
public void refreshLinks() {
for (AbstractLink alink : linkList) {
alink.updateLink(tokenMap);
}
}
/**
* This function is called when ever the history of the browser is changed.
*
* @see ValueChangeHandler#onValueChange(ValueChangeEvent)
*/
@Override
public void onValueChange(ValueChangeEvent<String> event) {
parseToken(event.getValue());
if (!tokenMap.containsKey(null)) {
GWT.log("History Token has no location - returning to home page");
goHome();
} else {
updateLocationInfo();
}
}
/**
* Decode the map like described in {@link ActivityManager}
*
* If an invalid rawString is provided it returns to the home activity
*
* @param rawHistoryString
* the anchor part of the current url
* @return a map of parameters
*/
private void parseToken(String rawHistoryString) {
tokenMap.clear();
String[] values = rawHistoryString.split(";");
for (String placestring : values) {
String[] l = placestring.split("[=]", 2);
if (l.length > 1) {
assert (!tokenMap.containsKey(l[0]));
tokenMap.put(l[0], l[1]);
} else {
assert (!tokenMap.containsKey(null));
tokenMap.put(null, l[0]);
}
}
}
/**
* Return the application to the default home activity
*/
public void goHome() {
History.newItem(defaultplace);
}
/**
* Change location to the new place
*
* @param place
* new location to go to
*/
public void go(String placename) {
assert(placename != null && !placename.equals(""));
History.newItem(placename);
}
/**
* This one trims the place name a bit before using activitymap to lookup
* the name. It is done to provide some nice looking urls. Everything after
* $ or / is cut off
*
* @param tokens
* a map if input tokens, where the null key is the main
* navigation element
* @return the class matching the token set
*/
private void updateLocationInfo() {
assert (tokenMap.containsKey(null));
String location = tokenMap.get(null);
String[] matches = location.split("[/$]", 2);
// Reset Location Info and start filling in new information
locationInfo.setActivity(matches[0]);
locationInfo.setSubint(null);
locationInfo.setSubpath(null);
GWT.log("mainplace: " + matches[0]);
if (matches.length > 1) {
switch (location.charAt(matches[0].length())) {
case '/':
locationInfo.setSubpath(matches[1]);
break;
case '$':
locationInfo.setSubint(Integer.valueOf(matches[1]));
break;
default:
GWT.log("LocationManager:Problems with history token" + location);
}
} else {
GWT.log("LocationManager:No Tab selected");
}
eventBus.fireEvent(new LocationChangedEvent(locationInfo));
refreshLinks();
}
public AbstractLink registerLink(Hyperlink link, String... values) {
AbstractLink al = new AbstractHyperLink(link, generateMap(values));
linkList.add(al);
return al;
}
public AbstractLink registerLink(Anchor link, String... values) {
AbstractLink al = new AbstractAnchor(link, generateMap(values));
linkList.add(al);
al.updateLink(tokenMap);
return al;
}
private Map<String, String> generateMap(String[] values) {
assert(values.length % 2 == 0);
HashMap<String, String> map = new HashMap<String, String>();
for (int i = 0; i < values.length - 1; i = i + 2) {
map.put(values[i], values[i + 1]);
}
return map;
}
public static String combineMaps(Map<String, String> baseValues, Map<String, String> tokenMap) {
HashMap<String, String> tik = new HashMap<String, String>();
if (tokenMap != null) {
tik.putAll(tokenMap);
}
if (baseValues != null) {
tik.putAll(baseValues);
}
StringBuffer sb = new StringBuffer();
if (tik.containsKey(null)) {
sb.append("#");
sb.append(tik.get(null));
}
for (Entry<String,String> token : tik.entrySet()) {
if (token.getKey() != null) {
sb.append(";");
sb.append(token.getKey());
sb.append("=");
sb.append(token.getValue());
}
}
return sb.toString();
}
public interface AbstractLink {
public void updateLink(Map<String, String> tokenMap);
public void changeValues(String... baseValues);
}
private class AbstractHyperLink implements AbstractLink {
private Hyperlink link;
private Map<String, String> baseValues;
/**
* @param link
* @param map
*/
public AbstractHyperLink(Hyperlink link, Map<String, String> map) {
this.link = link;
this.baseValues = map;
}
/* (non-Javadoc)
* @see com.kk_electronic.kkportal.core.activity.LocationManager.AbstractLink#updateLink(java.util.Map)
*/
@Override
public void updateLink(Map<String, String> tokenMap) {
String linkText = LocationManager.combineMaps(tokenMap, baseValues);
link.setTargetHistoryToken(linkText);
}
/* (non-Javadoc)
* @see com.kk_electronic.kkportal.core.activity.LocationManager.AbstractLink#changeLink(java.util.Map)
*/
@Override
public void changeValues(String... baseValues) {
this.baseValues = generateMap(baseValues);
}
}
private class AbstractAnchor implements AbstractLink {
private final Anchor link;
private Map<String, String> baseValues;
/**
* @param link
* @param generateMap
*/
public AbstractAnchor(Anchor link, Map<String, String> generateMap) {
this.link = link;
this.baseValues = generateMap;
}
/* (non-Javadoc)
* @see com.kk_electronic.kkportal.core.activity.LocationManager.AbstractLink#updateLink(java.util.Map)
*/
@Override
public void updateLink(Map<String, String> tokenMap) {
String linkText = LocationManager.combineMaps(tokenMap, baseValues);
link.setHref(linkText);
}
/* (non-Javadoc)
* @see com.kk_electronic.kkportal.core.activity.LocationManager.AbstractLink#changeLink(java.util.Map)
*/
@Override
public void changeValues(String... baseValues) {
this.baseValues = generateMap(baseValues);
this.updateLink(LocationManager.tokenMap);
}
}
}