public class AnalysisException extends Exception {
	private static final long serialVersionUID = 9063173251339905559L;
	
	private int responseCode;
    private String responseBody;

    public AnalysisException(int responseCode, String responseBody) {
        this.responseBody = responseBody;
        this.responseCode = responseCode;
    }

    @Override
    public String getMessage() {
        return String.format("TextRazor returned HTTP Code %d with response: %s", responseCode, responseBody);
    }

    public String getResponseBody() {
        return responseBody;
    }
    
    public int getResponseCode() {
        return responseCode;
    }
} public class NetworkException extends IOException {
    
	private static final long serialVersionUID = -821976320048704953L;

	public NetworkException(String message, Throwable cause) {
        super(message);
        initCause(cause);
    }
} public class AllDictionariesResponse extends TextRazorResponse {
	private List<Dictionary> dictionaries = new ArrayList<Dictionary>();

	public List<Dictionary> getDictionaries() {
		return dictionaries;
	}	
} public class AllEntriesResponse extends TextRazorResponse {

	private PagedAllEntries response;

	public PagedAllEntries getResponse() {
		return response;
	}
	
} public class Dictionary {
	public static class Builder {
		private String id;
		private String matchType = "token";
		private Boolean caseInsensitive = false;
		private String language = "any";
		
		public Builder setId(String id) {
			this.id = id;
			return this;
		}
		
		public Builder setMatchType(String matchType) {
			this.matchType = matchType;
			return this;
		}
		
		public Builder setCaseInsensitive(Boolean caseInsensitive) {
			this.caseInsensitive = caseInsensitive;
			return this;
		}
		
		public Builder setLanguage(String language) {
			this.language = language;
			return this;
		}
		
		public Dictionary build() {
			return new Dictionary(id, matchType, caseInsensitive, language);
		}	
	}
	
	private String id;
	private String matchType;
	private Boolean caseInsensitive;
	private String language;
	
	public Dictionary() {}
	
	public Dictionary(String id, String matchType, Boolean caseInsensitive, String language) {
		this.id = id;
		this.matchType = matchType;
		this.caseInsensitive = caseInsensitive;
		this.language = language;
	}

	public static Builder builder() {
        return new Builder();
    }
	
	public String getId() {
		return id;
	}
	
	public String getMatchType() {
		return matchType;
	}
	
	public Boolean getCaseInsensitive() {
		return caseInsensitive;
	}
	
	public String getLanguage() {
		return language;
	}
	
	
} public class DictionaryEntry {

	public static class Builder {
		private String id;
		
		private String text;
		
		private Map<String, List<String>> data = new HashMap<String, List<String>>();

		public Builder setId(String id) {
			this.id = id;
			return this;
		}

		public Builder setText(String text) {
			this.text = text;
			return this;
		}

		public Builder addData(String key, List<String> value) {
			this.data.put(key, value);
			return this;
		}
		
		public DictionaryEntry build() {
			return new DictionaryEntry(id, text, data);
		}	
	}
	
	private String id;
	
	private String text;
	
	private Map<String, List<String>> data = new HashMap<String, List<String>>();

	public DictionaryEntry() {}
	
	private DictionaryEntry(String id, String text, Map<String, List<String>> data) {
		this.id = id;
		this.text = text;
		this.data = data;
	}
	
	public static Builder builder() {
        return new Builder();
    }
	
	public String getId() {
		return id;
	}

	public String getText() {
		return text;
	}

	public Map<String, List<String>> getData() {
		return data;
	}
} public class GetDictionaryEntryResponse extends TextRazorResponse {

	private DictionaryEntry response;

	public DictionaryEntry getResponse() {
		return response;
	}
} public class GetDictionaryResponse extends TextRazorResponse  {
	
	private Dictionary response;

	public Dictionary getResponse() {
		return response;
	}
} public class PagedAllEntries {
	
	int total;
	
	int limit;
	
	int offset;
	
	List<DictionaryEntry> entries;

	public int getTotal() {
		return total;
	}

	public int getLimit() {
		return limit;
	}

	public int getOffset() {
		return offset;
	}

	public List<DictionaryEntry> getEntries() {
		return entries;
	}
	
} public class QueryBuilder {

	private StringBuilder path = new StringBuilder();
	
	private boolean firstComponent = true;
	
	public QueryBuilder addParam(String name, String value) throws UnsupportedEncodingException {
		if (null == name || null == value || name.isEmpty() || value.isEmpty()) {
			return this;
		}
		
		if (firstComponent) {
			firstComponent = false;
		}
		else {
			path.append("&");
		}
		
		path.append(URLEncoder.encode(name, "UTF-8"));
		path.append("=");
		path.append(URLEncoder.encode(value, "UTF-8"));
		
		return this;
	}
	
	public QueryBuilder addParam(String name, boolean value) throws UnsupportedEncodingException {
		return addParam(name, value ? "true" : "false");
	}
	
	public QueryBuilder addParam(String name, List<String> values) throws UnsupportedEncodingException {
		if (null != values) {
			for (String value : values) {
				addParam(name, value);
			}
		}
		
		return this;
	}
	
	public QueryBuilder addParam(String name, Integer value) throws UnsupportedEncodingException {
		if (null != value) {
			return addParam(name, value.toString());
		}
		
		return this;
	}
	
	public String build() {
		return path.toString();
	}
	
} public class TextRazorConnection {
	
	protected enum ContentType {
		FORM, JSON, CSV
	}
	
	private String apiKey;

	private String textrazorEndpoint;
	private String secureTextrazorEndpoint;
	
	private boolean doCompression;
	private boolean doEncryption;
	
	private Proxy proxy = null;
	
	public TextRazorConnection(String apiKey) {
		if (apiKey == null) {
			throw new RuntimeException("You must provide a TextRazor API key");
		}
		
		this.apiKey = apiKey;

		this.textrazorEndpoint = "http://api.textrazor.com/";
		this.secureTextrazorEndpoint = "https://api.textrazor.com/";
		
		this.doEncryption = true;
		this.doCompression = true;
	}
	
	protected <ResponseType> ResponseType sendRequest(
			String path, 
			String requestBody,
			ContentType contentType,
			String method, 
			Class<ResponseType> responseClass) throws AnalysisException, NetworkException {
		URL url = null;
		HttpURLConnection connection = null;
		
		try {
			if (doEncryption) {
				url = new URL(secureTextrazorEndpoint + path);
			}
			else {
				url = new URL(textrazorEndpoint + path);
			}
			
			if (null != proxy) {
				connection = (HttpURLConnection)url.openConnection(proxy);
			}
			else {
				connection = (HttpURLConnection)url.openConnection();
			}
			
			connection.setRequestMethod(method);
			connection.setDoOutput(true);
			
			switch (contentType) {
			case JSON:
				connection.setRequestProperty("Content-Type", "application/json");
				break;
			case FORM:
				connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
				break;
			case CSV:
				connection.setRequestProperty("Content-Type", "application/csv");
				break;
			}
			
			if (null != requestBody) {
				connection.setRequestProperty("Content-Length", ""+ requestBody.length());
			}
			
			connection.setRequestProperty("X-TextRazor-Key", apiKey);

			if (doCompression) {
				connection.setRequestProperty("Accept-Encoding", "gzip, deflate");
			}
			
			if (null != requestBody) {
				OutputStream os = connection.getOutputStream();
				os.write( requestBody.getBytes("utf-8") );
			}
			
			connection.connect();

			InputStream resultingInputStream;
			try {
				String encoding = connection.getContentEncoding();
				
				if (encoding != null && encoding.equalsIgnoreCase("gzip")) {
					resultingInputStream = new GZIPInputStream(connection.getInputStream());
				} 
				else if (encoding != null && encoding.equalsIgnoreCase("deflate")) {
					resultingInputStream = new InflaterInputStream(connection.getInputStream(), new Inflater(true));
				}
				else {
					resultingInputStream = connection.getInputStream();
				}
			}
			catch (IOException ex) {
				ex.printStackTrace();
				resultingInputStream = connection.getErrorStream();
			}
			
			final Reader reader = new InputStreamReader(resultingInputStream);
			final char[] buf = new char[16384];
			int read;
			final StringBuffer sbuff = new StringBuffer();
			while((read = reader.read(buf)) > 0) {
				sbuff.append(buf, 0, read);
			}

			int status = connection.getResponseCode();
			if (status != 200) {
				throw new AnalysisException(status, sbuff.toString());
			}

			connection.disconnect();
			
			ObjectMapper mapper = new ObjectMapper(); 
			
			// Note - removing this is handy for debugging holes in the library, but for production
			// use we don't want to fail on new properties to allow forward compatibility.
			mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
			
			try {
				return mapper.readValue(sbuff.toString(), responseClass);
			} catch (IOException e) {
				 e.printStackTrace();
				throw new RuntimeException("Unable to decode TextRazor response: ");
			}
		}
		catch (IOException e) {
			throw new NetworkException("Network Error when connecting to TextRazor", e);
		}
	}
	
	protected <RequestType, ResponseType> ResponseType sendRequest(
			String path, 
			RequestType requestBody,
			ContentType contentType,
			String method, 
			Class<ResponseType> responseClass) throws AnalysisException, NetworkException {
		ObjectMapper mapper = new ObjectMapper();

		// Optional params are represented by "null" in the build objects.
		// We don't need to actually send a null to the server.
		mapper.setSerializationInclusion(Include.NON_NULL);
		
		try {
			String jsonString = mapper.writeValueAsString(requestBody);
			return sendRequest(path, jsonString, contentType, method, responseClass);
		} catch (JsonProcessingException e) {
			throw new RuntimeException("Unable to encode TextRazor request.");
		}
	}
	
	protected String encodeURLParam(String param) {
		if (null == param) {
			return null; 
		}
		
		try {
			return URLEncoder.encode(param, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			return null;
		}
	}
	
	/**
	 * @return The API Key used to authenticate requests.
	 */
	public String getApiKey() {
		return apiKey;
	}

	/**
	 * @param apiKey The API Key used to authenticate requests
	 */
	public TextRazorConnection setApiKey(String apiKey) {
		this.apiKey = apiKey;
		return this;
	}

	/**
	 * @return The TextRazor Endpoint used for requests.
	 */
	public String getTextrazorEndpoint() {
		return textrazorEndpoint;
	}
	
	/**
	 * @return true if compression is enabled on all TextRazor requests.
	 */
	public boolean isDoCompression() {
		return doCompression;
	}

	/**
	 * @param doCompression When true do compression on all TextRazor requests.
	 */
	public TextRazorConnection setDoCompression(boolean doCompression) {
		this.doCompression = doCompression;
		return this;
	}

	/**
	 * @return True if TextRazor requests are encrypted.
	 */
	public boolean isDoEncryption() {
		return doEncryption;
	}
	
	/**
	 * Set to true to encrypt all TextRazor requests.
	 * 
	 * @param doEncryption
	 */
	public TextRazorConnection setDoEncryption(boolean doEncryption) {
		this.doEncryption = doEncryption;
		return this;
	}
	
	/**
	 * @param textrazorEndpoint The custom TextRazor Endpoint for requests made by this class.
	 */
	public TextRazorConnection setTextrazorEndpoint(String textrazorEndpoint) {
		this.textrazorEndpoint = textrazorEndpoint;
		return this;
	}
	
	/**
	 * @return The TextRazor Endpoint used for making encrypted requests in this class.
	 */
	public String getSecureTextrazorEndpoint() {
		return secureTextrazorEndpoint;
	}

	/**
	 * @param secureTextrazorEndpoint The TextRazor Endpoint used for making encrypted requests.
	 */
	public void setSecureTextrazorEndpoint(String secureTextrazorEndpoint) {
		this.secureTextrazorEndpoint = secureTextrazorEndpoint;
	}
	
	/**
	 * @param proxy The java.net.Proxy instance containing proxy settings for this connection.
	 * Example: 
	 * 
	 * textrazor.setProxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(PROXY_IP, PROXY_PORT));
	 * 
	 * When null, the default JVM proxy settings are used. If they are not set no proxy is used. These can be set with:
	 * 
	 * System.setProperty("https.proxyHost", textRazorProxyHost);
	 * System.setProperty("https.proxyPort", textRazorProxyPort);
	 */
	public void setProxy(Proxy proxy) {
		this.proxy = proxy;
	}
} public class TextRazorResponse {
	private double time;
	
	private boolean ok;
	
	private String error;
	
	private String message;
	
	/** 
	 * @return Total time in seconds TextRazor took to process this request. This does not include any time spent sending or receiving the request/response.
	 */
	public double getTime() {
		return time;
	}

	/**
	 * @return True if TextRazor successfully analyzed your document, False if there was some error.
	 */
	public boolean isOk() {
		return ok;
	}

	/**
	 * @return Descriptive error message of any problems that may have occurred during analysis, or null if there was no error.
	 */
	public String getError() {
		return error;
	}

	/**
	 * @return Any warning or informational messages returned from the server, or an null if there was no message.
	 */
	public String getMessage() {
		return message;
	}
}
import java.io.UnsupportedEncodingException;
import java.util.List;
import com.textrazor.AnalysisException;
import com.textrazor.NetworkException;
import com.textrazor.dictionary.model.AllDictionariesResponse;
import com.textrazor.dictionary.model.AllEntriesResponse;
import com.textrazor.dictionary.model.Dictionary;
import com.textrazor.dictionary.model.DictionaryEntry;
import com.textrazor.dictionary.model.GetDictionaryEntryResponse;
import com.textrazor.dictionary.model.GetDictionaryResponse;
import com.textrazor.dictionary.model.PagedAllEntries;
import com.textrazor.net.QueryBuilder;
import com.textrazor.net.TextRazorConnection;
import com.textrazor.net.TextRazorResponse;
package com.textrazor.dictionary;



public class DictionaryManager extends TextRazorConnection {

	public DictionaryManager(String apiKey) {
		super(apiKey);
	}

	public TextRazorResponse createDictionary(Dictionary dictionary) throws NetworkException, AnalysisException {
		String encodedId = encodeURLParam(dictionary.getId());

		if (null == encodedId) {
			throw new RuntimeException("Invalid Dictionary ID."); 
		}

		return sendRequest("entities/" + encodedId,
				dictionary, 
				ContentType.JSON,
				"PUT",
				TextRazorResponse.class);
	}

	public TextRazorResponse deleteDictionary(String id) throws NetworkException, AnalysisException {
		String encodedId = encodeURLParam(id);

		if (null == encodedId) {
			throw new RuntimeException("Invalid Dictionary ID."); 
		}

		return sendRequest("entities/" + encodedId,
				null, 
				ContentType.JSON,
				"DELETE",
				TextRazorResponse.class);
	}

	public TextRazorResponse deleteDictionary(Dictionary dictionary) throws NetworkException, AnalysisException {
		return deleteDictionary(dictionary.getId());
	}

	public List<Dictionary> allDictionaries() throws NetworkException, AnalysisException {	
		return sendRequest("entities/",
				null, 
				ContentType.JSON,
				"GET",
				AllDictionariesResponse.class).getDictionaries();
	}

	public Dictionary getDictionary(String id) throws NetworkException, AnalysisException {	
		String encodedId = encodeURLParam(id);

		if (null == encodedId) {
			throw new RuntimeException("Invalid Dictionary ID."); 
		}

		GetDictionaryResponse response = sendRequest("entities/" + encodedId,
				null, 
				ContentType.JSON,
				"GET",
				GetDictionaryResponse.class);
		
		return response.getResponse();
	}

	public PagedAllEntries allEntries(String id, int limit, int offset) throws NetworkException, AnalysisException {	
		String encodedId = encodeURLParam(id);

		if (null == encodedId) {
			throw new RuntimeException("Invalid Dictionary ID."); 
		}

		QueryBuilder paramBuilder = new QueryBuilder();

		try {
			if (limit > 0) {
				paramBuilder.addParam("limit", Integer.toString(limit));
			}

			if (offset > 0) {
				paramBuilder.addParam("offset", Integer.toString(offset));
			}
		}
		catch (UnsupportedEncodingException e) {
			throw new RuntimeException("Unable to encode dictionary paging paramaters."); 
		}

		AllEntriesResponse response = sendRequest("entities/" + encodedId + "/_all?" + paramBuilder.build(),
				null, 
				ContentType.JSON,
				"GET",
				AllEntriesResponse.class);
		
		return response.getResponse();
	}

	public PagedAllEntries allEntries(String id, int limit) throws NetworkException, AnalysisException {	
		return allEntries(id, limit, -1);
	}

	public PagedAllEntries allEntries(String id) throws NetworkException, AnalysisException {	
		return allEntries(id, -1);
	}

	public TextRazorResponse addEntries(String id, List<DictionaryEntry> entries) throws NetworkException, AnalysisException {	
		String encodedId = encodeURLParam(id);

		if (null == encodedId) {
			throw new RuntimeException("Invalid Dictionary ID."); 
		}

		return sendRequest("entities/" + encodedId + "/",
				entries, 
				ContentType.JSON,
				"POST",
				TextRazorResponse.class);
	}

	public DictionaryEntry getEntry(String dictionaryId, String entryId) throws NetworkException, AnalysisException {	
		String encodedDictionaryId = encodeURLParam(dictionaryId);
		String encodedEntryId = encodeURLParam(entryId);

		if (null == encodedDictionaryId) {
			throw new RuntimeException("Invalid Dictionary ID."); 
		}

		if (null == encodedEntryId) {
			throw new RuntimeException("Invalid Entry ID."); 
		}

		GetDictionaryEntryResponse response = sendRequest("entities/" + encodedDictionaryId + "/" + encodedEntryId,