/* Copyright (c) 2006 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.api.gbase.client;
import com.google.gdata.util.common.base.StringUtil;
import com.google.common.collect.Multimap;
import com.google.gdata.data.DateTime;
import java.util.ArrayList;
import java.util.Collection;
/**
* Some methods for converting strings found in XML files
* into other types.
*/
class ConversionUtil {
private enum TaxField {
Country("country"),
Region("region"),
Rate("rate"),
TaxShip("tax_ship");
private final String elemName;
private final String attrName;
TaxField(String elemName) {
this.elemName = elemName;
this.attrName = elemName.replace('_', ' ');
}
public String getElemName() {
return elemName;
}
public String getAttrName() {
return attrName;
}
}
private enum ShippingField {
Country("country"),
Region("region"),
Price("price"),
Service("service");
private final String elemName;
private final String attrName;
ShippingField(String elemName) {
this.elemName = elemName;
this.attrName = elemName.replace('_', ' ');
}
public String getElemName() {
return elemName;
}
public String getAttrName() {
return attrName;
}
}
/**
* Name of the element inside an attribute of type location
* that contains the latitude.
*/
private static final String LATITUDE_ELEMENT_NAME = "latitude";
/**
* Name of the element inside of an attribute of type location
* that contains the longitude.
*/
private static final String LONGITUDE_ELEMENT_NAME = "longitude";
/** This class is not meant to be instantiated. */
private ConversionUtil() {
}
/** Converts a string to a Float. */
static Float toFloat(String text) {
if (text == null) {
return null;
}
return new Float(text);
}
/** Converts a string to an Integer. */
static Integer toInteger(String text) {
if (text == null) {
return null;
}
return new Integer(text);
}
/** Converts a string to a Boolean. */
static Boolean toBoolean(String text) {
if (text == null) {
return null;
}
return new Boolean(text);
}
/** Converts a string containing a date or a date and a time to a DateTime. */
static DateTime toDateOrDateTime(String text) {
if (text == null) {
return null;
}
return DateTime.parseDateTimeChoice(text);
}
/**
* Converts a integer followed by a unit name into an object.
*
* @param string a string of the form: <code>integer " " unit</code> or null
* @return the corresponding object or null
* @exception NumberFormatException if the conversion failed
*/
public static NumberUnit<Integer> toIntUnit(String string) {
if (string == null) {
return null;
}
int firstSpace = findFirstSpace(string);
String beforeSpace = beforeSpace(string, firstSpace);
return new NumberUnit<Integer>(new Integer(beforeSpace),
parseUnit(string, firstSpace));
}
/**
* Converts a float followed by a unit name into an object.
*
* @param string a string of the form: <code>float " " unit</code> or null
* @return the corresponding object or null
* @exception NumberFormatException if the conversion failed
*/
public static NumberUnit<Float> toFloatUnit(String string) {
if (string == null) {
return null;
}
int firstSpace = findFirstSpace(string);
String beforeSpace = beforeSpace(string, firstSpace);
return new NumberUnit<Float>(new Float(beforeSpace),
parseUnit(string, firstSpace));
}
private static String beforeSpace(String string, int firstSpace) {
return string.substring(0, firstSpace);
}
private static String parseUnit(String string, int firstSpace) {
return string.substring(firstSpace+1).trim();
}
private static int findFirstSpace(String string) {
int firstSpace = string.indexOf(" ");
if (firstSpace == -1 || firstSpace==string.length()) {
throw new NumberFormatException("missing unit in '" + string + "'");
}
return firstSpace;
}
/**
* Creates a {@link com.google.api.gbase.client.GoogleBaseAttribute} of type
* {@link GoogleBaseAttributeType#DATE_TIME_RANGE}
* and initializes it using the current state of the object.
*
* @param name attribute name
* @param range attribute value
* @return a new {@link com.google.api.gbase.client.GoogleBaseAttribute}
*/
public static GoogleBaseAttribute createAttribute(String name,
DateTimeRange range) {
return new GoogleBaseAttribute(name,
GoogleBaseAttributeType.DATE_TIME_RANGE,
range.toString());
}
/**
* Extracts a {@link DateTimeRange} from the value of a
* {@link com.google.api.gbase.client.GoogleBaseAttribute}.
*
* @param attribute
* @exception NumberFormatException if the conversion failed, either
* because on of the date/time was invalid or if the expected start
* and end tags were missing
*/
public static DateTimeRange
extractDateTimeRange(GoogleBaseAttribute attribute) {
String range = attribute.getValueAsString();
int space = range.indexOf(' ');
if (space == -1) {
/* Not really a range, but since date and dateTime are subtypes
* of DateTimeRange, try and convert them into a DateTimeRange.
*/
return new DateTimeRange(toDateOrDateTime(range));
} else {
String startStr = range.substring(0, space);
String endStr = range.substring(space+1);
return new DateTimeRange(toDateOrDateTime(startStr),
toDateOrDateTime(endStr));
}
}
/**
* Extracts a {@link Group} object from the value of a
* {@link com.google.api.gbase.client.GoogleBaseAttribute}.
*
* @param attribute
*/
public static Group extractGroup(GoogleBaseAttribute attribute) {
return new Group(attribute.getSubAttributes());
}
/**
* Creates a {@link GoogleBaseAttribute} of type
* {@link GoogleBaseAttributeType#GROUP} and initializes it using the
* current state of the object. Sub-attributes of group with type
* GROUP or GROUP's derived types are ignored.
*
* @param name attribute name
* @param group attribute value
* @return a new {@link com.google.api.gbase.client.GoogleBaseAttribute}
*/
public static GoogleBaseAttribute createAttribute(String name, Group group) {
GoogleBaseAttribute attribute = new GoogleBaseAttribute(name, GoogleBaseAttributeType.GROUP);
Multimap<String, GoogleBaseAttribute> subAttributes = group.getAllSubAttributes();
for (GoogleBaseAttribute attr : subAttributes.values()) {
GoogleBaseAttributeType type = attr.getType();
if (!GoogleBaseAttributeType.GROUP.equals(type)
&& !GoogleBaseAttributeType.GROUP.equals(type.getSupertype())
&& (attr.hasValue() || attr.hasSubElements())) {
attribute.addSubAttribute(attr);
}
}
return attribute;
}
/**
* Extracts a {@link Shipping} object from the value of a
* {@link com.google.api.gbase.client.GoogleBaseAttribute}.
*
* @param attribute
* @exception NumberFormatException if the conversion failed
* because the price is missing or not a number.
*/
public static Shipping extractShipping(GoogleBaseAttribute attribute) {
if (attribute.hasSubAttributes()) {
return extractShippingFromAttributes(attribute);
} else {
return extractShippingFromElements(attribute);
}
}
private static Shipping extractShippingFromAttributes(GoogleBaseAttribute attribute) {
NumberUnit<Float> priceUnit = null;
String country = null;
Collection<String> regions = new ArrayList<String>();;
String service = null;
for (GoogleBaseAttribute attr : attribute.getSubAttributes()) {
String name = attr.getName();
if (ShippingField.Country.getAttrName().equals(name)) {
country = attr.getValueAsString();
} else if (ShippingField.Region.getAttrName().equals(name)) {
regions.add(attr.getValueAsString());
} else if (ShippingField.Price.getAttrName().equals(name)) {
String price = attr.getValueAsString();
try {
priceUnit = toFloatUnit(price);
} catch (NumberFormatException e) {
priceUnit = new NumberUnit<Float>(Float.parseFloat(price), null);
}
} else if (ShippingField.Service.getAttrName().equals(name)) {
service = attr.getValueAsString();
} else {
throw new IllegalArgumentException("Sub-attribute " + name
+ " is not supported in Shipping.");
}
}
if (priceUnit == null) {
throw new NumberFormatException(
"missing 'price' element in shipping attribute: " + attribute);
}
return new Shipping(country, regions, service, priceUnit.getValue(),
priceUnit.getUnit());
}
private static Shipping extractShippingFromElements(GoogleBaseAttribute attribute) {
String country = attribute.getSubElementValue(ShippingField.Country.getElemName());
Collection<String> regions = attribute.getSubElementValues(ShippingField.Region.getElemName());
String price = attribute.getSubElementValue(ShippingField.Price.getElemName());
String service = attribute.getSubElementValue(ShippingField.Service.getElemName());
if (price == null) {
throw new NumberFormatException(
"missing 'price' element in shipping attribute: " + attribute);
}
NumberUnit<Float> priceUnit;
try {
priceUnit = toFloatUnit(price);
} catch (NumberFormatException e) {
priceUnit = new NumberUnit<Float>(Float.parseFloat(price), null);
}
return new Shipping(country, regions, service, priceUnit.getValue(),
priceUnit.getUnit());
}
/**
* Creates a {@link GoogleBaseAttribute} of type
* {@link GoogleBaseAttributeType#SHIPPING} and initializes it using the
* current state of the object.
* @param name attribute name
* @param shipping attribute value
* @return a new {@link com.google.api.gbase.client.GoogleBaseAttribute}
*/
public static GoogleBaseAttribute createAttribute(String name,
Shipping shipping) {
GoogleBaseAttribute attribute =
new GoogleBaseAttribute(name, GoogleBaseAttributeType.SHIPPING);
if(shipping.getCountry() != null) {
GoogleBaseAttribute countryAttr = new GoogleBaseAttribute(
ShippingField.Country.getAttrName(), null, shipping.getCountry());
attribute.addSubAttribute(countryAttr);
}
for (String region : shipping.getRegions()) {
GoogleBaseAttribute regionAttr = new GoogleBaseAttribute(
ShippingField.Region.getAttrName(), null, region);
attribute.addSubAttribute(regionAttr);
}
if (shipping.getService() != null) {
GoogleBaseAttribute serviceAttr = new GoogleBaseAttribute(
ShippingField.Service.getAttrName(), null, shipping.getService());
attribute.addSubAttribute(serviceAttr);
}
String priceWithUnit = Float.toString(shipping.getPrice());
if (shipping.getCurrency() != null) {
priceWithUnit += " " + shipping.getCurrency();
}
GoogleBaseAttribute priceAttr = new GoogleBaseAttribute(ShippingField.Price.getAttrName(),
null, priceWithUnit);
attribute.addSubAttribute(priceAttr);
return attribute;
}
/**
* Extracts a {@link Tax} object from the value of a
* {@link com.google.api.gbase.client.GoogleBaseAttribute}.
*
* @param attribute
* @exception NumberFormatException if the conversion failed, usually
* because the rate is missing or not a number.
*/
public static Tax extractTax(GoogleBaseAttribute attribute) {
if (attribute.hasSubAttributes()) {
return extractTaxFromAttributes(attribute);
} else {
return extractTaxFromElements(attribute);
}
}
private static Tax extractTaxFromAttributes(GoogleBaseAttribute attribute) {
String rateString = null;
String country = null;
Collection<String> regions = new ArrayList<String>();
Boolean taxShip = null;
for (GoogleBaseAttribute attr : attribute.getSubAttributes()) {
String name = attr.getName();
if (TaxField.Country.getAttrName().equals(name)) {
country = attr.getValueAsString();
} else if (TaxField.Region.getAttrName().equals(name)) {
regions.add(attr.getValueAsString());
} else if (TaxField.Rate.getAttrName().equals(name)) {
rateString = attr.getValueAsString();
} else if (TaxField.TaxShip.getAttrName().equals(name)) {
String taxShipString = attr.getValueAsString();
if (taxShipString != null) {
taxShip = Boolean.valueOf(taxShipString);
}
} else {
throw new IllegalArgumentException("Sub-attribute " + name + " is not supported in Tax.");
}
}
if (StringUtil.isEmpty(rateString)) {
throw new NumberFormatException(
"missing 'rate' element in tax attribute: " + attribute);
}
float rate = Float.valueOf(rateString.trim()).floatValue();
return new Tax(country, regions, rate, taxShip);
}
private static Tax extractTaxFromElements(GoogleBaseAttribute attribute) {
String country = attribute.getSubElementValue(TaxField.Country.getElemName());
Collection<String> regions = attribute.getSubElementValues(TaxField.Region.getElemName());
String rateString = attribute.getSubElementValue(TaxField.Rate.getElemName());
if (rateString == null) {
throw new NumberFormatException(
"missing 'rate' element in tax attribute: " + attribute);
}
float rate = Float.valueOf(rateString.trim()).floatValue();
String taxShipString = attribute.getSubElementValue(TaxField.TaxShip.getElemName());
Boolean taxShip = null;
if (taxShipString != null) {
taxShip = Boolean.valueOf(taxShipString);
}
return new Tax(country, regions, rate, taxShip);
}
/**
* Creates a {@link GoogleBaseAttribute} of type
* {@link GoogleBaseAttributeType#TAX} and initializes it using the
* current state of the object.
* @param name attribute name
* @param tax attribute value
* @return a new {@link com.google.api.gbase.client.GoogleBaseAttribute}
*/
public static GoogleBaseAttribute createAttribute(String name, Tax tax) {
GoogleBaseAttribute attribute = new GoogleBaseAttribute(name, GoogleBaseAttributeType.TAX);
GoogleBaseAttribute attr = null;
if (tax.getCountry() != null) {
attr = new GoogleBaseAttribute(TaxField.Country.getAttrName(), null, tax.getCountry());
attribute.addSubAttribute(attr);
}
for (String region : tax.getRegions()) {
attr = new GoogleBaseAttribute(TaxField.Region.getAttrName(), null, region);
attribute.addSubAttribute(attr);
}
attr = new GoogleBaseAttribute(TaxField.Rate.getAttrName(), null,
Float.toString(tax.getRate()));
attribute.addSubAttribute(attr);
if (tax.getTaxShip() != null) {
attr = new GoogleBaseAttribute(TaxField.TaxShip.getAttrName(), null,
tax.getTaxShip() ? "true":"false");
attribute.addSubAttribute(attr);
}
return attribute;
}
/**
* Extracts a {@link Location} object from the value of a
* {@link GoogleBaseAttribute}.
*
* @param attribute
* @return NumberFormatException if the conversion failed, usually
* because the latitude or longitude information are invalid numbers
*/
public static Location extractLocation(GoogleBaseAttribute attribute) {
String address = attribute.getValueAsString();
Location retval = new Location(address);
String latString = attribute.getSubElementValue(LATITUDE_ELEMENT_NAME);
String longString = attribute.getSubElementValue(LONGITUDE_ELEMENT_NAME);
if (latString != null && longString != null) {
retval.setLatitude(Float.parseFloat(latString));
retval.setLongitude(Float.parseFloat(longString));
}
return retval;
}
public static GoogleBaseAttribute createAttribute(String name,
Location location) {
GoogleBaseAttribute attribute =
new GoogleBaseAttribute(name, GoogleBaseAttributeType.LOCATION);
attribute.setValue(location.getAddress());
if (location.hasCoordinates()) {
attribute.setSubElement(LATITUDE_ELEMENT_NAME,
Float.toString(location.getLatitude()));
attribute.setSubElement(LONGITUDE_ELEMENT_NAME,
Float.toString(location.getLongitude()));
}
return attribute;
}
/**
* Extracts a {@link Number} from a {@link GoogleBaseAttribute}.
*
* @param attr an attribute of type NUMBER, FLOAT or INT, may be null
* @return a Number, which might be an Integer or a Float depending
* on the type of the attribute, or null if {@code attr} was null
*/
public static Number extractNumber(GoogleBaseAttribute attr) {
if (attr == null) {
return null;
}
/* If the attribute has been declared as being an int,
* treat it as such, otherwise create a Float.
*/
if (GoogleBaseAttributeType.FLOAT.equals(attr.getAttributeId().getType())) {
return toFloat(attr.getValueAsString());
} else {
return toInteger(attr.getValueAsString());
}
}
/**
* Extracts a <code>NumberUnit<Number></code> from a
* {@link GoogleBaseAttribute}.
*
* @param attr an attribute of type NUMBER_UNIT, FLOAT_UNIT or INT_UNIT,
* may be null
* @return a NumberUnit object whose number might be an Integer
* or a Float, depending on the type of the attribute, or null
* if {@code attr} was null
*/
public static NumberUnit<? extends Number> extractNumberUnit(
GoogleBaseAttribute attr) {
if (attr == null) {
return null;
}
if (GoogleBaseAttributeType.INT_UNIT.equals(attr.getAttributeId().getType())) {
return toIntUnit(attr.getValueAsString());
}
return toFloatUnit(attr.getValueAsString());
}
}