/*
 * Created on Jul 26, 2010
 *
 * 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.
 *
 * Copyright @2010-2013 the original author or authors.
 */
package org.fest.assertions.api;

import javax.annotation.Nonnull;

import org.fest.assertions.core.EnumerableAssert;
import org.fest.assertions.core.IndexedObjectEnumerableAssert;
import org.fest.assertions.data.Index;
import org.fest.assertions.description.Description;
import org.fest.assertions.internal.ObjectArrays;
import org.fest.util.VisibleForTesting;

/**
 * Assertion methods for arrays of objects. To create an instance of this class, invoke
 * {@link Assertions#assertThat(Object[])}.
 *
 * @author Yvonne Wang
 * @author Alex Ruiz
 */
public class ObjectArrayAssert extends AbstractAssert<ObjectArrayAssert, Object[]> implements
    EnumerableAssert<ObjectArrayAssert> {

  @VisibleForTesting
  ObjectArrays arrays = ObjectArrays.instance();

  protected ObjectArrayAssert(Object[] actual) {
    super(actual, ObjectArrayAssert.class);
  }

  protected ObjectArrayAssert(Object[] actual, Description description) {
    super(actual, ObjectArrayAssert.class, description);
  }

  /** {@inheritDoc} */
  @Override
  public ObjectArrayAssert isNullOrEmpty() {
    arrays.assertNullOrEmpty(description, actual);
    return this;
  }

  /** {@inheritDoc} */
  @Override
  public ObjectArrayAssert isEmpty() {
    arrays.assertEmpty(description, actual);
    return this;
  }

  /** {@inheritDoc} */
  @Override
  @Nonnull
  public ObjectArrayAssert isNotEmpty() {
    arrays.assertNotEmpty(description, actual);
    return this;
  }

  /** {@inheritDoc} */
  @Override
  @Nonnull
  public ObjectArrayAssert hasSize(int expectedSize) {
    arrays.assertHasSize(description, actual, expectedSize);
    return this;
  }

  /**
   * Verifies that the given array contains the given object at the given index.
   *
   * @param value the object to look for.
   * @param index the index where the object should be stored in the given array.
   * @return {@code this} assertion object.
   * @throws AssertionError if the given array is {@code null} or empty.
   * @throws NullPointerException if the given {@code Index} is {@code null}.
   * @throws IndexOutOfBoundsException if the value of the given {@code Index} is equal to or greater than the size of
   *           the given array.
   * @throws AssertionError if the given array does not contain the given object at the given index.
   */
  public ObjectArrayAssert contains(Object value, Index index) {
    arrays.assertContains(description, actual, value, index);
    return this;
  }

  /**
   * Verifies that the given array contains the given object at the given index.
   *
   * @param value the object to look for.
   * @param index the index where the object should be stored in the given array.
   * @return {@code this} assertion object.
   * @throws AssertionError if the given array is {@code null} or empty.
   * @throws NullPointerException if the given {@code Index} is {@code null}.
   * @throws IndexOutOfBoundsException if the value of the given {@code Index} is equal to or greater than the size of
   *           the given array.
   * @throws AssertionError if the given array does not contain the given object at the given index.
   */
  public ObjectArrayAssert doesNotContain(Object value, Index index) {
    arrays.assertDoesNotContain(description, actual, value, index);
    return this;
  }

  /**
   * Verifies that the actual array contains the given values, in any order.
   *
   * @param values the given values.
   * @return {@code this} assertion object.
   * @throws NullPointerException if the given argument is {@code null}.
   * @throws IllegalArgumentException if the given argument is an empty array.
   * @throws AssertionError if the actual array is {@code null}.
   * @throws AssertionError if the actual array does not contain the given values.
   */
  public ObjectArrayAssert contains(Object... values) {
    arrays.assertContains(description, actual, values);
    return this;
  }

  /**
   * Verifies that the actual array contains only the given values and nothing else, in any order.
   *
   * @param values the given values.
   * @return {@code this} assertion object.
   * @throws NullPointerException if the given argument is {@code null}.
   * @throws IllegalArgumentException if the given argument is an empty array.
   * @throws AssertionError if the actual array is {@code null}.
   * @throws AssertionError if the actual array does not contain the given values, i.e. the actual array contains some
   *           or none of the given values, or the actual array contains more values than the given ones.
   */
  public ObjectArrayAssert containsOnly(Object... values) {
    arrays.assertContainsOnly(description, actual, values);
    return this;
  }

  /**
   * Verifies that the actual array contains the given sequence, without any other values between them.
   *
   * @param sequence the sequence of values to look for.
   * @return this assertion object.
   * @throws AssertionError if the actual array is {@code null}.
   * @throws AssertionError if the given array is {@code null}.
   * @throws AssertionError if the actual array does not contain the given sequence.
   */
  public ObjectArrayAssert containsSequence(Object... sequence) {
    arrays.assertContainsSequence(description, actual, sequence);
    return this;
  }

  /**
   * Verifies that the actual array does not contain the given values.
   *
   * @param values the given values.
   * @return {@code this} assertion object.
   * @throws NullPointerException if the given argument is {@code null}.
   * @throws IllegalArgumentException if the given argument is an empty array.
   * @throws AssertionError if the actual array is {@code null}.
   * @throws AssertionError if the actual array contains any of the given values.
   */
  public ObjectArrayAssert doesNotContain(Object... values) {
    arrays.assertDoesNotContain(description, actual, values);
    return this;
  }

  /**
   * Verifies that the actual array does not contain duplicates.
   *
   * @return {@code this} assertion object.
   * @throws AssertionError if the actual array is {@code null}.
   * @throws AssertionError if the actual array contains duplicates.
   */
  public ObjectArrayAssert doesNotHaveDuplicates() {
    arrays.assertDoesNotHaveDuplicates(description, actual);
    return this;
  }

  /**
   * Verifies that the actual array starts with the given sequence of values, without any other values between them.
   * Similar to {@link #containsSequence(Object...)}, but it also verifies that the first element in the
   * sequence is also first element of the actual array.
   *
   * @param sequence the sequence of values to look for.
   * @return this assertion object.
   * @throws NullPointerException if the given argument is {@code null}.
   * @throws IllegalArgumentException if the given argument is an empty array.
   * @throws AssertionError if the actual array is {@code null}.
   * @throws AssertionError if the actual array does not start with the given sequence.
   */
  public ObjectArrayAssert startsWith(Object... sequence) {
    arrays.assertStartsWith(description, actual, sequence);
    return this;
  }

  /**
   * Verifies that the actual array ends with the given sequence of values, without any other values between them.
   * Similar to {@link #containsSequence(Object...)}, but it also verifies that the last element in the
   * sequence is also last element of the actual array.
   *
   * @param sequence the sequence of values to look for.
   * @return this assertion object.
   * @throws NullPointerException if the given argument is {@code null}.
   * @throws IllegalArgumentException if the given argument is an empty array.
   * @throws AssertionError if the actual array is {@code null}.
   * @throws AssertionError if the actual array does not end with the given sequence.
   */
  public ObjectArrayAssert endsWith(Object... sequence) {
    arrays.assertEndsWith(description, actual, sequence);
    return this;
  }

  /**
   * Verifies that the actual array contains the {@code null} value(s), in any order.
   *
   * @return {@code this} assertion object.
   * @throws NullPointerException if the given argument is {@code null}.
   * @throws IllegalArgumentException if the given argument is an empty array.
   * @throws AssertionError if the actual array is {@code null}.
   * @throws AssertionError if the actual array does not contain the given values.
   */
  public ObjectArrayAssert containsNull() {
    arrays.assertContainsNull(description, actual);
    return this;
  }

  /**
   * Verifies that the actual array does not contain the {@code null} value(s), in any order.
   *
   * @return {@code this} assertion object.
   * @throws NullPointerException if the given argument is {@code null}.
   * @throws IllegalArgumentException if the given argument is an empty array.
   * @throws AssertionError if the actual array is {@code null}.
   * @throws AssertionError if the actual array does not contain the given values.
   */
  public ObjectArrayAssert doesNotContainNull() {
    arrays.assertDoesNotContainNull(description, actual);
    return this;
  }
}


/*
 * Created on Oct 18, 2010
 *
 * 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.
 *
 * Copyright @2010-2011 the original author or authors.
 */
package org.fest.assertions.error;

/**
 * Creates an error message indicating that an assertion that verifies a group of elements contains only a given set of values and
 * nothing else failed. A group of elements can be a collection, an array or a {@code String}.
 *
 * @author Alex Ruiz
 * @author Yvonne Wang
 * @author Joel Costigliola
 */
public class ShouldContainOnly extends BasicErrorMessageFactory {

  /**
   * Creates a new </code>{@link ShouldContainOnly}</code>.
   * @param actual the actual value in the failed assertion.
   * @param expected values expected to be contained in {@code actual}.
   * @param notFound values in {@code expected} not found in {@code actual}.
   * @param notExpected values in {@code actual} that were not in {@code expected}.
   * @return the created {@code ErrorMessageFactory}.
   */
  public static ErrorMessageFactory shouldContainOnly(Object actual, Object expected, Object notFound, Object notExpected) {
    return new ShouldContainOnly(actual, expected, notFound, notExpected);
  }

  private ShouldContainOnly(Object actual, Object expected, Object notFound, Object notExpected) {
    super("expecting:\n<%s>\n to contain only:\n<%s>\n elements not found:\n<%s>\n and elements not expected:\n<%s>", actual,
        expected, notFound, notExpected);
  }

}


/*
 * Created on Feb 18, 2012
 *
 * 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.
 *
 * Copyright @2012-2013 the original author or authors.
 */
package org.fest.assertions.error;

/**
 * Creates an error message indicating that an assertion that verifies that an Iterable is a subset of an other set
 * Iterable failed.
 *
 * @author Maciej Jaskowski
 * @author Yvonne Wang
 */
public class ShouldBeSubsetOf extends BasicErrorMessageFactory {

  /**
   * Creates a new {@link ShouldBeSubsetOf}.
   * @param actual the actual set
   * @param values the expected superset
   * @param unexpected the elements found in the actual set, but not in the given values.
   * @return the created {@code ErrorMessageFactory}.
   */
  public static ErrorMessageFactory shouldBeSubsetOf(Object actual, Object values, Iterable<?> unexpected) {
    return new ShouldBeSubsetOf(actual, values, unexpected);
  }

  private ShouldBeSubsetOf(Object actual, Object values, Iterable<?> unexpected) {
    super("expecting:\n<%s>\n to be subset of\n<%s>\n but found those extra elements:\n<%s>", actual,
        values, unexpected);
  }
}


/*
 * Created on Sep 17, 2010
 *
 * 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.
 *
 * Copyright @2010-2013 the original author or authors.
 */
package org.fest.assertions.internal;

import org.fest.assertions.description.Description;
import org.fest.assertions.error.ShouldContainNull;
import org.fest.util.VisibleForTesting;

import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;

import static org.fest.assertions.error.ShouldBeEmpty.shouldBeEmpty;
import static org.fest.assertions.error.ShouldBeNullOrEmpty.shouldBeNullOrEmpty;
import static org.fest.assertions.error.ShouldContain.shouldContain;
import static org.fest.assertions.error.ShouldContainOnly.shouldContainOnly;
import static org.fest.assertions.error.ShouldContainSequence.shouldContainSequence;
import static org.fest.assertions.error.ShouldEndWith.shouldEndWith;
import static org.fest.assertions.error.ShouldHaveSize.shouldHaveSize;
import static org.fest.assertions.error.ShouldNotBeEmpty.shouldNotBeEmpty;
import static org.fest.assertions.error.ShouldNotContain.shouldNotContain;
import static org.fest.assertions.error.ShouldNotContainNull.shouldNotContainNull;
import static org.fest.assertions.error.ShouldNotHaveDuplicates.shouldNotHaveDuplicates;
import static org.fest.assertions.error.ShouldStartWith.shouldStartWith;
import static org.fest.assertions.internal.CommonErrors.arrayOfValuesToLookForIsEmpty;
import static org.fest.assertions.internal.CommonErrors.arrayOfValuesToLookForIsNull;
import static org.fest.util.Collections.duplicatesFrom;
import static org.fest.util.Objects.areEqual;

/**
 * Reusable assertions for {@link Collection}s.
 *
 * @author Alex Ruiz
 * @author Yvonne Wang
 */
public class Collections {

  private static final Collections INSTANCE = new Collections();
  @VisibleForTesting
  Failures failures = Failures.instance();

  @VisibleForTesting
  Collections() {
  }

  /**
   * Returns the singleton instance of this class.
   *
   * @return the singleton instance of this class.
   */
  public static Collections instance() {
    return INSTANCE;
  }

  private static Set<Object> containsOnly(Set<Object> actual, Object[] values) {
    Set<Object> notFound = new LinkedHashSet<Object>();
    for (Object o : org.fest.util.Sets.newLinkedHashSet(values)) {
      if (actual.contains(o)) {
        actual.remove(o);
      } else {
        notFound.add(o);
      }
    }
    return notFound;
  }

  /**
   * Asserts that the given {@link Collection} is {@code null} or empty.
   *
   * @param description contains information about the assertion.
   * @param actual      the given {@code Collection}.
   * @throws AssertionError if the given {@code Collection} is not {@code null} *and* contains one or more elements.
   */
  public void assertNullOrEmpty(Description description, Collection<?> actual) {
    if (actual == null || actual.isEmpty()) {
      return;
    }
    throw failures.failure(description, shouldBeNullOrEmpty(actual));
  }

  /**
   * Asserts that the given {@code Collection} is empty.
   *
   * @param description contains information about the assertion.
   * @param actual      the given {@code Collection}.
   * @throws AssertionError if the given {@code Collection} is {@code null}.
   * @throws AssertionError if the given {@code Collection} is not empty.
   */
  public void assertEmpty(Description description, Collection<?> actual) {
    assertNotNull(description, actual);
    if (!actual.isEmpty()) {
      throw failures.failure(description, shouldBeEmpty(actual));
    }
  }

  /**
   * Asserts that the given {@code Collection} is not empty.
   *
   * @param description contains information about the assertion.
   * @param actual      the given {@code Collection}.
   * @throws AssertionError if the given {@code Collection} is {@code null}.
   * @throws AssertionError if the given {@code Collection} is empty.
   */
  public void assertNotEmpty(Description description, Collection<?> actual) {
    assertNotNull(description, actual);
    if (actual.isEmpty()) {
      throw failures.failure(description, shouldNotBeEmpty());
    }
  }

  /**
   * Asserts that the number of elements in the given {@code Collection} is equal to the expected one.
   *
   * @param description  contains information about the assertion.
   * @param actual       the given {@code Collection}.
   * @param expectedSize the expected size of {@code actual}.
   * @throws AssertionError if the given {@code Collection} is {@code null}.
   * @throws AssertionError if the number of elements in the given {@code Collection} is different than the expected
   *                        one.
   */
  public void assertHasSize(Description description, Collection<?> actual, int expectedSize) {
    assertNotNull(description, actual);
    int sizeOfActual = actual.size();
    if (sizeOfActual != expectedSize) {
      throw failures.failure(description, shouldHaveSize(actual, sizeOfActual, expectedSize));
    }
  }

  /**
   * Asserts that the given {@code Collection} contains the given values, in any order.
   *
   * @param description contains information about the assertion.
   * @param actual      the given {@code Collection}.
   * @param values      the values that are expected to be in the given {@code Collection}.
   * @throws NullPointerException     if the array of values is {@code null}.
   * @throws IllegalArgumentException if the array of values is empty.
   * @throws AssertionError           if the given {@code Collection} is {@code null}.
   * @throws AssertionError           if the given {@code Collection} does not contain the given values.
   */
  public void assertContains(Description description, Collection<?> actual, Object[] values) {
    checkIsNotNullAndNotEmpty(values);
    assertNotNull(description, actual);
    Set<Object> notFound = new LinkedHashSet<Object>();
    for (Object value : values) {
      if (!actual.contains(value)) {
        notFound.add(value);
      }
    }
    if (notFound.isEmpty()) {
      return;
    }
    throw failures.failure(description, shouldContain(actual, values, notFound));
  }

  /**
   * Asserts that the given {@code Collection} contains {@code null}.
   *
   * @param description contains information about the assertion.
   * @param actual      the given {@code Collection}.
   * @throws AssertionError if the given {@code Collection} is {@code null}.
   * @throws AssertionError if the given {@code Collection} does not contain null.
   */
  public void assertContainsNull(Description description, Collection<?> actual) {
    assertNotNull(description, actual);
    if (!actual.contains(null)) {
      throw failures.failure(description, ShouldContainNull.shouldContainNull(actual));
    }
  }

  /**
   * Asserts that the given {@code Collection} contains only the given values and nothing else, in any order.
   *
   * @param description contains information about the assertion.
   * @param actual      the given {@code Collection}.
   * @param values      the values that are expected to be in the given {@code Collection}.
   * @throws NullPointerException     if the array of values is {@code null}.
   * @throws IllegalArgumentException if the array of values is empty.
   * @throws AssertionError           if the given {@code Collection} is {@code null}.
   * @throws AssertionError           if the given {@code Collection} does not contain the given values or if the given
   *                                  {@code Collection} contains values that are not in the given array.
   */
  public void assertContainsOnly(Description description, Collection<?> actual, Object[] values) {
    checkIsNotNullAndNotEmpty(values);
    assertNotNull(description, actual);
    Set<Object> notExpected = new LinkedHashSet<Object>(actual);
    Set<Object> notFound = containsOnly(notExpected, values);
    if (notExpected.isEmpty() && notFound.isEmpty()) {
      return;
    }
    throw failures.failure(description, shouldContainOnly(actual, values, notFound, notExpected));
  }

  /**
   * Verifies that the given {@link Collection} contains the given sequence of objects, without any other
   * objects between them.
   *
   * @param description contains information about the assertion.
   * @param actual      the given {@code Collection}.
   * @param sequence    the sequence of objects to look for.
   * @throws AssertionError           if the given {@code Collection} is {@code null}.
   * @throws NullPointerException     if the given sequence is {@code null}.
   * @throws IllegalArgumentException if the given sequence is empty.
   * @throws AssertionError           if the given {@code Collection} does not contain the given sequence of objects.
   */
  public void assertContainsSequence(Description description, Collection<?> actual, Object[] sequence) {
    checkIsNotNullAndNotEmpty(sequence);
    assertNotNull(description, actual);
    boolean firstAlreadyFound = false;
    int i = 0;
    int sequenceSize = sequence.length;
    for (Object o : actual) {
      if (i >= sequenceSize) {
        break;
      }
      if (!firstAlreadyFound) {
        if (!areEqual(o, sequence[i])) {
          continue;
        }
        firstAlreadyFound = true;
        i++;
        continue;
      }
      if (areEqual(o, sequence[i++])) {
        continue;
      }
      throw actualDoesNotContainSequence(description, actual, sequence);
    }
    if (!firstAlreadyFound || i < sequenceSize) {
      throw actualDoesNotContainSequence(description, actual, sequence);
    }
  }

  private AssertionError actualDoesNotContainSequence(Description description, Collection<?> actual, Object[] sequence) {
    return failures.failure(description, shouldContainSequence(actual, sequence));
  }

  /**
   * Asserts that the given {@code Collection} does not contain the given values.
   *
   * @param description contains information about the assertion.
   * @param actual      the given {@code Collection}.
   * @param values      the values that are expected not to be in the given {@code Collection}.
   * @throws NullPointerException     if the array of values is {@code null}.
   * @throws IllegalArgumentException if the array of values is empty.
   * @throws AssertionError           if the given {@code Collection} is {@code null}.
   * @throws AssertionError           if the given {@code Collection} contains any of given values.
   */
  public void assertDoesNotContain(Description description, Collection<?> actual, Object[] values) {
    checkIsNotNullAndNotEmpty(values);
    assertNotNull(description, actual);
    Set<Object> found = new LinkedHashSet<Object>();
    for (Object o : values) {
      if (actual.contains(o)) {
        found.add(o);
      }
    }
    if (found.isEmpty()) {
      return;
    }
    throw failures.failure(description, shouldNotContain(actual, values, found));
  }

  /**
   * Asserts that the given {@code Collection} does not contain {@code null}.
   *
   * @param description contains information about the assertion.
   * @param actual      the given {@code Collection}.
   * @throws AssertionError if the given {@code Collection} is {@code null}.
   * @throws AssertionError if the given {@code Collection} contains any of given values.
   */
  public void assertDoesNotContainNull(Description description, Collection<?> actual) {
    assertNotNull(description, actual);
    assertNotEmpty(description, actual);
    if (actual.contains(null)) {
      throw failures.failure(description, shouldNotContainNull(actual));
    }
  }

  /**
   * Asserts that the given {@code Collection} does not have duplicate values.
   *
   * @param description contains information about the assertion.
   * @param actual      the given {@code Collection}.
   * @throws NullPointerException     if the array of values is {@code null}.
   * @throws IllegalArgumentException if the array of values is empty.
   * @throws AssertionError           if the given {@code Collection} is {@code null}.
   * @throws AssertionError           if the given {@code Collection} contains duplicate values.
   */
  public void assertDoesNotHaveDuplicates(Description description, Collection<?> actual) {
    assertNotNull(description, actual);
    Collection<?> duplicates = duplicatesFrom(actual);
    if (duplicates.isEmpty()) {
      return;
    }
    throw failures.failure(description, shouldNotHaveDuplicates(actual, duplicates));
  }

  /**
   * Verifies that the given {@code Collection} starts with the given sequence of objects, without any other objects
   * between them. Similar to {@link #assertContainsSequence(Description, Collection, Object[])}, but it
   * also verifies that the first element in the sequence is also the first element of the given {@code Collection}.
   *
   * @param description contains information about the assertion.
   * @param actual      the given {@code Collection}.
   * @param sequence    the sequence of objects to look for.
   * @throws NullPointerException     if the given argument is {@code null}.
   * @throws IllegalArgumentException if the given argument is an empty array.
   * @throws AssertionError           if the given {@code Collection} is {@code null}.
   * @throws AssertionError           if the given {@code Collection} does not start with the given sequence of objects.
   */
  public void assertStartsWith(Description description, Collection<?> actual, Object[] sequence) {
    checkIsNotNullAndNotEmpty(sequence);
    assertNotNull(description, actual);
    int sequenceSize = sequence.length;
    if (actual.size() < sequenceSize) {
      throw actualDoesNotStartWithSequence(description, actual, sequence);
    }
    int i = 0;
    for (Object o : actual) {
      if (i >= sequenceSize) {
        break;
      }
      if (areEqual(o, sequence[i++])) {
        continue;
      }
      throw actualDoesNotStartWithSequence(description, actual, sequence);
    }
  }

  private AssertionError actualDoesNotStartWithSequence(Description description, Collection<?> actual, Object[] sequence) {
    return failures.failure(description, shouldStartWith(actual, sequence));
  }

  /**
   * Verifies that the given {@code Collection} ends with the given sequence of objects, without any other objects
   * between them. Similar to {@link #assertContainsSequence(Description, Collection, Object[])}, but it
   * also verifies that the last element in the sequence is also the last element of the given {@code Collection}.
   *
   * @param description contains information about the assertion.
   * @param actual      the given {@code Collection}.
   * @param sequence    the sequence of objects to look for.
   * @throws NullPointerException     if the given argument is {@code null}.
   * @throws IllegalArgumentException if the given argument is an empty array.
   * @throws AssertionError           if the given {@code Collection} is {@code null}.
   * @throws AssertionError           if the given {@code Collection} does not end with the given sequence of objects.
   */
  public void assertEndsWith(Description description, Collection<?> actual, Object[] sequence) {
    checkIsNotNullAndNotEmpty(sequence);
    assertNotNull(description, actual);
    int sequenceSize = sequence.length;
    int sizeOfActual = actual.size();
    if (sizeOfActual < sequenceSize) {
      throw actualDoesNotEndWithSequence(description, actual, sequence);
    }
    int start = actual.size() - sequenceSize;
    int sequenceIndex = 0, indexOfActual = 0;
    for (Object o : actual) {
      if (indexOfActual++ < start) {
        continue;
      }
      if (areEqual(o, sequence[sequenceIndex++])) {
        continue;
      }
      throw actualDoesNotEndWithSequence(description, actual, sequence);
    }
  }

  private void checkIsNotNullAndNotEmpty(Object[] values) {
    if (values == null) {
      throw arrayOfValuesToLookForIsNull();
    }
    if (values.length == 0) {
      throw arrayOfValuesToLookForIsEmpty();
    }
  }

  private void assertNotNull(Description description, Collection<?> actual) {
    Objects.instance().assertNotNull(description, actual);
  }

  private AssertionError actualDoesNotEndWithSequence(Description description, Collection<?> actual, Object[] sequence) {
    return failures.failure(description, shouldEndWith(actual, sequence));
  }
}


/*
 * Created on Oct 23, 2010
 *
 * 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.
 *
 * Copyright @2010-2012 the original author or authors.
 */
package org.fest.assertions.data;

import static org.fest.util.Objects.*;
import static org.fest.util.Preconditions.checkNotNull;

/**
 * A positive offset.
 *
 * @param <T> the type of the offset value.
 *
 * @author Alex Ruiz
 * @author Yvonne Wang
 */
public class Offset<T extends Number> {
  public final T value;

  /**
   * Creates a new {@link Offset}.
   *
   * @param value the value of the offset.
   * @return the created {@code Offset}.
   * @throws NullPointerException if the given value is {@code null}.
   * @throws IllegalArgumentException if the given value is negative.
   */
  public static Offset<Double> offset(Double value) {
    checkNotNull(value);
    if (value.doubleValue() < 0d) {
      throw valueNotPositive();
    }
    return new Offset<Double>(value);
  }

  /**
   * Creates a new {@link Offset}.
   *
   * @param value the value of the offset.
   * @return the created {@code Offset}.
   * @throws NullPointerException if the given value is {@code null}.
   * @throws IllegalArgumentException if the given value is negative.
   */
  public static Offset<Float> offset(Float value) {
    checkNotNull(value);
    if (value.floatValue() < 0f) {
      throw valueNotPositive();
    }
    return new Offset<Float>(value);
  }

  /**
   * Creates a new {@link Offset}.
   *
   * @param value the value of the offset.
   * @return the created {@code Offset}.
   * @throws NullPointerException if the given value is {@code null}.
   * @throws IllegalArgumentException if the given value is negative.
   */
  public static Offset<Integer> offset(Integer value) {
    checkNotNull(value);
    if (value.intValue() < 0) {
      throw valueNotPositive();
    }
    return new Offset<Integer>(value);
  }

  private static IllegalArgumentException valueNotPositive() {
    return new IllegalArgumentException("The value of the offset should be greater than zero");
  }

  private Offset(T value) {
    this.value = value;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj) {
      return true;
    }
    if (obj == null) {
      return false;
    }
    if (getClass() != obj.getClass()) {
      return false;
    }
    Offset<?> other = (Offset<?>) obj;
    return areEqual(value, other.value);
  }

  @Override
  public int hashCode() {
    int result = 1;
    result = HASH_CODE_PRIME * result + hashCodeFor(value);
    return result;
  }

  @Override
  public String toString() {
    return String.format("%s[value=%s]", getClass().getSimpleName(), value);
  }
}


package org.valid4j;

import org.hamcrest.Matcher;
import org.valid4j.provider.AssertiveInstance;
import org.valid4j.provider.AssertiveProvider;

import static org.valid4j.BooleanContractCondition.booleanContract;
import static org.valid4j.MatcherContractCondition.matcherContract;
import static org.valid4j.Message.describing;

/**
 * Entry point for the global assertive policy.
 *

 * Violations indicate the presence of a programming error.
 * Never try to catch and recover from a programming error. Rather
 * a reasonable global approach would be to return a HTTP 500 response,
 * restart the application or similar.
 *

 * Make use of hamcrest matchers to conveniently express conditions and get clear error messages.
 * Example:
 *

 * {@code
 *   require(i, greaterThan(0));
 * }
 *

 *

 * All error messages are {@link java.lang.String#format format strings} as defined by {@link java.lang.String}.
 *

 * Example:
 *

 * {@code
 *   require(i > 0, "The value must be greater than zero: %d", i);
 * }
 *

 */
public class Assertive {

  private static final String NULL_MESSAGE = null;
  private static final Throwable NO_CAUSE = null;
  private static final Error DEFAULT_ERROR = null;

  private Assertive() {
    throw neverGetHereError("Prevent instantiation");
  }

  /**
   * Precondition that clients are required to fulfill.
   * Violations are considered to be programming errors,
   * on the clients part.
   *
   * @param condition the condition to check
   */
  public static void require(boolean condition) {
    getProvider().requirePolicy().check(booleanContract(condition, NULL_MESSAGE));
  }

  /**
   * Precondition that clients are required to fulfill.
   * Violations are considered to be programming errors,
   * on the clients part.
   *
   * @param <T>     type of object to check
   * @param o       the object to check
   * @param matcher the matcher that the given object must satisfy
   * @return the validated object
   */
  public static <T> T require(T o, Matcher<?> matcher) {
    getProvider().requirePolicy().check(matcherContract(o, matcher));
    return o;
  }

  /**
   * Precondition that clients are required to fulfill.
   * Violations are considered to be programming errors,
   * on the clients part.
   *
   * @param condition the condition to check
   * @param msg       message {@link java.lang.String#format format string}
   * @param values    values passed into the msg format string
   */
  public static void require(boolean condition, String msg, Object... values) {
    getProvider().requirePolicy().check(booleanContract(condition, describing(msg, values)));
  }

  /**
   * Postcondition that supplier are supposed to ensure.
   * Violations are considered to be a programming error,
   * on the suppliers part.
   *
   * @param condition the condition to check
   */
  public static void ensure(boolean condition) {
    getProvider().ensurePolicy().check(booleanContract(condition, NULL_MESSAGE));
  }

  /**
   * Postcondition that supplier are supposed to ensure.
   * Violations are considered to be programming errors,
   * on the suppliers part.
   *
   * @param <T>     type of object to check
   * @param o       the object to check
   * @param matcher the matcher that the given object must satisfy
   * @return        the validated object
   */
  public static <T> T ensure(T o, Matcher<?> matcher) {
    getProvider().ensurePolicy().check(matcherContract(o, matcher));
    return o;
  }

  /**
   * Postcondition that supplier are supposed to ensure.
   * Violations are considered to be programming errors,
   * on the suppliers part.
   *
   * @param condition the condition to check
   * @param msg       message {@link java.lang.String#format format string}
   * @param values    values passed into the msg format string
   */
  public static void ensure(boolean condition, String msg, Object... values) {
    getProvider().ensurePolicy().check(booleanContract(condition, describing(msg, values)));
  }

  /**
   * Unreachable code have been reached.
   * This is considered to be a programming error.
   */
  public static void neverGetHere() {
    getProvider().neverGetHerePolicy().neverGetHere(NO_CAUSE, NULL_MESSAGE);
  }

  /**
   * Unreachable code have been reached. This is
   * considered to be a programming error.
   *
   * @param msg descriptive message
   */
  public static void neverGetHere(String msg) {
    getProvider().neverGetHerePolicy().neverGetHere(NO_CAUSE, msg);
  }

  /**
   * Unreachable code have been reached. This is
   * considered to be a programming error.
   *
   * @param msg    message {@link java.lang.String#format format string}
   * @param values values passed into the msg format string
   */
  public static void neverGetHere(String msg, Object... values) {
    getProvider().neverGetHerePolicy().neverGetHere(NO_CAUSE, describing(msg, values));
  }

  /**
   * Unexpected exception have been caught. This is considered
   * to be a programming error.
   *
   * @param t the throwable being unexpectedly caught.
   */
  public static void neverGetHere(Throwable t) {
    getProvider().neverGetHerePolicy().neverGetHere(t, NULL_MESSAGE);
  }

  /**
   * Unexpected exception have been caught. This is considered
   * to be a programming error.
   *
   * @param t      the throwable being unexpectedly caught.
   * @param msg    message {@link java.lang.String#format format string}
   * @param values values passed into the msg format string
   */
  public static void neverGetHere(Throwable t, String msg, Object... values) {
    getProvider().neverGetHerePolicy().neverGetHere(t, describing(msg, values));
  }

  /**
   * Unreachable code have been reached.
   * This is considered to be a programming error.
   * @return an error
   */
  public static Error neverGetHereError() {
    getProvider().neverGetHerePolicy().neverGetHere(NO_CAUSE, NULL_MESSAGE);
    return DEFAULT_ERROR;
  }

  /**
   * Unreachable code have been reached. This is
   * considered to be a programming error.
   *
   * @param msg descriptive message
   * @return an error
   */
  public static Error neverGetHereError(String msg) {
    getProvider().neverGetHerePolicy().neverGetHere(NO_CAUSE, msg);
    return DEFAULT_ERROR;
  }

  /**
   * Unreachable code have been reached. This is
   * considered to be a programming error.
   *
   * @param msg    message {@link java.lang.String#format format string}
   * @param values values passed into the msg format string
   * @return an error
   */
  public static Error neverGetHereError(String msg, Object... values) {
    getProvider().neverGetHerePolicy().neverGetHere(NO_CAUSE, describing(msg, values));
    return DEFAULT_ERROR;
  }

  /**
   * Unexpected exception have been caught. This is considered
   * to be a programming error.
   *
   * @param t the throwable being unexpectedly caught.
   * @return an error
   */
  public static Error neverGetHereError(Throwable t) {
    getProvider().neverGetHerePolicy().neverGetHere(t, NULL_MESSAGE);
    return DEFAULT_ERROR;
  }

  /**
   * Unexpected exception have been caught. This is considered
   * to be a programming error.
   *
   * @param t      the throwable being unexpectedly caught.
   * @param msg    message {@link java.lang.String#format format string}
   * @param values values passed into the msg format string
   * @return an error
   */
  public static Error neverGetHereError(Throwable t, String msg, Object... values) {
    getProvider().neverGetHerePolicy().neverGetHere(t, describing(msg, values));
    return DEFAULT_ERROR;
  }

  private static AssertiveProvider getProvider() {
    return AssertiveInstance.getInstance();
  }

}


package org.valid4j.matchers;

import org.hamcrest.CustomTypeSafeMatcher;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;

import static org.hamcrest.CoreMatchers.describedAs;
import static org.valid4j.Assertive.neverGetHereError;

/**
 * Common convenience matchers for arguments
 */
public class ArgumentMatchers {

  private ArgumentMatchers() {
    throw neverGetHereError("Prevent instantiation");
  }

  /**
   * Wraps an existing matcher, decorating its description with the name specified. All other functions are
   * delegated to the decorated matcher, including its mismatch description.
   *
   * Example:
   *
named("theNameOfMyArgument", notNullValue())

   *
   * @param <T>
   *     the type of object to match
   * @param name
   *     the given name of the wrapped matcher
   * @param matcher
   *     the matcher to decorate with a name
   * @return
   *     the decorated matcher
   */
  @Factory
  public static <T> Matcher<T> named(String name, Matcher<T> matcher) {
    return describedAs("%0 = %1", matcher, name, matcher);

  }

  /**
   * A matcher matching non-null and non-empty strings
   *
   * Example:
   *
assertThat("this is not an empty string", notEmptyString())

   *
   * @return
   *     matcher for a not empty string
   */
  @Factory
  public static Matcher<String> notEmptyString() {
    return new CustomTypeSafeMatcher<String>("not empty") {
      @Override
      protected boolean matchesSafely(String s) {
        return !s.isEmpty();
      }
    };
  }
}


package org.valid4j;

import org.hamcrest.Matcher;

import static org.valid4j.Assertive.neverGetHereError;
import static org.valid4j.ExceptionFactories.exception;
import static org.valid4j.Message.describingMismatchOf;

/**
 * Entry point for validation methods.
 *
 * Use these methods to throw recoverable exceptions if validation fails.
 * Recoverable exceptions are exceptions that a correct program can and
 * should catch and recover from. (Recoverable exceptions may be checked
 * or unchecked.)
 */
public class Validation {

  private Validation() {
    throw neverGetHereError("Prevent instantiation");
  }

  public static <X extends Exception> ExceptionFactory<X> otherwiseThrowing(Class<X> exceptionClass) {
    return exception(exceptionClass);
  }

  /**
   * Validate the boolean expression that is supposed to be true.
   * If not, a recoverable exception will be thrown that the client
   * is supposed to catch and recover from.
   *
   * @param <X> type of exception to throw if validation fails
   * @param condition the condition to validate. Supposed to be true.
   * @param exception the recoverable exception to be thrown if validation fails.
   * @throws X the recoverable exception, if validation fails
   */
  public static <X extends Exception> void validate(boolean condition, X exception) throws X {
    if (!condition) {
      throw exception;
    }
  }

  /**
   * Validate the boolean expression that is supposed to be true.
   * If not, a recoverable exception will be thrown that the client
   * is supposed to catch and recover from.
   *
   * @param <X> type of exception to throw if validation fails
   * @param condition the condition to validate. Supposed to be true.
   * @param exceptionClass the class of recoverable exception to be thrown if validation fails.
   * @throws X the recoverable exception, if validation fails
   */
  public static <X extends Exception> void validate(boolean condition, Class<X> exceptionClass) throws X {
    if (!condition) {
      throw exception(exceptionClass).newInstance(null);
    }
  }

  /**
   * Validate the boolean expression that is supposed to be true.
   * If not, a recoverable exception will be thrown that the client
   * is supposed to catch and recover from.
   *
   * @param <X> type of exception to throw if validation fails
   * @param condition the condition to validate. Supposed to be true.
   * @param factory the factory of recoverable exception to be thrown if validation fails.
   * @throws X the recoverable exception, if validation fails
   */
  public static <X extends Exception> void validate(boolean condition, ExceptionFactory<X> factory) throws X {
    if (!condition) {
      throw factory.newInstance(null);
    }
  }

  /**
   * Validate the given object that is supposed to satisfy the matcher.
   * If not, a recoverable exception will be thrown that the client
   * is supposed to catch and recover from.
   *
   * @param <T> type of object to validate
   * @param <X> type of exception to throw if validation fails
   * @param o the object to validate
   * @param matcher the matcher that the object must satisfy
   * @param exception the recoverable exception to be thrown if validation fails.
   * @return the validated object, if valid
   * @throws X the recoverable exception, if validation fails
   */
  public static <T, X extends Exception> T validate(T o, Matcher<?> matcher, X exception) throws X {
    if (!matcher.matches(o)) {
      throw exception;
    }
    return o;
  }

  /**
   * Validate the given object that is supposed to satisfy the matcher.
   * If not, a recoverable exception will be thrown that the client
   * is supposed to catch and recover from.
   *
   * @param <T> type of object to validate
   * @param <X> type of exception to throw if validation fails
   * @param o the object to validate
   * @param matcher the matcher that the object must satisfy
   * @param exceptionClass the class of recoverable exception to be thrown if validation fails.
   * @return the validated object, if valid
   * @throws X the recoverable exception, if validation fails
   */
  public static <T, X extends Exception> T validate(T o, Matcher<?> matcher, Class<X> exceptionClass) throws X {
    if (!matcher.matches(o)) {
      throw exception(exceptionClass).newInstance(describingMismatchOf(o, matcher).toString());
    }
    return o;
  }

  /**
   * Validate the given object that is supposed to satisfy the matcher.
   * If not, a recoverable exception will be thrown that the client
   * is supposed to catch and recover from.
   *
   * @param <T> type of object to validate
   * @param <X> type of exception to throw if validation fails
   * @param o the object to validate
   * @param matcher the matcher that the object must satisfy
   * @param factory the factory of recoverable exception to be thrown if validation fails.
   * @return the validated object, if valid
   * @throws X the recoverable exception, if validation fails
   */
  public static <T, X extends Exception> T validate(T o, Matcher<?> matcher, ExceptionFactory<X> factory) throws X {
    if (!matcher.matches(o)) {
      throw factory.newInstance(describingMismatchOf(o, matcher).toString());
    }
    return o;
  }
}


/**
 * Copyright (C) 2009-2012 the original author or authors.
 * See the notice.md file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * 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.fusesource.restygwt.client;

import com.google.gwt.http.client.URL;

import java.util.Map;

/**
 *
 * @author Hiram Chirino
 */
public class Resource {

    public static final String CONTENT_TYPE_TEXT = "text/plain";
    public static final String CONTENT_TYPE_JSON = "application/json";
    public static final String CONTENT_TYPE_XML = "application/xml";
    public static final String CONTENT_TYPE_RSS = "application/rss+xml";
    public static final String CONTENT_TYPE_ATOM = "application/atom+xml";
    public static final String CONTENT_TYPE_FORM = "application/x-www-form-urlencoded";
    public static final String HEADER_ACCEPT = "Accept";
    public static final String HEADER_CONTENT_TYPE = "Content-Type";

    final String path;
    final String query;
    final Map<String, String> headers;

    public Resource(String uri) {
        this(uri, (Map<String, String>) null);
    }

    public Resource(String uri, String query) {
        this(uri, query, null);
    }

    public Resource(String uri, Map<String, String> headers) {
        int pos = uri.indexOf('?');
        if (pos >= 0) {
            path = uri.substring(0, pos);
            query = uri.substring(pos + 1);
        } else {
            // Strip off trailing "/" so we have a known format to work off of when concatenating paths
            path = uri.endsWith("/") ? uri.substring(0, uri.length() - 1) : uri;
            query = null;
        }
        this.headers = (headers != null) ? headers : defaultHeaders();
    }

    public Resource(String uri, String query, Map<String, String> headers) {
        // Strip off trailing "/" so we have a known format to work off of when concatenating paths
        path = uri.endsWith("/") ? uri.substring(0, uri.length() - 1) : uri;
        this.query = query;
        this.headers = (headers != null) ? headers : defaultHeaders();
    }

    public Method head() {
        return new Method(this, "HEAD").headers(headers);
    }

    public Method get() {
        return new Method(this, "GET").headers(headers);
    }

    public Method put() {
        return new Method(this, "PUT").headers(headers);
    }

    public Method post() {
        return new Method(this, "POST").headers(headers);
    }

    public Method delete() {
        return new Method(this, "DELETE").headers(headers);
    }

    public Method options() {
        return new Method(this, "OPTIONS").headers(headers);
    }

    public JsonpMethod jsonp() {
        return new JsonpMethod(this);
    }

    public String getPath() {
        return path;
    }

    public String getQuery() {
        return query;
    }

    public String getUri() {
        if (query != null) {
            return path + "?" + query;
        }
        return path;
    }

    public Map<String, String> getHeaders() {
        return headers;
    }

    protected Map<String, String> defaultHeaders() {
        return null;
    }

    // TODO: support fancier resolutions
    public Resource resolve(String path) {

        // it might be an absolute path...
        if (path.startsWith("http:") || path.startsWith("https:") || path.startsWith("file:")) {
            return new Resource(path, query, headers);
        }

        // strip prefix / if needed...
        if (path.startsWith("/")) {
            path = path.substring(1);
        }
        return new Resource(this.path + "/" + path, query, headers);
    }

    public Resource addQueryParam(String key, String value) {
        if (value == null) {
            return this;
        }
        key = URL.encodeQueryString(key);
        value = URL.encodeQueryString(value);
        String q = query == null ? "" : query + "&";
        return new Resource(path, q + key + "=" + value, headers);
    }

    public Resource addQueryParams(String key, Iterable<String> values) {
        if (values == null) {
            return this;
        }
        key = URL.encodeQueryString(key);
        StringBuilder q = new StringBuilder(query == null ? "" : query + "&");
        boolean ampersand = false;
        for (String value : values) {
            if (value == null) {
                continue;
            }
            if (ampersand) {
                q.append('&');
            } else {
                ampersand = true;
            }
            value = URL.encodeQueryString(value);
            q.append(key).append("=").append(value);
        }

        return new Resource(path, q.toString(), headers);
    }

}


/**
 * Copyright (C) 2009-2012 the original author or authors.
 * See the notice.md file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * 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.fusesource.restygwt.client.callback;

import org.fusesource.restygwt.client.Method;

/**
 * default callback factory with a given set of callback filters which
 * gets added to a new callback after creating it.
 *
 * @author Kristian
 *
 */
public class DefaultCallbackFactory implements CallbackFactory {

    private final CallbackFilter[] callbackFilters;

    public DefaultCallbackFactory(CallbackFilter... callbackFilter) {
        callbackFilters = callbackFilter;
    }

    /**
     * helper method to create the callback with all configurations wanted
     *
     * @param method
     * @return
     */
    @Override
    public FilterawareRequestCallback createCallback(Method method) {
        FilterawareRequestCallback callback = new DefaultFilterawareRequestCallback(method);

        for (CallbackFilter filter : callbackFilters) {
            callback.addFilter(filter);
        }
        return callback;
    }
}


/**
 * Copyright (C) 2009-2012 the original author or authors.
 * See the notice.md file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * 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.fusesource.restygwt.client;

import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONBoolean;
import com.google.gwt.json.client.JSONException;
import com.google.gwt.json.client.JSONNull;
import com.google.gwt.json.client.JSONNumber;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONParser;
import com.google.gwt.json.client.JSONString;
import com.google.gwt.json.client.JSONValue;
import com.google.gwt.xml.client.Document;
import com.google.gwt.xml.client.XMLParser;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.fusesource.restygwt.client.Json.Style;
import org.fusesource.restygwt.client.util.Base64Codec;

/**
 *
 * @author Hiram Chirino
 * @author Dave Finch
 */
public abstract class AbstractJsonEncoderDecoder<T> implements JsonEncoderDecoder<T> {

    @Override
    public T decode(String value) throws DecodingException {
        try {
            return decode(JSONParser.parseStrict(value));
        } catch (JSONException e) {
            // that can happen for generic key types like Object and then a String key gets passed in
            return decode(JSONParser.parseStrict("\"" + value + "\""));
        }
    }

    // /////////////////////////////////////////////////////////////////
    // Built in encoders for the native types.
    // /////////////////////////////////////////////////////////////////
    public static final AbstractJsonEncoderDecoder<Boolean> BOOLEAN = new AbstractJsonEncoderDecoder<Boolean>() {

        @Override
        public Boolean decode(JSONValue value) throws DecodingException {
            if (value == null || value.isNull() != null) {
                return null;
            }
            JSONBoolean bool = value.isBoolean();
            if (bool == null) {
                throw new DecodingException("Expected a json boolean, but was given: " + value);
            }
            return bool.booleanValue();
        }

        @Override
        public JSONValue encode(Boolean value) throws EncodingException {
            return (value == null) ? getNullType() : JSONBoolean.getInstance(value);
        }
    };

    public static final AbstractJsonEncoderDecoder<Character> CHAR = new AbstractJsonEncoderDecoder<Character>() {

        @Override
        public Character decode(JSONValue value) throws DecodingException {
            if (value == null || value.isNull() != null) {
                return null;
            }
            return (char) toDouble(value);
        }

        @Override
        public JSONValue encode(Character value) throws EncodingException {
            return (value == null) ? getNullType() : new JSONNumber(value);
        }
    };

    public static final AbstractJsonEncoderDecoder<Byte> BYTE = new AbstractJsonEncoderDecoder<Byte>() {

        @Override
        public Byte decode(JSONValue value) throws DecodingException {
            if (value == null || value.isNull() != null) {
                return null;
            }
            return (byte) toDouble(value);

        }

        @Override
        public JSONValue encode(Byte value) throws EncodingException {
            if (value == null) {
                return null;
            }
            return new JSONNumber(value);
        }
    };

    public static final AbstractJsonEncoderDecoder<Short> SHORT = new AbstractJsonEncoderDecoder<Short>() {

        @Override
        public Short decode(JSONValue value) throws DecodingException {
            if (value == null || value.isNull() != null) {
                return null;
            }
            return (short) toDouble(value);
        }

        @Override
        public JSONValue encode(Short value) throws EncodingException {
            return (value == null) ? getNullType() : new JSONNumber(value);
        }
    };

    public static final AbstractJsonEncoderDecoder<Integer> INT = new AbstractJsonEncoderDecoder<Integer>() {

        @Override
        public Integer decode(JSONValue value) throws DecodingException {
            if (value == null || value.isNull() != null) {
                return null;
            }
            return (int) toDouble(value);
        }

        @Override
        public JSONValue encode(Integer value) throws EncodingException {
            return (value == null) ? getNullType() : new JSONNumber(value);
        }
    };

    public static final AbstractJsonEncoderDecoder<Long> LONG = new AbstractJsonEncoderDecoder<Long>() {

        @Override
        public Long decode(JSONValue value) throws DecodingException {
            if (value == null || value.isNull() != null) {
                return null;
            }
            JSONString valueString = value.isString();
            if (valueString != null) {
                return Long.parseLong(valueString.stringValue());
            }
            return (long) toDouble(value);
        }

        @Override
        public JSONValue encode(Long value) throws EncodingException {
            return (value == null) ? getNullType() : new JSONNumber(value);
        }
    };

    public static final AbstractJsonEncoderDecoder<Float> FLOAT = new AbstractJsonEncoderDecoder<Float>() {

        @Override
        public Float decode(JSONValue value) throws DecodingException {
            if (value == null || value.isNull() != null) {
                return null;
            }
            return (float) toDouble(value);
        }

        @Override
        public JSONValue encode(Float value) throws EncodingException {
            return (value == null) ? getNullType() : new JSONNumber(value);
        }
    };

    public static final AbstractJsonEncoderDecoder<Double> DOUBLE = new AbstractJsonEncoderDecoder<Double>() {

        @Override
        public Double decode(JSONValue value) throws DecodingException {
            if (value == null || value.isNull() != null) {
                return null;
            }
            return toDouble(value);
        }

        @Override
        public JSONValue encode(Double value) throws EncodingException {
            return (value == null) ? getNullType() : new JSONNumber(value);
        }
    };

    public static final AbstractJsonEncoderDecoder<String> STRING = new AbstractJsonEncoderDecoder<String>() {

        @Override
        public String decode(JSONValue value) throws DecodingException {
            if (value == null || value.isNull() != null) {
                return null;
            }
            JSONString str = value.isString();
            if (str == null) {
                if (value.isBoolean() != null || value.isNumber() != null) {
                    return value.toString();
                }
                throw new DecodingException("Expected a json string, but was given: " + value);
            }
            return str.stringValue();
        }

        @Override
        public String decode(String value) throws DecodingException {
            return value;
        }

        @Override
        public JSONValue encode(String value) throws EncodingException {
            return (value == null) ? getNullType() : new JSONString(value);
        }
    };

    public static final AbstractJsonEncoderDecoder<BigDecimal> BIG_DECIMAL =
        new AbstractJsonEncoderDecoder<BigDecimal>() {

            @Override
            public BigDecimal decode(JSONValue value) throws DecodingException {
                if (value == null || value.isNull() != null) {
                    return null;
                }
                return toBigDecimal(value);
            }

            @Override
            public JSONValue encode(BigDecimal value) throws EncodingException {
                return (value == null) ? getNullType() : new JSONString(value.toString());
            }
        };

    public static final AbstractJsonEncoderDecoder<BigInteger> BIG_INTEGER =
        new AbstractJsonEncoderDecoder<BigInteger>() {

            @Override
            public BigInteger decode(JSONValue value) throws DecodingException {
                if (value == null || value.isNull() != null) {
                    return null;
                }
                JSONNumber number = value.isNumber();
                if (number == null) {
                    JSONString str = value.isString();
                    if (str == null) {
                        throw new DecodingException("Expected a json number r string, but was given: " + value);
                    }

                    // Doing a straight conversion from string to BigInteger will
                    // not work for large values
                    // So we convert to BigDecimal first and then convert it to
                    // BigInteger.
                    return new BigDecimal(str.stringValue()).toBigInteger();
                }

                // Doing a straight conversion from string to BigInteger will not
                // work for large values
                // So we convert to BigDecimal first and then convert it to
                // BigInteger.
                return new BigDecimal(value.toString()).toBigInteger();
            }

            @Override
            public JSONValue encode(BigInteger value) throws EncodingException {
                return (value == null) ? getNullType() : new JSONString(value.toString());
            }
        };

    public static final AbstractJsonEncoderDecoder<Document> DOCUMENT = new AbstractJsonEncoderDecoder<Document>() {

        @Override
        public Document decode(JSONValue value) throws DecodingException {
            if (value == null || value.isNull() != null) {
                return null;
            }
            JSONString str = value.isString();
            if (str == null) {
                throw new DecodingException("Expected a json string, but was given: " + value);
            }
            return XMLParser.parse(str.stringValue());
        }

        @Override
        public JSONValue encode(Document value) throws EncodingException {
            return (value == null) ? getNullType() : new JSONString(value.toString());
        }
    };

    public static final AbstractJsonEncoderDecoder<JSONValue> JSON_VALUE = new AbstractJsonEncoderDecoder<JSONValue>() {

        @Override
        public JSONValue decode(JSONValue value) throws DecodingException {
            return value;
        }

        @Override
        public JSONValue encode(JSONValue value) throws EncodingException {
            return value;
        }
    };

    public static final AbstractJsonEncoderDecoder<Date> DATE = new AbstractJsonEncoderDecoder<Date>() {

        @Override
        public Date decode(JSONValue value) throws DecodingException {
            if (value == null || value.isNull() != null) {
                return null;
            }

            String format = Defaults.getDateFormat();

            if (format == null) {
                JSONNumber num = value.isNumber();
                if (num == null) {
                    throw new DecodingException("Expected a json number, but was given: " + value);
                }
                return new Date((long) num.doubleValue());
            }

            JSONString str = value.isString();
            if (str == null) {
                throw new DecodingException("Expected a json string, but was given: " + value);
            }

            if (Defaults.getTimeZone() == null || Defaults.dateFormatHasTimeZone()) {
                return DateTimeFormat.getFormat(format).parse(str.stringValue());
            }
            // We need to provide time zone information to the GWT date parser.
            // Unfortunately, DateTimeFormat has no overload specifying a TimeZone,
            // so the only way is to extend the format string.
            return DateTimeFormat.getFormat(format + " v")
                .parse(str.stringValue() + " " + Defaults.getTimeZone().getID());
        }

        @Override
        public JSONValue encode(Date value) throws EncodingException {
            if (value == null) {
                return getNullType();
            }

            String format = Defaults.getDateFormat();

            if (format == null) {
                return new JSONNumber(value.getTime());
            }

            if (Defaults.getTimeZone() == null || Defaults.dateFormatHasTimeZone()) {
                return new JSONString(DateTimeFormat.getFormat(format).format(value));
            }
            return new JSONString(DateTimeFormat.getFormat(format).format(value, Defaults.getTimeZone()));
        }
    };

    // /////////////////////////////////////////////////////////////////
    // Helper Methods.
    // /////////////////////////////////////////////////////////////////

    public static BigDecimal toBigDecimal(JSONValue value) {
        JSONNumber number = value.isNumber();
        if (number != null) {
            return new BigDecimal(number.toString());
        }

        JSONString string = value.isString();
        if (string != null) {
            try {
                return new BigDecimal(string.stringValue());
            } catch (NumberFormatException e) {
            }
        }

        throw new DecodingException("Expected a json number, but was given: " + value);
    }

    public static double toDouble(JSONValue value) {
        JSONNumber number = value.isNumber();
        if (number == null) {
            JSONString val = value.isString();
            if (val != null) {
                try {
                    return Double.parseDouble(val.stringValue());
                } catch (NumberFormatException e) {
                    // just through exception below
                }
            }
            throw new DecodingException("Expected a json number, but was given: " + value);
        }
        return number.doubleValue();
    }

    public static JSONObject toObject(JSONValue value) {
        JSONObject object = value.isObject();
        if (object == null) {
            throw new DecodingException("Expected a json object, but was given: " + object);
        }
        return object;
    }

    public static JSONObject toObjectFromWrapper(JSONValue value, String name) {
        JSONObject object = value.isObject();
        if (object == null) {
            throw new DecodingException("Expected a json object, but was given: " + object);
        }
        JSONValue result = object.get(name);
        if (result == null) {
            // no wrapper found but that is possible within the hierarchy
            return toObject(value);
        }
        return toObject(result);
    }

    static JSONArray asArray(JSONValue value) {
        JSONArray array = value.isArray();
        if (array == null) {
            //Jersey render arrays with one object as object and not as array.
            JSONObject object = value.isObject();
            if (object == null) {
                throw new DecodingException("Expected a json array, but was given: " + value);
            }
            //Found a object and return it as array.
            array = new JSONArray();
            array.set(0, object);
        }
        return array;
    }

    public static <Type> List<Type> toList(JSONValue value, AbstractJsonEncoderDecoder<Type> encoder) {
        if (value == null || value.isNull() != null) {
            return null;
        }
        JSONArray array = asArray(value);

        ArrayList<Type> rc = new ArrayList<Type>(array.size());
        int size = array.size();
        for (int i = 0; i < size; i++) {
            rc.add(encoder.decode(array.get(i)));
        }
        return rc;
    }

    public static <Type> Type[] toArray(JSONValue value, AbstractJsonEncoderDecoder<Type> encoder, Type[] template) {
        if (value == null || value.isNull() != null) {
            return null;
        }
        JSONArray array = asArray(value);

        int size = array.size();
        for (int i = 0; i < size; i++) {
            template[i] = encoder.decode(array.get(i));
        }
        return template;
    }

    public static short[] toArray(JSONValue value, AbstractJsonEncoderDecoder<Short> encoder, short[] template) {
        if (value == null || value.isNull() != null) {
            return null;
        }
        JSONArray array = asArray(value);

        int size = array.size();
        for (int i = 0; i < size; i++) {
            template[i] = encoder.decode(array.get(i));
        }
        return template;
    }

    public static long[] toArray(JSONValue value, AbstractJsonEncoderDecoder<Long> encoder, long[] template) {
        if (value == null || value.isNull() != null) {
            return null;
        }
        JSONArray array = asArray(value);

        int size = array.size();
        for (int i = 0; i < size; i++) {
            template[i] = encoder.decode(array.get(i));
        }
        return template;
    }

    public static int[] toArray(JSONValue value, AbstractJsonEncoderDecoder<Integer> encoder, int[] template) {
        if (value == null || value.isNull() != null) {
            return null;
        }
        JSONArray array = asArray(value);

        int size = array.size();
        for (int i = 0; i < size; i++) {
            template[i] = encoder.decode(array.get(i));
        }
        return template;
    }

    public static float[] toArray(JSONValue value, AbstractJsonEncoderDecoder<Float> encoder, float[] template) {
        if (value == null || value.isNull() != null) {
            return null;
        }
        JSONArray array = asArray(value);

        int size = array.size();
        for (int i = 0; i < size; i++) {
            template[i] = encoder.decode(array.get(i));
        }
        return template;
    }

    public static double[] toArray(JSONValue value, AbstractJsonEncoderDecoder<Double> encoder, double[] template) {
        if (value == null || value.isNull() != null) {
            return null;
        }
        JSONArray array = asArray(value);

        int size = array.size();
        for (int i = 0; i < size; i++) {
            template[i] = encoder.decode(array.get(i));
        }
        return template;
    }

    public static byte[] toArray(JSONValue value, AbstractJsonEncoderDecoder<Byte> encoder) {
        if (value == null || value.isNull() != null) {
            return null;
        }

        if (value.isString() != null) {
            return Base64Codec.decode(value.isString().stringValue());
        }
        JSONArray array = asArray(value);
        int size = array.size();
        byte template[] = new byte[size];
        for (int i = 0; i < size; i++) {
            template[i] = encoder.decode(array.get(i));
        }
        return template;
    }

    public static char[] toArray(JSONValue value, AbstractJsonEncoderDecoder<Character> encoder, char[] template) {
        if (value == null || value.isNull() != null) {
            return null;
        }
        JSONArray array = asArray(value);

        int size = array.size();
        for (int i = 0; i < size; i++) {
            template[i] = encoder.decode(array.get(i));
        }
        return template;
    }

    public static boolean[] toArray(JSONValue value, AbstractJsonEncoderDecoder<Boolean> encoder, boolean[] template) {
        if (value == null || value.isNull() != null) {
            return null;
        }
        JSONArray array = asArray(value);

        int size = array.size();
        for (int i = 0; i < size; i++) {
            template[i] = encoder.decode(array.get(i));
        }
        return template;
    }

    public static int getSize(JSONValue value) {
        if (value == null || value.isNull() != null) {
            return 0;
        }
        JSONArray array = asArray(value);
        return array.size();
    }

    public static <Type> Set<Type> toSet(JSONValue value, AbstractJsonEncoderDecoder<Type> encoder) {
        if (value == null || value.isNull() != null) {
            return null;
        }
        JSONArray array = asArray(value);

        HashSet<Type> rc = new HashSet<Type>(array.size() * 2);
        int size = array.size();
        for (int i = 0; i < size; i++) {
            rc.add(encoder.decode(array.get(i)));
        }
        return rc;
    }

    public static <Type> Map<String, Type> toMap(JSONValue value, AbstractJsonEncoderDecoder<Type> encoder,
                                                 Style style) {
        if (value == null || value.isNull() != null) {
            return null;
        }

        switch (style) {
            case DEFAULT:
            case SIMPLE: {
                JSONObject object = value.isObject();
                if (object == null) {
                    throw new DecodingException("Expected a json object, but was given: " + value);
                }

                HashMap<String, Type> rc = new HashMap<String, Type>(object.size() * 2);
                for (String key : object.keySet()) {
                    rc.put(key, encoder.decode(object.get(key)));
                }
                return rc;
            }
            case JETTISON_NATURAL: {
                JSONObject object = value.isObject();
                if (object == null) {
                    throw new DecodingException("Expected a json object, but was given: " + value);
                }
                value = object.get("entry");
                if (value == null) {
                    throw new DecodingException("Expected an entry array not found");
                }
                JSONArray entries = value.isArray();
                if (entries == null) {
                    throw new DecodingException("Expected an entry array, but was given: " + value);
                }

                HashMap<String, Type> rc = new HashMap<String, Type>(object.size() * 2);
                for (int i = 0; i < entries.size(); i++) {
                    JSONObject entry = entries.get(i).isObject();
                    if (entry == null) {
                        throw new DecodingException("Expected an entry object, but was given: " + value);
                    }
                    JSONValue key = entry.get("key");
                    if (key == null) {
                        throw new DecodingException("Expected an entry key field not found");
                    }
                    JSONString k = key.isString();
                    if (k == null) {
                        throw new DecodingException("Expected an entry key to be a string, but was given: " + value);
                    }

                    rc.put(k.stringValue(), encoder.decode(entry.get("value")));
                }
                return rc;
            }
            default:
                throw new UnsupportedOperationException("The encoding style is not yet suppored: " + style.name());
        }
    }

    public static <KeyType, ValueType> Map<KeyType, ValueType> toMap(JSONValue value,
                                                                     AbstractJsonEncoderDecoder<KeyType> keyEncoder,
                                                                     AbstractJsonEncoderDecoder<ValueType> valueEncoder,
                                                                     Style style) {
        if (value == null || value.isNull() != null) {
            return null;
        }

        switch (style) {
            case DEFAULT:
            case SIMPLE: {
                JSONObject object = value.isObject();
                if (object == null) {
                    throw new DecodingException("Expected a json object, but was given: " + value);
                }

                HashMap<KeyType, ValueType> rc = new HashMap<KeyType, ValueType>(object.size() * 2);
                for (String key : object.keySet()) {
                    rc.put(keyEncoder.decode(key), valueEncoder.decode(object.get(key)));
                }
                return rc;
            }
            case JETTISON_NATURAL: {
                JSONObject object = value.isObject();
                if (object == null) {
                    throw new DecodingException("Expected a json object, but was given: " + value);
                }
                value = object.get("entry");
                if (value == null) {
                    throw new DecodingException("Expected an entry array not found");
                }
                JSONArray entries = value.isArray();
                if (entries == null) {
                    throw new DecodingException("Expected an entry array, but was given: " + value);
                }

                HashMap<KeyType, ValueType> rc = new HashMap<KeyType, ValueType>(object.size() * 2);
                for (int i = 0; i < entries.size(); i++) {
                    JSONObject entry = entries.get(i).isObject();
                    if (entry == null) {
                        throw new DecodingException("Expected an entry object, but was given: " + value);
                    }
                    JSONValue key = entry.get("key");
                    if (key == null) {
                        throw new DecodingException("Expected an entry key field not found");
                    }
                    JSONString k = key.isString();
                    if (k == null) {
                        throw new DecodingException("Expected an entry key to be a string, but was given: " + value);
                    }
                    rc.put(keyEncoder.decode(k.stringValue()), valueEncoder.decode(entry.get("value")));
                }
                return rc;
            }
            default:
                throw new UnsupportedOperationException("The encoding style is not yet supported: " + style.name());
        }
    }

    // TODO(sbeutel): new map method to handle other key values than String
    public static <KeyType, ValueType> JSONValue toJSON(Map<KeyType, ValueType> value,
                                                        AbstractJsonEncoderDecoder<? super KeyType> keyEncoder,
                                                        AbstractJsonEncoderDecoder<? super ValueType> valueEncoder,
                                                        Style style) {
        if (value == null) {
            return getNullType();
        }

        switch (style) {
            case DEFAULT:
            case SIMPLE: {
                JSONObject rc = new JSONObject();

                for (Entry<KeyType, ValueType> t : value.entrySet()) {
                    //TODO find a way to check only once
                    JSONValue k = keyEncoder.encode(t.getKey());
                    if (k.isString() != null) {
                        rc.put(k.isString().stringValue(), valueEncoder.encode(t.getValue()));
                    } else {
                        rc.put(k.toString(), valueEncoder.encode(t.getValue()));
                    }
                }
                return rc;
            }
            case JETTISON_NATURAL: {
                JSONObject rc = new JSONObject();
                JSONArray entries = new JSONArray();
                int i = 0;
                for (Entry<KeyType, ValueType> t : value.entrySet()) {
                    JSONObject entry = new JSONObject();
                    //TODO find a way to check only once
                    JSONValue k = keyEncoder.encode(t.getKey());
                    if (k.isString() != null) {
                        entry.put("key", k);
                    } else {
                        entry.put("key", new JSONString(k.toString()));
                    }
                    entry.put("value", valueEncoder.encode(t.getValue()));
                    entries.set(i++, entry);
                }
                rc.put("entry", entries);
                return rc;
            }
            default:
                throw new UnsupportedOperationException("The encoding style is not yet supported: " + style.name());
        }
    }

    public static <Type> JSONValue toJSON(Map<String, Type> value, AbstractJsonEncoderDecoder<Type> encoder,
                                          Style style) {
        if (value == null) {
            return getNullType();
        }

        switch (style) {
            case DEFAULT:
            case SIMPLE: {
                JSONObject rc = new JSONObject();
                for (Entry<String, Type> t : value.entrySet()) {
                    rc.put(t.getKey(), encoder.encode(t.getValue()));
                }
                return rc;
            }
            case JETTISON_NATURAL: {
                JSONObject rc = new JSONObject();
                JSONArray entries = new JSONArray();
                int i = 0;
                for (Entry<String, Type> t : value.entrySet()) {
                    JSONObject entry = new JSONObject();
                    entry.put("key", new JSONString(t.getKey()));
                    entry.put("value", encoder.encode(t.getValue()));
                    entries.set(i++, entry);
                }
                rc.put("entry", entries);
                return rc;
            }
            default:
                throw new UnsupportedOperationException("The encoding style is not yet suppored: " + style.name());
        }
    }

    public static <Type> JSONValue toJSON(Collection<Type> value, AbstractJsonEncoderDecoder<Type> encoder) {
        if (value == null) {
            return getNullType();
        }
        JSONArray rc = new JSONArray();
        int i = 0;
        for (Type t : value) {
            rc.set(i++, encoder.encode(t));
        }
        return rc;
    }

    public static <Type> JSONValue toJSON(Type[] value, AbstractJsonEncoderDecoder<Type> encoder) {
        if (value == null) {
            return getNullType();
        }
        JSONArray rc = new JSONArray();
        int i = 0;
        for (Type t : value) {
            rc.set(i++, encoder.encode(t));
        }
        return rc;
    }

    public static <Type> JSONValue toJSON(short[] value, AbstractJsonEncoderDecoder<Type> encoder) {
        if (value == null) {
            return getNullType();
        }
        JSONArray rc = new JSONArray();
        int i = 0;
        for (short t : value) {
            rc.set(i++, new JSONNumber(t));
        }
        return rc;
    }

    public static <Type> JSONValue toJSON(int[] value, AbstractJsonEncoderDecoder<Type> encoder) {
        if (value == null) {
            return getNullType();
        }
        JSONArray rc = new JSONArray();
        int i = 0;
        for (int t : value) {
            rc.set(i++, new JSONNumber(t));
        }
        return rc;
    }

    public static <Type> JSONValue toJSON(long[] value, AbstractJsonEncoderDecoder<Type> encoder) {
        if (value == null) {
            return getNullType();
        }
        JSONArray rc = new JSONArray();
        int i = 0;
        for (long t : value) {
            rc.set(i++, new JSONNumber(t));
        }
        return rc;
    }

    public static <Type> JSONValue toJSON(float[] value, AbstractJsonEncoderDecoder<Type> encoder) {
        if (value == null) {
            return getNullType();
        }
        JSONArray rc = new JSONArray();
        int i = 0;
        for (float t : value) {
            rc.set(i++, new JSONNumber(t));
        }
        return rc;
    }

    public static <Type> JSONValue toJSON(double[] value, AbstractJsonEncoderDecoder<Type> encoder) {
        if (value == null) {
            return getNullType();
        }
        JSONArray rc = new JSONArray();
        int i = 0;
        for (double t : value) {
            rc.set(i++, new JSONNumber(t));
        }
        return rc;
    }

    public static <Type> JSONValue toJSON(boolean[] value, AbstractJsonEncoderDecoder<Type> encoder) {
        if (value == null) {
            return getNullType();
        }
        JSONArray rc = new JSONArray();
        int i = 0;
        for (boolean t : value) {
            rc.set(i++, JSONBoolean.getInstance(t));
        }
        return rc;
    }

    public static <Type> JSONValue toJSON(char[] value, AbstractJsonEncoderDecoder<Type> encoder) {
        if (value == null) {
            return getNullType();
        }
        JSONArray rc = new JSONArray();
        int i = 0;
        for (char t : value) {
            rc.set(i++, new JSONNumber(t));
        }
        return rc;
    }

    public static <Type> JSONValue toJSON(byte[] value, AbstractJsonEncoderDecoder<Type> encoder) {
        if (value == null) {
            return getNullType();
        }

        if (Defaults.isByteArraysToBase64()) {
            return new JSONString(Base64Codec.encode(value));
        }
        JSONArray rc = new JSONArray();
        int i = 0;
        for (byte t : value) {
            rc.set(i++, new JSONNumber(t));
        }
        return rc;
    }

    protected static short getValueToSetForShort(Short value, int defaultValue) {
        if (value == null) {
            return (short) defaultValue;
        }
        return value;
    }

    protected static <T> T getValueToSet(T value, T defaultValue) {
        if (value instanceof JSONNull || value == null) {
            return defaultValue;
        }
        return value;
    }

    protected static void isNotNullValuePut(JSONValue object, JSONObject value, String jsonName) {
        if (object != null) {
            value.put(jsonName, object);
        }
    }

    protected static boolean isNotNullAndCheckDefaults(Object object, JSONObject value, String jsonName) {
        if (object != null) {
            return true;
        } else if (Defaults.doesIgnoreJsonNulls()) {
            return false;
        } else {
            value.put(jsonName, JSONNull.getInstance());
            return false;
        }
    }

    protected static JSONNull getNullType() {
        return (Defaults.doesIgnoreJsonNulls()) ? null : JSONNull.getInstance();
    }
}


/**
 * Copyright (C) 2009-2012 the original author or authors.
 * See the notice.md file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * 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.fusesource.restygwt.examples.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONValue;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;

import org.fusesource.restygwt.client.JsonCallback;
import org.fusesource.restygwt.client.Method;
import org.fusesource.restygwt.client.MethodCallback;
import org.fusesource.restygwt.client.Resource;
import org.fusesource.restygwt.client.RestServiceProxy;

/**
 * Entry point classes define onModuleLoad().
 *
 * @author Hiram Chirino
 */
public class UI implements EntryPoint {

    /**
     * This is the entry point method.
     */
    @Override
    public void onModuleLoad() {
        Button button = new Button("Place Pizza Order");
        button.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                placeOrder();
            }
        });
        RootPanel.get().add(button);

        button = new Button("Do Put");
        button.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                doPut();
            }
        });
        RootPanel.get().add(button);

        button = new Button("Do A Search on Yahoo /w Jsonp");
        button.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                doJsonp();
            }
        });

        RootPanel.get().add(button);
    }

    protected void doPut() {
        MethodService service = GWT.create(MethodService.class);
        service.put(new MethodCallback<String>() {
            @Override
            public void onFailure(Method method, Throwable exception) {
                Window.alert("Error x: " + exception);
            }

            @Override
            public void onSuccess(Method method, String response) {
                RootPanel.get().add(new Label("done."));
            }
        });
    }

    private void placeOrder() {
        PizzaService service = GWT.create(PizzaService.class);
        Resource resource = new Resource(GWT.getModuleBaseURL() + "pizza-service");
        ((RestServiceProxy) service).setResource(resource);

        PizzaOrder order = new PizzaOrder();
        order.delivery = true;
        order.delivery_address.add("3434 Pinerun Ave.");
        order.delivery_address.add("Tampa, FL  33734");

        Pizza pizza = new Pizza();
        pizza.crust = "thin";
        pizza.quantity = 1;
        pizza.size = 16;
        pizza.toppings.add("ham");
        pizza.toppings.add("pineapple");
        order.pizzas.add(pizza);

        pizza = new Pizza();
        pizza.crust = "thin";
        pizza.quantity = 1;
        pizza.size = 16;
        pizza.toppings.add("extra cheese");
        order.pizzas.add(pizza);

        service.order(order, new MethodCallback<OrderConfirmation>() {
            @Override
            public void onSuccess(Method method, OrderConfirmation receipt) {
                RootPanel.get().add(new Label("got receipt: " + receipt));
            }

            @Override
            public void onFailure(Method method, Throwable exception) {
                Window.alert("Error: " + exception);
            }
        });
    }

    protected void doJsonp() {
        Resource resource = new Resource(
            "http://search.yahooapis.com/WebSearchService/V1/webSearch?appid=YahooDemo&query=finances&format=pdf" +
                "&output=json&callback=callback");
        resource.jsonp().send(new JsonCallback() {
            @Override
            public void onSuccess(Method method, JSONValue response) {
                JSONObject obj = (JSONObject) ((JSONObject) response).get("ResultSet");
                RootPanel.get().add(new Label("Search Results Available: " + obj.get("totalResultsAvailable")));
            }

            @Override
            public void onFailure(Method method, Throwable exception) {
                Window.alert("Error x: " + exception);
            }
        });
    }

}


/**
 * Copyright (C) 2009-2012 the original author or authors.
 * See the notice.md file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * 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.fusesource.restygwt.client.dispatcher;

import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.logging.client.LogConfiguration;

import java.util.logging.Logger;

import org.fusesource.restygwt.client.Dispatcher;
import org.fusesource.restygwt.client.Method;
import org.fusesource.restygwt.client.callback.CallbackFactory;
import org.fusesource.restygwt.client.callback.FilterawareRequestCallback;

public class DefaultDispatcherFilter implements DispatcherFilter {

    /**
     * where to get a callback from. gives us the ability to use
     * customized {@link FilterawareRequestCallback}
     */
    private CallbackFactory callbackFactory;

    /**
     * the one and only constructor
     * @param cf
     */
    public DefaultDispatcherFilter(CallbackFactory cf) {
        callbackFactory = cf;
    }

    /**
     * main filter method for a dispatcherfilter.
     *
     * @return continue filtering or not
     */
    @Override
    public boolean filter(Method method, RequestBuilder builder) {
        if (LogConfiguration.loggingIsEnabled()) {
            Logger.getLogger(Dispatcher.class.getName())
                .info("Sending http request: " + builder.getHTTPMethod() + " " + builder.getUrl());
        }

        builder.setCallback(callbackFactory.createCallback(method));
        return true;
    }
}