/**
 * Copyright (c) 2012-2017, jcabi.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met: 1) Redistributions of source code must retain the above
 * copyright notice, this list of conditions and the following
 * disclaimer. 2) Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following
 * disclaimer in the documentation and/or other materials provided
 * with the distribution. 3) Neither the name of the jcabi.com nor
 * the names of its contributors may be used to endorse or promote
 * products derived from this software without specific prior written
 * permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.jcabi.aspects;

import com.jcabi.aspects.version.Version;
import java.util.regex.Pattern;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

/**
 * Test case for {@link Immutable} annotation and its implementation.
 *
 * @author Yegor Bugayenko (yegor@tpc2.com)
 * @version $Id: 749376021adc36e00e3389d60082261e7bcecb30 $
 * @checkstyle ConstantUsageCheck (500 lines)
 */
@SuppressWarnings
    (
        {
            "PMD.UnusedPrivateField",
            "PMD.UnusedLocalVariable",
            "PMD.FinalFieldCouldBeStatic"
        }
    )
public final class ImmutableTest {

    /**
     * Exception rule.
     */
    @Rule
    // @checkstyle VisibilityModifierCheck (1 line)
    public final transient ExpectedException thrown;

    /**
     * Ctor.
     */
    public ImmutableTest() {
        this.thrown = ExpectedException.none();
    }

    /**
     * Immutable can catch mutable classes.
     */
    @Test(expected = IllegalStateException.class)
    public void catchedMutableTypes() {
        new Mutable();
    }

    /**
     * ImmutabilityChecker can catch mutable classes with arrays.
     */
    @Test(expected = IllegalStateException.class)
    public void catchedMutableTypesWithArrays() {
        new MutableWithArray();
    }

    /**
     * Immutable can pass immutable classes.
     */
    @Test
    public void passesImmutableObjects() {
        final Object obj = new TruelyImmutable(
            new TruelyImmutableWithNonPrivateFields()
        );
    }

    /**
     * Immutable can pass immutable classes.
     */
    @Test
    public void passesImmutableObjectsWithNonPrivateFields() {
        new TruelyImmutableWithNonPrivateFields();
    }

    /**
     * Immutable can catch mutable classes with interfaces.
     */
    @Test(expected = IllegalStateException.class)
    public void catchesTypesMutableByClassInheritance() {
        new MutableByInheritance();
    }

    /**
     * Informs version on error.
     */
    @Test
    public void informsVersionOnError() {
        this.thrown.expect(IllegalStateException.class);
        this.thrown.expectMessage(Version.CURRENT.projectVersion());
        this.thrown.expectMessage(Version.CURRENT.buildNumber());
        new Mutable();
    }

    /**
     * Supposedly immutable class.
     */
    @Immutable
    private static final class Mutable {
        /**
         * Mutable class member.
         */
        @SuppressWarnings("PMD.ImmutableField")
        private transient String data = "hello";
    }

    /**
     * Mutable class because of array.
     */
    @Immutable
    private static final class MutableWithArray {
        /**
         * Mutable class member.
         */
        private final transient int[] data = null;
    }

    /**
     * Other vague interface.
     */
    @Immutable
    private interface ImmutableInterface {
        /**
         * This function seems to be harmless.
         * @param input An input
         */
        void willBreakImmutability(int input);
    }

    /**
     * Truely immutable class.
     */
    @Immutable
    private static final class TruelyImmutable {
        /**
         * Something static final.
         */
        private static final Pattern PATTERN = Pattern.compile(".?");
        /**
         * Something just static.
         */
        private static Pattern ptrn = Pattern.compile("\\d+");
        /**
         * Immutable class member.
         */
        private final transient String data;
        /**
         * Another immutable class member.
         */
        private final transient int number;
        /**
         * Another immutable class member.
         */
        private final transient String text;
        /**
         * An immutable array member.
         */
        @Immutable.Array
        private final transient String[] texts;
        /**
         * Immutable iface.
         */
        private final transient ImmutableInterface iface;
        /**
         * Ctor.
         */
        public TruelyImmutable() {
            this("Hello, world!");
        }
        /**
         * Ctor.
         * @param ipt Input
         */
        public TruelyImmutable(final TruelyImmutableWithNonPrivateFields ipt) {
            this(ipt.text);
        }
        /**
         * Ctor.
         * @param ipt Input
         */
        @SuppressWarnings("PMD.NullAssignment")
        public TruelyImmutable(final String ipt) {
            this.text = ipt;
            this.texts = new String[] {"foo"};
            this.iface = null;
            this.data = null;
            this.number = 2;
        }
    }

    /**
     * Truely immutable class with non-private fields.
     *
     * @checkstyle VisibilityModifier (25 lines)
     */
    @Immutable
    private static final class TruelyImmutableWithNonPrivateFields {
        /**
         * Something static final.
         */
        public static final Pattern PATTERN = Pattern.compile(".*");
        /**
         * Something just static.
         */
        public static final Pattern PTRN = Pattern.compile(".+");
        /**
         * Immutable class member.
         */
        public final String data = null;
        /**
         * Another immutable class member.
         */
        public final int number = 2;
        /**
         * Another immutable class member.
         */
        public final String text = "Hello!";
    }

    /**
     * Almost immutable class. It can be inherited, because it is non-final;
     * thus methods in the child class can return nonsensical values (e.g.
     * getters that do no return the original value of their corresponding
     * fields). Moreover, immutability cannot be forced to a subclass.
     * See <a href=
     * "http://marxsoftware.blogspot.se/2009/09/
     * is-java-immutable-class-always-final.html">
     * Is java immutable class always final?</a>
     */
    @Immutable
    private static class MutableByInheritance {
        /**
         * Immutable class member.
         */
        private final transient String data = null;
        /**
         * Could be overloaded by a child of the class and then return
         * nonsensical value.
         *
         * @return A value that could differ from what is expected if returned by an overriding method
         */
        public String getData() {
            return this.data;
        }
    }

}
