org.eclipse.andmore.android.localization.translators.GoogleTranslator.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.andmore.android.localization.translators.GoogleTranslator.java

Source

/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * 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 org.eclipse.andmore.android.localization.translators;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.Authenticator;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.eclipse.andmore.android.common.log.AndmoreLogger;
import org.eclipse.andmore.android.common.log.UsageDataConstants;
import org.eclipse.andmore.android.common.proxy.ProxyAuthenticator;
import org.eclipse.andmore.android.common.utilities.EclipseUtils;
import org.eclipse.andmore.android.json.JSONArray;
import org.eclipse.andmore.android.json.JSONObject;
import org.eclipse.andmore.android.json.JSONPair;
import org.eclipse.andmore.android.json.JSONString;
import org.eclipse.andmore.android.json.JSONValue;
import org.eclipse.andmore.android.json.Jason;
import org.eclipse.andmore.android.localization.translators.i18n.TranslateNLS;
import org.eclipse.core.internal.net.ProxyManager;
import org.eclipse.core.net.proxy.IProxyData;
import org.eclipse.core.net.proxy.IProxyService;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.osgi.util.NLS;
import org.eclipse.sequoyah.localization.tools.datamodel.node.TranslationResult;
import org.eclipse.sequoyah.localization.tools.extensions.classes.ITranslator;
import org.eclipse.sequoyah.localization.tools.extensions.implementation.generic.ITranslateDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Link;

/**
 * Uses the Google translator web service (via executing a http request and
 * parsing the answer) in order to translate a text string.
 * 
 * Google v2 supports only one source and one destination per request, but
 * support many words.
 * 
 */
@SuppressWarnings("restriction")
public final class GoogleTranslator extends ITranslator implements GoogleTranslatorConstants {

    /**
     * Translate all words from some source language to destination language.
     * This method handles any needed splits in original request due to API
     * limitations.
     * 
     * @param words
     *            the words being translated
     * @param fromLanguage
     *            the origin language
     * @param toLanguage
     *            the destination language
     * @param monitor
     *            progress monitor
     * @return a list of translation results
     * @throws Exception
     */
    public List<TranslationResult> translate(List<String> words, String fromLanguage, String toLanguage,
            IProgressMonitor monitor) throws Exception {
        List<TranslationResult> translationResults = new ArrayList<TranslationResult>();
        int characterCount = 0;
        int MAX_REQUEST_SIZE = getMaxQuerySize(fromLanguage, toLanguage);

        int maxRequestSize = MAX_REQUEST_SIZE;
        List<String> wordsToTranslate = new ArrayList<String>();

        Iterator<String> wordsIterator = words.iterator();
        int counter = 0;

        while (wordsIterator.hasNext()) {
            maxRequestSize -= STRING_PAR.length();
            String word = wordsIterator.next();
            /*
             * try to add some more words to the request. If there is no more
             * room left to request, execute the translation and continue
             * afterwards
             */
            if (characterCount + word.length() < maxRequestSize) {
                wordsToTranslate.add(word);
                characterCount += word.length();
            } else {
                URL translationURL = createTranslationURL(wordsToTranslate, fromLanguage, toLanguage);
                String httpRequestResponseBody = executeHttpGetRequest(translationURL);

                List<String> responses = parseTranslationResponse(httpRequestResponseBody);

                for (int i = 0; i < wordsToTranslate.size(); i++) {
                    translationResults.add(new TranslationResult(words.get(counter++), this, responses.get(i),
                            fromLanguage, toLanguage, Calendar.getInstance().getTime(), true));
                }
                characterCount = 0;
                maxRequestSize = MAX_REQUEST_SIZE;
                wordsToTranslate.clear();
                wordsToTranslate.add(word);
            }
        }

        /*
         * execute the request with remaining sentences
         */
        if (!wordsToTranslate.isEmpty()) {
            URL translationURL = createTranslationURL(wordsToTranslate, fromLanguage, toLanguage);
            String httpRequestResponseBody = executeHttpGetRequest(translationURL);
            List<String> responses = parseTranslationResponse(httpRequestResponseBody);

            for (int i = 0; i < wordsToTranslate.size(); i++) {
                translationResults.add(new TranslationResult(words.get(counter++), this, responses.get(i),
                        fromLanguage, toLanguage, Calendar.getInstance().getTime(), true));
            }
        }

        return translationResults;
    }

    /**
     * Translates a string.
     * 
     * @param text
     *            The String to be translated.
     * @param from
     *            Original language.
     * @param to
     *            Target language.
     * @return The translated String.
     * @throws Exception
     *             on errors.
     */
    @Override
    public TranslationResult translate(final String text, String from, String to) throws Exception {
        TranslationResult translationResult;

        if (text != null && !text.equals("") && text.length() < getMaxQuerySize(from, to)) //$NON-NLS-1$
        {
            String httpResult = ""; //$NON-NLS-1$
            URL url = null;

            // Creates the URL to be used as request
            try {
                List<String> sentences = new ArrayList<String>();
                sentences.add(text);
                url = createTranslationURL(sentences, from, to);
                httpResult = executeHttpGetRequest(url);
                translationResult = new TranslationResult(text, this, parseTranslationResponse(httpResult).get(0),
                        from, to, Calendar.getInstance().getTime(), true);
            } catch (UnsupportedEncodingException e) {
                throw new HttpException(TranslateNLS.GoogleTranslator_Error_UnsupportedEncoding + ENCODING_TYPE);
            }

        } else if (text.length() >= getMaxQuerySize(from, to)) {
            throw new Exception(TranslateNLS.GoogleTranslator_Error_QueryTooBig);
        } else {
            translationResult = new TranslationResult(text, this, text, from, to, new Date(), true);
        }

        try {
            String descriptionToLog = UsageDataConstants.KEY_TRANSLATION_PROVIDER + UsageDataConstants.VALUE_GOOGLE
                    + UsageDataConstants.SEPARATOR + UsageDataConstants.KEY_TRANSLATION_FROM_LANG + from
                    + UsageDataConstants.SEPARATOR + UsageDataConstants.KEY_TRANSLATION_TO_LANG + to;
            AndmoreLogger.collectUsageData(UsageDataConstants.WHAT_LOCALIZATION_AUTOMATICTRANSLATION,
                    UsageDataConstants.KIND_LOCALIZATION, descriptionToLog, TranslationPlugin.PLUGIN_ID,
                    TranslationPlugin.getDefault().getBundle().getVersion().toString());
        } catch (Throwable t) {
            // Do nothing, usage data collection is for statistics and should
            // not prevent tool from work
        }

        return translationResult;

    }

    /**
     * Translate a single word from one language to several other
     * 
     * @param sentences
     *            sentence being translated
     * @param fromLanguage
     *            source language
     * @param toLanguages
     *            target languages
     */
    @Override
    public List<TranslationResult> translate(String sentence, String fromLanguage, List<String> toLanguages)
            throws Exception {
        List<TranslationResult> translationResults = new ArrayList<TranslationResult>();

        // Lets start with some checkings, one can never be too careful
        if (fromLanguage == null || toLanguages == null || toLanguages.isEmpty()) {
            // We must have a FROM and a TO languages
            throw new IllegalArgumentException(TranslateNLS.GoogleTranslator_Error_ToAndFromLanguagesAreEmpty);
        } else if (sentence == null || sentence.equals("")) //$NON-NLS-1$
        {
            // We must have something to be translated
            sentence = ""; //$NON-NLS-1$
            if (toLanguages.size() == 1) {
                translationResults.add(new TranslationResult("", this, "", fromLanguage, //$NON-NLS-1$ //$NON-NLS-2$
                        toLanguages.get(0), new Date(), true));
            }
        } else if (sentence.length() >= getMaxQuerySize(fromLanguage, toLanguages.get(0))) {
            throw new Exception(TranslateNLS.GoogleTranslator_Error_QueryTooBig);
        }
        /*
         * Delegate the translation to another method
         */
        else {
            for (String toLanguage : toLanguages) {
                translationResults.add(translate(sentence, fromLanguage, toLanguage));
            }
        }

        try {
            // Collecting usage data for statistic purposes
            String descriptionToLog = UsageDataConstants.KEY_TRANSLATION_PROVIDER + UsageDataConstants.VALUE_GOOGLE
                    + UsageDataConstants.SEPARATOR + UsageDataConstants.KEY_TRANSLATION_FROM_LANG + fromLanguage
                    + UsageDataConstants.SEPARATOR + UsageDataConstants.KEY_TRANSLATION_TO_LANG
                    + "several languages"; //$NON-NLS-1$

            AndmoreLogger.collectUsageData(UsageDataConstants.WHAT_LOCALIZATION_AUTOMATICTRANSLATION,
                    UsageDataConstants.KIND_LOCALIZATION, descriptionToLog, TranslationPlugin.PLUGIN_ID,
                    TranslationPlugin.getDefault().getBundle().getVersion().toString());
        } catch (Throwable t) {
            // Do nothing, usage data collection is for statistics and should
            // not prevent tool from work
        }

        return translationResults;
    }

    /**
     * Translate a list of sentences from one language to another
     * 
     * @param sentences
     *            sentences being translated
     * @param fromLanguage
     *            source language
     * @param toLanguage
     *            target language
     * @param monitor
     *            progress monitor
     */
    @Override
    public List<TranslationResult> translateAll(List<String> sentences, String fromLanguage, String toLanguage,
            IProgressMonitor monitor) throws Exception {
        // The result (duhh)
        List<TranslationResult> translationResults = new ArrayList<TranslationResult>();

        // Lets start with some checkings, one can never be too carefull
        if (fromLanguage == null || toLanguage == null) {
            // We must have a FROM and a TO languages
            throw new IllegalArgumentException(TranslateNLS.GoogleTranslator_Error_ToAndFromLanguagesAreEmpty);
        } else if (sentences == null || sentences.size() == 0) {
            // We must have something to be translated
            throw new IllegalArgumentException(TranslateNLS.GoogleTranslator_Error_NoAvailableData);
        } else {
            translationResults.addAll(translate(sentences, fromLanguage, toLanguage, monitor));
        }

        try {
            // Collecting usage data for statistic purposes
            String descriptionToLog = UsageDataConstants.KEY_TRANSLATION_PROVIDER + UsageDataConstants.VALUE_GOOGLE
                    + UsageDataConstants.SEPARATOR + UsageDataConstants.KEY_TRANSLATION_FROM_LANG + fromLanguage
                    + UsageDataConstants.SEPARATOR + UsageDataConstants.KEY_TRANSLATION_TO_LANG + toLanguage;

            AndmoreLogger.collectUsageData(UsageDataConstants.WHAT_LOCALIZATION_AUTOMATICTRANSLATION,
                    UsageDataConstants.KIND_LOCALIZATION, descriptionToLog, TranslationPlugin.PLUGIN_ID,
                    TranslationPlugin.getDefault().getBundle().getVersion().toString());
        } catch (Throwable t) {
            // Do nothing, usage data collection is for statistics and should
            // not prevent tool from work
        }

        return translationResults;
    }

    /**
     * Translates a list of strings from a list of given languages to other
     * given languages (given by a list, or course), using google Ajax API's for
     * that.
     * 
     * The three lists have the same number of elements.
     * 
     * This comment feels like the
     * "Three Swatch watch switching witches watched switched Swatch watch witches switch"
     * , but I'll let it here anyway.
     */
    @Override
    public List<TranslationResult> translateAll(List<String> words, List<String> fromLanguage,
            List<String> toLanguage, IProgressMonitor monitor) throws Exception {
        // The result (duhh)
        List<TranslationResult> translationResults = new ArrayList<TranslationResult>();

        // Lets start with some checkings, one can never be too carefull
        if (fromLanguage == null || toLanguage == null) {
            // We must have a FROM and a TO languages
            throw new IllegalArgumentException(TranslateNLS.GoogleTranslator_Error_ToAndFromLanguagesAreEmpty);
        } else if (words == null || words.size() == 0) {
            // We must have something to be translated
            throw new IllegalArgumentException(TranslateNLS.GoogleTranslator_Error_NoAvailableData);
        } else {
            translationResults.addAll(groupAndTranslate(words, fromLanguage, toLanguage, monitor));
        }

        try {
            // Collecting usage data for statistic purposes
            String descriptionToLog = UsageDataConstants.KEY_TRANSLATION_PROVIDER + UsageDataConstants.VALUE_GOOGLE
                    + UsageDataConstants.SEPARATOR + UsageDataConstants.KEY_TRANSLATION_FROM_LANG + fromLanguage
                    + UsageDataConstants.SEPARATOR + UsageDataConstants.KEY_TRANSLATION_TO_LANG + toLanguage;

            AndmoreLogger.collectUsageData(UsageDataConstants.WHAT_LOCALIZATION_AUTOMATICTRANSLATION,
                    UsageDataConstants.KIND_LOCALIZATION, descriptionToLog, TranslationPlugin.PLUGIN_ID,
                    TranslationPlugin.getDefault().getBundle().getVersion().toString());
        } catch (Throwable t) {
            // Do nothing, usage data collection is for statistics and should
            // not prevent tool from work
        }

        return translationResults;
    }

    private List<TranslationResult> groupAndTranslate(List<String> words, List<String> fromLanguage,
            List<String> toLanguage, IProgressMonitor monitor) throws Exception {

        List<TranslationResult> results = new ArrayList<TranslationResult>();
        /*
         * Get all words with same source and same destination and group them to
         * make translation more efficient Notice that this implementation
         * relies on input condition that all lists have the same size and all
         * elements with same index makes one request
         */

        class StringItem {
            private String sentence = "";

            private int orderNumber = -1;

            public StringItem(String sentence, int orderNumber) {
                this.sentence = sentence;
                this.orderNumber = orderNumber;
            }

            public Integer getOrderNumber() {
                return orderNumber;
            }

            public String getSentence() {
                return sentence;
            }

            @Override
            public String toString() {
                return orderNumber + "|" + sentence;
            }
        }

        /*
         * This map holds a list of group of stringitems being translated. These
         * items have the same from and to languages Using linkedHashMap to keep
         * insertionOrder
         */
        Map<String, List<StringItem>> sameSourceDestMap = new LinkedHashMap<String, List<StringItem>>();
        Map<Integer, TranslationResult> translations = new HashMap<Integer, TranslationResult>();

        // group things with same from and to languages
        for (int i = 0; i < words.size(); i++) {
            /*
             * Check if one of the words are big enough to not be translated
             */
            if (words.get(i).length() >= getMaxQuerySize(fromLanguage.get(i), toLanguage.get(i))
                    - STRING_PAR.length() - 1) {
                throw new Exception(TranslateNLS.GoogleTranslator_Error_QueryTooBig);
            }

            String key = fromLanguage.get(i) + "|" + toLanguage.get(i);
            List<StringItem> itemsToTranslate = sameSourceDestMap.get(key);
            if (itemsToTranslate == null) {
                itemsToTranslate = new ArrayList<StringItem>();
                sameSourceDestMap.put(key, itemsToTranslate);
            }
            itemsToTranslate.add(new StringItem(words.get(i), i));
        }

        for (String key : sameSourceDestMap.keySet()) {
            List<StringItem> itemsToTranslate = sameSourceDestMap.get(key);
            List<String> items = new ArrayList<String>();
            for (StringItem item : itemsToTranslate) {
                items.add(item.getSentence());
            }
            List<TranslationResult> tempResults = translate(items, key.split("\\|")[0], key.split("\\|")[1],
                    monitor);
            for (int i = 0; i < itemsToTranslate.size(); i++) {
                translations.put(itemsToTranslate.get(i).getOrderNumber(), tempResults.get(i));
            }

        }

        for (int i = 0; i < words.size(); i++) {
            results.add(translations.get(i));
        }

        return results;
    }

    private int getMaxQuerySize(String fromLanguage, String toLanguage) {
        return MAX_QUERY_SIZE - TRANSLATE_URL_WITHOUT_PARAMS.length() - API_KEY_PARAM.length()
                - getApiKey().length() - SOURCE_PAR.length() - fromLanguage.length() - TARGET_PARAM.length()
                - toLanguage.length();
    }

    /**
     * Parse the translation response of the http request
     * 
     * @param httpRequestResponseBody
     *            the response body
     * @param sourceLanguage
     *            the source language
     * @param destinationLanguage
     *            the destination language
     * @return a list of String objects for the strings translated for
     *         source/destination languages pair
     */
    private List<String> parseTranslationResponse(String httpRequestResponseBody) {
        JSONPair translationSection = getTranslationSection(httpRequestResponseBody);

        return getTranslations(translationSection);
    }

    /*
     * { "data": { "translations": [ { "translatedText": "bla bla bla" }, {
     * "translatedText": "foo bar" } ] } }
     */
    private JSONPair getTranslationSection(String httpRequestResponseBody) {
        Jason ripper = new Jason(httpRequestResponseBody);
        JSONPair translationsSection = null;
        Iterator<JSONObject> jsonIterator = ripper.getJSON().iterator();
        while (translationsSection == null && jsonIterator.hasNext()) {
            translationsSection = findPair(jsonIterator.next(), TRANSLATIONS_SECTION);
        }

        return translationsSection;
    }

    private static JSONPair findPair(JSONValue origin, String name) {
        JSONPair pair = null;
        if (origin instanceof JSONObject) {
            JSONObject object = (JSONObject) origin;
            Iterator<JSONPair> pairs = object.getValue().iterator();
            while (pair == null && pairs.hasNext()) {
                JSONPair jsonPair = pairs.next();
                if (jsonPair.getName().equals(name)) {
                    pair = jsonPair;
                } else {
                    pair = findPair(jsonPair.getValue(), name);
                }

            }
        }

        return pair;
    }

    private List<String> getTranslations(JSONPair translationSection) {
        List<String> translations = new ArrayList<String>();
        if (translationSection.getValue() instanceof JSONArray) {
            JSONArray translationsArray = (JSONArray) translationSection.getValue();
            for (JSONValue translationObject : translationsArray.getValue()) {
                translations.add(getTranslation(translationObject));
            }
        }

        return translations;
    }

    /**
     * @param translationObject
     *            { "translatedText": "Hallo Welt" }
     * @return pure translation Hallo Welt
     */
    private String getTranslation(JSONValue translationObject) {
        String translation = null;
        if (translationObject instanceof JSONObject) {
            JSONObject jsonObject = (JSONObject) translationObject;
            translation = ((JSONString) jsonObject.getValue().iterator().next().getValue()).getValue();
        }
        return translation != null ? fixHTMLTags(translation) : null;
    }

    private URL createTranslationURL(List<String> wordsToTranslate, String fromLanguage, String toLanguage)
            throws UnsupportedEncodingException {
        URL translationURL = null;

        // We need to unescape the ' (apostrophe) before sending it to
        // translation
        String regex = "\\\\'"; //$NON-NLS-1$
        Pattern pattern = Pattern.compile(regex);

        StringBuilder urlBuilder = new StringBuilder(TRANSLATE_URL_WITHOUT_PARAMS);
        urlBuilder.append(URL_PARAMETERS.replace("#FROM#", fromLanguage).replace("#TO#", toLanguage)
                .replace("#API_KEY#", getApiKey()));

        for (String word : wordsToTranslate) {
            String wordToTranslate = pattern.matcher(word).replaceAll("'");
            urlBuilder.append(STRING_PAR);
            urlBuilder.append(URLEncoder.encode(wordToTranslate, ENCODING_TYPE));
        }

        try {
            translationURL = new URL(urlBuilder.toString());
        } catch (MalformedURLException e) {
            AndmoreLogger.error(getClass(), "Unable to create translation URL", e);
        }

        return translationURL;
    }

    /**
     * The Android localization files text must accept three HTML tags: i, b and
     * u. Nevertheless, google translator returns the close part of this tags
     * with a extra-space that makes the sintax wrong. This method will try to
     * fix it.
     * 
     * @param originalText
     * @return the text with the tags fixed
     */
    private static String fixHTMLTags(String originalText) {
        String result = ""; //$NON-NLS-1$
        if (originalText != null) {
            result = originalText;
        }
        result = originalText.replaceAll("</ b>", "</b>"); //$NON-NLS-1$ //$NON-NLS-2$
        result = originalText.replaceAll("</ i>", "</i>"); //$NON-NLS-1$ //$NON-NLS-2$
        result = originalText.replaceAll("</ u>", "</u>"); //$NON-NLS-1$ //$NON-NLS-2$

        result = originalText.replaceAll("</ B>", "</B>"); //$NON-NLS-1$ //$NON-NLS-2$
        result = originalText.replaceAll("</ I>", "</I>"); //$NON-NLS-1$ //$NON-NLS-2$
        result = originalText.replaceAll("</ U>", "</U>"); //$NON-NLS-1$ //$NON-NLS-2$

        result = originalText.replaceAll(" \\\\ n ", " \\\\n "); //$NON-NLS-1$ //$NON-NLS-2$

        return result;
    }

    /**
     * Creates an HTTP request with the URL, execute it as a get, and returns
     * the a string with the result.
     * 
     * @param url
     *            URL to be executed.
     * @return String with the URL execution result.
     * @throws IOException
     *             If an exception occurs on transport
     * @throws HttpException
     *             If an exception occurs on the protocol
     * @throws Exception
     *             on error.
     */
    protected static String executeHttpGetRequest(final URL url) throws HttpException {

        // Checking query size due to google policies
        if (url.toString().length() > MAX_QUERY_SIZE) {
            throw new HttpException(TranslateNLS.GoogleTranslator_Error_QueryTooBig);
        }

        // Try to retrieve proxy configuration to use if necessary
        IProxyService proxyService = ProxyManager.getProxyManager();
        IProxyData proxyData = null;
        if (proxyService.isProxiesEnabled() || proxyService.isSystemProxiesEnabled()) {
            Authenticator.setDefault(new ProxyAuthenticator());
            String urlStr = url.toString();
            if (urlStr.startsWith("https")) {
                proxyData = proxyService.getProxyData(IProxyData.HTTPS_PROXY_TYPE);
                AndmoreLogger.debug(GoogleTranslator.class, "Using https proxy"); //$NON-NLS-1$
            } else if (urlStr.startsWith("http")) {
                proxyData = proxyService.getProxyData(IProxyData.HTTP_PROXY_TYPE);
                AndmoreLogger.debug(GoogleTranslator.class, "Using http proxy"); //$NON-NLS-1$
            } else {
                AndmoreLogger.debug(GoogleTranslator.class, "Not using any proxy"); //$NON-NLS-1$
            }
        }

        // Creates the http client and the method to be executed
        HttpClient client = null;
        client = new HttpClient();

        // If there is proxy data, work with it
        if (proxyData != null) {
            if (proxyData.getHost() != null) {
                // Sets proxy host and port, if any
                client.getHostConfiguration().setProxy(proxyData.getHost(), proxyData.getPort());
            }

            if (proxyData.getUserId() != null && proxyData.getUserId().trim().length() > 0) {
                // Sets proxy user and password, if any
                Credentials cred = new UsernamePasswordCredentials(proxyData.getUserId(),
                        proxyData.getPassword() == null ? "" : proxyData.getPassword()); //$NON-NLS-1$
                client.getState().setProxyCredentials(AuthScope.ANY, cred);
            }
        }

        // Creating the method to be executed, the URL at this point is enough
        // because it is complete
        GetMethod method = new GetMethod(url.toString());

        // Set method to be retried three times in case of error
        method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
                new DefaultHttpMethodRetryHandler(RETRIES, false));

        method.setRequestHeader(REFERER_HEADER, REFERER_SITE);

        // Set the connection timeout
        client.getHttpConnectionManager().getParams().setConnectionTimeout(new Integer(TIMEOUT));

        String result = ""; //$NON-NLS-1$
        try {
            // Execute the method.
            int statusCode;
            try {
                statusCode = client.executeMethod(method);
                result = method.getResponseBodyAsString(MAX_SIZE);
            } catch (IOException e) {
                throw new HttpException(TranslateNLS.GoogleTranslator_Error_CannotConnectToServer + e.getMessage());
            }

            checkStatusCode(statusCode, result);

            // Unescape any possible unicode char
            result = unescapeUnicode(result);

            // Unescape any possible HTML sequence
            result = unescapeHTML(result);

        }

        finally {
            // Release the connection.
            method.releaseConnection();
        }

        return result;
    }

    private static void checkStatusCode(int statusCode, String response) throws HttpException {
        switch (statusCode) {
        case HttpStatus.SC_OK:
            // do nothing
            break;
        case HttpStatus.SC_BAD_REQUEST:
            throw new HttpException(NLS.bind(TranslateNLS.GoogleTranslator_ErrorMessageExecutingRequest,
                    getErrorMessage(response)));

        case HttpStatus.SC_REQUEST_URI_TOO_LONG:
            throw new HttpException(TranslateNLS.GoogleTranslator_Error_QueryTooBig);

        case HttpStatus.SC_FORBIDDEN:
            throw new HttpException(NLS.bind(TranslateNLS.GoogleTranslator_ErrorMessageNoValidTranslationReturned,
                    getErrorMessage(response)));

        default:
            throw new HttpException(NLS.bind(TranslateNLS.GoogleTranslator_Error_HTTPRequestError,
                    new Object[] { statusCode, getErrorMessage(response) }));

        }
    }

    /**
     * According to APIv2, the error message is in the end of the response {
     * "error": { "errors": [ { "domain": "global", "reason": "invalid",
     * "message": "Invalid Value" } ], "code": 400, "message": "Invalid Value" }
     * }
     * 
     * @param response
     *            the method response body
     * @return the error message
     */
    private static String getErrorMessage(String response) {
        Jason ripper = new Jason(response);
        JSONPair translationsSection = null;
        Iterator<JSONObject> jsonIterator = ripper.getJSON().iterator();
        while (translationsSection == null && jsonIterator.hasNext()) {
            translationsSection = findPair(jsonIterator.next(), MESSAGE_TEXT);
        }

        return translationsSection != null ? ((JSONString) translationsSection.getValue()).getValue() : null;
    }

    /**
     * Unescape any HTML sequence that exists inside the string. For example,
     * the sequence &#39; will be changed to the ' symbol
     * 
     * @param source
     *            original text
     * @return the result
     */
    private static String unescapeHTML(String source) {
        Pattern p = Pattern.compile("&#([0-9]+);"); //$NON-NLS-1$
        String result = source;
        Matcher m = p.matcher(result);
        while (m.find()) {
            char c = (char) Integer.parseInt(m.group(1));
            if (c == "'".charAt(0)) //$NON-NLS-1$
            {
                // Apostrophes must be escaped by preceding it with a backslash
                // (\) on the XML file
                result = result.replaceAll(m.group(0), "\\\\'"); //$NON-NLS-1$
            } else {
                result = result.replaceAll(m.group(0), "" + c); //$NON-NLS-1$
            }
        }

        return result;
    }

    /**
     * Unescape any Unicode sequence that exists inside the string.
     * 
     * For example, the sequence \u0000 will be changed to the symbol
     * correnponded to the 0000 unicode value.
     * 
     * @param source
     *            original text
     * @return the result
     */
    private static String unescapeUnicode(String source) {
        int i = 0, len = source.length();
        char c;
        StringBuffer buffer = new StringBuffer(len);
        while (i < len) {
            c = source.charAt(i++);
            if (c == '\\') {
                if (i < len) {
                    c = source.charAt(i++);
                    if (c == 'u') {
                        c = (char) Integer.parseInt(source.substring(i, i + 4), 16);
                        i += 4;
                    }
                }
            }
            buffer.append(c);
        }
        return buffer.toString();
    }

    private static String getApiKey() {
        String apiKey = GoogleTranslatorConstants.API_KEY;
        IPreferenceStore prefStore = TranslationPlugin.getDefault().getPreferenceStore();
        if (!prefStore.isDefault(GoogleTranslatorConstants.API_KEY_VALUE_PREFERENCE)) {
            apiKey = prefStore.getString(GoogleTranslatorConstants.API_KEY_VALUE_PREFERENCE);
            if (apiKey == null) {
                apiKey = GoogleTranslatorConstants.API_KEY;
            }
        }

        return apiKey;
    }

    @Override
    public Composite createCustomArea(Composite parent, final ITranslateDialog dialog) {
        Composite mainComposite = new Composite(parent, SWT.NONE);
        mainComposite.setLayout(new GridLayout(1, false));

        final Link prefPageLink = new Link(mainComposite, SWT.NONE);
        prefPageLink.setText(TranslateNLS.GoogleTranslator_ChangeAPIkeyLabel);
        prefPageLink.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true, true, 1, 1));
        prefPageLink.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                EclipseUtils.openPreference(prefPageLink.getShell(),
                        "org.eclipse.andmore.android.localization.translators.preferencepage"); //$NON-NLS-1$
                dialog.validate();
            }
        });
        mainComposite.setLayoutData(new GridData(SWT.RIGHT, SWT.BOTTOM, false, false));
        return mainComposite;
    }

    @Override
    public String canTranslate(String fromLanguage, String[] toLanguages) {
        return getApiKey() == null || GoogleTranslatorConstants.API_KEY.equals(getApiKey())
                ? TranslateNLS.GoogleTranslator_ErrorNoAPIkeySet
                : null;
    }
}