Wednesday, 4 April 2012

Convert currency using Google API calculator and Java

So why do I propose this option against the numerous converter apis out there on the web? For me the answer is pretty simple, I can't think of any API out there which stands virtually at par with google on various matching grounds. Free, Robust, 247uptime for critical systems and over all easy to use. Google currency calculator like most of its apis like Dojo, Ext Core, jQuery etc. are used both by google developement team and developers all over. These apis are accessible so long as the google project is alive.

Although I will not stick my neck on it and say it is "mission-critical", which I believe it is, I can only say - if you dont trust the google conversion api on the grounds of critical usage, then you're going to find it difficult to arrive on a specific api for your currency conversion, unless you are looking to subscribe for one which the providers can stick their neck on and say it is "mission-critical". To demonstrate how easy and quick it is, I opted to use Core Java to create a simple currency converter util to interract with the api.


A request to convert say 12GBP to USD, simply translates to:
http://www.google.com/ig/calculator?h1=en&q=12gbp=?usd
With a typical standard Jason result as shown below:
{lhs: "12 British pounds",rhs: "19.0188 U.S. dollars",error: "",icc: true}

This makes it pretty straight forward, all we need now is an enumumeration class to hold all the various currency codes, and helper functions relevant for a specific convertion. Took time to compile this to support various currencies your application is possible to support.
/**Currency and code used mostly for conversion and other purposes
 * 
 * @author Bright Dadson
 *
 */
public enum Currency
{
  UAE_Dirham("AED", "UAE Dirham"),
  Afghanistan_Afghani("AFA", "Afghanistan Afghani"),
  Albanian_Lek("ALL", "Albanian Lek"),
  Neth_Antilles_Guilder("ANG", "Neth Antilles Guilder"),
  Argentine_Peso("ARS", "Argentine Peso"),
  Australian_Dollar("AUD", "Australian Dollar"),
  Aruba_Florin("AWG", "Aruba Florin"),
  Barbados_Dollar("BBD", "Barbados Dollar"),
  Bangladesh_Taka("BDT", "Bangladesh Taka"),
  Bahraini_Dinar("BHD", "Bahraini Dinar"),
  Burundi_Franc("BIF", "Burundi Franc"),
  Bermuda_Dollar("BMD", "Bermuda Dollar"),
  Brunei_Dollar("BND", "Brunei Dollar"),
  Bolivian_Boliviano("BOB", "Bolivian Boliviano"),
  Brazilian_Real("BRL", "Brazilian Real"),
  Bahamian_Dollar("BSD", "Bahamian Dollar"),
  Bhutan_Ngultrum("BTN", "Bhutan Ngultrum"),
  Botswana_Pula("BWP", "Botswana Pula"),
  Belize_Dollar("BZD", "Belize Dollar"),
  Canadian_Dollar("CAD", "Canadian Dollar"),
  Swiss_Franc("CHF", "Swiss Franc"),
  Chilean_Peso("CLP", "Chilean Peso"),
  Chinese_Yuan("CNY", "Chinese Yuan"),
  Colombian_Peso("COP", "Colombian Peso"),
  Costa_Rica_Colon("CRC", "Costa Rica Colon"),
  Cuban_Peso("CUP", "Cuban Peso"),
  Cape_Verde_Escudo("CVE", "Cape Verde Escudo"),
  Cyprus_Pound("CYP", "Cyprus Pound"),
  Czech_Koruna("CZK", "Czech Koruna"),
  Dijibouti_Franc("DJF", "Dijibouti Franc"),
  Danish_Krone("DKK", "Danish Krone"),
  Dominican_Peso("DOP", "Dominican Peso"),
  Algerian_Dinar("DZD", "Algerian Dinar"),
  Estonian_Kroon("EEK", "Estonian Kroon"),
  Egyptian_Pound("EGP", "Egyptian Pound"),
  Ethiopian_Birr("ETB", "Ethiopian Birr"),
  Euro("EUR", "Euro"),
  Falkland_Islands_Pound("FKP", "Falkland Islands Pound"),
  British_Pound("GBP", "British Pound"),
  Ghanian_Cedi("GHC", "Ghanian Cedi"),
  Gibraltar_Pound("GIP", "Gibraltar Pound"),
  Gambian_Dalasi("GMD", "Gambian Dalasi"),
  Guinea_Franc("GNF", "Guinea Franc"),
  Guatemala_Quetzal("GTQ", "Guatemala Quetzal"),
  Guyana_Dollar("GYD", "Guyana Dollar"),
  Hong_Kong_Dollar("HKD", "Hong Kong Dollar"),
  Honduras_Lempira("HNL", "Honduras Lempira"),
  Croatian_Kuna("HRK", "Croatian Kuna"),
  Haiti_Gourde("HTG", "Haiti Gourde"),
  Hungarian_Forint("HUF", "Hungarian Forint"),
  Indonesian_Rupiah("IDR", "Indonesian Rupiah"),
  Israeli_Shekel("ILS", "Israeli Shekel"),
  Indian_Rupee("INR", "Indian Rupee"),
  Iraqi_Dinar("IQD", "Iraqi Dinar"),
  Iceland_Krona("ISK", "Iceland Krona"),
  Jamaican_Dollar("JMD", "Jamaican Dollar"),
  Jordanian_Dinar("JOD", "Jordanian Dinar"),
  Japanese_Yen("JPY", "Japanese Yen"),
  Kenyan_Shilling("KES", "Kenyan Shilling"),
  Cambodia_Riel("KHR", "Cambodia Riel"),
  Comoros_Franc("KMF", "Comoros Franc"),
  North_Korean_Won("KPW", "North Korean Won"),
  Korean_Won("KRW", "Korean Won"),
  Kuwaiti_Dinar("KWD", "Kuwaiti Dinar"),
  Cayman_Islands_Dollar("KYD", "Cayman Islands Dollar"),
  Kazakhstan_Tenge("KZT", "Kazakhstan Tenge"),
  Lao_Kip("LAK", "Lao Kip"),
  Lebanese_Pound("LBP", "Lebanese Pound"),
  Sri_Lanka_Rupee("LKR", "Sri Lanka Rupee"),
  Liberian_Dollar("LRD", "Liberian Dollar"),
  Lesotho_Loti("LSL", "Lesotho Loti"),
  Lithuanian_Lita("LTL", "Lithuanian Lita"),
  Latvian_Lat("LVL", "Latvian Lat"),
  Libyan_Dinar("LYD", "Libyan Dinar"),
  Moroccan_Dirham("MAD", "Moroccan Dirham"),
  Moldovan_Leu("MDL", "Moldovan Leu"),
  Malagasy_Franc("MGF", "Malagasy Franc"),
  Macedonian_Denar("MKD", "Macedonian Denar"),
  Myanmar_Kyat("MMK", "Myanmar Kyat"),
  Mongolian_Tugrik("MNT", "Mongolian Tugrik"),
  Macau_Pataca("MOP", "Macau Pataca"),
  Mauritania_Ougulya("MRO", "Mauritania Ougulya"),
  Maltese_Lira("MTL", "Maltese Lira"),
  Mauritius_Rupee("MUR", "Mauritius Rupee"),
  Maldives_Rufiyaa("MVR", "Maldives Rufiyaa"),
  Malawi_Kwacha("MWK", "Malawi Kwacha"),
  Mexican_Peso("MXN", "Mexican Peso"),
  Malaysian_Ringgit("MYR", "Malaysian Ringgit"),
  Mozambique_Metical("MZM", "Mozambique Metical"),
  Namibian_Dollar("NAD", "Namibian Dollar"),
  Nigerian_Naira("NGN", "Nigerian Naira"),
  Nicaragua_Cordoba("NIO", "Nicaragua Cordoba"),
  Norwegian_Krone("NOK", "Norwegian Krone"),
  Nepalese_Rupee("NPR", "Nepalese Rupee"),
  New_Zealand_Dollar("NZD", "New Zealand Dollar"),
  Omani_Rial("OMR", "Omani Rial"),
  Panama_Balboa("PAB", "Panama Balboa"),
  Peruvian_Nuevo_Sol("PEN", "Peruvian Nuevo Sol"),
  Papua_New_Guinea_Kina("PGK", "Papua New Guinea Kina"),
  Philippine_Peso("PHP", "Philippine Peso"),
  Pakistani_Rupee("PKR", "Pakistani Rupee"),
  Polish_Zloty("PLN", "Polish Zloty"),
  Paraguayan_Guarani("PYG", "Paraguayan Guarani"),
  Qatar_Rial("QAR", "Qatar Rial"),
  Romanian_Leu("ROL", "Romanian Leu"),
  Russian_Rouble("RUB", "Russian Rouble"),
  Saudi_Arabian_Riyal("SAR", "Saudi Arabian Riyal"),
  Solomon_Islands_Dollar("SBD", "Solomon Islands Dollar"),
  Seychelles_Rupee("SCR", "Seychelles Rupee"),
  Sudanese_Dinar("SDD", "Sudanese Dinar"),
  Swedish_Krona("SEK", "Swedish Krona"),
  Singapore_Dollar("SGD", "Singapore Dollar"),
  St_Helena_Pound("SHP", "St Helena Pound"),
  Slovenian_Tolar("SIT", "Slovenian Tolar"),
  Slovak_Koruna("SKK", "Slovak Koruna"),
  Sierra_Leone_Leone("SLL", "Sierra Leone Leone"),
  Somali_Shilling("SOS", "Somali Shilling"),
  Surinam_Guilder("SRG", "Surinam Guilder"),
  Sao_Tome_Dobra("STD", "Sao Tome Dobra"),
  El_Salvador_Colon("SVC", "El Salvador Colon"),
  Syrian_Pound("SYP", "Syrian Pound"),
  Swaziland_Lilageni("SZL", "Swaziland Lilageni"),
  Thai_Baht("THB", "Thai Baht"),
  Tunisian_Dinar("TND", "Tunisian Dinar"),
  Tonga_Pa_anga("TOP", "Tonga Pa'anga"),
  Turkish_Lira("TRL", "Turkish Lira"),
  Turkey_Lira("TRY", "Turkey Lira"),
  Trinidad_And_Tobago_Dollar("TTD", "Trinidad And Tobago Dollar"),
  Taiwan_Dollar("TWD", "Taiwan Dollar"),
  Tanzanian_Shilling("TZS", "Tanzanian Shilling"),
  Ukraine_Hryvnia("UAH", "Ukraine Hryvnia"),
  Ugandan_Shilling("UGX", "Ugandan Shilling"),
  US_Dollar("USD", "US Dollar"),
  Uruguayan_New_Peso("UYU", "Uruguayan New Peso"),
  Venezuelan_Bolivar("VEB", "Venezuelan Bolivar"),
  Vietnam_Dong("VND", "Vietnam Dong"),
  Vanuatu_Vatu("VUV", "Vanuatu Vatu"),
  Samoa_Tala("WST", "Samoa Tala"),
  CFA_Franc_BEAC("XAF", "CFA Franc (BEAC)"),
  Silver_Ounces("XAG", "Silver Ounces"),
  Gold_Ounces("XAU", "Gold Ounces"),
  East_Caribbean_Dollar("XCD", "East Caribbean Dollar"),
  CFA_Franc_BCEAO("XOF", "CFA Franc (BCEAO)"),
  Palladium_Ounces("XPD", "Palladium Ounces"),
  Pacific_Franc("XPF", "Pacific Franc"),
  Platinum_Ounces("XPT", "Platinum Ounces"),
  Yemen_Riyal("YER", "Yemen Riyal"),
  Yugoslav_Dinar("YUM", "Yugoslav Dinar"),
  South_African_Rand("ZAR", "South African Rand"),
  Zambian_Kwacha("ZMK", "Zambian Kwacha"),
  Zimbabwe_Dollar("ZWD", "Zimbabwe Dollar");

  private final String currencyCode;
  private final String currencyFullName;
  private static final Mapa<String, String> currencyLookup = new HashMap<String, String>();

  static
  {
    for (Currency currencyEnum : EnumSet.allOf(Currency.class))
    {
      currencyLookup.put(currencyEnum.getCurrencyCode(), currencyEnum.getCurrencyCodeFullName());
    }
  }
  
  private Currency(String currencyCode, String currencyFullName)
  {
    this.currencyCode = currencyCode;
    this.currencyFullName = currencyFullName;
  }

  public String getCurrencyCode()
  {
    return this.currencyCode;
  }

  public String getCurrencyCodeFullName()
  {
    return currencyFullName;
  }
  
  /**Using the currency code as an argument, fetch and return associated full name
   * 
   * @param currencyCode - The Currency Code of which you want to retrieve its full reference name.
   */
  public static String getCurrencyCodeFullName(String currencyCode)
  {
    if(currencyLookup.containsKey(currencyCode))
    {
      return currencyLookup.get(currencyCode);
    }
    return null;
  }

  /***Checks and return currency code*/
  public static String getCurrency(String currencyCode)
  {
    if(currencyLookup.containsKey(currencyCode))
    {
      return currencyCode;
    }
    return null;
  }

}
We have all the required currency codes available, what we now need is the converter handler which will accept currency code for both the currency to convert from and the final currency to convert to.

Calling getConvertedValue(..) passing in the amount, currency code to convert to and from, will build and submit a query against google api, retrieve the result and extract the converted value.

We could have happily done this with Jason dependency to our project to perform the extraction. Yet if you're like me who prefer to keep things lightweight and free from overlayered dependencies, then core classic java URL and string manipulations will surely do the simple tasks.
/**
 * Using this class, we convert currencies to its foreign exchange. This class uses google calculator to perform the conversion process
 * @author Bright Dadson
 */
public final class Converter
{
  private static final String error = "error:";
  private static final String noErrorFound = "\"\"";
  private static final String regExp = "-?\\d+(.\\d+)?";

  private int valueToConvert;
  private String convertFrom, convertTo;

  public Converter()
  {}

  /**
   * Convert submitted value from and to the submitted conversion codes.
   * 
   * @param valueToConvert - Amount to convert
   * @param convertFrom - Currency code to convert from
   * @param convertTo - Currency code to convert to
   * @return
   */
  public double getConvertedValue(int valueToConvert, String convertFrom, String convertTo)
  {
    try
    {
      this.valueToConvert = valueToConvert;
      this.convertFrom = convertFrom;
      this.convertTo = convertTo;
      String convertedValue = extractConvertedValue(convert());
      if (convertedValue != null && isNumeric(convertedValue))
      {
        BigDecimal roundVal = new BigDecimal(convertedValue);
        roundVal.round(new MathContext(2, RoundingMode.HALF_UP));
        return roundVal.doubleValue();
      }
    }
    catch (Exception ex)
    {
      ex.printStackTrace(System.out);
    }
    return 0d;
  }

  /**Connect to Google api using http GET request to perform the currency conversion**/
  private String convert()
  {
    try
    {
      String code = String.valueOf("/ig/calculator?h1=en&q=" + valueToConvert + "" + convertFrom + "=?" + convertTo);
      URL converterUrl = new URL("http://www.google.com" + code);
      URLConnection urlConnection = converterUrl.openConnection();

      InputStream inputStream = urlConnection.getInputStream();
      BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));

      String conversionResult = bufferedReader.readLine();
      bufferedReader.close();
      inputStream.close();
      urlConnection = null;

      return conversionResult;
    }
    catch (Exception e)
    {
      e.printStackTrace(System.out);
    }
    return null;
  }

  /**If error is found within the response string, throw runtime exception to report, else parse the result for extraction**/
  private String extractConvertedValue(String convertedResult) throws Exception
  {
    String[] convertedResStrings = convertedResult.split(",");
    for (int i = 0; i < convertedResStrings.length; i++)
    {
      if ((convertedResStrings[i].contains(error)) && convertedResStrings[i].split(" ")[1].equals(noErrorFound))
      {
        String convertedValue = extract(convertedResStrings[i - 1]);
        if (!(convertedValue.isEmpty()))
        {
          return convertedValue;
        }
      }
      else if ((convertedResStrings[i].contains(error)) && !convertedResStrings[i].split(" ")[1].equals(noErrorFound))
      {
        throw new RuntimeException("Error occured while converting amount: "+convertedResStrings[i].split(" ")[1]);
      }
    }
    return null;
  }

  private String extract(String str)
  {
    StringBuffer sBuffer = new StringBuffer();
    Pattern p = Pattern.compile(regExp);
    Matcher m = p.matcher(str);
    if (m.find())
    {
      sBuffer.append(m.group());
    }
    return sBuffer.toString();
  }

  private boolean isNumeric(String str)
  {
    return str.matches(regExp);
  }

}


Example usage
  public static void main(String[] args)
  {
   Converter converter = new Converter();
   //Convert 12GBP to USD and print the result to console
   System.out.println(converter.getConvertedValue(12, Currency.British_Pound.getCurrencyCode(),
                       Currency.US_Dollar.getCurrencyCode()));
  }
As you can see, it is pretty straight forward and free from injected dependencies. If you have read from the top to the bottom of this post - chances are you have already fired your IDE and started playing with this. Do let me know of any enchancement, suggestions or if this directed you on the right selective path.