/**
* Copyright (C) 2012-2014 Gist Labs, LLC. (http://gistlabs.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.gistlabs.mechanize.document.json.hypermedia;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.damnhandy.uri.template.UriTemplate;
import com.gistlabs.mechanize.document.json.node.JsonNode;
/**
* Wraps a JSON node with Link generatation behavior. See <a href="https://github.com/GistLabs/mechanize/wiki/JSON-Linking">https://github.com/GistLabs/mechanize/wiki/JSON-Linking</a>
*
*/
public class JsonLink {
private final JsonNode node;
private final String attrName;
private final URL baseUrl;
private final String linkRel;
private Map<String, Object> variables = new HashMap<String, Object>();
public JsonLink(JsonNode node) {
this(null, node, "href", null);
}
public JsonLink(JsonNode node, String attrName) {
this(null, node, attrName, null);
}
public JsonLink(String baseUrl, JsonNode node, String attrName, String linkRel) {
if (node==null || attrName==null) throw new NullPointerException(String.format("baseUrl=%s, node=%s, attributeName=%s, linkRel=%s", baseUrl, node, attrName, linkRel));
if (!node.hasAttribute(attrName)) throw new IllegalArgumentException(String.format("Node %s does not have an attribute named %s", node, attrName));
this.node = node;
this.baseUrl = baseUrl(baseUrl);
this.attrName = attrName;
this.linkRel = linkRel;
}
public String attrName() {
return this.attrName;
}
/**
* the String name of the link relationship from this resource to the resource identified by the link
*
* @return Either the linkRel arg at construction, the value of the "rel" attribute, or the name of the node itself.
*/
public String linkRel() {
if (this.linkRel!=null) return this.linkRel;
if (node.hasAttribute("rel")) return node.getAttribute("rel");
//else
return node.getName();
}
protected URL baseUrl(String baseUrl) {
if (baseUrl==null) return null;
try {
return new URL(baseUrl);
} catch (MalformedURLException e) {
throw new RuntimeException(String.format("Problem with %s", baseUrl), e);
}
}
public String uri() {
return template(buildTemplate());
}
protected UriTemplate buildTemplate() {
try {
return UriTemplate.fromTemplate(combine(raw()));
} catch (Exception e) {
throw new RuntimeException(String.format("Problem building UriTemplate"), e);
}
}
protected String template(UriTemplate template) {
try {
String[] variables = template.getVariables();
if (variables.length>0) {
for (String var : variables) {
template.set(var, lookupVar(var));
}
}
return template.expand();
} catch (Exception e) {
throw new RuntimeException(String.format("Problem processing %s", template), e);
}
}
public String[] getVariables() {
return buildTemplate().getVariables();
}
protected Object lookupVar(String var) {
if (variables.containsKey(var)) {
return variables.get(var);
} else {
return lookupWalkForVar(var);
}
}
/**
* Return an Object that is either String or List<String> or Map<String, String>.
*
* @param var maybe null if not found
* @return
*/
protected Object lookupWalkForVar(String var) {
JsonNode current = node;
while (current!=null) {
Object result = lookupVar(current, var);
if (result!=null) return result;
if (current.hasAttribute("inheritProperties")) {
current = current.getParent();
} else {
current = null;
}
}
return lookupVar(node, var);
}
/**
* Set value for a template link. Can be Object, List<String> and Map<String,String>, objects need to have .toString() method.
* @param name
* @param value
*/
public void set(String name, Object value) {
this.variables.put(name, value);
}
/**
* Set multiple values for a template link. Values in Map can be List<Object> and Map<Object>, objects need to have .toString() method.
* @param values
*/
public void setAll(Map<String, Object> values) {
this.variables.putAll(values);
}
/**
* Return an Object that is either String or List<String> or Map<String, String>
* @param var maybe null if not found
* @return
*/
protected Object lookupVar(JsonNode node, String var) {
List<? extends JsonNode> children = node.getChildren(var);
if (children.size()>1) { // multiple with name, treat as list of values
List<String> result = new ArrayList<String>();
for (JsonNode child : children) {
result.add(child.getValue());
}
return result;
} else if (children.size()==1) {
JsonNode child = children.get(0);
List<String> attributeNames = child.getAttributeNames();
Collections.sort(attributeNames);
if (attributeNames.size()==0) { // treat child as the attribute value
return child.getValue();
} else { // treat child as object with map values
Map<String, String> result = new LinkedHashMap<String, String>();
for (String attrName : attributeNames) {
result.put(attrName, child.getAttribute(attrName));
}
return result;
}
} else { // return null
return null;
}
}
protected String combine(String raw) {
if (baseUrl==null) {
return raw;
} else {
try {
return new URL(baseUrl, raw).toExternalForm();
} catch (MalformedURLException e) {
throw new RuntimeException(String.format("Problem conbining %s with %s", baseUrl, raw), e);
}
}
}
public String raw() {
return node.getAttribute(attrName);
}
public JsonNode node() {
return node;
}
}