/*
 * Copyright 2014-2019 Web Firm Framework
 *
 * 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.
 * @author WFF
 */
package com.webfirmframework.wffweb.tag.html.attribute.global;

import java.util.UUID;

import com.webfirmframework.wffweb.tag.html.attribute.AttributeNameConstants;
import com.webfirmframework.wffweb.tag.html.attribute.core.AbstractAttribute;
import com.webfirmframework.wffweb.tag.html.identifier.GlobalAttributable;

/**
 * @author WFF
 *
 */
public class Id extends AbstractAttribute implements GlobalAttributable {

    private static final long serialVersionUID = 1_0_0L;

    private UUID uuid;

    {
        super.setAttributeName(AttributeNameConstants.ID);
        init();
    }

    /**
     * sets with empty value as id=""
     *
     * @since 1.0.0
     * @author WFF
     */
    public Id() {
        super.setAttributeValue("");
    }

    /**
     * value for the id attribute.
     *
     * @param value
     * @since 1.0.0
     * @author WFF
     */
    public Id(final String value) {
        super.setAttributeValue(value);
    }

    /**
     * value for the id attribute.
     *
     * @param uuid
     * @since 1.0.0
     * @author WFF
     */
    public Id(final UUID uuid) {
        this.uuid = uuid;
        super.setAttributeValue(uuid.toString());
    }

    /**
     * value for the id attribute.
     *
     * @param value
     * @since 1.0.0
     * @author WFF
     */
    public Id(final int value) {
        super.setAttributeValue(String.valueOf(value));
    }

    /**
     * value for the id attribute.
     *
     * @param value
     * @since 1.0.0
     * @author WFF
     */
    public Id(final float value) {
        super.setAttributeValue(String.valueOf(value));
    }

    /**
     * value for the id attribute.
     *
     * @param value
     * @since 1.0.0
     * @author WFF
     */
    public Id(final long value) {
        super.setAttributeValue(String.valueOf(value));
    }

    /**
     * value for the id attribute.
     *
     * @param value
     * @since 1.0.0
     * @author WFF
     */
    public Id(final double value) {
        super.setAttributeValue(String.valueOf(value));
    }

    /**
     * invokes only once per object
     *
     * @author WFF
     * @since 1.0.0
     */
    protected void init() {
        // to override and use this method
    }

    /**
     * value for the id attribute.
     *
     * @param id
     * @since 1.0.0
     * @author WFF
     */
    public void setValue(final String id) {
        if (id != null) {
            super.setAttributeValue(id);
            uuid = null;
        }
    }

    /**
     * value for the id attribute.
     *
     * @param id
     * @since 1.0.0
     * @author WFF
     */
    public void setValue(final int id) {
        super.setAttributeValue(String.valueOf(id));
        uuid = null;
    }

    /**
     * value for the id attribute.
     *
     * @param id
     * @since 1.0.0
     * @author WFF
     */
    public void setValue(final long id) {
        super.setAttributeValue(String.valueOf(id));
        uuid = null;
    }

    /**
     * value for the id attribute.
     *
     * @param id
     * @since 1.0.0
     * @author WFF
     */
    public void setValue(final float id) {
        super.setAttributeValue(String.valueOf(id));
        uuid = null;
    }

    /**
     * value for the id attribute.
     *
     * @param id
     * @since 1.0.0
     * @author WFF
     */
    public void setValue(final double id) {
        super.setAttributeValue(String.valueOf(id));
        uuid = null;
    }

    /**
     * value for the id attribute.
     *
     * @param uuid
     * @since 1.0.0
     * @author WFF
     */
    public void setValue(final UUID uuid) {
        if (uuid != null) {
            super.setAttributeValue(uuid.toString());
            this.uuid = uuid;
        }
    }

    /**
     * @return the value for the attribute set
     * @since 1.0.0
     * @author WFF
     */
    public String getValue() {
        return super.getAttributeValue();
    }

    /**
     * @return the {@code UUID} set by {@code Id#setValue(UUID)},
     *         {@code Id#setUuid(UUID)} or {@code Id#Id(UUID)}.
     * @since 1.0.0
     * @author WFF
     */
    public UUID getUuid() {
        return uuid;
    }

    /**
     * To set {@code UUID} as id value.
     *
     * @param uuid
     *                 the {@code UUID} object
     * @since 1.0.0
     * @author WFF
     */
    public void setUuid(final UUID uuid) {
        super.setAttributeValue(uuid.toString());
        this.uuid = uuid;
    }
}


/*
 * Copyright 2014-2019 Web Firm Framework
 *
 * 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 com.webfirmframework.wffweb.util;

import java.nio.ByteBuffer;

/**
 *
 * @author WFF
 * @since 3.0.1
 */
public final class ByteBufferUtil {

    private ByteBufferUtil() {
        throw new AssertionError();
    }

    /**
     * Eg:
     *
     *

     *
     * final String s = "1234567890abc";
     * final ByteBuffer inputData = ByteBuffer
     *         .wrap(s.getBytes(StandardCharsets.UTF_8));
     *
     * final StringBuilder builder = new StringBuilder();
     *
     * final int totalSlices = ByteBufferUtil.slice(inputData, 3,
     *         (part, last) -> {
     *             builder.append(
     *                     new String(part.array(), StandardCharsets.UTF_8));
     *             return !last;
     *         });
     *
     * System.out.println(s.equals(builder.toString()));
     * System.out.println(5 == totalSlices);
     *
     *

     *
     * prints
     *
     *

     * true
     * true
     *

     *
     * @param data
     *                         ByteBuffer to be sliced
     * @param maxSliceSize
     *                         the maximum size of ByteBuffer of the sliced
     *                         part.
     * @param slice
     *                         the Slice functional interface. The method
     *                         Slice.each must return true to continue slicing.
     * @return the total number of slices made. NB: it is just the count of
     *         invocation of Slice.each method. If the Slice.each returned false
     *         while the last (second param in Slice.each) is false then the
     *         returned slice count will be less than the probable number of
     *         slices in the input data.
     * @since 3.0.1
     */
    public static int slice(final ByteBuffer data, final int maxSliceSize,
            final Slice<ByteBuffer> slice) {

        ByteBuffer d = data;

        int remaining = 0;
        int sliceCount = 0;
        while ((remaining = d.remaining()) > 0) {
            sliceCount++;
            final byte[] bytes = new byte[maxSliceSize < remaining
                    ? maxSliceSize
                    : remaining];

            final boolean goOn = slice.each(ByteBuffer.wrap(bytes),
                    d.get(bytes).remaining() == 0);

            if (!goOn) {
                break;
            }

            d = d.slice();
        }

        return sliceCount;
    }

    /**
     *
     * It slices only if required, i.e. if the capacity of input data is equal
     * to the remaining data in the buffer and the capacity is less than or
     * equal to the maxSliceSize then the Slice.each method will reuse the same
     * input data as the first argument.

     *

     * Eg:
     *
     *

     *
     * final String s = "1234567890abc";
     * final ByteBuffer inputData = ByteBuffer
     *         .wrap(s.getBytes(StandardCharsets.UTF_8));
     *
     * final StringBuilder builder = new StringBuilder();
     *
     * final int totalSlices = ByteBufferUtil.sliceIfRequired(inputData, 3,
     *         (part, last) -> {
     *             builder.append(
     *                     new String(part.array(), StandardCharsets.UTF_8));
     *             return !last;
     *         });
     *
     * System.out.println(s.equals(builder.toString()));
     * System.out.println(5 == totalSlices);
     *
     *

     *
     * prints
     *
     *

     * true
     * true
     *

     *
     * @param data
     *                         ByteBuffer to be sliced
     * @param maxSliceSize
     *                         the maximum size of ByteBuffer of the sliced
     *                         part.
     * @param slice
     *                         the Slice functional interface. The method
     *                         Slice.each must return true to continue slicing.
     * @return the total number of slices made. NB: it is just the count of
     *         invocation of Slice.each method. If the Slice.each returned false
     *         while the last (second param in Slice.each) is false then the
     *         returned slice count will be less than the probable number of
     *         slices in the input data.
     * @since 3.0.1
     */
    public static int sliceIfRequired(final ByteBuffer data,
            final int maxSliceSize, final Slice<ByteBuffer> slice) {

        final int capacity = data.capacity();
        if (capacity == data.remaining() && capacity <= maxSliceSize) {
            slice.each(data, true);
            return 1;
        }

        ByteBuffer d = data;

        int remaining = 0;
        int sliceCount = 0;
        while ((remaining = d.remaining()) > 0) {
            sliceCount++;
            final byte[] bytes = new byte[maxSliceSize < remaining
                    ? maxSliceSize
                    : remaining];

            final boolean goOn = slice.each(ByteBuffer.wrap(bytes),
                    d.get(bytes).remaining() == 0);

            if (!goOn) {
                break;
            }

            d = d.slice();
        }

        return sliceCount;
    }

    /**
     * Merges and returns ByteBuffer from the given dataArray. The ByteBuffer
     * will be returned after flip.
     *
     * @param dataArray
     *                      the ByteByffers to merge
     * @return a single ByteBuffer after flip merged from all ByteBuffer objects
     *         from dataArray.
     *
     * @since 3.0.2
     */
    public static ByteBuffer merge(final ByteBuffer... dataArray) {
        int totalCapacity = 0;
        for (final ByteBuffer data : dataArray) {
            totalCapacity += data.capacity();
        }
        final ByteBuffer wholeData = ByteBuffer.allocate(totalCapacity);
        for (final ByteBuffer data : dataArray) {
            wholeData.put(data);
        }
        wholeData.flip();

        return wholeData;
    }

}


/*
 * Copyright 2014-2019 Web Firm Framework
 *
 * 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.
 * @author WFF
 */
package com.webfirmframework.wffweb.css.css3;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.logging.Logger;

import com.webfirmframework.wffweb.InvalidValueException;
import com.webfirmframework.wffweb.NullValueException;
import com.webfirmframework.wffweb.css.CssLengthUnit;
import com.webfirmframework.wffweb.css.CssNameConstants;
import com.webfirmframework.wffweb.css.core.AbstractCssProperty;
import com.webfirmframework.wffweb.css.core.CssProperty;
import com.webfirmframework.wffweb.informer.StateChangeInformer;
import com.webfirmframework.wffweb.util.CssLengthUtil;
import com.webfirmframework.wffweb.util.ObjectUtil;
import com.webfirmframework.wffweb.util.StringUtil;
import com.webfirmframework.wffweb.util.TagStringUtil;

/**
 *

 *
 * border-image-outset: length|number|initial|inherit;
 *
 * The border-image-outset property specifies the amount by which the border image area extends beyond the border box.
 *
 * Default value:  0
 * Inherited:      no
 * Animatable:     no
 * Version:        CSS3
 * JavaScript syntax:      object.style.borderImageOutset="10px"
 *
 * Examples:
 *
 *     border-image-outset:5px 10px 15px 10px;
 *         top border is 5px
 *         right border is 10px
 *         bottom border is 15px
 *         border is 10px
 *
 *     border-image-outset:5px 10px 15px;
 *         top border is 5px
 *         right and left borders are 10px
 *         bottom border is 15px
 *
 *     border-image-outset:5px 10px;
 *         top and bottom borders are 5px
 *         right and left borders are 10px
 *
 *     border-image-outset:5px;
 *         all four borders are 5px
 *
 *

 *
 *
 * @author WFF
 * @since 1.0.0
 */
public class BorderImageOutset extends AbstractCssProperty<BorderImageOutset> {

    private static final long serialVersionUID = 1_0_0L;

    public static final Logger LOGGER = Logger
            .getLogger(BorderImageOutset.class.getName());

    public static final String INITIAL = "initial";
    public static final String INHERIT = "inherit";

    protected static final String DEFAULT_VALUE = "0";

    private static final List<String> PREDEFINED_CONSTANTS = Arrays
            .asList(INITIAL, INHERIT);

    private String cssValue;

    private CssLengthUnit topUnit;
    private CssLengthUnit rightUnit;
    private CssLengthUnit bottomUnit;
    private CssLengthUnit leftUnit;

    private Float top;
    private Float right;
    private Float bottom;
    private Float left;

    /**
     * The {@code 0} will be set as the value
     */
    public BorderImageOutset() {
        setCssValue(DEFAULT_VALUE);
    }

    /**
     * @param cssValue
     *                     the css value to set.
     */
    public BorderImageOutset(final String cssValue) {
        setCssValue(cssValue);
    }

    /**
     * @param bottom
     *                   the {@code BorderBottomWidth} object from which the
     *                   cssValue to set.And, {@code null} will throw
     *                   {@code NullValueException}
     */
    public BorderImageOutset(final BorderImageOutset bottom) {
        if (bottom == null) {
            throw new NullValueException("bottom can not be null");
        }
        setCssValue(bottom.getCssValue());
    }

    /**
     * @param percent
     *                    the percentage value to set. The cssLengthUnit will
     *                    automatically set to %.
     * @since 1.0.0
     * @author WFF
     */
    public BorderImageOutset(final float percent) {
        setCssValue(String.valueOf(percent) + CssLengthUnit.PER);
    }

    /**
     * @param value
     * @param cssLengthUnit
     */
    public BorderImageOutset(final float value,
            final CssLengthUnit cssLengthUnit) {
        setCssValue(String.valueOf(value) + cssLengthUnit);
    }

    /**
     * @param percent
     *                    the percent to set
     * @since 1.0.0
     * @author WFF
     */
    public void setPercent(final float percent) {
        setCssValue(String.valueOf(percent) + CssLengthUnit.PER);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.webfirmframework.wffweb.css.CssProperty#getCssName()
     *
     * @since 1.0.0
     *
     * @author WFF
     */
    @Override
    public String getCssName() {
        return CssNameConstants.BORDER_IMAGE_OUTSET;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.webfirmframework.wffweb.css.CssProperty#getCssValue()
     *
     * @since 1.0.0
     *
     * @author WFF
     */
    @Override
    public String getCssValue() {
        return cssValue;
    }

    @Override
    public String toString() {
        return getCssName() + ": " + getCssValue();
    }

    /**
     * @param cssValue
     *                     the value should be in the format of
     *                     55px or 95%. {@code null}
     *                     is considered as an invalid value and it will throw
     *                     {@code NullValueException}.
     * @since 1.0.0
     * @author WFF
     */
    @Override
    public BorderImageOutset setCssValue(final String cssValue) {
        if (cssValue == null) {
            throw new NullValueException(
                    "null is an invalid value. The value format should be as for example 75px or 85%. Or, initial/inherit/medium/thin/thick.");
        } else if (StringUtil.isBlank(cssValue)) {
            throw new InvalidValueException(
                    "blank string is an invalid value. The value format should be as for example 75px or 85%. Or, initial/inherit/medium/thin/thick.");
        } else {
            final String trimmedCssValue = TagStringUtil
                    .toLowerCase(StringUtil.strip(cssValue));

            if (PREDEFINED_CONSTANTS.contains(trimmedCssValue)) {
                this.cssValue = trimmedCssValue;
                top = right = bottom = left = null;
                topUnit = rightUnit = bottomUnit = leftUnit = null;
                if (getStateChangeInformer() != null) {
                    getStateChangeInformer().stateChanged(this);
                }
                return this;
            }

            final String borderImageWidthString = StringUtil
                    .convertToSingleSpace(trimmedCssValue);

            final String[] extractedWidths = StringUtil
                    .splitBySpace(borderImageWidthString);

            if (extractedWidths.length == 1) {
                final Object[] lengthValueAndUnitAll = CssLengthUtil
                        .getLengthValueAndUnit(extractedWidths[0]);
                if (lengthValueAndUnitAll.length == 2) {
                    top = right = bottom = left = (Float) lengthValueAndUnitAll[0];
                    topUnit = rightUnit = bottomUnit = leftUnit = (CssLengthUnit) lengthValueAndUnitAll[1];
                } else if (lengthValueAndUnitAll.length == 1) {
                    top = right = bottom = left = (Float) lengthValueAndUnitAll[0];
                    topUnit = rightUnit = bottomUnit = leftUnit = null;
                } else {
                    throw new InvalidValueException(
                            "'" + extractedWidths[0] + "' is invalid in '"
                                    + borderImageWidthString + "'");
                }

            } else if (extractedWidths.length == 2) {

                final Object[] lengthValueAndUnitTopBottom = CssLengthUtil
                        .getLengthValueAndUnit(extractedWidths[0]);

                if (lengthValueAndUnitTopBottom.length == 2) {
                    top = bottom = (Float) lengthValueAndUnitTopBottom[0];
                    topUnit = bottomUnit = (CssLengthUnit) lengthValueAndUnitTopBottom[1];
                } else if (lengthValueAndUnitTopBottom.length == 1) {
                    top = bottom = (Float) lengthValueAndUnitTopBottom[0];
                    topUnit = bottomUnit = null;
                } else {
                    throw new InvalidValueException(
                            "'" + extractedWidths[0] + "' is invalid in '"
                                    + borderImageWidthString + "'");
                }

                final Object[] lengthValueAndUnitRightLeft = CssLengthUtil
                        .getLengthValueAndUnit(extractedWidths[1]);

                if (lengthValueAndUnitRightLeft.length == 2) {
                    right = left = (Float) lengthValueAndUnitRightLeft[0];
                    rightUnit = leftUnit = (CssLengthUnit) lengthValueAndUnitRightLeft[1];
                } else if (lengthValueAndUnitRightLeft.length == 1) {
                    right = left = (Float) lengthValueAndUnitRightLeft[0];
                    rightUnit = leftUnit = null;
                } else {
                    throw new InvalidValueException(
                            "'" + extractedWidths[1] + "' is invalid in '"
                                    + borderImageWidthString + "'");
                }

            } else if (extractedWidths.length == 3) {

                final Object[] lengthValueAndUnitTop = CssLengthUtil
                        .getLengthValueAndUnit(extractedWidths[0]);
                if (lengthValueAndUnitTop.length == 2) {
                    top = (Float) lengthValueAndUnitTop[0];
                    topUnit = (CssLengthUnit) lengthValueAndUnitTop[1];
                } else if (lengthValueAndUnitTop.length == 1) {
                    top = (Float) lengthValueAndUnitTop[0];
                    topUnit = null;
                } else {
                    throw new InvalidValueException(
                            "'" + extractedWidths[0] + "' is invalid in '"
                                    + borderImageWidthString + "'");
                }

                final Object[] lengthValueAndUnitRightLeft = CssLengthUtil
                        .getLengthValueAndUnit(extractedWidths[1]);
                if (lengthValueAndUnitRightLeft.length == 2) {
                    right = left = (Float) lengthValueAndUnitRightLeft[0];
                    rightUnit = leftUnit = (CssLengthUnit) lengthValueAndUnitRightLeft[1];
                } else if (lengthValueAndUnitRightLeft.length == 1) {
                    right = left = (Float) lengthValueAndUnitRightLeft[0];
                    rightUnit = leftUnit = null;
                } else {
                    throw new InvalidValueException(
                            "'" + extractedWidths[1] + "' is invalid in '"
                                    + borderImageWidthString + "'");
                }

                final Object[] lengthValueAndUnitBottom = CssLengthUtil
                        .getLengthValueAndUnit(extractedWidths[2]);
                if (lengthValueAndUnitBottom.length == 2) {
                    bottom = (Float) lengthValueAndUnitBottom[0];
                    bottomUnit = (CssLengthUnit) lengthValueAndUnitBottom[1];
                } else if (lengthValueAndUnitBottom.length == 1) {
                    bottom = (Float) lengthValueAndUnitBottom[0];
                    bottomUnit = null;
                } else {
                    throw new InvalidValueException(
                            "'" + extractedWidths[2] + "' is invalid in '"
                                    + borderImageWidthString + "'");
                }

            } else if (extractedWidths.length == 4) {

                final Object[] lengthValueAndUnitTop = CssLengthUtil
                        .getLengthValueAndUnit(extractedWidths[0]);
                if (lengthValueAndUnitTop.length == 2) {
                    top = (Float) lengthValueAndUnitTop[0];
                    topUnit = (CssLengthUnit) lengthValueAndUnitTop[1];
                } else if (lengthValueAndUnitTop.length == 1) {
                    top = (Float) lengthValueAndUnitTop[0];
                    topUnit = null;
                } else {
                    throw new InvalidValueException(
                            "'" + extractedWidths[0] + "' is invalid in '"
                                    + borderImageWidthString + "'");
                }

                final Object[] lengthValueAndUnitRight = CssLengthUtil
                        .getLengthValueAndUnit(extractedWidths[1]);
                if (lengthValueAndUnitRight.length == 2) {
                    right = (Float) lengthValueAndUnitRight[0];
                    rightUnit = (CssLengthUnit) lengthValueAndUnitRight[1];
                } else if (lengthValueAndUnitRight.length == 1) {
                    right = (Float) lengthValueAndUnitRight[0];
                    rightUnit = null;
                } else {
                    throw new InvalidValueException(
                            "'" + extractedWidths[1] + "' is invalid in '"
                                    + borderImageWidthString + "'");
                }
                final Object[] lengthValueAndUnitBottom = CssLengthUtil
                        .getLengthValueAndUnit(extractedWidths[2]);
                if (lengthValueAndUnitBottom.length == 2) {
                    bottom = (Float) lengthValueAndUnitBottom[0];
                    bottomUnit = (CssLengthUnit) lengthValueAndUnitBottom[1];
                } else if (lengthValueAndUnitBottom.length == 1) {
                    bottom = (Float) lengthValueAndUnitBottom[0];
                    bottomUnit = null;
                } else {
                    throw new InvalidValueException(
                            "'" + extractedWidths[2] + "' is invalid in '"
                                    + borderImageWidthString + "'");
                }

                final Object[] lengthValueAndUnitLeft = CssLengthUtil
                        .getLengthValueAndUnit(extractedWidths[3]);
                if (lengthValueAndUnitLeft.length == 2) {
                    left = (Float) lengthValueAndUnitLeft[0];
                    leftUnit = (CssLengthUnit) lengthValueAndUnitLeft[1];
                } else if (lengthValueAndUnitLeft.length == 1) {
                    left = (Float) lengthValueAndUnitLeft[0];
                    leftUnit = null;
                } else {
                    throw new InvalidValueException(
                            "'" + extractedWidths[3] + "' is invalid in '"
                                    + borderImageWidthString + "'");
                }

            } else {
                throw new InvalidValueException(
                        "the given cssValue is invalid");
            }

            this.cssValue = getProducedCssValue(top, topUnit, right, rightUnit,
                    bottom, bottomUnit, left, leftUnit);
            if (getStateChangeInformer() != null) {
                getStateChangeInformer().stateChanged(this);
            }
        }
        return this;
    }

    /**
     * sets as {@code initial}
     *
     * @since 1.0.0
     * @author WFF
     */
    public void setAsInitial() {
        setCssValue(INITIAL);
    }

    /**
     * sets as {@code inherit}
     *
     * @since 1.0.0
     * @author WFF
     */
    public void setAsInherit() {
        setCssValue(INHERIT);
    }

    /**
     * sets the top, right, bottom and left in {@code BorderImageWidth}.
     *
     * @param top
     *                   the value for top. The value {@code auto} will be
     *                   assigned for null value.
     * @param right
     *                   the value for right. The value {@code auto} will be
     *                   assigned for null value.
     * @param bottom
     *                   the value for bottom. The value {@code auto} will be
     *                   assigned for null value.
     * @param left
     *                   the value for left. The value {@code auto} will be
     *                   assigned for null value.
     * @param unit
     *                   the {@code CssLengthUnit} for all of the given top,
     *                   right, bottom and left values.
     * @author WFF
     * @since 1.0.0
     */
    public void setBorderImageTopRightBottomLeft(final Float top,
            final Float right, final Float bottom, final Float left,
            final CssLengthUnit unit) {

        cssValue = getProducedCssValue(top, unit, right, unit, bottom, unit,
                left, unit);

        this.top = top;
        this.right = right;
        this.bottom = bottom;
        this.left = left;
        topUnit = rightUnit = bottomUnit = leftUnit = unit;

        final StateChangeInformer<CssProperty> stateChangeInformer = getStateChangeInformer();
        if (stateChangeInformer != null) {
            stateChangeInformer.stateChanged(this);
        }
    }

    public void setBorderImageTopRightBottomLeft(final Float top,
            final CssLengthUnit topUnit, final Float right,
            final CssLengthUnit rightUnit, final Float bottom,
            final CssLengthUnit bottomUnit, final Float left,
            final CssLengthUnit leftUnit) {

        cssValue = getProducedCssValue(top, topUnit, right, rightUnit, bottom,
                bottomUnit, left, leftUnit);

        this.top = top;
        this.right = right;
        this.bottom = bottom;
        this.left = left;

        this.topUnit = topUnit;
        this.rightUnit = rightUnit;
        this.bottomUnit = bottomUnit;
        this.leftUnit = leftUnit;

        final StateChangeInformer<CssProperty> stateChangeInformer = getStateChangeInformer();
        if (stateChangeInformer != null) {
            stateChangeInformer.stateChanged(this);
        }
    }

    /**
     * @param top
     *                the top to set
     * @author WFF
     * @since 1.0.0
     */
    public void setTop(final Float top, final CssLengthUnit topUnit) {
        cssValue = getProducedCssValue(top, topUnit, right, rightUnit, bottom,
                bottomUnit, left, leftUnit);
        this.top = top;
        this.topUnit = topUnit;
        final StateChangeInformer<CssProperty> stateChangeInformer = getStateChangeInformer();
        if (stateChangeInformer != null) {
            stateChangeInformer.stateChanged(this);
        }
    }

    /**
     * @param right
     *                  the right to set
     * @author WFF
     * @param rightUnit
     * @since 1.0.0
     */
    public void setRight(final Float right, final CssLengthUnit rightUnit) {
        cssValue = getProducedCssValue(top, topUnit, right, rightUnit, bottom,
                bottomUnit, left, leftUnit);
        this.right = right;
        this.rightUnit = rightUnit;
        final StateChangeInformer<CssProperty> stateChangeInformer = getStateChangeInformer();
        if (stateChangeInformer != null) {
            stateChangeInformer.stateChanged(this);
        }
    }

    /**
     * @param bottom
     *                   the bottom to set
     * @author WFF
     * @param bottomUnit
     * @since 1.0.0
     */
    public void setBottom(final Float bottom, final CssLengthUnit bottomUnit) {
        cssValue = getProducedCssValue(top, topUnit, right, rightUnit, bottom,
                bottomUnit, left, leftUnit);
        this.bottom = bottom;
        this.bottomUnit = bottomUnit;
        final StateChangeInformer<CssProperty> stateChangeInformer = getStateChangeInformer();
        if (stateChangeInformer != null) {
            stateChangeInformer.stateChanged(this);
        }
    }

    /**
     * @param left
     *                 the left to set
     * @author WFF
     * @param leftUnit
     * @since 1.0.0
     */
    public void setLeft(final Float left, final CssLengthUnit leftUnit) {
        cssValue = getProducedCssValue(top, topUnit, right, rightUnit, bottom,
                bottomUnit, left, leftUnit);
        this.left = left;
        this.leftUnit = leftUnit;
        final StateChangeInformer<CssProperty> stateChangeInformer = getStateChangeInformer();
        if (stateChangeInformer != null) {
            stateChangeInformer.stateChanged(this);
        }
    }

    /**
     * @return the top. It will return null for auto or the cssValue is
     *         predefined constant.
     * @author WFF
     * @since 1.0.0
     */
    public Float getTop() {
        return top;
    }

    /**
     * @return the topUnit
     * @author WFF
     * @since 1.0.0
     */
    public CssLengthUnit getTopUnit() {
        return topUnit;
    }

    /**
     * @return the right. It will return null for auto or the cssValue is
     *         predefined constant.
     * @author WFF
     * @since 1.0.0
     */
    public Float getRight() {
        return right;
    }

    /**
     * @return the rightUnit
     * @author WFF
     * @since 1.0.0
     */
    public CssLengthUnit getRightUnit() {
        return rightUnit;
    }

    /**
     * @return the bottom. It will return null for auto or the cssValue is
     *         predefined constant.
     * @author WFF
     * @since 1.0.0
     */
    public Float getBottom() {
        return bottom;
    }

    /**
     * @return the bottomUnit
     * @author WFF
     * @since 1.0.0
     */
    public CssLengthUnit getBottomUnit() {
        return bottomUnit;
    }

    /**
     * @return the left. It will return null for auto or the cssValue is
     *         predefined constant.
     * @author WFF
     * @since 1.0.0
     */
    public Float getLeft() {
        return left;
    }

    /**
     * @return the leftUnit
     * @author WFF
     * @since 1.0.0
     */
    public CssLengthUnit getLeftUnit() {
        return leftUnit;
    }

    /**
     *
     * @param top
     *                       the value for top. The value {@code auto} will be
     *                       assigned for null value.
     * @param topUnit
     * @param right
     *                       the value for right. The value {@code auto} will be
     *                       assigned for null value.
     * @param rightUnit
     * @param bottom
     *                       the value for bottom. The value {@code auto} will
     *                       be assigned for null value.
     * @param bottomUnit
     * @param left
     *                       the value for left. The value {@code auto} will be
     *                       assigned for null value.
     * @param leftUnit
     * @author WFF
     * @return
     * @since 1.0.0
     */
    protected static String getProducedCssValue(final Float top,
            final CssLengthUnit topUnit, final Float right,
            final CssLengthUnit rightUnit, final Float bottom,
            final CssLengthUnit bottomUnit, final Float left,
            final CssLengthUnit leftUnit) {

        if ((ObjectUtil.isEqual(top, right)
                && Objects.equals(topUnit, rightUnit))
                && (ObjectUtil.isEqual(right, bottom)
                        && ObjectUtil.isEqual(rightUnit, bottomUnit))
                && (ObjectUtil.isEqual(bottom, left)
                        && Objects.equals(bottomUnit, leftUnit))) {

            return top != null && topUnit != null
                    ? String.valueOf(top).concat(topUnit.getUnit())
                    : top != null ? String.valueOf(top) : DEFAULT_VALUE;

        } else if ((ObjectUtil.isEqual(top, bottom)
                && Objects.equals(topUnit, bottomUnit))
                && (ObjectUtil.isEqual(right, left)
                        && Objects.equals(rightUnit, leftUnit))) {

            final StringBuilder cssValueBuilder = new StringBuilder();
            if (top != null) {
                cssValueBuilder.append(top);
                if (topUnit != null) {
                    cssValueBuilder.append(topUnit.getUnit());
                }
            }

            cssValueBuilder.append(' ');

            if (right != null) {
                cssValueBuilder.append(right);
                if (rightUnit != null) {
                    cssValueBuilder.append(rightUnit.getUnit());
                }
            }

            return cssValueBuilder.toString();

        } else if ((ObjectUtil.isEqual(right, left)
                && Objects.equals(rightUnit, leftUnit))) {
            final StringBuilder cssValueBuilder = new StringBuilder();
            if (top != null) {
                cssValueBuilder.append(top);
                if (topUnit != null) {
                    cssValueBuilder.append(topUnit.getUnit());
                }
            }

            cssValueBuilder.append(' ');

            if (right != null) {
                cssValueBuilder.append(right);
                if (rightUnit != null) {
                    cssValueBuilder.append(rightUnit.getUnit());
                }
            }

            cssValueBuilder.append(' ');

            if (bottom != null) {
                cssValueBuilder.append(bottom);
                if (bottomUnit != null) {
                    cssValueBuilder.append(bottomUnit.getUnit());
                }
            }

            return cssValueBuilder.toString();

        } else {
            final StringBuilder cssValueBuilder = new StringBuilder();
            if (top != null) {
                cssValueBuilder.append(top);
                if (topUnit != null) {
                    cssValueBuilder.append(topUnit.getUnit());
                }
            }

            cssValueBuilder.append(' ');

            if (right != null) {
                cssValueBuilder.append(right);
                if (rightUnit != null) {
                    cssValueBuilder.append(rightUnit.getUnit());
                }
            }

            cssValueBuilder.append(' ');

            if (bottom != null) {
                cssValueBuilder.append(bottom);
                if (bottomUnit != null) {
                    cssValueBuilder.append(bottomUnit.getUnit());
                }
            }

            cssValueBuilder.append(' ');

            if (left != null) {
                cssValueBuilder.append(left);
                if (leftUnit != null) {
                    cssValueBuilder.append(leftUnit.getUnit());
                }
            }
            return cssValueBuilder.toString();
        }
    }

    /**
     * validates if the given cssValue is valid for this class.
     *
     * @param cssValue
     *                     the value to check.
     * @return true if valid and false if invalid.
     * @author WFF
     * @since 1.0.0
     */
    public static boolean isValid(final String cssValue) {
        final String trimmedCssValue = TagStringUtil
                .toLowerCase(StringUtil.strip(cssValue));

        if (PREDEFINED_CONSTANTS.contains(trimmedCssValue)) {
            return true;
        }

        final String[] cssValueParts = StringUtil.splitBySpace(trimmedCssValue);
        if (cssValueParts.length > 4) {
            return false;
        }

        for (final String cssValuePart : cssValueParts) {
            final Object[] lengthValueAndUnit = CssLengthUtil
                    .getLengthValueAndUnit(cssValuePart);
            if (lengthValueAndUnit.length == 0) {
                return false;
            }
        }
        return true;
    }

    /**
     * @return true if its cssValue is any of the values
     *          auto, initial or inherit.
     * @author WFF
     * @since 1.0.0
     */
    public boolean hasPredefinedConstantValue() {
        return PREDEFINED_CONSTANTS.contains(cssValue);
    }

}


/*
 * Copyright 2014-2019 Web Firm Framework
 *
 * 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.
 * @author WFF
 */
package com.webfirmframework.wffweb.css;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.webfirmframework.wffweb.InvalidValueException;
import com.webfirmframework.wffweb.NullValueException;
import com.webfirmframework.wffweb.css.core.AbstractCssProperty;
import com.webfirmframework.wffweb.css.core.CssProperty;
import com.webfirmframework.wffweb.informer.StateChangeInformer;
import com.webfirmframework.wffweb.util.CssValueUtil;
import com.webfirmframework.wffweb.util.StringBuilderUtil;
import com.webfirmframework.wffweb.util.StringUtil;
import com.webfirmframework.wffweb.util.TagStringUtil;

/**
 *

 * The border-top shorthand property sets all the top border properties in one declaration.
 *
 * The properties that can be set, are (in order): border-top-width, border-top-style, and border-top-color.
 *
 * If one of the values above are missing, e.g. border-top:solid #ff0000, the default value for the missing property will be inserted, if any.
 * Default value:  medium none color
 * Inherited:      no
 * Animatable:     yes
 * Version:        CSS1
 * JavaScript syntax:      object.style.borderTop="3px dashed blue"
 *

 *
 * @author WFF
 * @since 1.0.0
 */
public class BorderTop extends AbstractCssProperty<BorderTop>
        implements StateChangeInformer<CssProperty> {

    private static final long serialVersionUID = 1_0_0L;

    public static final Logger LOGGER = Logger
            .getLogger(BorderTop.class.getName());

    public static final String INITIAL = "initial";
    public static final String INHERIT = "inherit";

    private static final List<String> PREDEFINED_CONSTANTS = Arrays
            .asList(INITIAL, INHERIT);

    private String cssValue;

    private BorderTopWidth borderTopWidth;

    private BorderTopStyle borderTopStyle;

    private BorderTopColor borderTopColor;

    /**
     * The value medium none black will be assigned as the
     * cssValue.
     *
     * @author WFF
     * @since 1.0.0
     */
    public BorderTop() {
        setCssValue("medium none black");
    }

    /**
     * @param cssValue
     *                     the css value to set.
     */
    public BorderTop(final String cssValue) {
        setCssValue(cssValue);
    }

    /**
     * @param borderTop
     *                      the {@code BorderTop} object from which the cssValue
     *                      to set.And, {@code null} will throw
     *                      {@code NullValueException}
     */
    public BorderTop(final BorderTop borderTop) {
        if (borderTop == null) {
            throw new NullValueException("borderTop can not be null");
        }
        setCssValue(borderTop.getCssValue());
    }

    /*
     * (non-Javadoc)
     *
     * @see com.webfirmframework.wffweb.css.CssProperty#getCssName()
     *
     * @since 1.0.0
     *
     * @author WFF
     */
    @Override
    public String getCssName() {
        return CssNameConstants.BORDER_TOP;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.webfirmframework.wffweb.css.CssProperty#getCssValue()
     *
     * @since 1.0.0
     *
     * @author WFF
     */
    @Override
    public String getCssValue() {
        return cssValue;
    }

    @Override
    public String toString() {
        return getCssName() + ": " + getCssValue();
    }

    /**
     * @param cssValue
     *                     the value should be in the format of
     *                     medium none color, initial
     *                     or inherit. {@code null} is considered
     *                     as an invalid value and it will throw
     *                     {@code NullValueException}.
     * @since 1.0.0
     * @author WFF
     */
    @Override
    public BorderTop setCssValue(final String cssValue) {
        final String trimmedCssValue;
        if (cssValue == null) {
            throw new NullValueException(
                    "null is an invalid value. The value format should be as for example medium none color Or initial/inherit.");
        } else if ((trimmedCssValue = TagStringUtil
                .toLowerCase(StringUtil.strip(cssValue))).isEmpty()) {
            throw new NullValueException(cssValue
                    + " is an invalid value. The value format should be as for example medium none color Or initial/inherit.");
        }

        final List<String> cssValueParts = CssValueUtil.split(trimmedCssValue);

        if (cssValueParts.size() > 1) {
            if (trimmedCssValue.contains(BorderTopColor.INITIAL)) {
                throw new InvalidValueException(
                        "The string 'initial' makes the given cssValue invalid, please remove 'initial' from it and try.");
            }
            if (trimmedCssValue.contains(BorderTopColor.INHERIT)) {
                throw new InvalidValueException(
                        "The string 'inherit' makes the given cssValue invalid, please remove 'inherit' from it and try.");
            }
        } else {
            if (PREDEFINED_CONSTANTS.contains(trimmedCssValue)) {

                if (borderTopWidth != null) {
                    borderTopWidth.setAlreadyInUse(false);
                    borderTopWidth = null;
                }

                borderTopStyle = null;

                if (borderTopColor != null) {
                    borderTopColor.setAlreadyInUse(false);
                    borderTopColor = null;
                }

                this.cssValue = trimmedCssValue;
                if (getStateChangeInformer() != null) {
                    getStateChangeInformer().stateChanged(this);
                }
                return this;
            }
        }

        BorderTopWidth borderTopWidth = null;
        BorderTopStyle borderTopStyle = null;
        BorderTopColor borderTopColor = null;

        for (final String eachPart : cssValueParts) {
            if (borderTopWidth == null && BorderTopWidth.isValid(eachPart)) {
                if (this.borderTopWidth == null) {
                    borderTopWidth = new BorderTopWidth(eachPart);
                    borderTopWidth.setStateChangeInformer(this);
                    borderTopWidth.setAlreadyInUse(true);
                } else {
                    this.borderTopWidth.setCssValue(eachPart);
                    borderTopWidth = this.borderTopWidth;
                }
            } else if (borderTopStyle == null
                    && BorderTopStyle.isValid(eachPart)) {
                borderTopStyle = BorderTopStyle.getThis(eachPart);
            } else if (borderTopColor == null
                    && BorderTopColor.isValid(eachPart)) {
                if (this.borderTopColor == null) {
                    borderTopColor = new BorderTopColor(eachPart);
                    borderTopColor.setStateChangeInformer(this);
                    borderTopColor.setAlreadyInUse(true);
                } else {
                    this.borderTopColor.setCssValue(eachPart);
                    borderTopColor = this.borderTopColor;
                }
            }
        }

        final StringBuilder cssValueBuilder = new StringBuilder();
        boolean invalid = true;
        if (borderTopWidth != null) {
            cssValueBuilder.append(borderTopWidth.getCssValue()).append(' ');
            invalid = false;
        } else if (this.borderTopWidth != null) {
            this.borderTopWidth.setAlreadyInUse(false);
        }
        if (borderTopStyle != null) {
            cssValueBuilder.append(borderTopStyle.getCssValue()).append(' ');
            invalid = false;
        }
        if (borderTopColor != null) {
            cssValueBuilder.append(borderTopColor.getCssValue()).append(' ');
            invalid = false;
        } else if (this.borderTopColor != null) {
            this.borderTopColor.setAlreadyInUse(false);
        }
        if (invalid) {
            throw new InvalidValueException(cssValue
                    + " is an invalid value. The value format should be as for example medium none color Or initial/inherit.");
        }
        this.cssValue = StringBuilderUtil.getTrimmedString(cssValueBuilder);
        this.borderTopWidth = borderTopWidth;
        this.borderTopStyle = borderTopStyle;
        this.borderTopColor = borderTopColor;

        if (getStateChangeInformer() != null) {
            getStateChangeInformer().stateChanged(this);
        }

        return this;
    }

    /**
     * checks the given css value is valid for this class. It does't do a strict
     * validation.
     *
     * @param cssValue
     * @return
     * @since 1.0.0
     * @author WFF
     */
    public static boolean isValid(final String cssValue) {
        // TODO modify to make a strict validation
        if (cssValue == null || StringUtil.isBlank(cssValue)) {
            return false;
        }

        final List<String> cssValueParts = CssValueUtil.split(cssValue);

        BorderTopWidth borderTopWidth = null;
        BorderTopStyle borderTopStyle = null;
        BorderTopColor borderTopColor = null;

        for (final String eachPart : cssValueParts) {

            boolean invalid = true;
            if (borderTopWidth == null && BorderTopWidth.isValid(eachPart)) {
                borderTopWidth = new BorderTopWidth(eachPart);
                invalid = false;
            } else if (borderTopStyle == null
                    && BorderTopStyle.isValid(eachPart)) {
                borderTopStyle = BorderTopStyle.getThis(eachPart);
                invalid = false;
            } else if (borderTopColor == null
                    && BorderTopColor.isValid(eachPart)) {
                borderTopColor = new BorderTopColor(eachPart);
                invalid = false;
            }
            if (invalid) {
                return false;
            }
        }

        return borderTopWidth != null || borderTopStyle != null
                || borderTopColor != null;
    }

    /**
     * sets as initial
     *
     * @since 1.0.0
     * @author WFF
     */
    public void setAsInitial() {
        setCssValue(INITIAL);
    }

    /**
     * sets as inherit
     *
     * @since 1.0.0
     * @author WFF
     */
    public void setAsInherit() {
        setCssValue(INHERIT);
    }

    public BorderTopColor getBorderTopColor() {
        return borderTopColor;
    }

    public BorderTopStyle getBorderTopStyle() {
        return borderTopStyle;
    }

    public BorderTopWidth getBorderTopWidth() {
        return borderTopWidth;
    }

    public BorderTop setBorderTopWidth(final BorderTopWidth borderTopWidth) {

        final StringBuilder cssValueBuilder = new StringBuilder();

        if (borderTopWidth != null) {

            final String borderTopWidthCssValue = borderTopWidth.getCssValue();
            if (BorderTopWidth.INITIAL.equals(borderTopWidthCssValue)
                    || BorderTopWidth.INHERIT.equals(borderTopWidthCssValue)) {
                throw new InvalidValueException(
                        "borderTopWidth cannot have initial/inherit as its cssValue");
            }

            cssValueBuilder.append(borderTopWidth.getCssValue()).append(' ');
        }

        if (borderTopStyle != null) {
            cssValueBuilder.append(borderTopStyle.getCssValue()).append(' ');
        }

        if (borderTopColor != null) {
            cssValueBuilder.append(borderTopColor.getCssValue()).append(' ');
        }

        final String trimmedCssValue = StringBuilderUtil
                .getTrimmedString(cssValueBuilder).toString();
        cssValue = trimmedCssValue.isEmpty() ? INHERIT : trimmedCssValue;

        if (borderTopWidth != null && borderTopWidth.isAlreadyInUse()
                && !Objects.equals(this.borderTopWidth, borderTopWidth)) {
            if (LOGGER.isLoggable(Level.WARNING)) {
                LOGGER.warning(
                        "the given borderTopWidth is already used by another object so a new object or the previous object (if it exists) of BorderTopWidth will be used");
            }
            return setCssValue(cssValue);
        }

        if (this.borderTopWidth != null) {
            this.borderTopWidth.setAlreadyInUse(false);
        }

        this.borderTopWidth = borderTopWidth;

        if (this.borderTopWidth != null) {
            this.borderTopWidth.setStateChangeInformer(this);
            this.borderTopWidth.setAlreadyInUse(true);
        }

        if (getStateChangeInformer() != null) {
            getStateChangeInformer().stateChanged(this);
        }
        return this;
    }

    public BorderTop setBorderTopStyle(final BorderTopStyle borderTopStyle) {

        final StringBuilder cssValueBuilder = new StringBuilder();

        if (borderTopWidth != null) {
            cssValueBuilder.append(borderTopWidth.getCssValue()).append(' ');
        }

        if (borderTopStyle != null) {
            cssValueBuilder.append(borderTopStyle.getCssValue()).append(' ');
        }

        if (borderTopColor != null) {
            cssValueBuilder.append(borderTopColor.getCssValue()).append(' ');
        }

        final String trimmedCssValue = StringBuilderUtil
                .getTrimmedString(cssValueBuilder).toString();
        cssValue = trimmedCssValue.isEmpty() ? INHERIT : trimmedCssValue;

        this.borderTopStyle = borderTopStyle;

        if (getStateChangeInformer() != null) {
            getStateChangeInformer().stateChanged(this);
        }
        return this;
    }

    public BorderTop setBorderTopColor(final BorderTopColor borderTopColor) {

        final StringBuilder cssValueBuilder = new StringBuilder();

        if (borderTopWidth != null) {
            cssValueBuilder.append(borderTopWidth.getCssValue()).append(' ');
        }

        if (borderTopStyle != null) {
            cssValueBuilder.append(borderTopStyle.getCssValue()).append(' ');
        }

        if (borderTopColor != null) {
            final String borderTopColorCssValue = borderTopColor.getCssValue();
            if (BorderTopColor.INITIAL.equals(borderTopColorCssValue)
                    || BorderTopColor.INHERIT.equals(borderTopColorCssValue)) {
                throw new InvalidValueException(
                        "borderTopColor cannot have initial/inherit as its cssValue");
            }
            cssValueBuilder.append(borderTopColorCssValue);
        }

        final String trimmedCssValue = StringBuilderUtil
                .getTrimmedString(cssValueBuilder).toString();
        cssValue = trimmedCssValue.isEmpty() ? INHERIT : trimmedCssValue;

        if (borderTopColor != null && borderTopColor.isAlreadyInUse()
                && !Objects.equals(this.borderTopColor, borderTopColor)) {
            if (LOGGER.isLoggable(Level.WARNING)) {
                LOGGER.warning(
                        "the given borderTopColor is already used by another object so a new object or the previous object (if it exists) of BorderTopColor will be used");
            }
            return setCssValue(cssValue);
        }

        if (this.borderTopColor != null) {
            this.borderTopColor.setAlreadyInUse(false);
        }

        this.borderTopColor = borderTopColor;

        if (this.borderTopColor != null) {
            this.borderTopColor.setStateChangeInformer(this);
            this.borderTopColor.setAlreadyInUse(true);
        }

        if (getStateChangeInformer() != null) {
            getStateChangeInformer().stateChanged(this);
        }
        return this;
    }

    @Override
    public void stateChanged(final CssProperty stateChangedObject) {
        if (stateChangedObject instanceof BorderTopColor) {

            final BorderTopColor borderTopColor = (BorderTopColor) stateChangedObject;

            final String cssValue = borderTopColor.getCssValue();

            if (BorderTopColor.INITIAL.equals(cssValue)
                    || BorderTopColor.INHERIT.equals(cssValue)) {
                throw new InvalidValueException(
                        "initial/inherit cannot be set as borderTopColor cssValue");
            }

            setBorderTopColor(borderTopColor);
        }
    }

    /**
     *
     * @author WFF
     * @since 1.0.0
     */
    protected void addPredefinedConstant(final String constant) {
        PREDEFINED_CONSTANTS.add(constant);
    }

}


/*
 * Copyright 2014-2019 Web Firm Framework
 *
 * 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.
 * @author WFF
 */
package com.webfirmframework.wffweb.clone;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * A utility class for clone operations.
 *
 * @author WFF
 * @since 1.0.0
 */
public final class CloneUtil {

    private static final Logger LOGGER = Logger
            .getLogger(CloneUtil.class.getName());

    private CloneUtil() {
        throw new AssertionError();
    }

    /**
     * clones only if {@code objects} contains the given
     * {@code objectToBeClonned}, otherwise returns the same object.
     *
     * @param objectToBeClonned
     *                              from which a new object will be cloned.
     *                              {@code null} will be returned for null
     *                              value.
     * @param objects
     *                              to check if it contains
     *                              {@code objectToBeClonned}.
     * @return the new cloned object only if {@code objects} contains the given
     *         {@code objectToBeClonned}, otherwise returns the same object. If
     *         {@code objectToBeClonned} is null then returns {@code null}. If
     *         {@code objects} is null then {@code objectToBeClonned} will be
     *         returned.
     * @throws CloneNotSupportedException
     * @since 1.0.0
     * @author WFF
     */
    public static <T> T deepCloneOnlyIfContains(final T objectToBeClonned,
            final T[] objects) throws CloneNotSupportedException {
        if (objectToBeClonned == null || objects == null) {
            return objectToBeClonned;
        }
        final Set<T> objectsSet = new HashSet<>(objects.length);
        Collections.addAll(objectsSet, objects);
        return objectsSet.contains(objectToBeClonned)
                ? deepClone(objectToBeClonned)
                : objectToBeClonned;
    }

    /**
     * clones only if {@code objects} contains the given
     * {@code objectToBeClonned}, otherwise returns the same object.
     *
     * @param objectToBeClonned
     *                              from which a new object will be cloned.
     *                              {@code null} will be returned for null
     *                              value.
     * @param objects
     *                              to check if it contains
     *                              {@code objectToBeClonned}.
     * @return the new cloned object only if {@code objects} contains the given
     *         {@code objectToBeClonned}, otherwise returns the same object. If
     *         {@code objectToBeClonned} is null then returns {@code null}. If
     *         {@code objects} is null then {@code objectToBeClonned} will be
     *         returned.
     * @throws CloneNotSupportedException
     * @since 1.0.0
     * @author WFF
     */
    public static <T> T deepCloneOnlyIfContains(final T objectToBeClonned,
            final List<T> objects) throws CloneNotSupportedException {
        if (objectToBeClonned == null || objects == null) {
            return objectToBeClonned;
        }
        final Set<T> objectsSet = new HashSet<>(objects);
        return objectsSet.contains(objectToBeClonned)
                ? deepClone(objectToBeClonned)
                : objectToBeClonned;
    }

    /**
     * clones only if {@code objects} contains the given
     * {@code objectToBeClonned}, otherwise returns the same object.
     *
     * @param objectToBeClonned
     *                              from which a new object will be cloned.
     *                              {@code null} will be returned for null
     *                              value.
     * @param objects
     *                              to check if it contains
     *                              {@code objectToBeClonned}.
     * @return the new cloned object only if {@code objects} contains the given
     *         {@code objectToBeClonned}, otherwise returns the same object. If
     *         {@code objectToBeClonned} is null then returns {@code null}. If
     *         {@code objects} is null then {@code objectToBeClonned} will be
     *         returned.
     * @throws CloneNotSupportedException
     * @since 1.0.0
     * @author WFF
     */
    public static <T> T deepCloneOnlyIfContains(final T objectToBeClonned,
            final Set<T> objects) throws CloneNotSupportedException {
        if (objectToBeClonned == null || objects == null) {
            return objectToBeClonned;
        }
        return objects.contains(objectToBeClonned)
                ? deepClone(objectToBeClonned)
                : objectToBeClonned;
    }

    /**
     * clones only if {@code objects} doesn't contain the given
     * {@code objectToBeClonned}, otherwise returns the same object.
     *
     * @param objectToBeClonned
     *                              from which a new object will be cloned.
     *                              {@code null} will be returned for null
     *                              value.
     * @param objects
     *                              to check if it doesn't contain
     *                              {@code objectToBeClonned}.
     * @return the new cloned object only if {@code objects} doesn't contain the
     *         given {@code objectToBeClonned}, otherwise returns the same
     *         object. If {@code objectToBeClonned} is null then returns
     *         {@code null}. If {@code objects} is null then the cloned object
     *         will be returned.
     * @throws CloneNotSupportedException
     * @since 1.0.0
     * @author WFF
     */
    public static <T> T deepCloneOnlyIfDoesNotContain(final T objectToBeClonned,
            final T[] objects) throws CloneNotSupportedException {
        if (objectToBeClonned == null) {
            return objectToBeClonned;
        }
        if (objects == null) {
            return deepClone(objectToBeClonned);
        }
        final Set<T> objectsSet = new HashSet<>(objects.length);
        Collections.addAll(objectsSet, objects);
        return objectsSet.contains(objectToBeClonned) ? objectToBeClonned
                : deepClone(objectToBeClonned);
    }

    /**
     * clones only if {@code objects} doesn't contain the given
     * {@code objectToBeClonned}, otherwise returns the same object.
     *
     * @param objectToBeClonned
     *                              from which a new object will be cloned.
     *                              {@code null} will be returned for null
     *                              value.
     * @param objects
     *                              to check if it doesn't contain
     *                              {@code objectToBeClonned}.
     * @return the new cloned object only if {@code objects} doesn't contain the
     *         given {@code objectToBeClonned}, otherwise returns the same
     *         object. If {@code objectToBeClonned} is null then returns
     *         {@code null}. If {@code objects} is null then the cloned object
     *         will be returned.
     * @throws CloneNotSupportedException
     * @since 1.0.0
     * @author WFF
     */
    public static <T> T deepCloneOnlyIfDoesNotContain(final T objectToBeClonned,
            final List<T> objects) throws CloneNotSupportedException {
        if (objectToBeClonned == null) {
            return objectToBeClonned;
        }
        if (objects == null) {
            return deepClone(objectToBeClonned);
        }
        final Set<T> objectsSet = new HashSet<>(objects);
        return objectsSet.contains(objectToBeClonned) ? objectToBeClonned
                : deepClone(objectToBeClonned);
    }

    /**
     * clones only if {@code objects} doesn't contain the given
     * {@code objectToBeClonned}, otherwise returns the same object.
     *
     * @param objectToBeClonned
     *                              from which a new object will be cloned.
     *                              {@code null} will be returned for null
     *                              value.
     * @param objects
     *                              to check if it doesn't contain
     *                              {@code objectToBeClonned}.
     * @return the new cloned object only if {@code objects} doesn't contain the
     *         given {@code objectToBeClonned}, otherwise returns the same
     *         object. If {@code objectToBeClonned} is null then returns
     *         {@code null}. If {@code objects} is null then the cloned object
     *         will be returned.
     * @throws CloneNotSupportedException
     * @since 1.0.0
     * @author WFF
     */
    public static <T> T deepCloneOnlyIfDoesNotContain(final T objectToBeClonned,
            final Set<T> objects) throws CloneNotSupportedException {
        if (objectToBeClonned == null) {
            return objectToBeClonned;
        }
        if (objects == null) {
            return deepClone(objectToBeClonned);
        }
        return objects.contains(objectToBeClonned) ? objectToBeClonned
                : deepClone(objectToBeClonned);
    }

    /**
     * @param objectToBeClonned
     *                              from which a new object will be cloned.
     *                              {@code null} will be returned for null
     *                              value.
     * @return the newly cloned object or {@code null} if
     *         {@code objectToBeClonned} is null.
     * @throws CloneNotSupportedException
     * @since 1.0.0
     * @author WFF
     */
    @SuppressWarnings("unchecked")
    public static <T> T deepClone(final T objectToBeClonned)
            throws CloneNotSupportedException {
        if (objectToBeClonned == null) {
            return null;
        }
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        try {
            final ByteArrayOutputStream bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);

            oos.writeObject(objectToBeClonned);
            oos.flush();

            final ByteArrayInputStream bin = new ByteArrayInputStream(
                    bos.toByteArray());
            ois = new ObjectInputStream(bin);

            return (T) ois.readObject();
        } catch (final NotSerializableException e) {
            throw new CloneNotSupportedException(e.getMessage()
                    + " is not serializable. Implement java.io.Serializable in "
                    + e.getMessage());
        } catch (final Exception e) {
            throw new CloneNotSupportedException(e.getMessage());
        } finally {
            try {
                if (oos != null) {
                    oos.close();
                }
                if (ois != null) {
                    ois.close();
                }
            } catch (final IOException e) {
                if (LOGGER.isLoggable(Level.SEVERE)) {
                    LOGGER.log(Level.SEVERE, e.getMessage(), e);
                }
            }
        }
    }
}