/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.jackrabbit.test.api;

import org.apache.jackrabbit.test.AbstractJCRTest;
import org.apache.jackrabbit.test.NotExecutableException;
import org.apache.jackrabbit.test.api.nodetype.NodeTypeUtil;

import javax.jcr.PropertyType;
import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.Value;
import javax.jcr.ValueFormatException;
import javax.jcr.RepositoryException;

import java.io.InputStream;
import java.io.ByteArrayInputStream;
import java.util.Calendar;

/**
 * <code>SetValueValueFormatExceptionTest</code> tests if Property.setValue() throws
 * a ValueFormatException if a best-effort conversion fails.
 * The ValueFormatException has to be thrown immediately (not on save).
 *
 */
public class SetValueValueFormatExceptionTest extends AbstractJCRTest {

    /**
     * Tests if setValue(Value) throws a ValueFormatException immediately (not
     * on save) if a conversion fails.
     */
    public void testValue()
        throws NotExecutableException, RepositoryException {

        Property booleanProperty = createProperty(PropertyType.BOOLEAN, false);
        try {
            Value dateValue = NodeTypeUtil.getValueOfType(superuser, PropertyType.DATE);
            booleanProperty.setValue(dateValue);
            fail("Property.setValue(Value) must throw a ValueFormatException " +
                    "immediately if a conversion fails.");
        } catch (ValueFormatException e) {
            // success
        }
    }

    /**
     * Tests if setValue(Value[]) throws a ValueFormatException immediately (not
     * on save) if a conversion fails.
     */
    public void testValueArray()
        throws NotExecutableException, RepositoryException {

        Property booleanProperty = createProperty(PropertyType.BOOLEAN, true);
        try {
            Value dateValues[] =
                    new Value[]{NodeTypeUtil.getValueOfType(superuser, PropertyType.DOUBLE)};
            booleanProperty.setValue(dateValues);
            fail("Property.setValue(Value[]) must throw a ValueFormatException " +
                    "immediately if a conversion fails.");
        } catch (ValueFormatException e) {
            // success
        }
    }

    /**
     * Tests if setValue(String) throws a ValueFormatException immediately (not
     * on save) if a conversion fails.
     */
    public void testString()
        throws NotExecutableException, RepositoryException {

        Property dateProperty = createProperty(PropertyType.DATE, false);
        try {
            dateProperty.setValue("abc");
            fail("Property.setValue(String) must throw a ValueFormatException " +
                    "immediately if a conversion fails.");
        } catch (ValueFormatException e) {
            // success
        }
    }

    /**
     * Tests if setValue(String[]) throws a ValueFormatException immediately (not
     * on save) if a conversion fails.
     */
    public void testStringArray()
        throws NotExecutableException, RepositoryException {

        Property dateProperty = createProperty(PropertyType.DATE, true);
        try {
            String values[] = new String[]{"abc"};
            dateProperty.setValue(values);
            fail("Property.setValue(String[]) must throw a ValueFormatException " +
                    "immediately if a conversion fails.");
        } catch (ValueFormatException e) {
            // success
        }
    }

    /**
     * Tests if setValue(InputStream) throws a ValueFormatException immediately (not
     * on save) if a conversion fails.
     */
    public void testInputStream()
        throws NotExecutableException, RepositoryException {

        Property dateProperty = createProperty(PropertyType.DATE, false);
        try {
            byte[] bytes = {123};
            InputStream value = new ByteArrayInputStream(bytes);
            dateProperty.setValue(value);
            fail("Property.setValue(InputStream) must throw a ValueFormatException " +
                    "immediately if a conversion fails.");
        } catch (ValueFormatException e) {
            // success
        }
    }

    /**
     * Tests if setValue(long) throws a ValueFormatException immediately (not
     * on save) if a conversion fails.
     */
    public void testLong()
        throws NotExecutableException, RepositoryException {

        Property booleanProperty = createProperty(PropertyType.BOOLEAN, false);
        try {
            booleanProperty.setValue(123);
            fail("Property.setValue(long) must throw a ValueFormatException " +
                    "immediately if a conversion fails.");
        } catch (ValueFormatException e) {
            // success
        }
    }

    /**
     * Tests if setValue(double) throws a ValueFormatException immediately (not
     * on save) if a conversion fails.
     */
    public void testDouble()
        throws NotExecutableException, RepositoryException {

        Property booleanProperty = createProperty(PropertyType.BOOLEAN, false);
        try {
            booleanProperty.setValue(1.23);
            fail("Property.setValue(double) must throw a ValueFormatException " +
                    "immediately if a conversion fails.");
        } catch (ValueFormatException e) {
            // success
        }
    }

    /**
     * Tests if setValue(Calendar) throws a ValueFormatException immediately (not
     * on save) if a conversion fails.
     */
    public void testCalendar()
        throws NotExecutableException, RepositoryException {

        Property booleanProperty = createProperty(PropertyType.BOOLEAN, false);
        try {
            booleanProperty.setValue(Calendar.getInstance());
            fail("Property.setValue(Calendar) must throw a ValueFormatException " +
                    "immediately if a conversion fails.");
        } catch (ValueFormatException e) {
            // success
        }
    }

    /**
     * Tests if setValue(boolean) throws a ValueFormatException immediately (not
     * on save) if a conversion fails.
     */
    public void testBoolean()
        throws NotExecutableException, RepositoryException {

        Property dateProperty = createProperty(PropertyType.DATE, false);
        try {
            dateProperty.setValue(true);
            fail("Property.setValue(boolean) must throw a ValueFormatException " +
                    "immediately if a conversion fails.");
        } catch (ValueFormatException e) {
            // success
        }
    }

    /**
     * Tests if setValue(Node) throws a ValueFormatException immediately (not
     * on save) if the property is not of type REFERENCE.
     */
    public void testNode()
        throws NotExecutableException, RepositoryException {

        Property booleanProperty = createProperty(PropertyType.BOOLEAN, false);
        try {
            Node referenceableNode = testRootNode.addNode(nodeName3, testNodeType);
            if (needsMixin(referenceableNode, mixReferenceable)) {
                ensureMixinType(referenceableNode, mixReferenceable);
            }

            // some implementations may require a save after addMixin()
            testRootNode.getSession().save();

            // make sure the node is now referenceable
            assertTrue("test node should be mix:referenceable", referenceableNode.isNodeType(mixReferenceable));

            booleanProperty.setValue(referenceableNode);
            fail("Property.setValue(Node) must throw a ValueFormatException " +
                    "immediately if the property is not of type REFERENCE.");
        }
        catch (ValueFormatException e) {
            // success
        }
    }

    /**
     * Tests if setValue(Node) throws a ValueFormatException immediately (not
     * on save) if the specified node is not referencable.
     */
    public void testNodeNotReferenceable()
        throws NotExecutableException, RepositoryException {

        if (testRootNode.isNodeType(mixReferenceable)) {
            throw new NotExecutableException("test requires testRootNode to be non-referenceable");
        }

        Property referenceProperty = createProperty(PropertyType.REFERENCE, false);
        try {
            referenceProperty.setValue(testRootNode);
            fail("Property.setValue(Node) must throw a ValueFormatException " +
                    "immediately if the specified node is not referenceable.");
        } catch (ValueFormatException e) {
            // success
        }
    }

    /**
     * Creates a node under {@link #testRootNode} and sets a property on with
     * <code>propertyType</code> on the newly created node.
     *
     * @param propertyType the type of the property to create.
     * @param multiple     if the property must support multiple values.
     * @return the property
     * @throws RepositoryException    if an error occurs
     * @throws NotExecutableException if there is no such property defined on
     *                                the node type for the new child node.
     */
    private Property createProperty(int propertyType, boolean multiple) throws RepositoryException, NotExecutableException {
        Node n = testRootNode.addNode(nodeName1, testNodeType);
        if (propertyType == PropertyType.REFERENCE && !n.isNodeType(mixReferenceable)) {
            if (!n.canAddMixin(mixReferenceable)) {
                throw new NotExecutableException(testNodeType + " does not support adding of mix:referenceable");
            } else {
                n.addMixin(mixReferenceable);
                // some implementations may require a save after addMixin()
                testRootNode.getSession().save();
            }
        }

        Value initValue;
        if (propertyType == PropertyType.REFERENCE) {
            initValue = superuser.getValueFactory().createValue(n);
        } else {
            initValue = NodeTypeUtil.getValueOfType(superuser, propertyType);
        }

        if (multiple) {
            Value[] initValues = new Value[]{initValue};
            if (!n.getPrimaryNodeType().canSetProperty(propertyName1, initValues)) {

                throw new NotExecutableException("Node type: " + testNodeType +
                        " does not support a multi valued " +
                        PropertyType.nameFromValue(propertyType) + " property " +
                        "called: " + propertyName1);
            }
            return n.setProperty(propertyName1, initValues);
        } else {
            if (!n.getPrimaryNodeType().canSetProperty(propertyName1, initValue)) {

                throw new NotExecutableException("Node type: " + testNodeType +
                        " does not support a single valued " +
                        PropertyType.nameFromValue(propertyType) + " property " +
                        "called: " + propertyName1);
            }
            return n.setProperty(propertyName1, initValue);
        }
    }
}
