/*
 * 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.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-left shorthand property sets all the left border properties in one declaration.
 *
 * The properties that can be set, are (in order): border-left-width, border-left-style, and border-left-color.
 *
 * If one of the values above are missing, e.g. border-left: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.borderLeft="3px dashed blue"
 *

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

    private static final long serialVersionUID = 1_0_0L;

    public static final Logger LOGGER = Logger
            .getLogger(BorderLeft.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 BorderLeftWidth borderLeftWidth;

    private BorderLeftStyle borderLeftStyle;

    private BorderLeftColor borderLeftColor;

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

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

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

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

    /*
     * (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 BorderLeft 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(BorderLeftColor.INITIAL)) {
                throw new InvalidValueException(
                        "The string 'initial' makes the given cssValue invalid, please remove 'initial' from it and try.");
            }
            if (trimmedCssValue.contains(BorderLeftColor.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 (borderLeftWidth != null) {
                    borderLeftWidth.setAlreadyInUse(false);
                    borderLeftWidth = null;
                }

                borderLeftStyle = null;

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

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

        BorderLeftWidth borderLeftWidth = null;
        BorderLeftStyle borderLeftStyle = null;
        BorderLeftColor borderLeftColor = null;

        for (final String eachPart : cssValueParts) {
            if (borderLeftWidth == null && BorderLeftWidth.isValid(eachPart)) {
                if (this.borderLeftWidth == null) {
                    borderLeftWidth = new BorderLeftWidth(eachPart);
                    borderLeftWidth.setStateChangeInformer(this);
                    borderLeftWidth.setAlreadyInUse(true);
                } else {
                    this.borderLeftWidth.setCssValue(eachPart);
                    borderLeftWidth = this.borderLeftWidth;
                }
            } else if (borderLeftStyle == null
                    && BorderLeftStyle.isValid(eachPart)) {
                borderLeftStyle = BorderLeftStyle.getThis(eachPart);
            } else if (borderLeftColor == null
                    && BorderLeftColor.isValid(eachPart)) {
                if (this.borderLeftColor == null) {
                    borderLeftColor = new BorderLeftColor(eachPart);
                    borderLeftColor.setStateChangeInformer(this);
                    borderLeftColor.setAlreadyInUse(true);
                } else {
                    this.borderLeftColor.setCssValue(eachPart);
                    borderLeftColor = this.borderLeftColor;
                }
            }
        }

        final StringBuilder cssValueBuilder = new StringBuilder();
        boolean invalid = true;
        if (borderLeftWidth != null) {
            cssValueBuilder.append(borderLeftWidth.getCssValue()).append(' ');
            invalid = false;
        } else if (this.borderLeftWidth != null) {
            this.borderLeftWidth.setAlreadyInUse(false);
        }
        if (borderLeftStyle != null) {
            cssValueBuilder.append(borderLeftStyle.getCssValue()).append(' ');
            invalid = false;
        }
        if (borderLeftColor != null) {
            cssValueBuilder.append(borderLeftColor.getCssValue()).append(' ');
            invalid = false;
        } else if (this.borderLeftColor != null) {
            this.borderLeftColor.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.borderLeftWidth = borderLeftWidth;
        this.borderLeftStyle = borderLeftStyle;
        this.borderLeftColor = borderLeftColor;

        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);

        BorderLeftWidth borderLeftWidth = null;
        BorderLeftStyle borderLeftStyle = null;
        BorderLeftColor borderLeftColor = null;

        for (final String eachPart : cssValueParts) {

            boolean invalid = true;
            if (borderLeftWidth == null && BorderLeftWidth.isValid(eachPart)) {
                borderLeftWidth = new BorderLeftWidth(eachPart);
                invalid = false;
            } else if (borderLeftStyle == null
                    && BorderLeftStyle.isValid(eachPart)) {
                borderLeftStyle = BorderLeftStyle.getThis(eachPart);
                invalid = false;
            } else if (borderLeftColor == null
                    && BorderLeftColor.isValid(eachPart)) {
                borderLeftColor = new BorderLeftColor(eachPart);
                invalid = false;
            }
            if (invalid) {
                return false;
            }
        }

        return borderLeftWidth != null || borderLeftStyle != null
                || borderLeftColor != 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 BorderLeftColor getBorderLeftColor() {
        return borderLeftColor;
    }

    public BorderLeftStyle getBorderLeftStyle() {
        return borderLeftStyle;
    }

    public BorderLeftWidth getBorderLeftWidth() {
        return borderLeftWidth;
    }

    public BorderLeft setBorderLeftWidth(
            final BorderLeftWidth borderLeftWidth) {

        final StringBuilder cssValueBuilder = new StringBuilder();

        if (borderLeftWidth != null) {

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

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

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

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

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

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

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

        this.borderLeftWidth = borderLeftWidth;

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

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

    public BorderLeft setBorderLeftStyle(
            final BorderLeftStyle borderLeftStyle) {

        final StringBuilder cssValueBuilder = new StringBuilder();

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

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

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

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

        this.borderLeftStyle = borderLeftStyle;

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

    public BorderLeft setBorderLeftColor(
            final BorderLeftColor borderLeftColor) {

        final StringBuilder cssValueBuilder = new StringBuilder();

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

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

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

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

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

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

        this.borderLeftColor = borderLeftColor;

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

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

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

            final BorderLeftColor borderLeftColor = (BorderLeftColor) stateChangedObject;

            final String cssValue = borderLeftColor.getCssValue();

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

            setBorderLeftColor(borderLeftColor);
        }
    }

    /**
     *
     * @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.css.core;

import java.util.Collection;

import com.webfirmframework.wffweb.util.TagStringUtil;

/**
 * A utility class for css enum classes.
 *
 * @author WFF
 * @since 1.0.0
 */
public final class CssEnumUtil {

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

    /**
     * checks whether the given {@code cssValues} contains the given
     * {@code cssValue}.
     *
     * @param cssValue
     *                          the value to be checked.
     * @param cssValues
     *                          the {@code cssValue} will be checked in this
     *                          {@code cssValues} .
     * @param lowestLength
     *                          the lowest length of the string contained in the
     *                          given {@code cssValues}.
     * @param highestLength
     *                          the highest length of the string contained in
     *                          the given {@code cssValues}.
     * @return true if the given {@code cssValues} contains the given
     *         {@code cssValue}, and the length of {@code cssValue} is not less
     *         than {@code lowestLength} and not greater than
     *         {@code highestLength}.
     * @since 1.0.0
     * @author WFF
     */
    public static boolean contains(final String cssValue,
            final Collection<String> cssValues, final int lowestLength,
            final int highestLength) {
        if (cssValue == null || cssValue.length() < lowestLength
                || cssValue.length() > highestLength) {
            return false;
        }
        final boolean contains = cssValues
                .contains(TagStringUtil.toUpperCase(cssValue));
        return contains;
    }

}


/*
 * 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 com.webfirmframework.wffweb.InvalidValueException;
import com.webfirmframework.wffweb.NullValueException;
import com.webfirmframework.wffweb.WffRuntimeException;
import com.webfirmframework.wffweb.css.core.AbstractCssProperty;
import com.webfirmframework.wffweb.util.StringUtil;

/**
 * list-style-image: none|url|initial|inherit;
 *
 * @author WFF
 * @since 1.0.0
 */
public class ListStyleImage extends AbstractCssProperty<ListStyleImage> {

    private static final long serialVersionUID = 1_0_0L;

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

    private String cssValue;

    private String url;

    /**
     * The {@code auto} will be set as the value
     */
    public ListStyleImage() {
        setCssValue(NONE);
    }

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

    /**
     * @param value
     *                     the css value or url value to set.
     * @param urlValue
     *                     images/TestImage.png
     */
    public ListStyleImage(final String value, final boolean urlValue) {
        if (urlValue) {
            setUrl(value);
        } else {
            setCssValue(value);
        }
    }

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

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

    /*
     * (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 ListStyleImage setCssValue(final String cssValue) {
        final String previousCssValue = this.cssValue;
        try {
            String trimmedCssValue = null;
            if (cssValue == null) {
                throw new NullValueException(
                        "null is an invalid value. The value format should be as for example url(/images/Test.png). Or, initial/inherit.");
            } else if ((trimmedCssValue = StringUtil.strip(cssValue))
                    .equalsIgnoreCase(INITIAL)
                    || trimmedCssValue.equalsIgnoreCase(INHERIT)
                    || trimmedCssValue.equalsIgnoreCase(NONE)) {
                this.cssValue = StringUtil.strip(cssValue).toLowerCase();
                url = null;
            } else if (trimmedCssValue.toLowerCase().startsWith("url(")
                    && trimmedCssValue.endsWith(")")) {
                url = trimmedCssValue.substring(
                        trimmedCssValue.toLowerCase().indexOf("url(") + 4,
                        trimmedCssValue.indexOf(')'));
                if (url.length() > 0 && url.charAt(0) == '"'
                        && url.charAt(url.length() - 1) == '"') {
                    url = url.substring(url.indexOf('\"') + 1,
                            url.lastIndexOf('\"'));
                }

                this.cssValue = "url(\"" + url + "\")";
            } else {
                throw new InvalidValueException(cssValue
                        + " is an invalid value. The value format should be as for example url(/images/Test.png). Or, initial/inherit.");
            }

            if (getStateChangeInformer() != null) {
                getStateChangeInformer().stateChanged(this);
            }
        } catch (final WffRuntimeException e) {
            this.cssValue = previousCssValue;
            throw new InvalidValueException(e.getMessage(), e);
        } catch (final NumberFormatException e) {
            this.cssValue = previousCssValue;
            throw new InvalidValueException(cssValue
                    + " is an invalid value. The value format should be as for example url(/images/Test.png). Or, initial/inherit.",
                    e);
        }
        return this;
    }

    /**
     * @param url
     *                eg:- images/Test.png
     * @return the current object
     * @since 1.0.0
     * @author WFF
     */
    public ListStyleImage setUrl(final String url) {
        this.url = url;
        setCssValue("url(\"" + url + "\")");
        return this;
    }

    /**
     * @return the url value set. Or, {@code null} if it is set with any inbuilt
     *         value like {@code inherit}
     * @since 1.0.0
     * @author WFF
     */
    public String getUrl() {
        return url;
    }

    /*
     * public String getValue() { return url != null ? url : getCssValue(); }
     */

    /**
     * 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);
    }

    /**
     * sets as {@code none}.
     *
     * @since 1.0.0
     * @author WFF
     */
    public void setAsNone() {
        setCssValue(NONE);
    }

    public boolean hasInbuiltValue() {
        if (getCssValue() == null) {
            return false;
        }
        final String trimmedCssValue = StringUtil.strip(getCssValue());
        return trimmedCssValue.equalsIgnoreCase(INITIAL)
                || trimmedCssValue.equalsIgnoreCase(INHERIT)
                || trimmedCssValue.equalsIgnoreCase(NONE);
    }

    /**
     * To check whether the given {@code cssValue} is valid for this class.
     *
     * @param cssValue
     * @return true if the given {@code cssValue} is valid for this class.
     * @since 1.0.0
     * @author WFF
     */
    public static boolean isValid(final String cssValue) {
        String trimmedCssValue = null;
        if (cssValue == null) {
            return false;
        } else if ((trimmedCssValue = StringUtil.strip(cssValue))
                .equalsIgnoreCase(INITIAL)
                || trimmedCssValue.equalsIgnoreCase(INHERIT)
                || trimmedCssValue.equalsIgnoreCase(NONE)) {
            return true;
        } else if (trimmedCssValue.toLowerCase().startsWith("url(")
                && trimmedCssValue.endsWith(")")) {
            return true;
        }
        return false;
    }
}


/*
 * 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.tag.html.formsandinputs;

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

import com.webfirmframework.wffweb.settings.WffConfiguration;
import com.webfirmframework.wffweb.tag.html.AbstractHtml;
import com.webfirmframework.wffweb.tag.html.TagNameConstants;
import com.webfirmframework.wffweb.tag.html.attribute.core.AbstractAttribute;
import com.webfirmframework.wffweb.tag.html.identifier.GlobalAttributable;
import com.webfirmframework.wffweb.tag.html.identifier.TextAreaAttributable;
import com.webfirmframework.wffweb.tag.htmlwff.NoTag;

/**
 * @author WFF
 * @since 1.0.0
 * @version 1.0.0
 *
 */
public class TextArea extends AbstractHtml {

    private static final long serialVersionUID = 1_0_1L;

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

    {
        init();
    }

    /**
     *
     * @param base
     *                       i.e. parent tag of this tag
     * @param attributes
     *                       An array of {@code AbstractAttribute}
     *
     * @since 1.0.0
     */
    public TextArea(final AbstractHtml base,
            final AbstractAttribute... attributes) {
        super(TagNameConstants.TEXTAREA, base, attributes);
        if (WffConfiguration.isDirectionWarningOn()) {
            warnForUnsupportedAttributes(attributes);
        }
    }

    private static void warnForUnsupportedAttributes(
            final AbstractAttribute... attributes) {
        for (final AbstractAttribute abstractAttribute : attributes) {
            if (!(abstractAttribute != null
                    && (abstractAttribute instanceof TextAreaAttributable
                            || abstractAttribute instanceof GlobalAttributable))) {
                LOGGER.warning(abstractAttribute
                        + " is not an instance of TextAreaAttribute");
            }
        }
    }

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

    /**
     * sets the child text for this tag.
     *
     * @param text
     *                 String which needs to be shown as a child of this tag.
     * @since 2.1.4
     */
    public void setChildText(final String text) {
        if (text == null || text.isEmpty()) {
            removeAllChildren();
        } else {
            addInnerHtml(new NoTag(null, text));
        }
    }

    /**
     * sets the child text for this tag.
     *
     * @param updateClient
     *                         true to update client browser page if it is
     *                         available. The default value is true but it will
     *                         be ignored if there is no client browser page.
     *
     * @param text
     *                         String which needs to be shown as a child of this
     *                         tag.
     * @since 2.1.15
     */
    public void setChildText(final boolean updateClient, final String text) {
        if (text == null || text.isEmpty()) {
            removeAllChildren();
        } else {
            addInnerHtmls(updateClient, new NoTag(null, text));
        }
    }

    /**
     * gets the child text set for this tag.
     *
     * @return the child text set for this tag in string format. If there is no
     *         children/child text then will return empty string.
     * @since 2.1.4
     */
    public String getChildText() {
        final List<AbstractHtml> children = getChildren();
        if (children.size() > 0) {
            final StringBuilder builder = new StringBuilder();
            for (final AbstractHtml child : children) {
                builder.append(child.toHtmlString());
            }
            return builder.toString();
        }
        return "";
    }

}


/*
 * 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.css;

import java.util.Arrays;
import java.util.List;
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.StringUtil;
import com.webfirmframework.wffweb.util.TagStringUtil;

/**
 *

 * border-color: color|transparent|initial|inherit;
 *
 * The border-color property sets the color of an element's four borders. This property can have from one to four values.
 *
 * Examples:
 *
 *     border-color:red green blue pink;
 *         top border is red
 *         right border is green
 *         bottom border is blue
 *         left border is pink
 *
 *     border-color:red green blue;
 *         top border is red
 *         right and left borders are green
 *         bottom border is blue
 *
 *     border-color:red green;
 *         top and bottom borders are red
 *         right and left borders are green
 *
 *     border-color:red;
 *         all four borders are red
 *
 * Note: Always declare the border-style property before the border-color property. An element must have borders before you can change the color.
 * Default value:  The current color of the element
 * Inherited:      no
 * Animatable:     yes
 * Version:        CSS1
 * JavaScript syntax:      object.style.borderColor="#FF0000 blue"
 *

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

    private static final long serialVersionUID = 1_0_0L;

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

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

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

    private String cssValue;

    private BorderTopColor borderTopColor;
    private BorderRightColor borderRightColor;
    private BorderBottomColor borderBottomColor;
    private BorderLeftColor borderLeftColor;

    /**
     * The {@code initial} will be set as the value
     */
    public BorderColor() {
        setCssValue(INITIAL);
    }

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

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

    /**
     * the color/color code to set. The alternative method {@code setCssValue}
     * can also be used.
     *
     * @param value
     * @return the current object
     * @since 1.0.0
     * @author WFF
     */
    public BorderColor setValue(final String value) {
        setCssValue(value);
        return this;
    }

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

    /*
     * (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();
    }

    /**
     * gets the value, {@code getCssValue} method can also be used to get the
     * same value.
     *
     * @return the value in String.
     * @since 1.0.0
     * @author WFF
     */
    public String getValue() {
        return getCssValue();
    }

    /**
     * @param cssValue
     *                     the value should be a color/color code, for example
     *                     #0000ff. {@code null} is considered as
     *                     an invalid value and it will throw
     *                     {@code NullValueException}.And an empty string is
     *                     also considered as an invalid value and it will throw
     *                     {@code InvalidValueException}.
     * @since 1.0.0
     * @author WFF
     */
    @Override
    public BorderColor setCssValue(final String cssValue) {
        if (cssValue == null) {
            throw new NullValueException(
                    "null is an invalid value. The value should be any color for example #0000ff. Or, initial/inherit/transparent.");
        } else if (StringUtil.isBlank(cssValue)) {
            throw new InvalidValueException(
                    "blank string is an invalid value. The value should be any color for example #0000ff. Or, initial/inherit/transparent.");
        } else {
            final String trimmedCssValue = StringUtil.strip(cssValue);

            if (PREDEFINED_CONSTANTS.contains(trimmedCssValue)) {
                this.cssValue = trimmedCssValue;
                if (borderTopColor != null) {
                    borderTopColor.setAlreadyInUse(false);
                    borderTopColor = null;
                }
                if (borderRightColor != null) {
                    borderRightColor.setAlreadyInUse(false);
                    borderRightColor = null;
                }
                if (borderBottomColor != null) {
                    borderBottomColor.setAlreadyInUse(false);
                    borderBottomColor = null;
                }
                if (borderLeftColor != null) {
                    borderLeftColor.setAlreadyInUse(false);
                    borderLeftColor = null;
                }
                if (getStateChangeInformer() != null) {
                    getStateChangeInformer().stateChanged(this);
                }

                return this;
            }

            final String borderColorString = StringUtil
                    .convertToSingleSpace(trimmedCssValue);

            final List<String> extractedColors = CssValueUtil
                    .split(borderColorString);

            if (extractedColors.size() == 1) {
                if (borderTopColor == null) {
                    borderTopColor = new BorderTopColor(extractedColors.get(0));
                    borderTopColor.setStateChangeInformer(this);
                    borderTopColor.setAlreadyInUse(true);
                } else {
                    borderTopColor.setCssValue(extractedColors.get(0));
                }
                if (borderRightColor == null) {
                    borderRightColor = new BorderRightColor(
                            extractedColors.get(0));
                    borderRightColor.setStateChangeInformer(this);
                    borderRightColor.setAlreadyInUse(true);
                } else {
                    borderRightColor.setCssValue(extractedColors.get(0));
                }
                if (borderBottomColor == null) {
                    borderBottomColor = new BorderBottomColor(
                            extractedColors.get(0));
                    borderBottomColor.setStateChangeInformer(this);
                    borderBottomColor.setAlreadyInUse(true);
                } else {
                    borderBottomColor.setCssValue(extractedColors.get(0));
                }
                if (borderLeftColor == null) {
                    borderLeftColor = new BorderLeftColor(
                            extractedColors.get(0));
                    borderLeftColor.setStateChangeInformer(this);
                    borderLeftColor.setAlreadyInUse(true);
                } else {
                    borderLeftColor.setCssValue(extractedColors.get(0));
                }
            } else if (extractedColors.size() == 2) {
                if (borderTopColor == null) {
                    borderTopColor = new BorderTopColor(extractedColors.get(0));
                    borderTopColor.setStateChangeInformer(this);
                    borderTopColor.setAlreadyInUse(true);
                } else {
                    borderTopColor.setCssValue(extractedColors.get(0));
                }
                if (borderBottomColor == null) {
                    borderBottomColor = new BorderBottomColor(
                            extractedColors.get(0));
                    borderBottomColor.setStateChangeInformer(this);
                    borderBottomColor.setAlreadyInUse(true);
                } else {
                    borderBottomColor.setCssValue(extractedColors.get(0));
                }
                if (borderRightColor == null) {
                    borderRightColor = new BorderRightColor(
                            extractedColors.get(1));
                    borderRightColor.setStateChangeInformer(this);
                    borderRightColor.setAlreadyInUse(true);
                } else {
                    borderRightColor.setCssValue(extractedColors.get(1));
                }
                if (borderLeftColor == null) {
                    borderLeftColor = new BorderLeftColor(
                            extractedColors.get(1));
                    borderLeftColor.setStateChangeInformer(this);
                    borderLeftColor.setAlreadyInUse(true);
                } else {
                    borderLeftColor.setCssValue(extractedColors.get(1));
                }
            } else if (extractedColors.size() == 3) {
                if (borderTopColor == null) {
                    borderTopColor = new BorderTopColor(extractedColors.get(0));
                    borderTopColor.setStateChangeInformer(this);
                    borderTopColor.setAlreadyInUse(true);
                } else {
                    borderTopColor.setCssValue(extractedColors.get(0));
                }
                if (borderRightColor == null) {
                    borderRightColor = new BorderRightColor(
                            extractedColors.get(1));
                    borderRightColor.setStateChangeInformer(this);
                    borderRightColor.setAlreadyInUse(true);
                } else {
                    borderRightColor.setCssValue(extractedColors.get(1));
                }
                if (borderLeftColor == null) {
                    borderLeftColor = new BorderLeftColor(
                            extractedColors.get(1));
                    borderLeftColor.setStateChangeInformer(this);
                    borderLeftColor.setAlreadyInUse(true);
                } else {
                    borderLeftColor.setCssValue(extractedColors.get(1));
                }
                if (borderBottomColor == null) {
                    borderBottomColor = new BorderBottomColor(
                            extractedColors.get(2));
                    borderBottomColor.setStateChangeInformer(this);
                    borderBottomColor.setAlreadyInUse(true);
                } else {
                    borderBottomColor.setCssValue(extractedColors.get(2));
                }
            } else if (extractedColors.size() == 4) {
                if (borderTopColor == null) {
                    borderTopColor = new BorderTopColor(extractedColors.get(0));
                    borderTopColor.setStateChangeInformer(this);
                    borderTopColor.setAlreadyInUse(true);
                } else {
                    borderTopColor.setCssValue(extractedColors.get(0));
                }
                if (borderRightColor == null) {
                    borderRightColor = new BorderRightColor(
                            extractedColors.get(1));
                    borderRightColor.setStateChangeInformer(this);
                    borderRightColor.setAlreadyInUse(true);
                } else {
                    borderRightColor.setCssValue(extractedColors.get(1));
                }
                if (borderBottomColor == null) {
                    borderBottomColor = new BorderBottomColor(
                            extractedColors.get(2));
                    borderBottomColor.setStateChangeInformer(this);
                    borderBottomColor.setAlreadyInUse(true);
                } else {
                    borderBottomColor.setCssValue(extractedColors.get(2));
                }
                if (borderLeftColor == null) {
                    borderLeftColor = new BorderLeftColor(
                            extractedColors.get(3));
                    borderLeftColor.setStateChangeInformer(this);
                    borderLeftColor.setAlreadyInUse(true);
                } else {
                    borderLeftColor.setCssValue(extractedColors.get(3));
                }
            } else {
                throw new InvalidValueException(
                        "the given cssValue is invalid");
            }

            this.cssValue = borderColorString;
            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 as {@code transparent}
     *
     * @since 1.0.0
     * @author WFF
     */
    public void setAsTransparent() {
        setCssValue(TRANSPARENT);
    }

    /**
     * @return the borderTopColor
     * @author WFF
     * @since 1.0.0
     */
    public BorderTopColor getBorderTopColor() {
        return borderTopColor;
    }

    /**
     * @param borderTopColor
     * @param borderRightColor
     * @param borderBottomColor
     * @param borderLeftColor
     * @author WFF
     * @since 1.0.0
     */
    public void setBorderColor(final BorderTopColor borderTopColor,
            final BorderRightColor borderRightColor,
            final BorderBottomColor borderBottomColor,
            final BorderLeftColor borderLeftColor) {

        if (borderTopColor != null && borderRightColor != null
                && borderBottomColor != null && borderLeftColor != null) {

            if (BorderTopColor.INITIAL.equals(borderTopColor.getCssValue())
                    || BorderTopColor.INHERIT
                            .equals(borderTopColor.getCssValue())
                    || BorderRightColor.INITIAL
                            .equals(borderRightColor.getCssValue())
                    || BorderRightColor.INHERIT
                            .equals(borderRightColor.getCssValue())
                    || BorderBottomColor.INITIAL
                            .equals(borderBottomColor.getCssValue())
                    || BorderBottomColor.INHERIT
                            .equals(borderBottomColor.getCssValue())
                    || BorderLeftColor.INITIAL
                            .equals(borderLeftColor.getCssValue())
                    || BorderLeftColor.INHERIT
                            .equals(borderLeftColor.getCssValue())) {
                throw new InvalidValueException(
                        "Any or all of the given arguments have initial/inherit constant value as its cssValue");
            }

            try {
                final BorderTopColor borderTopColorTemp;
                final BorderRightColor borderRightColorTemp;
                final BorderBottomColor borderBottomColorTemp;
                final BorderLeftColor borderLeftColorTemp;

                if (this.borderTopColor != null) {

                    assignProducedCssValue(borderTopColor, borderRightColor,
                            borderBottomColor, borderLeftColor);

                    this.borderTopColor.setAlreadyInUse(false);
                    this.borderRightColor.setAlreadyInUse(false);
                    this.borderBottomColor.setAlreadyInUse(false);
                    this.borderLeftColor.setAlreadyInUse(false);

                    borderTopColorTemp = borderTopColor;
                    borderRightColorTemp = borderRightColor;
                    borderBottomColorTemp = borderBottomColor;
                    borderLeftColorTemp = borderLeftColor;

                } else {
                    if (borderTopColor.isAlreadyInUse()) {
                        borderTopColorTemp = borderTopColor.clone();
                        if (LOGGER.isLoggable(Level.WARNING)) {
                            LOGGER.warning(
                                    "the given borderTopColor is already used by another object so its clone is assigned");
                        }
                    } else {
                        borderTopColorTemp = borderTopColor;
                    }

                    if (borderRightColor.isAlreadyInUse()) {
                        borderRightColorTemp = borderRightColor.clone();
                        if (LOGGER.isLoggable(Level.WARNING)) {
                            LOGGER.warning(
                                    "the given borderRightColor is already used by another object so its clone is assigned");
                        }
                    } else {
                        borderRightColorTemp = borderRightColor;
                    }

                    if (borderBottomColor.isAlreadyInUse()) {
                        borderBottomColorTemp = borderBottomColor.clone();
                        if (LOGGER.isLoggable(Level.WARNING)) {
                            LOGGER.warning(
                                    "the given borderBottomColor is already used by another object so its clone is assigned");
                        }
                    } else {
                        borderBottomColorTemp = borderBottomColor;
                    }

                    if (borderLeftColor.isAlreadyInUse()) {
                        borderLeftColorTemp = borderLeftColor.clone();
                        if (LOGGER.isLoggable(Level.WARNING)) {
                            LOGGER.warning(
                                    "the given borderLeftColor is already used by another object so its clone is assigned");
                        }
                    } else {
                        borderLeftColorTemp = borderLeftColor;
                    }
                }

                borderTopColorTemp.setAlreadyInUse(true);
                borderTopColorTemp.setStateChangeInformer(this);

                borderRightColorTemp.setAlreadyInUse(true);
                borderRightColorTemp.setStateChangeInformer(this);

                borderBottomColorTemp.setAlreadyInUse(true);
                borderBottomColorTemp.setStateChangeInformer(this);

                borderLeftColorTemp.setAlreadyInUse(true);
                borderLeftColorTemp.setStateChangeInformer(this);

                assignProducedCssValue(borderTopColor, borderRightColor,
                        borderBottomColor, borderLeftColor);

                this.borderTopColor = borderTopColorTemp;
                this.borderRightColor = borderRightColorTemp;
                this.borderBottomColor = borderBottomColorTemp;
                this.borderLeftColor = borderLeftColorTemp;

            } catch (final CloneNotSupportedException e) {
                throw new InvalidValueException(e);
            } catch (final Exception e) {
                throw new InvalidValueException(e);
            }

        } else {
            throw new NullValueException("cannot accept null arguments");
        }

    }

    /**
     * @return the borderRightColor
     * @author WFF
     * @since 1.0.0
     */
    public BorderRightColor getBorderRightColor() {
        return borderRightColor;
    }

    /**
     * @return the borderBottomColor
     * @author WFF
     * @since 1.0.0
     */
    public BorderBottomColor getBorderBottomColor() {
        return borderBottomColor;
    }

    /**
     * @return the borderLeftColor
     * @author WFF
     * @since 1.0.0
     */
    public BorderLeftColor getBorderLeftColor() {
        return borderLeftColor;
    }

    /**
     *
     * @author WFF
     * @since 1.0.0
     */
    private void assignProducedCssValue(final BorderTopColor borderTopColor,
            final BorderRightColor borderRightColor,
            final BorderBottomColor borderBottomColor,
            final BorderLeftColor borderLeftColor) {

        final String borderTopColorCssValue = borderTopColor.getCssValue();
        final String borderRightColorCssValue = borderRightColor.getCssValue();
        final String borderBottomColorCssValue = borderBottomColor
                .getCssValue();
        final String borderLeftColorCssValue = borderLeftColor.getCssValue();

        if (borderTopColorCssValue.equals(borderRightColorCssValue)
                && borderRightColorCssValue.equals(borderBottomColorCssValue)
                && borderBottomColorCssValue.equals(borderLeftColorCssValue)) {

            cssValue = borderTopColorCssValue;

            final StateChangeInformer<CssProperty> stateChangeInformer = getStateChangeInformer();
            if (stateChangeInformer != null) {
                stateChangeInformer.stateChanged(this);
            }
        } else if (borderTopColorCssValue.equals(borderBottomColorCssValue)
                && borderRightColorCssValue.equals(borderLeftColorCssValue)) {

            cssValue = new StringBuilder(borderTopColorCssValue).append(' ')
                    .append(borderRightColorCssValue).toString();

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

        } else if (borderRightColorCssValue.equals(borderLeftColorCssValue)) {

            cssValue = new StringBuilder(borderTopColorCssValue).append(' ')
                    .append(borderRightColorCssValue).append(' ')
                    .append(borderBottomColorCssValue).toString();

            final StateChangeInformer<CssProperty> stateChangeInformer = getStateChangeInformer();
            if (stateChangeInformer != null) {
                stateChangeInformer.stateChanged(this);
            }
        } else {
            cssValue = new StringBuilder(borderTopColorCssValue).append(' ')
                    .append(borderRightColorCssValue).append(' ')
                    .append(borderBottomColorCssValue).append(' ')
                    .append(borderLeftColorCssValue).toString();

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

    /*
     * (non-Javadoc)
     *
     * @see
     * com.webfirmframework.wffweb.informer.StateChangeInformer#stateChanged(
     * java.lang .Object)
     */
    @Override
    public void stateChanged(final CssProperty stateChangedObject) {

        if (stateChangedObject instanceof BorderTopColor) {
            final BorderTopColor borderTopColor = (BorderTopColor) stateChangedObject;
            if (BorderTopColor.INITIAL.equals(borderTopColor.getCssValue())
                    || BorderTopColor.INHERIT
                            .equals(borderTopColor.getCssValue())) {
                throw new InvalidValueException(
                        "borderTopColor cannot have initial/inherit as its cssValue");
            }
        } else if (stateChangedObject instanceof BorderRightColor) {
            final BorderRightColor borderRightColor = (BorderRightColor) stateChangedObject;
            if (BorderRightColor.INITIAL.equals(borderRightColor.getCssValue())
                    || BorderRightColor.INHERIT
                            .equals(borderRightColor.getCssValue())) {
                throw new InvalidValueException(
                        "borderRightColor cannot have initial/inherit as its cssValue");
            }
        } else if (stateChangedObject instanceof BorderBottomColor) {
            final BorderBottomColor borderBottomColor = (BorderBottomColor) stateChangedObject;
            if (BorderBottomColor.INITIAL
                    .equals(borderBottomColor.getCssValue())
                    || BorderBottomColor.INHERIT
                            .equals(borderBottomColor.getCssValue())) {
                throw new InvalidValueException(
                        "borderBottomColor cannot have initial/inherit as its cssValue");
            }
        } else if (stateChangedObject instanceof BorderLeftColor) {
            final BorderLeftColor borderLeftColor = (BorderLeftColor) stateChangedObject;
            if (BorderLeftColor.INITIAL.equals(borderLeftColor.getCssValue())
                    || BorderLeftColor.INHERIT
                            .equals(borderLeftColor.getCssValue())) {
                throw new InvalidValueException(
                        "borderLeftColor cannot have initial/inherit as its cssValue");
            }
        }

        assignProducedCssValue(borderTopColor, borderRightColor,
                borderBottomColor, borderLeftColor);

    }

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

    /**
     *
     * @author WFF
     * @since 1.0.0
     */
    public static boolean isValid(final String cssValue) {
        final String trimmedCssValue;
        if (cssValue == null || (trimmedCssValue = TagStringUtil
                .toLowerCase(StringUtil.strip(cssValue))).isEmpty()) {
            return false;
        }
        if (INITIAL.equalsIgnoreCase(trimmedCssValue)
                || INHERIT.equalsIgnoreCase(trimmedCssValue)) {
            return true;
        }

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

        for (final String eachPart : cssValueParts) {
            final boolean valid = BorderTopColor.isValid(eachPart);
            if ((valid
                    && (INITIAL.equals(eachPart) || INHERIT.equals(eachPart)))
                    || !valid) {
                return false;
            }
        }

        return true;
    }
}


/*
 * 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 com.webfirmframework.wffweb.InvalidValueException;
import com.webfirmframework.wffweb.NullValueException;
import com.webfirmframework.wffweb.css.CssNameConstants;
import com.webfirmframework.wffweb.css.core.AbstractCssProperty;
import com.webfirmframework.wffweb.util.StringUtil;
import com.webfirmframework.wffweb.util.TagStringUtil;

/**
 *

 * -webkit-flex-grow: number|initial|inherit;
 *
 * The flex-grow property specifies how much the item will grow relative to the rest of the flexible items inside the same container.
 *
 * Note: If the element is not a flexible item, the -webkit-flex-grow property has no effect.
 * Default value:  0
 * Inherited:      no
 * Animatable:     yes
 * Version:        CSS3
 * JavaScript syntax:      object.style.webkitFlexGrow="5"
 *

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

    private static final long serialVersionUID = 1_0_0L;

    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 Float value;

    /**
     * The default value 0 will be set as the cssValue.
     *
     * @author WFF
     * @since 1.0.0
     */
    public WebkitFlexGrow() {
        value = Float.valueOf(0);
        cssValue = value.toString();
    }

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

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

    /**
     * @param value
     */
    public WebkitFlexGrow(final float value) {
        this.value = value;
        cssValue = String.valueOf(value);
    }

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

    /*
     * (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();
    }

    /**
     * gets the webkitFlexGrow in {@code Float} value.
     *
     * @return the value in float or null if the cssValue is
     *         initial or inherit.
     * @since 1.0.0
     * @author WFF
     */
    public Float getValue() {
        return value;
    }

    /**
     * @param value
     *                  the value to set
     * @author WFF
     * @since 1.0.0
     */
    public void setValue(final float value) {
        this.value = value;
        cssValue = String.valueOf(value);
        if (getStateChangeInformer() != null) {
            getStateChangeInformer().stateChanged(this);
        }
    }

    /**
     * @param cssValue
     *                     the value should be in the format of
     *                     0.5, initial/inherit.
     *                     {@code null} is considered as an invalid value and it
     *                     will throw {@code NullValueException}.
     * @since 1.0.0
     * @author WFF
     */
    @Override
    public WebkitFlexGrow setCssValue(final String cssValue) {
        if (cssValue == null) {
            throw new NullValueException(
                    "null is an invalid value. The value format should be as for example 0.5, initial/inherit.");
        } else {

            final String trimmedCssValue = TagStringUtil
                    .toLowerCase(StringUtil.strip(cssValue));

            if (INITIAL.equals(trimmedCssValue)
                    || INHERIT.equals(trimmedCssValue)) {
                this.cssValue = trimmedCssValue;
                value = null;
            } else {
                try {
                    value = Float.valueOf(trimmedCssValue);
                    this.cssValue = value.toString();
                } catch (final NumberFormatException e) {
                    throw new InvalidValueException(cssValue
                            + " is an invalid value. The value format should be as for example 0.5, initial, inherit etc..");
                }
            }

        }
        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);
    }

    /**
     * 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 (StringUtil.containsSpace(trimmedCssValue)) {
            return false;
        }
        try {
            Float.parseFloat(trimmedCssValue);
            return true;
        } catch (final NumberFormatException e) {
        }

        return PREDEFINED_CONSTANTS.contains(trimmedCssValue);
    }
}


/*
 * 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.streamer;

import java.io.IOException;
import java.io.OutputStream;

import com.webfirmframework.wffweb.util.WffBinaryMessageUtil;
import com.webfirmframework.wffweb.util.data.NameValue;

/**
 *
 * The default binary message version is 1.
 *
 * @author WFF
 * @since 1.1.2
 */
public class WffBinaryMessageOutputStreamer {

    private final OutputStream os;

    private boolean firstWrite = true;

    private final WffBinaryMessageVersion version;

    private int chunkSize;

    /**
     * @param os
     *               the output stream object to which the wff binary message
     *               bytes will be written
     * @since 1.0.0
     * @author WFF
     */
    public WffBinaryMessageOutputStreamer(final OutputStream os) {
        this.os = os;
        // by default it's considered as version 1.
        version = WffBinaryMessageVersion.VERSION_1;
    }

    /**
     * @param os
     *               the output stream object to which the wff binary message
     *               bytes will be written
     * @since 1.1.2
     * @author WFF
     */
    public WffBinaryMessageOutputStreamer(final OutputStream os,
            final WffBinaryMessageVersion version) {
        this.os = os;
        this.version = version;
        // by default it's considered as version 1.
    }

    /**
     * writes maximum bytes at chuckSize
     *
     * @param bytes
     * @throws IOException
     * @since 1.1.2
     * @author WFF
     */
    private void writeByChunk(final byte[] bytes) throws IOException {

        final int chunkSize = this.chunkSize;

        if (bytes.length == 0) {
            return;
        }

        if (chunkSize < bytes.length && chunkSize > 0) {

            int remaining = bytes.length;

            int offset = 0;

            while (remaining > 0) {
                if (chunkSize < remaining) {
                    os.write(bytes, offset, chunkSize);
                    remaining -= chunkSize;
                    offset += chunkSize;
                } else {
                    os.write(bytes, offset, remaining);
                    remaining = 0;
                }
            }

        } else {
            os.write(bytes);
        }
    }

    /**
     * @param nameValue
     *                      the nameValue to be written to the output stream
     * @return the number of bytes written to the output stream
     * @throws IOException
     * @since 1.1.2
     * @author WFF
     */
    public int write(final NameValue nameValue) throws IOException {

        final byte maxNoNameLengthBytes = 4;
        final byte maxNoValueLengthBytes = 4;

        int totalBytesWritten = 0;

        if (firstWrite) {
            writeByChunk(
                    new byte[] { maxNoNameLengthBytes, maxNoValueLengthBytes });
            totalBytesWritten = 2;
            firstWrite = false;
        }

        final byte[] name = nameValue.getName();

        final byte[] nameLegthBytes = WffBinaryMessageUtil
                .getBytesFromInt(name.length);

        writeByChunk(nameLegthBytes);

        totalBytesWritten += nameLegthBytes.length;

        writeByChunk(name);

        totalBytesWritten += name.length;

        final byte[][] values = nameValue.getValues();

        if (values.length == 0) {
            writeByChunk(new byte[] { 0, 0, 0, 0 });
            totalBytesWritten += maxNoValueLengthBytes;
        } else {

            int valueLegth = 0;
            for (final byte[] value : values) {
                valueLegth += value.length;
            }

            valueLegth += (maxNoValueLengthBytes * values.length);

            byte[] valueLegthBytes = WffBinaryMessageUtil
                    .getBytesFromInt(valueLegth);

            writeByChunk(valueLegthBytes);

            totalBytesWritten += valueLegthBytes.length;

            for (final byte[] value : values) {

                valueLegthBytes = WffBinaryMessageUtil
                        .getBytesFromInt(value.length);

                writeByChunk(valueLegthBytes);

                totalBytesWritten += valueLegthBytes.length;

                writeByChunk(value);

                totalBytesWritten += value.length;
            }

        }

        return totalBytesWritten;
    }

    public WffBinaryMessageVersion getVersion() {
        return version;
    }

    /**
     * @param chunkSize
     *                      the wff message bytes will be written maximum with
     *                      the given chuck size.
     * @since 1.1.2
     * @author WFF
     */
    public void setChunkSize(final int chunkSize) {
        this.chunkSize = chunkSize;
    }

    /**
     * @return
     * @since 1.1.2
     * @author WFF
     */
    public int getChunkSize() {
        return chunkSize;
    }
}


/*
 * 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.tag.html;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;

import com.webfirmframework.wffweb.tag.html.attribute.core.AbstractAttribute;

/**
 * @author WFF
 * @since 1.0.0
 * @version 1.0.0
 *
 */
public abstract class DocType extends AbstractHtml {

    private static final long serialVersionUID = 1_0_0L;

    private boolean prependDocType;

    private String docTypeTag = "<!DOCTYPE html>";

    /**
     * should be invoked to generate opening and closing tag
     *
     * @param tagName
     * @param attributes
     */
    public DocType(final String tagName, final AbstractHtml base,
            final AbstractAttribute[] attributes) {
        super(tagName, base, attributes);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.webfirmframework.wffweb.tag.html.AbstractHtml#toHtmlString()
     */
    @Override
    public String toHtmlString() {
        if (prependDocType) {
            return new String((docTypeTag + "\n" + super.toHtmlString())
                    .getBytes(getCharset()), getCharset());
        }
        return super.toHtmlString();
    }

    /*
     * (non-Javadoc)
     *
     * @see com.webfirmframework.wffweb.tag.html.AbstractHtml#toBigHtmlString()
     */
    @Override
    public String toBigHtmlString() {
        if (prependDocType) {
            return new String((docTypeTag + "\n" + super.toBigHtmlString())
                    .getBytes(getCharset()), getCharset());
        }
        return super.toBigHtmlString();
    }

    /*
     * (non-Javadoc)
     *
     * @see com.webfirmframework.wffweb.tag.html.AbstractHtml#toBigHtmlString(
     * boolean)
     */
    @Override
    public String toBigHtmlString(final boolean rebuild) {
        if (prependDocType) {
            return new String(
                    (docTypeTag + "\n" + super.toBigHtmlString(rebuild))
                            .getBytes(getCharset()),
                    getCharset());
        }
        return super.toBigHtmlString(rebuild);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * com.webfirmframework.wffweb.tag.html.AbstractHtml#toHtmlString(java.nio.
     * charset.Charset)
     */
    @Override
    public String toHtmlString(final Charset charset) {
        final Charset previousCharset = super.getCharset();
        try {
            super.setCharset(charset);
            if (prependDocType) {
                // assigning it to new variable is very important here as this
                // line of code should invoke before finally block
                final String htmlString = new String(
                        (super.toHtmlString(super.getCharset()))
                                .getBytes(super.getCharset()),
                        getCharset());
                return htmlString;
            }

            // assigning it to new variable is very important here as this line
            // of code should invoke before finally block
            final String htmlString = super.toHtmlString(super.getCharset());
            return htmlString;
        } finally {
            super.setCharset(previousCharset);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * com.webfirmframework.wffweb.tag.html.AbstractHtml#toHtmlString(java.lang.
     * String)
     */
    @Override
    public String toHtmlString(final String charset) {
        final Charset previousCharset = super.getCharset();
        try {
            super.setCharset(Charset.forName(charset));
            if (prependDocType) {
                // assigning it to new variable is very important here as this
                // line of code should invoke before finally block
                final String htmlString = new String(
                        (super.toHtmlString(super.getCharset()))
                                .getBytes(super.getCharset()),
                        getCharset());
                return htmlString;
            }
            // assigning it to new variable is very important here as this
            // line of code should invoke before finally block
            final String htmlString = super.toHtmlString(super.getCharset());
            return htmlString;
        } finally {
            super.setCharset(previousCharset);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * com.webfirmframework.wffweb.tag.html.AbstractHtml#toHtmlString(boolean)
     */
    @Override
    public String toHtmlString(final boolean rebuild) {
        if (prependDocType) {
            return new String((docTypeTag + "\n" + super.toHtmlString(rebuild))
                    .getBytes(getCharset()), getCharset());
        }
        return super.toHtmlString(rebuild);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * com.webfirmframework.wffweb.tag.html.AbstractHtml#toHtmlString(boolean,
     * java.nio.charset.Charset)
     */
    @Override
    public String toHtmlString(final boolean rebuild, final Charset charset) {
        final Charset previousCharset = super.getCharset();
        try {
            super.setCharset(charset);
            if (prependDocType) {
                // assigning it to new variable is very important here as this
                // line of code should invoke before finally block
                final String htmlString = new String(
                        (super.toHtmlString(rebuild, super.getCharset()))
                                .getBytes(getCharset()),
                        getCharset());
                return htmlString;
            }
            // assigning it to new variable is very important here as this
            // line of code should invoke before finally block
            final String htmlString = super.toHtmlString(rebuild,
                    super.getCharset());
            return htmlString;
        } finally {
            super.setCharset(previousCharset);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * com.webfirmframework.wffweb.tag.html.AbstractHtml#toHtmlString(boolean,
     * java.lang.String)
     */
    @Override
    public String toHtmlString(final boolean rebuild, final String charset) {
        final Charset previousCharset = super.getCharset();
        try {
            super.setCharset(previousCharset);
            if (prependDocType) {
                // assigning it to new variable is very important here as this
                // line of code should invoke before finally block
                final String htmlString = new String(
                        (super.toHtmlString(rebuild, super.getCharset()))
                                .getBytes(getCharset()),
                        getCharset());
                return htmlString;
            }
            // assigning it to new variable is very important here as this
            // line of code should invoke before finally block
            final String htmlString = super.toHtmlString(rebuild,
                    super.getCharset());
            return htmlString;
        } finally {
            super.setCharset(previousCharset);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * com.webfirmframework.wffweb.tag.html.AbstractHtml#toOutputStream(java.io.
     * OutputStream)
     */
    @Override
    public int toOutputStream(final OutputStream os) throws IOException {
        int docTypeTagLength = 0;
        if (prependDocType) {
            final byte[] docTypeTagBytes = (docTypeTag + "\n")
                    .getBytes(getCharset());
            os.write(docTypeTagBytes);
            docTypeTagLength = docTypeTagBytes.length;
        }
        return docTypeTagLength + super.toOutputStream(os);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * com.webfirmframework.wffweb.tag.html.AbstractHtml#toOutputStream(java.io.
     * OutputStream, boolean)
     */
    @Override
    public int toOutputStream(final OutputStream os, final boolean rebuild)
            throws IOException {
        int docTypeTagLength = 0;
        if (prependDocType) {
            final byte[] docTypeTagBytes = (docTypeTag + "\n")
                    .getBytes(getCharset());
            os.write(docTypeTagBytes);
            docTypeTagLength = docTypeTagBytes.length;
        }
        return docTypeTagLength + super.toOutputStream(os, rebuild);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * com.webfirmframework.wffweb.tag.html.AbstractHtml#toOutputStream(java.io.
     * OutputStream, boolean, boolean)
     */
    @Override
    public int toOutputStream(final OutputStream os, final boolean rebuild,
            final boolean flushOnWrite) throws IOException {
        int docTypeTagLength = 0;
        if (prependDocType) {
            final byte[] docTypeTagBytes = (docTypeTag + "\n")
                    .getBytes(getCharset());
            os.write(docTypeTagBytes);
            docTypeTagLength = docTypeTagBytes.length;
        }
        return docTypeTagLength
                + super.toOutputStream(os, rebuild, flushOnWrite);
    }

    /**
     * @param os
     *                    object of OutputStream to which the bytes to be
     *                    written
     * @param charset
     *                    the charset to encode for the bytes
     *
     * @return the total number of bytes written
     * @throws IOException
     *
     * @since 1.0.0
     * @author WFF
     */
    @Override
    public int toOutputStream(final OutputStream os, final Charset charset)
            throws IOException {
        int docTypeTagLength = 0;
        if (prependDocType) {
            final byte[] docTypeTagBytes = (docTypeTag + "\n")
                    .getBytes(charset);
            os.write(docTypeTagBytes);
            docTypeTagLength = docTypeTagBytes.length;
        }
        return docTypeTagLength + super.toOutputStream(os, charset);
    }

    /**
     * @param os
     *                         object of OutputStream to which the bytes to be
     *                         written
     * @param charset
     *                         the charset to encode for the bytes
     * @param flushOnWrite
     *                         true to flush on each write to OutputStream
     *
     * @return the total number of bytes written
     * @throws IOException
     *
     * @since 3.0.2
     * @author WFF
     */
    @Override
    public int toOutputStream(final OutputStream os, final Charset charset,
            final boolean flushOnWrite) throws IOException {
        int docTypeTagLength = 0;
        if (prependDocType) {
            final byte[] docTypeTagBytes = (docTypeTag + "\n")
                    .getBytes(charset);
            os.write(docTypeTagBytes);
            docTypeTagLength = docTypeTagBytes.length;
        }
        return docTypeTagLength
                + super.toOutputStream(os, charset, flushOnWrite);
    }

    /**
     * @param os
     *                    object of OutputStream to which the bytes to be
     *                    written
     * @param charset
     *                    the charset to encode for the bytes
     * @return the total number of bytes written
     * @throws IOException
     * @since 1.0.0
     * @author WFF
     */
    @Override
    public int toOutputStream(final OutputStream os, final String charset)
            throws IOException {
        int docTypeTagLength = 0;
        final Charset cs = Charset.forName(charset);
        if (prependDocType) {
            final byte[] docTypeTagBytes = (docTypeTag + "\n").getBytes(cs);
            os.write(docTypeTagBytes);
            docTypeTagLength = docTypeTagBytes.length;
        }
        return docTypeTagLength + super.toOutputStream(os, cs);
    }

    /**
     * @param os
     *                    object of OutputStream to which the bytes to be
     *                    written
     * @param rebuild
     *                    true to rebuild the tags
     * @param charset
     *                    the charset to encode for the bytes
     * @return the total number of bytes written
     * @throws IOException
     * @since 1.0.0
     * @author WFF
     * @return the total number of bytes written
     */
    @Override
    public int toOutputStream(final OutputStream os, final boolean rebuild,
            final Charset charset) throws IOException {
        int docTypeTagLength = 0;
        if (prependDocType) {
            final byte[] docTypeTagBytes = (docTypeTag + "\n")
                    .getBytes(charset);
            os.write(docTypeTagBytes);
            docTypeTagLength = docTypeTagBytes.length;
        }
        return docTypeTagLength + super.toOutputStream(os, rebuild, charset);
    }

    /**
     *
     * @param os
     *                         object of OutputStream to which the bytes to be
     *                         written
     * @param rebuild
     *                         true to rebuild the tags
     * @param charset
     *                         the charset to encode for the bytes
     * @param flushOnWrite
     *                         true to flush on each write to OutputStream
     * @return the total number of bytes written
     * @throws IOException
     * @since 3.0.2
     * @author WFF
     * @return the total number of bytes written
     */
    @Override
    public int toOutputStream(final OutputStream os, final boolean rebuild,
            final Charset charset, final boolean flushOnWrite)
            throws IOException {
        int docTypeTagLength = 0;
        if (prependDocType) {
            final byte[] docTypeTagBytes = (docTypeTag + "\n")
                    .getBytes(charset);
            os.write(docTypeTagBytes);
            docTypeTagLength = docTypeTagBytes.length;
        }
        return docTypeTagLength
                + super.toOutputStream(os, rebuild, charset, flushOnWrite);
    }

    /**
     * @param os
     *                    object of OutputStream to which the bytes to be
     *                    written
     * @param rebuild
     *                    true to rebuild the tags
     * @param charset
     *                    the charset to encode for the bytes
     * @return the total number of bytes written
     * @throws IOException
     * @since 1.0.0
     * @author WFF
     * @return the total number of bytes written
     */
    @Override
    public int toOutputStream(final OutputStream os, final boolean rebuild,
            final String charset) throws IOException {
        int docTypeTagLength = 0;
        final Charset cs = Charset.forName(charset);
        if (prependDocType) {
            final byte[] docTypeTagBytes = (docTypeTag + "\n").getBytes(cs);
            os.write(docTypeTagBytes);
            docTypeTagLength = docTypeTagBytes.length;
        }
        return docTypeTagLength + super.toOutputStream(os, rebuild, cs);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * com.webfirmframework.wffweb.tag.html.AbstractHtml#toBigOutputStream(java.
     * io.OutputStream)
     */
    @Override
    public int toBigOutputStream(final OutputStream os) throws IOException {
        int docTypeTagLength = 0;
        if (prependDocType) {
            final byte[] docTypeTagBytes = (docTypeTag + "\n")
                    .getBytes(getCharset());
            os.write(docTypeTagBytes);
            docTypeTagLength = docTypeTagBytes.length;
        }
        return docTypeTagLength + super.toBigOutputStream(os);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * com.webfirmframework.wffweb.tag.html.AbstractHtml#toBigOutputStream(java.
     * io.OutputStream, boolean)
     */
    @Override
    public int toBigOutputStream(final OutputStream os, final boolean rebuild)
            throws IOException {
        int docTypeTagLength = 0;
        if (prependDocType) {
            final byte[] docTypeTagBytes = (docTypeTag + "\n")
                    .getBytes(getCharset());
            os.write(docTypeTagBytes);
            docTypeTagLength = docTypeTagBytes.length;
        }
        return docTypeTagLength + super.toBigOutputStream(os, rebuild);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * com.webfirmframework.wffweb.tag.html.AbstractHtml#toBigOutputStream(java.
     * io.OutputStream, java.nio.charset.Charset)
     */
    @Override
    public int toBigOutputStream(final OutputStream os, final Charset charset)
            throws IOException {
        int docTypeTagLength = 0;
        if (prependDocType) {
            final byte[] docTypeTagBytes = (docTypeTag + "\n")
                    .getBytes(charset);
            os.write(docTypeTagBytes);
            docTypeTagLength = docTypeTagBytes.length;
        }
        return docTypeTagLength + super.toBigOutputStream(os, charset);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * com.webfirmframework.wffweb.tag.html.AbstractHtml#toBigOutputStream(java.
     * io.OutputStream, java.lang.String)
     */
    @Override
    public int toBigOutputStream(final OutputStream os, final String charset)
            throws IOException {
        int docTypeTagLength = 0;
        final Charset cs = Charset.forName(charset);
        if (prependDocType) {
            final byte[] docTypeTagBytes = (docTypeTag + "\n").getBytes(cs);
            os.write(docTypeTagBytes);
            docTypeTagLength = docTypeTagBytes.length;
        }
        return docTypeTagLength + super.toBigOutputStream(os, cs);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * com.webfirmframework.wffweb.tag.html.AbstractHtml#toBigOutputStream(java.
     * io.OutputStream, boolean, java.nio.charset.Charset)
     */
    @Override
    public int toBigOutputStream(final OutputStream os, final boolean rebuild,
            final Charset charset) throws IOException {
        int docTypeTagLength = 0;
        if (prependDocType) {
            final byte[] docTypeTagBytes = (docTypeTag + "\n")
                    .getBytes(charset);
            os.write(docTypeTagBytes);
            docTypeTagLength = docTypeTagBytes.length;
        }
        return docTypeTagLength + super.toBigOutputStream(os, rebuild, charset);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * com.webfirmframework.wffweb.tag.html.AbstractHtml#toBigOutputStream(java.
     * io.OutputStream, boolean, java.lang.String)
     */
    @Override
    public int toBigOutputStream(final OutputStream os, final boolean rebuild,
            final String charset) throws IOException {
        int docTypeTagLength = 0;
        final Charset cs = Charset.forName(charset);
        if (prependDocType) {
            final byte[] docTypeTagBytes = (docTypeTag + "\n").getBytes(cs);
            os.write(docTypeTagBytes);
            docTypeTagLength = docTypeTagBytes.length;
        }
        return docTypeTagLength + super.toBigOutputStream(os, rebuild, cs);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.webfirmframework.wffweb.tag.html.AbstractHtml#toString()
     */
    // it is not a best practice to print html string by this method because if
    // it is used in ThreadLocal class it may cause memory leak.
    @Override
    public String toString() {
        return super.toString();
    }

    /**
     * @return the prependDocType
     * @author WFF
     * @since 1.0.0
     */
    public boolean isPrependDocType() {
        return prependDocType;
    }

    /**
     * @param prependDocType
     *                           the prependDocType to set
     * @author WFF
     * @since 1.0.0
     */
    public void setPrependDocType(final boolean prependDocType) {
        this.prependDocType = prependDocType;
    }

    /**
     * @return the docTypeTag
     * @author WFF
     * @since 1.0.0
     */
    public String getDocTypeTag() {
        return docTypeTag;
    }

    /**
     * the default doc type is <code><!DOCTYPE html></code>
     *
     * @param docTypeTag
     *                       the docTypeTag to set
     * @author WFF
     * @since 1.0.0
     */
    public void setDocTypeTag(final String docTypeTag) {
        this.docTypeTag = docTypeTag;
    }

}