/*
 +Copyright 2014-2015 Appium contributors
 +Copyright 2014-2015 Software Freedom Conservancy
 +
 +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 io.appium.java_client;

import static io.appium.java_client.MobileCommand.CLOSE_APP;
import static io.appium.java_client.MobileCommand.COMPLEX_FIND;
import static io.appium.java_client.MobileCommand.CURRENT_ACTIVITY;
import static io.appium.java_client.MobileCommand.END_TEST_COVERAGE;
import static io.appium.java_client.MobileCommand.GET_NETWORK_CONNECTION;
import static io.appium.java_client.MobileCommand.GET_SETTINGS;
import static io.appium.java_client.MobileCommand.GET_STRINGS;
import static io.appium.java_client.MobileCommand.HIDE_KEYBOARD;
import static io.appium.java_client.MobileCommand.INSTALL_APP;
import static io.appium.java_client.MobileCommand.IS_APP_INSTALLED;
import static io.appium.java_client.MobileCommand.IS_LOCKED;
import static io.appium.java_client.MobileCommand.KEY_EVENT;
import static io.appium.java_client.MobileCommand.LAUNCH_APP;
import static io.appium.java_client.MobileCommand.LOCK;
import static io.appium.java_client.MobileCommand.OPEN_NOTIFICATIONS;
import static io.appium.java_client.MobileCommand.PERFORM_MULTI_TOUCH;
import static io.appium.java_client.MobileCommand.PERFORM_TOUCH_ACTION;
import static io.appium.java_client.MobileCommand.PULL_FILE;
import static io.appium.java_client.MobileCommand.PULL_FOLDER;
import static io.appium.java_client.MobileCommand.PUSH_FILE;
import static io.appium.java_client.MobileCommand.REMOVE_APP;
import static io.appium.java_client.MobileCommand.RESET;
import static io.appium.java_client.MobileCommand.RUN_APP_IN_BACKGROUND;
import static io.appium.java_client.MobileCommand.SET_NETWORK_CONNECTION;
import static io.appium.java_client.MobileCommand.SET_SETTINGS;
import static io.appium.java_client.MobileCommand.SET_VALUE;
import static io.appium.java_client.MobileCommand.SHAKE;
import static io.appium.java_client.MobileCommand.START_ACTIVITY;
import static io.appium.java_client.MobileCommand.TOGGLE_LOCATION_SERVICES;
import io.appium.java_client.remote.MobileCapabilityType;

import java.net.URL;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import javax.xml.bind.DatatypeConverter;

import org.openqa.selenium.By;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.Point;
import org.openqa.selenium.ScreenOrientation;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.html5.Location;
import org.openqa.selenium.remote.CommandInfo;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.DriverCommand;
import org.openqa.selenium.remote.ErrorHandler;
import org.openqa.selenium.remote.ExecuteMethod;
import org.openqa.selenium.remote.HttpCommandExecutor;
import org.openqa.selenium.remote.RemoteWebElement;
import org.openqa.selenium.remote.Response;
import org.openqa.selenium.remote.html5.RemoteLocationContext;
import org.openqa.selenium.remote.http.HttpMethod;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

/**
 * @param <RequiredElementType> means the required type from the list of allowed types below 
 * that implement {@link WebElement} Instances of the defined type will be 
 * returned via findElement* and findElements*. 
 * Warning (!!!). Allowed types:<br/>
 * {@link WebElement}<br/>
 * {@link TouchableElement}<br/>
 * {@link RemoteWebElement}<br/>
 * {@link MobileElement} and its subclasses that designed specifically for each target mobile OS (still Android and iOS)
 */
@SuppressWarnings("unchecked")
public abstract class AppiumDriver<RequiredElementType extends WebElement> extends DefaultGenericMobileDriver<RequiredElementType> {

	private final static ErrorHandler errorHandler = new ErrorHandler(
			new ErrorCodesMobile(), true);
	private URL remoteAddress;
	private RemoteLocationContext locationContext;
	private ExecuteMethod executeMethod;

	// frequently used command parameters
	protected final String KEY_CODE = "keycode";
	protected final String PATH = "path";
	private final String SETTINGS = "settings";

	private final String LANGUAGE_PARAM = "language";

	/**
	 * @param originalCapabilities
	 *            the given {@link Capabilities}
	 * @param newPlatform
	 *            a {@link MobileCapabilityType#PLATFORM_NAME} value which has
	 *            to be set up
	 * @return {@link Capabilities} with changed mobile platform value
	 */
	protected static Capabilities substituteMobilePlatform(
			Capabilities originalCapabilities, String newPlatform) {
		DesiredCapabilities dc = new DesiredCapabilities(originalCapabilities);
		dc.setCapability(MobileCapabilityType.PLATFORM_NAME, newPlatform);
		return dc;
	}

    @Override
    public List<RequiredElementType> findElements(By by){
        return super.findElements(by);
    }

    @Override
    public List<RequiredElementType> findElementsById(String id){
        return super.findElementsById(id);
    }

	public List<RequiredElementType> findElementsByLinkText(String using) {
        return super.findElementsByLinkText(using);
    }

    public List<RequiredElementType> findElementsByPartialLinkText(String using) {
        return super.findElementsByPartialLinkText(using);
    }

    public List<RequiredElementType> findElementsByTagName(String using) {
        return super.findElementsByTagName(using);
    }

    public List<RequiredElementType> findElementsByName(String using) {
        return super.findElementsByName(using);
    }

    public List<RequiredElementType> findElementsByClassName(String using) {
        return super.findElementsByClassName(using);
    }

    public List<RequiredElementType> findElementsByCssSelector(String using) {
        return super.findElementsByCssSelector(using);
    }

    public List<RequiredElementType> findElementsByXPath(String using) {
        return super.findElementsByXPath(using);
    }

    @Override
    public List<RequiredElementType> findElementsByAccessibilityId(String using) {
        return (List<RequiredElementType>) findElements("accessibility id", using);
    }

    /**
	 * @param param
	 *            is a parameter name
	 * @param value
	 *            is the parameter value
	 * @return built {@link ImmutableMap}
	 */
	protected static ImmutableMap<String, Object> getCommandImmutableMap(
			String param, Object value) {
		ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();
		builder.put(param, value);
		return builder.build();
	}

	/**
	 * 
	 * @param params
	 *            is the array with parameter names
	 * @param values
	 *            is the array with parameter values
	 * @return built {@link ImmutableMap}
	 */
	protected static ImmutableMap<String, Object> getCommandImmutableMap(
			String[] params, Object[] values) {
		ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();
		for (int i = 0; i < params.length; i++) {
			if (_isNotNullOrEmpty(params[i]) && _isNotNullOrEmpty(values[i])) {
				builder.put(params[i], values[i]);
			}
		}
		return builder.build();
	}

	public AppiumDriver(URL remoteAddress, Capabilities desiredCapabilities) {

		super(remoteAddress, desiredCapabilities);

		this.executeMethod = new AppiumExecutionMethod(this);
		this.remoteAddress = remoteAddress;
		locationContext = new RemoteLocationContext(executeMethod);

		ImmutableMap.Builder<String, CommandInfo> builder = ImmutableMap
				.builder();
		builder.put(RESET, postC("/session/:sessionId/appium/app/reset"))
				.put(GET_STRINGS,
						postC("/session/:sessionId/appium/app/strings"))
				.put(KEY_EVENT,
						postC("/session/:sessionId/appium/device/keyevent"))
				.put(CURRENT_ACTIVITY,
						getC("/session/:sessionId/appium/device/current_activity"))
				.put(SET_VALUE,
						postC("/session/:sessionId/appium/element/:id/value"))
				.put(PULL_FILE,
						postC("/session/:sessionId/appium/device/pull_file"))
				.put(PULL_FOLDER,
						postC("/session/:sessionId/appium/device/pull_folder"))
				.put(HIDE_KEYBOARD,
						postC("/session/:sessionId/appium/device/hide_keyboard"))
				.put(PUSH_FILE,
						postC("/session/:sessionId/appium/device/push_file"))
				.put(RUN_APP_IN_BACKGROUND,
						postC("/session/:sessionId/appium/app/background"))
				.put(PERFORM_TOUCH_ACTION,
						postC("/session/:sessionId/touch/perform"))
				.put(PERFORM_MULTI_TOUCH,
						postC("/session/:sessionId/touch/multi/perform"))
				.put(IS_APP_INSTALLED,
						postC("/session/:sessionId/appium/device/app_installed"))
				.put(INSTALL_APP,
						postC("/session/:sessionId/appium/device/install_app"))
				.put(REMOVE_APP,
						postC("/session/:sessionId/appium/device/remove_app"))
				.put(LAUNCH_APP, postC("/session/:sessionId/appium/app/launch"))
				.put(CLOSE_APP, postC("/session/:sessionId/appium/app/close"))
				.put(END_TEST_COVERAGE,
						postC("/session/:sessionId/appium/app/end_test_coverage"))
				.put(LOCK, postC("/session/:sessionId/appium/device/lock"))
				.put(IS_LOCKED,
						postC("/session/:sessionId/appium/device/is_locked"))
				.put(SHAKE, postC("/session/:sessionId/appium/device/shake"))
				.put(COMPLEX_FIND,
						postC("/session/:sessionId/appium/app/complex_find"))
				.put(OPEN_NOTIFICATIONS,
						postC("/session/:sessionId/appium/device/open_notifications"))
				.put(GET_NETWORK_CONNECTION,
						getC("/session/:sessionId/network_connection"))
				.put(SET_NETWORK_CONNECTION,
						postC("/session/:sessionId/network_connection"))
				.put(GET_SETTINGS, getC("/session/:sessionId/appium/settings"))
				.put(SET_SETTINGS, postC("/session/:sessionId/appium/settings"))
				.put(START_ACTIVITY,
						postC("/session/:sessionId/appium/device/start_activity"))
				.put(TOGGLE_LOCATION_SERVICES, postC("/session/:sessionId/appium/device/toggle_location_services"));

		ImmutableMap<String, CommandInfo> mobileCommands = builder.build();

		HttpCommandExecutor mobileExecutor = new HttpCommandExecutor(
				mobileCommands, remoteAddress);
		super.setCommandExecutor(mobileExecutor);

		super.setErrorHandler(errorHandler);
	}

	@Override
	protected Response execute(String command) {
		return super.execute(command, ImmutableMap.<String, Object>of());
	}

	@Override
	public ExecuteMethod getExecuteMethod() {
		return executeMethod;
	}

	/**
	 * @see InteractsWithApps#resetApp()
	 */
	@Override
	public void resetApp() {
		execute(MobileCommand.RESET);
	}

	/**
	 * @see InteractsWithApps#isAppInstalled(String)
	 */
	@Override
	public boolean isAppInstalled(String bundleId) {
		Response response = execute(IS_APP_INSTALLED,
				ImmutableMap.of("bundleId", bundleId));

		return Boolean.parseBoolean(response.getValue().toString());
	}

	/**
	 * @see InteractsWithApps#installApp(String)
	 */
	@Override
	public void installApp(String appPath) {
		execute(INSTALL_APP, ImmutableMap.of("appPath", appPath));
	}

	/**
	 * @see InteractsWithApps#removeApp(String)
	 */
	@Override
	public void removeApp(String bundleId) {
		execute(REMOVE_APP, ImmutableMap.of("bundleId", bundleId));
	}

	/**
	 * @see InteractsWithApps#launchApp()
	 */
	@Override
	public void launchApp() {
		execute(LAUNCH_APP);
	}

	/**
	 * @see InteractsWithApps#closeApp()
	 */
	@Override
	public void closeApp() {
		execute(CLOSE_APP);
	}

	/**
	 * @see InteractsWithApps#runAppInBackground(int)
	 */
	@Override
	public void runAppInBackground(int seconds) {
		execute(RUN_APP_IN_BACKGROUND, ImmutableMap.of("seconds", seconds));
	}

	/**
	 * @see DeviceActionShortcuts#hideKeyboard()
	 */
	@Override
	public void hideKeyboard() {
		execute(HIDE_KEYBOARD);
	}

	/**
	 * @see InteractsWithFiles#pullFile(String)
	 */
	@Override
	public byte[] pullFile(String remotePath) {
		Response response = execute(PULL_FILE,
				ImmutableMap.of(PATH, remotePath));
		String base64String = response.getValue().toString();

		return DatatypeConverter.parseBase64Binary(base64String);
	}

	/**
	 * @see InteractsWithFiles#pullFolder(String)
	 */
	@Override
	public byte[] pullFolder(String remotePath) {
		Response response = execute(PULL_FOLDER,
				ImmutableMap.of(PATH, remotePath));
		String base64String = response.getValue().toString();

		return DatatypeConverter.parseBase64Binary(base64String);
	}

	/**
	 * @see PerformsTouchActions#performTouchAction(TouchAction)
	 */
	@SuppressWarnings("rawtypes")
	@Override
	public TouchAction performTouchAction(TouchAction touchAction) {
		ImmutableMap<String, ImmutableList> parameters = touchAction
				.getParameters();
		touchAction.clearParameters();
		execute(PERFORM_TOUCH_ACTION, parameters);

		return touchAction;
	}

	/**
	 * @see PerformsTouchActions#performMultiTouchAction(MultiTouchAction)
	 */
	@Override
	@SuppressWarnings({ "rawtypes"})
	public void performMultiTouchAction(MultiTouchAction multiAction) {
		ImmutableMap<String, ImmutableList> parameters = multiAction
				.getParameters();
		execute(PERFORM_MULTI_TOUCH, parameters);
	}

	/**
	 * @see TouchShortcuts#tap(int, WebElement, int)
	 */
	@Override
	public void tap(int fingers, WebElement element, int duration) {
		MultiTouchAction multiTouch = new MultiTouchAction(this);

		for (int i = 0; i < fingers; i++) {
			multiTouch.add(createTap(element, duration));
		}

		multiTouch.perform();
	}

	/**
	 * @see TouchShortcuts#tap(int, int, int, int)
	 */
	@Override
	public void tap(int fingers, int x, int y, int duration) {
		MultiTouchAction multiTouch = new MultiTouchAction(this);

		for (int i = 0; i < fingers; i++) {
			multiTouch.add(createTap(x, y, duration));
		}

		multiTouch.perform();
	}

	/**
	 * @see TouchShortcuts#swipe(int, int, int, int, int)
	 */
	@Override
	public void swipe(int startx, int starty, int endx, int endy, int duration) {
		TouchAction touchAction = new TouchAction(this);

		// appium converts press-wait-moveto-release to a swipe action
		touchAction.press(startx, starty).waitAction(duration)
				.moveTo(endx, endy).release();

		touchAction.perform();
	}

  /**
   * Convenience method for pinching an element on the screen.
   * "pinching" refers to the action of two appendages pressing the screen and sliding towards each other.
   * NOTE:
   * This convenience method places the initial touches around the element, if this would happen to place one of them
   * off the screen, appium with return an outOfBounds error. In this case, revert to using the MultiTouchAction api
   * instead of this method.
   *
   * @param el The element to pinch
   */
  public void pinch(WebElement el) {
    MultiTouchAction multiTouch = new MultiTouchAction(this);

    Dimension dimensions = el.getSize();
    Point upperLeft = el.getLocation();
    Point center = new Point(upperLeft.getX() + dimensions.getWidth() / 2, upperLeft.getY() + dimensions.getHeight() / 2);
    int yOffset = center.getY() - upperLeft.getY();

    TouchAction action0 = new TouchAction(this).press(el, center.getX(), center.getY() - yOffset).moveTo(el).release();
    TouchAction action1 = new TouchAction(this).press(el, center.getX(), center.getY() + yOffset).moveTo(el).release();

    multiTouch.add(action0).add(action1);

    multiTouch.perform();
  }

  /**
   * Convenience method for pinching an element on the screen.
   * "pinching" refers to the action of two appendages pressing the screen and sliding towards each other.
   * NOTE:
   * This convenience method places the initial touches around the element at a distance, if this would happen to place
   * one of them off the screen, appium will return an outOfBounds error. In this case, revert to using the
   * MultiTouchAction api instead of this method.
   *
   * @param x x coordinate to terminate the pinch on
   * @param y y coordinate to terminate the pinch on
   */
  public void pinch(int x, int y) {
    MultiTouchAction multiTouch = new MultiTouchAction(this);

    int scrHeight = manage().window().getSize().getHeight();
    int yOffset = 100;

    if (y - 100 < 0) {
      yOffset = y;
    } else if (y + 100 > scrHeight) {
      yOffset = scrHeight - y;
    }

    TouchAction action0 = new TouchAction(this).press(x, y - yOffset).moveTo(x, y).release();
    TouchAction action1 = new TouchAction(this).press(x, y + yOffset).moveTo(x, y).release();

    multiTouch.add(action0).add(action1);

    multiTouch.perform();
  }

  /**
   * Convenience method for "zooming in" on an element on the screen.
   * "zooming in" refers to the action of two appendages pressing the screen and sliding away from each other.
   * NOTE:
   * This convenience method slides touches away from the element, if this would happen to place one of them
   * off the screen, appium will return an outOfBounds error. In this case, revert to using the MultiTouchAction api
   * instead of this method.
   *
   * @param el The element to pinch
   */
  public void zoom(WebElement el) {
    MultiTouchAction multiTouch = new MultiTouchAction(this);

    Dimension dimensions = el.getSize();
    Point upperLeft = el.getLocation();
    Point center = new Point(upperLeft.getX() + dimensions.getWidth() / 2, upperLeft.getY() + dimensions.getHeight() / 2);
    int yOffset = center.getY() - upperLeft.getY();

    TouchAction action0 = new TouchAction(this).press(el).moveTo(el, center.getX(), center.getY() - yOffset).release();
    TouchAction action1 = new TouchAction(this).press(el).moveTo(el, center.getX(), center.getY() + yOffset).release();

    multiTouch.add(action0).add(action1);

    multiTouch.perform();
  }

  /**
   * Convenience method for "zooming in" on an element on the screen.
   * "zooming in" refers to the action of two appendages pressing the screen and sliding away from each other.
   * NOTE:
   * This convenience method slides touches away from the element, if this would happen to place one of them
   * off the screen, appium will return an outOfBounds error. In this case, revert to using the MultiTouchAction api
   * instead of this method.
   *
   * @param x x coordinate to start zoom on
   * @param y y coordinate to start zoom on
   */
  public void zoom(int x, int y) {
    MultiTouchAction multiTouch = new MultiTouchAction(this);

    int scrHeight = manage().window().getSize().getHeight();
    int yOffset = 100;

    if (y - 100 < 0) {
      yOffset = y;
    } else if (y + 100 > scrHeight) {
      yOffset = scrHeight - y;
    }

    TouchAction action0 = new TouchAction(this).press(x, y).moveTo(x, y - yOffset).release();
    TouchAction action1 = new TouchAction(this).press(x, y).moveTo(x, y + yOffset).release();

    multiTouch.add(action0).add(action1);

    multiTouch.perform();
  }

	/**
	 * Get settings stored for this test session It's probably better to use a
	 * convenience function, rather than use this function directly. Try finding
	 * the method for the specific setting you want to read
	 * 
	 * @return JsonObject, a straight-up hash of settings
	 */
	public JsonObject getSettings() {
		Response response = execute(GET_SETTINGS);

		JsonParser parser = new JsonParser();
		JsonObject settings = (JsonObject) parser.parse(response.getValue()
				.toString());

		return settings;
	}

	/**
	 * Set settings for this test session It's probably better to use a
	 * convenience function, rather than use this function directly. Try finding
	 * the method for the specific setting you want to change
	 * 
	 * @param settings
	 *            Map of setting keys and values
	 */
	private void setSettings(ImmutableMap<?, ?> settings) {
		execute(SET_SETTINGS, getCommandImmutableMap(SETTINGS, settings));
	}

	/**
	 * Set a setting for this test session It's probably better to use a
	 * convenience function, rather than use this function directly. Try finding
	 * the method for the specific setting you want to change
	 * 
	 * @param setting
	 *            AppiumSetting you wish to set
	 * @param value
	 *            value of the setting
	 */
	protected void setSetting(AppiumSetting setting, Object value) {
		setSettings(getCommandImmutableMap(setting.toString(), value));
	}

	/**
	 * Lock the device (bring it to the lock screen) for a given number of
	 * seconds
	 * 
	 * @param seconds
	 *            number of seconds to lock the screen for
	 */
	public void lockScreen(int seconds) {
		execute(LOCK, ImmutableMap.of("seconds", seconds));
	}

  @Override
	public WebDriver context(String name) {
		if (!_isNotNullOrEmpty(name)) {
			throw new IllegalArgumentException("Must supply a context name");
		}

		execute(DriverCommand.SWITCH_TO_CONTEXT, ImmutableMap.of("name", name));
		return AppiumDriver.this;
	}

	@Override
	public Set<String> getContextHandles() {
		Response response = execute(DriverCommand.GET_CONTEXT_HANDLES);
		Object value = response.getValue();
		try {
			List<String> returnedValues = (List<String>) value;
			return new LinkedHashSet<String>(returnedValues);
		} catch (ClassCastException ex) {
			throw new WebDriverException(
					"Returned value cannot be converted to List<String>: "
							+ value, ex);
		}
	}

	@Override
	public String getContext() {
		String contextName = String.valueOf(execute(
				DriverCommand.GET_CURRENT_CONTEXT_HANDLE).getValue());
		if (contextName.equals("null")) {
			return null;
		}
		return contextName;
	}

	@Override
	public void rotate(ScreenOrientation orientation) {
		execute(DriverCommand.SET_SCREEN_ORIENTATION, ImmutableMap.of(
				"orientation", orientation.value().toUpperCase()));
	}

	@Override
	public ScreenOrientation getOrientation() {
		Response response = execute(DriverCommand.GET_SCREEN_ORIENTATION);
		String orientation = response.getValue().toString().toLowerCase();
		if (orientation.equals(ScreenOrientation.LANDSCAPE.value())) {
			return ScreenOrientation.LANDSCAPE;
		} else if (orientation.equals(ScreenOrientation.PORTRAIT.value())) {
			return ScreenOrientation.PORTRAIT;
		} else {
			throw new WebDriverException("Unexpected orientation returned: "
					+ orientation);
		}
	}

	@Override
	public Location location() {
		return locationContext.location();
	}

	@Override
	public void setLocation(Location location) {
		locationContext.setLocation(location);
	}

	/**
	 * @see HasAppStrings#getAppStrings()
 	 */
	@Override
	public String getAppStrings() {
		Response response = execute(GET_STRINGS);
		return response.getValue().toString();
	}

	/**
	 * @param language
	 *            strings language code
	 * @return a string of all the localized strings defined in the app
	 * 
	 * @see HasAppStrings#getAppStrings(String)
	 */
	@Override
	public String getAppStrings(String language) {
		Response response = execute(GET_STRINGS,
				getCommandImmutableMap(LANGUAGE_PARAM, language));
		return response.getValue().toString();
	}

	private TouchAction createTap(WebElement element, int duration) {
		TouchAction tap = new TouchAction(this);
		return tap.press(element).waitAction(duration).release();
	}

	private TouchAction createTap(int x, int y, int duration) {
		TouchAction tap = new TouchAction(this);
		return tap.press(x, y).waitAction(duration).release();
	}

	private static CommandInfo getC(String url) {
		return new CommandInfo(url, HttpMethod.GET);
	}

	private static CommandInfo postC(String url) {
		return new CommandInfo(url, HttpMethod.POST);
	}

	@SuppressWarnings("unused")
	private static CommandInfo deleteC(String url) {
		return new CommandInfo(url, HttpMethod.DELETE);
	}

	public URL getRemoteAddress() {
		return remoteAddress;
	}

	/**
	 * Checks if a string is null, empty, or whitespace.
	 * 
	 * @param str
	 *            String to check.
	 * 
	 * @return True if str is not null or empty.
	 */
	protected static boolean _isNotNullOrEmpty(String str) {
		return str != null && !str.isEmpty() && str.trim().length() > 0;
	}

	protected static boolean _isNotNullOrEmpty(Object ob) {
		return ob != null;
	}
}