/*
 * 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.Collections;
import java.util.List;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.webfirmframework.wffweb.InvalidValueException;
import com.webfirmframework.wffweb.NullValueException;
import com.webfirmframework.wffweb.clone.CloneUtil;
import com.webfirmframework.wffweb.css.CssNameConstants;
import com.webfirmframework.wffweb.css.UrlCss3Value;
import com.webfirmframework.wffweb.css.core.AbstractCssProperty;
import com.webfirmframework.wffweb.data.Bean;
import com.webfirmframework.wffweb.informer.StateChangeInformer;
import com.webfirmframework.wffweb.util.CssValueUtil;
import com.webfirmframework.wffweb.util.StringBuilderUtil;
import com.webfirmframework.wffweb.util.StringUtil;

/**
 *

 *
 * border-image-source: none|image|initial|inherit;
 *
 * The border-image-source property specifies the path to the image to be used as a border (instead of the normal border around an element).
 *
 * Tip: If the value is "none", or if the image cannot be displayed, the border styles will be used.
 *
 * Tip: Also look at the border-image property (a shorthand property for setting all the border-image-* properties).
 * Default value:  none
 * Inherited:      no
 * Animatable:     no
 * Version:        CSS3
 * JavaScript syntax:      object.style.borderImageSource="url(border.png)"
 *
 *

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

    private static final long serialVersionUID = 1_0_0L;

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

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

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

    private String cssValue;

    // currently this variable is unused but it might be required for future
    // update.
    @SuppressWarnings("unused")
    private String[] imageUrls;

    private UrlCss3Value[] urlCss3Values;

    /**
     * the default value is none
     *
     * @author WFF
     * @since 1.0.0
     */
    public BorderImageSource() {
        setCssValue(NONE);
    }

    /**
     * @param cssValue
     *                     the cssValue to set.

     *                     eg:- {@code url(images/BackgroundDesign.png)}
     * @author WFF
     */
    public BorderImageSource(final String cssValue) {
        setCssValue(cssValue);
    }

    /**
     * @param borderImageSource
     *                              the {@code BorderImageSource} object from
     *                              which the cssName and cssValue to set.
     * @author WFF
     */
    public BorderImageSource(final BorderImageSource borderImageSource) {
        if (borderImageSource == null) {
            throw new NullValueException("customCss can not be null");
        }
        if (borderImageSource.getCssName() == null) {
            throw new NullValueException(
                    "customCss.getCssName() can not be null");
        }
        setCssValue(borderImageSource.getCssValue());
    }

    /**
     *
     * sample code :- {@code new BorderImageSource("Test.gif", "TestImage.png")}
     * creates
     * background-image: url("Test.gif"), url("TestImage.png"); .
     * For css3 syntax method please use
     * {@code new BorderImageSource(UrlCss3Value... urlCss3Values)} or
     * {@code setImageUrls(UrlCss3Value... urlCss3Values)} method.
     *
     * @param imageUrls
     *                      an array of backgroundImage urls, eg:-
     *                      {@code backgroundImage.setImageUrls("Test.gif", "TestImage.png")}
     *                      and the generated css will be
     *                      background-image: url("Test.gif"), url("TestImage.png");
     * @since 1.0.0
     * @author WFF
     */
    public BorderImageSource(final String... imageUrls) {
        setImageUrls(imageUrls);
    }

    /**
     * @param urlCss3Values
     *                          an array of {@code UrlCss3Value} objects.
     * @author WFF
     */
    public BorderImageSource(final UrlCss3Value... urlCss3Values) {
        setImageUrls(urlCss3Values);
    }

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

    /*
     * (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
     *                     {@code null} is considered as an invalid value.
     * @since 1.0.0
     * @author WFF
     */
    @Override
    public BorderImageSource setCssValue(final String cssValue) {

        CssValueUtil.throwExceptionForInvalidValue(cssValue);

        if (PREDEFINED_CONSTANTS.contains(StringUtil.strip(cssValue))) {
            imageUrls = null;
            if (urlCss3Values != null) {
                for (final UrlCss3Value urlCss3Value : urlCss3Values) {
                    urlCss3Value.setAlreadyInUse(false);
                }
            }
            urlCss3Values = null;
            this.cssValue = cssValue;
            return this;
        }

        final String[] cssValueParts = StringUtil.splitByComma(cssValue);
        if (cssValueParts.length > 1) {
            final StringBuilder cssValueBuilder = new StringBuilder();
            if (urlCss3Values == null) {
                urlCss3Values = new UrlCss3Value[cssValueParts.length];
                for (int i = 0; i < cssValueParts.length; i++) {
                    final UrlCss3Value urlCss3Value = new UrlCss3Value(
                            cssValueParts[i]);
                    urlCss3Value.setAlreadyInUse(true);
                    urlCss3Value.setStateChangeInformer(this);
                    urlCss3Values[i] = urlCss3Value;

                    cssValueBuilder.append(urlCss3Value.getValue());
                    if (i < cssValueParts.length - 1) {
                        cssValueBuilder.append(", ");
                    } else {
                        cssValueBuilder.append(' ');
                    }
                }
            } else {

                UrlCss3Value[] urlCss3ValuesTemp = null;
                if (cssValueParts.length == urlCss3Values.length) {
                    urlCss3ValuesTemp = urlCss3Values;
                } else {
                    urlCss3ValuesTemp = new UrlCss3Value[cssValueParts.length];
                }
                for (int i = 0; i < cssValueParts.length; i++) {
                    final UrlCss3Value urlCss3Value;
                    if (i < urlCss3Values.length) {
                        urlCss3Value = urlCss3Values[i];
                        urlCss3Value.setUrlCssValue(cssValueParts[i]);
                    } else {
                        urlCss3Value = new UrlCss3Value(cssValueParts[i]);
                    }

                    urlCss3Value.setAlreadyInUse(true);
                    urlCss3Value.setStateChangeInformer(this);
                    urlCss3ValuesTemp[i] = urlCss3Value;
                    cssValueBuilder.append(urlCss3Value.getValue());
                    if (i < cssValueParts.length - 1) {
                        cssValueBuilder.append(", ");
                    } else {
                        cssValueBuilder.append(' ');
                    }

                }

                for (int i = cssValueParts.length; i < urlCss3Values.length; i++) {
                    urlCss3Values[i].setAlreadyInUse(false);
                }

                for (final UrlCss3Value urlCss3Value : urlCss3ValuesTemp) {
                    urlCss3Value.setStateChangeInformer(this);
                    urlCss3Value.setAlreadyInUse(true);
                }
                urlCss3Values = urlCss3ValuesTemp;
            }
            this.cssValue = StringBuilderUtil.getTrimmedString(cssValueBuilder);
        } else {
            this.cssValue = cssValue;
        }
        if (getStateChangeInformer() != null) {
            getStateChangeInformer().stateChanged(this);
        }
        return this;
    }

    /**
     * sample code :-
     * {@code backgroundImage.setImageUrls("Test.gif", "TestImage.png")} creates
     * background-image: url("Test.gif"), url("TestImage.png");.
     * For css3 syntax method please use
     * {@code setImageUrls(UrlCss3Value... urlCss3Values)} method.
     *
     * @param imageUrls
     *                      an array of backgroundImage urls, eg:-
     *                      {@code backgroundImage.setImageUrls("Test.gif", "TestImage.png")}
     *                      and the generated css will be
     *                      background-image: url("Test.gif"), url("TestImage.png");
     * @since 1.0.0
     * @author WFF
     */
    public void setImageUrls(final String... imageUrls) {
        if (imageUrls == null) {
            throw new NullValueException("imageUrls is null");
        }
        final StringBuilder sb = new StringBuilder();
        for (int i = 0; i < imageUrls.length; i++) {
            sb.append("url(\"").append(imageUrls[i]).append("\")");
            if (i < imageUrls.length - 1) {
                sb.append(", ");
            } else {
                sb.append(' ');
            }
        }
        setCssValue(StringBuilderUtil.getTrimmedString(sb));
        this.imageUrls = imageUrls;
    }

    /**
     * @param urlCss3Values
     *                          urlCss3Values an array of {@code UrlCss3Value}
     *                          objects.
     * @since 1.0.0
     * @author WFF
     */
    public void setImageUrls(final UrlCss3Value... urlCss3Values) {
        if (urlCss3Values == null) {
            throw new NullValueException("urlCss3Values is null");
        }
        if (this.urlCss3Values != null) {
            for (final UrlCss3Value urlCss3Value : this.urlCss3Values) {
                urlCss3Value.setAlreadyInUse(false);
            }
        }

        final StringBuilder cssValueBuilder = new StringBuilder();

        final int urlCss3ValuesLength = urlCss3Values.length;
        for (int i = 0; i < urlCss3ValuesLength; i++) {
            UrlCss3Value urlCss3Value = urlCss3Values[i];
            if (urlCss3Value.isAlreadyInUse()) {
                try {
                    final UrlCss3Value clonedUrlCss3Value = CloneUtil
                            .<UrlCss3Value> deepCloneOnlyIfDoesNotContain(
                                    urlCss3Value, this.urlCss3Values);
                    if (!Objects.equals(clonedUrlCss3Value, urlCss3Value)) {
                        urlCss3Value = clonedUrlCss3Value;
                        if (LOGGER.isLoggable(Level.WARNING)) {
                            LOGGER.warning("cloned urlCss3Value " + urlCss3Value
                                    + "(hashcode: " + urlCss3Value.hashCode()
                                    + ") as it is already used by another object");
                        }
                    }
                } catch (final CloneNotSupportedException e) {
                    if (LOGGER.isLoggable(Level.SEVERE)) {
                        LOGGER.log(Level.SEVERE, e.getMessage(), e);
                    }
                }
            }
            cssValueBuilder.append(urlCss3Value.getValue());
            if (i < urlCss3ValuesLength - 1) {
                cssValueBuilder.append(", ");
            } else {
                cssValueBuilder.append(' ');
            }
            urlCss3Value.setAlreadyInUse(true);
            urlCss3Value.setStateChangeInformer(this);
        }

        if (this.urlCss3Values != null) {
            for (final UrlCss3Value urlCss3Value : this.urlCss3Values) {
                urlCss3Value.setAlreadyInUse(false);
            }
        }

        for (final UrlCss3Value urlCss3Value : urlCss3Values) {
            urlCss3Value.setAlreadyInUse(true);
            urlCss3Value.setStateChangeInformer(this);
        }

        this.urlCss3Values = urlCss3Values;
        cssValue = StringBuilderUtil.getTrimmedString(cssValueBuilder);
    }

    /**
     * @return a new object of unmodifiable {@code List<UrlCss3Value>} whenever
     *         this method is called. Or null.
     *
     * @author WFF
     * @since 1.0.0
     */
    public List<UrlCss3Value> getUrlCss3Values() {
        // for security improvements for urlCss3Values without
        // compromising performance.
        // TODO the drawback of this the returning of new object each time even
        // if the urlCss3Values array has not been modified, as a minor
        // performance
        // improvement, return a new object only if the urlCss3Values is
        // modified.
        if (urlCss3Values == null) {
            return null;
        }
        return Collections.unmodifiableList(Arrays.asList(urlCss3Values));
    }

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

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

    @Override
    public void stateChanged(final Bean stateChangedObject) {
        if (urlCss3Values != null
                && stateChangedObject instanceof UrlCss3Value) {
            final UrlCss3Value urlCss3Value = (UrlCss3Value) stateChangedObject;
            if (urlCss3Value.getX() > -1 || urlCss3Value.getY() > -1) {
                throw new InvalidValueException(
                        "urlCss3Value cannot be set with x or y values.");
            }
            setImageUrls(urlCss3Values);
            if (getStateChangeInformer() != null) {
                getStateChangeInformer().stateChanged(this);
            }
        }
    }
}


/*
 * Copyright 2014-2019 Web Firm Framework
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * @author WFF
 */
package com.webfirmframework.wffweb.tag.html.html5.attribute;

import java.util.regex.PatternSyntaxException;

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

/**
 *
 * pattern attribute for the element.

 *
 * pattern attribute for input element :-

 * A regular expression that the control's value is checked against. The pattern
 * must match the entire value, not just some subset. Use the title attribute to
 * describe the pattern to help the user. This attribute applies when the value
 * of the type attribute is text, search, tel, url, email or password; otherwise
 * it is ignored. The regular expression language is the same as JavaScript's.
 * The pattern is not surrounded by forward slashes.
 *
 *
 * @author WFF
 * @since 1.0.0
 */
public class Pattern extends AbstractAttribute implements InputAttributable {

    private static final long serialVersionUID = 1_0_0L;

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

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

    /**
     * sets the value for this attribute
     *
     * @param value
     *                  the value for the attribute.
     * @since 1.0.0
     * @author WFF
     */
    public void setValue(final String value) {
        super.setAttributeValue(value);
    }

    /**
     * sets the value for this attribute
     *
     * @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 value
     *                         the value for the attribute.
     * @since 2.1.15
     * @author WFF
     */
    public void setValue(final boolean updateClient, final String value) {
        super.setAttributeValue(updateClient, value);
    }

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

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

    /**
     * checks the value in this attribute contains a valid regex.
     *
     * @return true if this attribute contains a valid regex
     * @since 3.0.1
     * @author WFF
     */
    public boolean containsValidRegEx() {

        try {
            java.util.regex.Pattern.compile(super.getAttributeValue());
            return true;
        } catch (final PatternSyntaxException e) {
            // NOP
        }

        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.
 * @author WFF
 */
package com.webfirmframework.wffweb.css;

import com.webfirmframework.wffweb.InvalidValueException;
import com.webfirmframework.wffweb.data.AbstractBean;
import com.webfirmframework.wffweb.util.StringUtil;

/**
 *
 * @author WFF
 * @since 1.0.0
 */
public class RgbaCssValue extends AbstractBean<RgbaCssValue> {

    private static final long serialVersionUID = 1_0_0L;

    private String rgba = "rgba(0, 0, 0, 1)";
    private int r = 0;
    private int g = 0;
    private int b = 0;
    private float a = 1F;

    /**
     * The default value is rgba(0, 0, 0, 1)
     *
     * @author WFF
     * @since 1.0.0
     */
    public RgbaCssValue() {
    }

    /**
     * @param rgbaCssValue
     *                         eg:- <code> rgba(15, 55, 155, 1) </code>
     */
    public RgbaCssValue(final String rgbaCssValue) {
        super();
        extractAndAssign(rgbaCssValue);
    }

    /**
     * @param rgbaCssValue
     * @author WFF
     * @since 1.0.0
     */
    public RgbaCssValue(final RgbaCssValue rgbaCssValue) {
        super();
        extractAndAssign(rgbaCssValue.getValue());
    }

    /**
     * @param rgba
     *                 eg:- <code> rgba(15, 55, 155, 0.5) </code>
     *
     * @since 1.0.0
     * @author WFF
     */
    public void setRgba(final String rgba) {
        extractAndAssign(rgba);
    }

    /**
     * @param rgbaString
     * @since 1.0.0
     * @author WFF
     */
    private void extractAndAssign(final String rgbaString) {

        final String rgbaStringLowerCase = rgbaString.replace(" ", "")
                .toLowerCase();

        if (rgbaStringLowerCase.startsWith("rgba(")
                && rgbaStringLowerCase.contains(")")) {
            final String rgba = rgbaStringLowerCase.replace(",", ", ");

            final String[] rgbaStringParts = StringUtil
                    .splitByComma(rgbaStringLowerCase.substring(
                            rgbaStringLowerCase.indexOf('(') + 1,
                            rgbaStringLowerCase.lastIndexOf(')')));

            if (rgbaStringParts.length == 4) {
                r = Integer.parseInt(rgbaStringParts[0]);
                if (r < 0 || r > 255) {
                    throw new InvalidValueException(rgbaStringLowerCase
                            + " is not a valid rgba string. r value in rgbaString should be inbetween 0 to 255, eg: rgba(255, 25, 15, 0.1)");
                }
                g = Integer.parseInt(rgbaStringParts[1]);
                if (g < 0 || g > 255) {
                    throw new InvalidValueException(rgbaStringLowerCase
                            + " is not a valid rgba string. g value in rgbaString should be inbetween 0 to 255, eg: rgba(255, 25, 15, 0.1)");
                }
                b = Integer.parseInt(rgbaStringParts[2]);
                if (b < 0 || b > 255) {
                    throw new InvalidValueException(rgbaStringLowerCase
                            + " is not a valid rgba string. b value in rgbaString should be inbetween 0 to 255, eg: rgba(255, 25, 15, 0.1)");
                }
                a = Float.parseFloat(rgbaStringParts[3]);
                if (a < 0 || a > 1) {
                    throw new InvalidValueException(rgbaStringLowerCase
                            + " is not a valid rgba string. a value (i.e. alpha) in rgbaString should be inbetween 0 to 1, eg: rgba(255, 25, 15, 0.1)");
                }
                this.rgba = rgba;
            } else {
                throw new InvalidValueException(rgbaStringLowerCase
                        + " is not a valid rgba string. It should be in the format of rgba(255, 25, 15, 0.1)");
            }
        } else {
            throw new InvalidValueException(rgbaStringLowerCase
                    + " is not a valid rgba string. It should be in the format of rgba(255, 25, 15, 0.1)");
        }
    }

    /**
     * @param r
     *              red value. accepts values only from 0 to 255.
     * @param g
     *              green value. accepts values only from 0 to 255.
     * @param b
     *              blue value. accepts values only from 0 to 255.
     * @param a
     *              alpha value. accepts values only from 0 to 1.
     */
    public RgbaCssValue(final int r, final int g, final int b, final float a) {
        super();
        if ((r < 0 || r > 255) || (g < 0 || g > 255) || (b < 0 || b > 255)
                || (a < 0 || a > 1)) {
            throw new InvalidValueException(
                    "r, b and g paramater accept values only from 0 to 255.");
        }

        this.r = r;
        this.g = g;
        this.b = b;
        this.a = a;
        rgba = "rgba(" + r + ", " + g + ", " + b + ", " + a + ")";
    }

    /**
     * @return the red value.
     * @author WFF
     * @since 1.0.0
     */
    public int getR() {
        return r;
    }

    /**
     * to set the red value which is in between 0 to 255.
     *
     * @param r
     *              represents red
     * @author WFF
     * @since 1.0.0
     */
    public void setR(final int r) {
        if (r < 0 || r > 255) {
            throw new InvalidValueException(
                    "r paramater accept values only from 0 to 255.");
        }
        this.r = r;
        rgba = "rgba(" + r + ", " + g + ", " + b + ", " + a + ")";
        if (getStateChangeInformer() != null) {
            getStateChangeInformer().stateChanged(this);
        }
    }

    /**
     * @return the green value.
     * @author WFF
     * @since 1.0.0
     */
    public int getG() {
        return g;
    }

    /**
     * to set the green value which is in between 0 to 255.
     *
     * @param g
     *              represents green
     * @author WFF
     * @since 1.0.0
     */
    public void setG(final int g) {
        if (g < 0 || g > 255) {
            throw new InvalidValueException(
                    "g paramater accept values only from 0 to 255.");
        }
        this.g = g;
        rgba = "rgba(" + r + ", " + g + ", " + b + ", " + a + ")";
        if (getStateChangeInformer() != null) {
            getStateChangeInformer().stateChanged(this);
        }
    }

    /**
     * @return the blue value.
     * @author WFF
     * @since 1.0.0
     */
    public int getB() {
        return b;
    }

    /**
     * @return the alpha value.
     * @author WFF
     * @since 1.0.0
     */
    public float getA() {
        return a;
    }

    /**
     * to set the blue value which is in between 0 to 255.
     *
     * @param b
     * @author WFF
     * @since 1.0.0
     */
    public void setB(final int b) {
        if (b < 0 || b > 255) {
            throw new InvalidValueException(
                    "b paramater accept values only from 0 to 255.");
        }
        this.b = b;
        rgba = "rgba(" + r + ", " + g + ", " + b + ", " + a + ")";
        if (getStateChangeInformer() != null) {
            getStateChangeInformer().stateChanged(this);
        }
    }

    /**
     * to set alpha. The default value is 0.
     *
     * @param a
     * @author WFF
     * @since 1.0.0
     */
    public void setA(final float a) {
        if (a < 0 || a > 1) {
            throw new InvalidValueException(
                    "a paramater accept values only from 0 to 255.");
        }
        this.a = a;
        rgba = "rgba(" + r + ", " + g + ", " + b + ", " + a + ")";
        if (getStateChangeInformer() != null) {
            getStateChangeInformer().stateChanged(this);
        }
    }

    @Override
    public String toString() {
        return rgba;
    }

    /**
     * @return the print format of these values as a css value.
     * @since 1.0.0
     * @author WFF
     */
    @Override
    public String getValue() {
        return rgba;
    }

    /**
     * @param rgbaString
     *                       eg:- rgba(25, 155, 55, 0.2)
     * @return true if valid and false for invalid.
     * @since 1.0.0
     * @author WFF
     */
    public static boolean isValid(final String rgbaString) {
        try {
            final String rgbaStringLowerCase = rgbaString.replace(" ", "")
                    .toLowerCase();
            if (rgbaStringLowerCase.startsWith("rgba(")
                    && rgbaStringLowerCase.contains(")")) {

                final String[] rgbaStringParts = StringUtil
                        .splitByComma(rgbaStringLowerCase.substring(
                                rgbaStringLowerCase.indexOf('(') + 1,
                                rgbaStringLowerCase.lastIndexOf(')')));

                if (rgbaStringParts.length == 4) {
                    final int r = Integer.parseInt(rgbaStringParts[0]);
                    if (r < 0 || r > 255) {
                        return false;
                    }
                    final int g = Integer.parseInt(rgbaStringParts[1]);
                    if (g < 0 || g > 255) {
                        return false;
                    }
                    final int b = Integer.parseInt(rgbaStringParts[2]);
                    if (b < 0 || b > 255) {
                        return false;
                    }

                    final float a = Float.parseFloat(rgbaStringParts[3]);

                    return !(a < 0 || a > 1);
                }
                return false;
            }
        } catch (final NumberFormatException e) {
        }
        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.
 * @author WFF
 */
package com.webfirmframework.wffweb.css;

import java.util.logging.Logger;

import com.webfirmframework.wffweb.NullValueException;
import com.webfirmframework.wffweb.css.core.AbstractCssProperty;
import com.webfirmframework.wffweb.util.StringBuilderUtil;
import com.webfirmframework.wffweb.util.StringUtil;

/**
 *

 * unicode-range: unicode-range;
 *

 *
 * Optional. Defines the range of unicode characters the font supports. Default
 * value is "U+0-10FFFF"
 *
 * @author WFF
 * @version 1.1.2
 * @since 1.1.2
 */
public class UnicodeRange extends AbstractCssProperty<UnicodeRange> {

    private static final long serialVersionUID = 1_0_0L;

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

    public static final String DEFAULT_VALUE = "U+0-10FFFF";

    public static final String[] EMPTY_ARRAY = new String[0];

    private String cssValue;

    private String[] unicodeChars;

    /**
     * @since 1.1.2 initial implementation
     * @since 3.0.2 the default value is U+0-10FFFF
     */
    public UnicodeRange() {
        setCssValue(DEFAULT_VALUE);
    }

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

    /**
     *

     * Eg:-
     * String[] unicodeChars = {"U+0400-045F", "U+0490-0491", "U+04B0-04B1", "U+2116"};
     *
     *

     *
     * @param unicodeChars
     *                         the unicodeChars to set
     * @author WFF
     * @since 1.1.2
     */
    public UnicodeRange(final String... unicodeChars) {
        this.unicodeChars = StringUtil.cloneArray(unicodeChars);
        cssValue = getBuiltCssValue(this.unicodeChars);
        if (getStateChangeInformer() != null) {
            getStateChangeInformer().stateChanged(this);
        }
    }

    /**
     * @param unicodeChars
     * @return Defines the range of unicode characters the font supports.
     *         Default value is "U+0-10FFFF"
     * @author WFF
     * @since 1.1.2
     */
    protected static String getBuiltCssValue(final String... unicodeChars) {
        final StringBuilder cssValueSB = new StringBuilder();
        int count = 1;
        for (final String unicodeChar : unicodeChars) {
            cssValueSB.append(unicodeChar);

            if (count != unicodeChars.length) {
                cssValueSB.append(", ");
            }
            count++;
        }
        return StringBuilderUtil.getTrimmedString(cssValueSB);
    }

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

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

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

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

    /**
     * @param cssValue
     *                     The value should be a unicodeChars sequence.
     * @since 1.1.2
     * @author WFF
     */
    @Override
    public UnicodeRange setCssValue(final String cssValue) {
        if (cssValue == null) {
            throw new NullValueException(
                    "null is an invalid value. The value should be a unicodeChars sequence.");
        } else {

            final String trimmedCssValue = StringUtil.strip(cssValue);

            unicodeChars = StringUtil.splitByComma(cssValue);
            for (int i = 0; i < unicodeChars.length; i++) {
                unicodeChars[i] = StringUtil.strip(unicodeChars[i]);
            }

            this.cssValue = trimmedCssValue;

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

    /**
     *

     * Eg:-
     * String[] unicodeChars = {"U+0400-045F", "U+0490-0491", "U+04B0-04B1", "U+2116"};
     *
     *

     *
     * @param unicodeChars
     *                         the unicodeChars to set
     * @author WFF
     * @since 1.1.2
     */
    public void setUnicodeChars(final String[] unicodeChars) {
        if (unicodeChars != null) {
            this.unicodeChars = StringUtil.cloneArray(unicodeChars);
            cssValue = getBuiltCssValue(this.unicodeChars);
        } else {
            this.unicodeChars = null;
            cssValue = DEFAULT_VALUE;
        }
        if (getStateChangeInformer() != null) {
            getStateChangeInformer().stateChanged(this);
        }
    }

    /**
     * gets the unicodeChars as an array and if here is no value set then
     * returns an empty array.
     *
     *

     * Note: it will never return null.
     *

     *
     * @return the unicodeChars. If there is no unicodeChars set then returns an
     *         empty array instead of null.
     * @author WFF
     * @since 1.1.2
     */
    public String[] getUnicodeChars() {
        if (unicodeChars == null) {
            return EMPTY_ARRAY;
        }
        return StringUtil.cloneArray(unicodeChars);
    }

}


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

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.webfirmframework.wffweb.InvalidValueException;
import com.webfirmframework.wffweb.tag.html.AbstractHtml;
import com.webfirmframework.wffweb.tag.html.BaseFont;
import com.webfirmframework.wffweb.tag.html.Body;
import com.webfirmframework.wffweb.tag.html.Br;
import com.webfirmframework.wffweb.tag.html.H1;
import com.webfirmframework.wffweb.tag.html.H2;
import com.webfirmframework.wffweb.tag.html.H3;
import com.webfirmframework.wffweb.tag.html.H4;
import com.webfirmframework.wffweb.tag.html.H5;
import com.webfirmframework.wffweb.tag.html.H6;
import com.webfirmframework.wffweb.tag.html.Hr;
import com.webfirmframework.wffweb.tag.html.Html;
import com.webfirmframework.wffweb.tag.html.P;
import com.webfirmframework.wffweb.tag.html.Qfn;
import com.webfirmframework.wffweb.tag.html.TagNameConstants;
import com.webfirmframework.wffweb.tag.html.TitleTag;
import com.webfirmframework.wffweb.tag.html.attribute.core.AbstractAttribute;
import com.webfirmframework.wffweb.tag.html.formatting.Abbr;
import com.webfirmframework.wffweb.tag.html.formatting.Address;
import com.webfirmframework.wffweb.tag.html.formatting.B;
import com.webfirmframework.wffweb.tag.html.formatting.Bdo;
import com.webfirmframework.wffweb.tag.html.formatting.BlockQuote;
import com.webfirmframework.wffweb.tag.html.formatting.Cite;
import com.webfirmframework.wffweb.tag.html.formatting.Code;
import com.webfirmframework.wffweb.tag.html.formatting.Del;
import com.webfirmframework.wffweb.tag.html.formatting.Dfn;
import com.webfirmframework.wffweb.tag.html.formatting.Em;
import com.webfirmframework.wffweb.tag.html.formatting.I;
import com.webfirmframework.wffweb.tag.html.formatting.Ins;
import com.webfirmframework.wffweb.tag.html.formatting.Kbd;
import com.webfirmframework.wffweb.tag.html.formatting.Pre;
import com.webfirmframework.wffweb.tag.html.formatting.Q;
import com.webfirmframework.wffweb.tag.html.formatting.S;
import com.webfirmframework.wffweb.tag.html.formatting.Samp;
import com.webfirmframework.wffweb.tag.html.formatting.Small;
import com.webfirmframework.wffweb.tag.html.formatting.Strong;
import com.webfirmframework.wffweb.tag.html.formatting.Sub;
import com.webfirmframework.wffweb.tag.html.formatting.Sup;
import com.webfirmframework.wffweb.tag.html.formatting.U;
import com.webfirmframework.wffweb.tag.html.formatting.Var;
import com.webfirmframework.wffweb.tag.html.formsandinputs.Button;
import com.webfirmframework.wffweb.tag.html.formsandinputs.FieldSet;
import com.webfirmframework.wffweb.tag.html.formsandinputs.Form;
import com.webfirmframework.wffweb.tag.html.formsandinputs.Input;
import com.webfirmframework.wffweb.tag.html.formsandinputs.Label;
import com.webfirmframework.wffweb.tag.html.formsandinputs.Legend;
import com.webfirmframework.wffweb.tag.html.formsandinputs.OptGroup;
import com.webfirmframework.wffweb.tag.html.formsandinputs.Option;
import com.webfirmframework.wffweb.tag.html.formsandinputs.Select;
import com.webfirmframework.wffweb.tag.html.formsandinputs.TextArea;
import com.webfirmframework.wffweb.tag.html.frames.IFrame;
import com.webfirmframework.wffweb.tag.html.html5.Circle;
import com.webfirmframework.wffweb.tag.html.html5.Data;
import com.webfirmframework.wffweb.tag.html.html5.Ellipse;
import com.webfirmframework.wffweb.tag.html.html5.HGroup;
import com.webfirmframework.wffweb.tag.html.html5.Line;
import com.webfirmframework.wffweb.tag.html.html5.MathTag;
import com.webfirmframework.wffweb.tag.html.html5.Path;
import com.webfirmframework.wffweb.tag.html.html5.Polygon;
import com.webfirmframework.wffweb.tag.html.html5.Polyline;
import com.webfirmframework.wffweb.tag.html.html5.Rect;
import com.webfirmframework.wffweb.tag.html.html5.Source;
import com.webfirmframework.wffweb.tag.html.html5.Svg;
import com.webfirmframework.wffweb.tag.html.html5.Template;
import com.webfirmframework.wffweb.tag.html.html5.Text;
import com.webfirmframework.wffweb.tag.html.html5.Track;
import com.webfirmframework.wffweb.tag.html.html5.Video;
import com.webfirmframework.wffweb.tag.html.html5.audiovideo.Audio;
import com.webfirmframework.wffweb.tag.html.html5.formatting.Bdi;
import com.webfirmframework.wffweb.tag.html.html5.formatting.Mark;
import com.webfirmframework.wffweb.tag.html.html5.formatting.Meter;
import com.webfirmframework.wffweb.tag.html.html5.formatting.Progress;
import com.webfirmframework.wffweb.tag.html.html5.formatting.Rp;
import com.webfirmframework.wffweb.tag.html.html5.formatting.Rt;
import com.webfirmframework.wffweb.tag.html.html5.formatting.Ruby;
import com.webfirmframework.wffweb.tag.html.html5.formatting.Time;
import com.webfirmframework.wffweb.tag.html.html5.formatting.Wbr;
import com.webfirmframework.wffweb.tag.html.html5.formsandinputs.DataList;
import com.webfirmframework.wffweb.tag.html.html5.formsandinputs.KeyGen;
import com.webfirmframework.wffweb.tag.html.html5.formsandinputs.Output;
import com.webfirmframework.wffweb.tag.html.html5.images.Canvas;
import com.webfirmframework.wffweb.tag.html.html5.images.FigCaption;
import com.webfirmframework.wffweb.tag.html.html5.images.Figure;
import com.webfirmframework.wffweb.tag.html.html5.images.Picture;
import com.webfirmframework.wffweb.tag.html.html5.links.Nav;
import com.webfirmframework.wffweb.tag.html.html5.lists.Menu;
import com.webfirmframework.wffweb.tag.html.html5.lists.MenuItem;
import com.webfirmframework.wffweb.tag.html.html5.programming.Embed;
import com.webfirmframework.wffweb.tag.html.html5.stylesandsemantics.Article;
import com.webfirmframework.wffweb.tag.html.html5.stylesandsemantics.Aside;
import com.webfirmframework.wffweb.tag.html.html5.stylesandsemantics.Details;
import com.webfirmframework.wffweb.tag.html.html5.stylesandsemantics.Dialog;
import com.webfirmframework.wffweb.tag.html.html5.stylesandsemantics.Footer;
import com.webfirmframework.wffweb.tag.html.html5.stylesandsemantics.Header;
import com.webfirmframework.wffweb.tag.html.html5.stylesandsemantics.Main;
import com.webfirmframework.wffweb.tag.html.html5.stylesandsemantics.Section;
import com.webfirmframework.wffweb.tag.html.html5.stylesandsemantics.Summary;
import com.webfirmframework.wffweb.tag.html.images.Area;
import com.webfirmframework.wffweb.tag.html.images.Img;
import com.webfirmframework.wffweb.tag.html.images.MapTag;
import com.webfirmframework.wffweb.tag.html.links.A;
import com.webfirmframework.wffweb.tag.html.links.Link;
import com.webfirmframework.wffweb.tag.html.lists.Dd;
import com.webfirmframework.wffweb.tag.html.lists.Dl;
import com.webfirmframework.wffweb.tag.html.lists.Dt;
import com.webfirmframework.wffweb.tag.html.lists.Li;
import com.webfirmframework.wffweb.tag.html.lists.Ol;
import com.webfirmframework.wffweb.tag.html.lists.Ul;
import com.webfirmframework.wffweb.tag.html.metainfo.Base;
import com.webfirmframework.wffweb.tag.html.metainfo.Head;
import com.webfirmframework.wffweb.tag.html.metainfo.Meta;
import com.webfirmframework.wffweb.tag.html.programming.NoScript;
import com.webfirmframework.wffweb.tag.html.programming.ObjectTag;
import com.webfirmframework.wffweb.tag.html.programming.Param;
import com.webfirmframework.wffweb.tag.html.programming.Script;
import com.webfirmframework.wffweb.tag.html.stylesandsemantics.Div;
import com.webfirmframework.wffweb.tag.html.stylesandsemantics.Span;
import com.webfirmframework.wffweb.tag.html.stylesandsemantics.StyleTag;
import com.webfirmframework.wffweb.tag.html.tables.Caption;
import com.webfirmframework.wffweb.tag.html.tables.Col;
import com.webfirmframework.wffweb.tag.html.tables.ColGroup;
import com.webfirmframework.wffweb.tag.html.tables.TBody;
import com.webfirmframework.wffweb.tag.html.tables.TFoot;
import com.webfirmframework.wffweb.tag.html.tables.THead;
import com.webfirmframework.wffweb.tag.html.tables.Table;
import com.webfirmframework.wffweb.tag.html.tables.Td;
import com.webfirmframework.wffweb.tag.html.tables.Th;
import com.webfirmframework.wffweb.tag.html.tables.Tr;

public class TagRegistry {

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

    private static final List<String> TAG_NAMES;

    private static final Set<String> TAG_NAMES_SET;

    private static final Map<String, String> TAG_CLASS_NAME_BY_TAG_NAME;

    private static final Map<String, Class<?>> TAG_CLASS_BY_TAG_NAME;
    private static Map<String, Class<?>> tagClassByTagNameTmp;

    static {

        final Field[] fields = TagNameConstants.class.getFields();
        final int initialCapacity = fields.length;

        final Map<String, Class<?>> tagClassByTagName = new ConcurrentHashMap<>(
                initialCapacity);
        tagClassByTagNameTmp = new HashMap<>(initialCapacity);

        tagClassByTagName.put(TagNameConstants.A, A.class);
        tagClassByTagName.put(TagNameConstants.ABBR, Abbr.class);
        tagClassByTagName.put(TagNameConstants.ADDRESS, Address.class);
        tagClassByTagName.put(TagNameConstants.AREA, Area.class);
        tagClassByTagName.put(TagNameConstants.ARTICLE, Article.class);
        tagClassByTagName.put(TagNameConstants.ASIDE, Aside.class);
        tagClassByTagName.put(TagNameConstants.AUDIO, Audio.class);
        tagClassByTagName.put(TagNameConstants.B, B.class);
        tagClassByTagName.put(TagNameConstants.BASE, Base.class);
        tagClassByTagName.put(TagNameConstants.BASEFONT, BaseFont.class);
        tagClassByTagName.put(TagNameConstants.BDI, Bdi.class);
        tagClassByTagName.put(TagNameConstants.BDO, Bdo.class);
        tagClassByTagName.put(TagNameConstants.BLOCKQUOTE, BlockQuote.class);
        tagClassByTagName.put(TagNameConstants.BODY, Body.class);
        tagClassByTagName.put(TagNameConstants.BR, Br.class);
        tagClassByTagName.put(TagNameConstants.BUTTON, Button.class);
        tagClassByTagName.put(TagNameConstants.CANVAS, Canvas.class);
        tagClassByTagName.put(TagNameConstants.CAPTION, Caption.class);
        tagClassByTagName.put(TagNameConstants.CITE, Cite.class);
        tagClassByTagName.put(TagNameConstants.CODE, Code.class);
        tagClassByTagName.put(TagNameConstants.COL, Col.class);
        tagClassByTagName.put(TagNameConstants.COLGROUP, ColGroup.class);
        tagClassByTagName.put(TagNameConstants.DATA, Data.class);
        tagClassByTagName.put(TagNameConstants.DATALIST, DataList.class);
        tagClassByTagName.put(TagNameConstants.DD, Dd.class);
        tagClassByTagName.put(TagNameConstants.DEL, Del.class);
        tagClassByTagName.put(TagNameConstants.DETAILS, Details.class);
        tagClassByTagName.put(TagNameConstants.DFN, Dfn.class);
        tagClassByTagName.put(TagNameConstants.DIALOG, Dialog.class);
        tagClassByTagName.put(TagNameConstants.DIV, Div.class);
        tagClassByTagName.put(TagNameConstants.DL, Dl.class);
        tagClassByTagName.put(TagNameConstants.DT, Dt.class);
        tagClassByTagName.put(TagNameConstants.EM, Em.class);
        tagClassByTagName.put(TagNameConstants.EMBED, Embed.class);
        tagClassByTagName.put(TagNameConstants.FIELDSET, FieldSet.class);
        tagClassByTagName.put(TagNameConstants.FIGCAPTION, FigCaption.class);
        tagClassByTagName.put(TagNameConstants.FIGURE, Figure.class);
        tagClassByTagName.put(TagNameConstants.FOOTER, Footer.class);
        tagClassByTagName.put(TagNameConstants.FORM, Form.class);
        tagClassByTagName.put(TagNameConstants.H1, H1.class);
        tagClassByTagName.put(TagNameConstants.H2, H2.class);
        tagClassByTagName.put(TagNameConstants.H3, H3.class);
        tagClassByTagName.put(TagNameConstants.H4, H4.class);
        tagClassByTagName.put(TagNameConstants.H5, H5.class);

        tagClassByTagName.put(TagNameConstants.HEAD, Head.class);
        tagClassByTagName.put(TagNameConstants.HEADER, Header.class);
        tagClassByTagName.put(TagNameConstants.HGROUP, HGroup.class);
        tagClassByTagName.put(TagNameConstants.HR, Hr.class);
        tagClassByTagName.put(TagNameConstants.HTML, Html.class);
        tagClassByTagName.put(TagNameConstants.I, I.class);
        tagClassByTagName.put(TagNameConstants.IFRAME, IFrame.class);
        tagClassByTagName.put(TagNameConstants.IMG, Img.class);
        tagClassByTagName.put(TagNameConstants.INPUT, Input.class);
        tagClassByTagName.put(TagNameConstants.INS, Ins.class);
        tagClassByTagName.put(TagNameConstants.KBD, Kbd.class);
        tagClassByTagName.put(TagNameConstants.KEYGEN, KeyGen.class);
        tagClassByTagName.put(TagNameConstants.LABEL, Label.class);
        tagClassByTagName.put(TagNameConstants.LEGEND, Legend.class);
        tagClassByTagName.put(TagNameConstants.LI, Li.class);
        tagClassByTagName.put(TagNameConstants.LINK, Link.class);
        tagClassByTagName.put(TagNameConstants.MAIN, Main.class);
        tagClassByTagName.put(TagNameConstants.MAP, MapTag.class);
        tagClassByTagName.put(TagNameConstants.MARK, Mark.class);
        tagClassByTagName.put(TagNameConstants.MATH, MathTag.class);
        tagClassByTagName.put(TagNameConstants.MENU, Menu.class);
        tagClassByTagName.put(TagNameConstants.MENUITEM, MenuItem.class);
        tagClassByTagName.put(TagNameConstants.META, Meta.class);
        tagClassByTagName.put(TagNameConstants.METER, Meter.class);
        tagClassByTagName.put(TagNameConstants.NAV, Nav.class);
        tagClassByTagName.put(TagNameConstants.NOSCRIPT, NoScript.class);
        tagClassByTagName.put(TagNameConstants.OBJECT, ObjectTag.class);
        tagClassByTagName.put(TagNameConstants.OL, Ol.class);
        tagClassByTagName.put(TagNameConstants.OPTGROUP, OptGroup.class);
        tagClassByTagName.put(TagNameConstants.OPTION, Option.class);
        tagClassByTagName.put(TagNameConstants.OUTPUT, Output.class);
        tagClassByTagName.put(TagNameConstants.P, P.class);
        tagClassByTagName.put(TagNameConstants.PARAM, Param.class);
        tagClassByTagName.put(TagNameConstants.PRE, Pre.class);
        tagClassByTagName.put(TagNameConstants.PROGRESS, Progress.class);
        tagClassByTagName.put(TagNameConstants.Q, Q.class);
        tagClassByTagName.put(TagNameConstants.QFN, Qfn.class);
        tagClassByTagName.put(TagNameConstants.RP, Rp.class);
        tagClassByTagName.put(TagNameConstants.RT, Rt.class);
        tagClassByTagName.put(TagNameConstants.RUBY, Ruby.class);
        tagClassByTagName.put(TagNameConstants.S, S.class);
        tagClassByTagName.put(TagNameConstants.SAMP, Samp.class);
        tagClassByTagName.put(TagNameConstants.SCRIPT, Script.class);
        tagClassByTagName.put(TagNameConstants.SECTION, Section.class);
        tagClassByTagName.put(TagNameConstants.SELECT, Select.class);
        tagClassByTagName.put(TagNameConstants.SMALL, Small.class);
        tagClassByTagName.put(TagNameConstants.SOURCE, Source.class);
        tagClassByTagName.put(TagNameConstants.SPAN, Span.class);
        tagClassByTagName.put(TagNameConstants.STRONG, Strong.class);
        tagClassByTagName.put(TagNameConstants.STYLE, StyleTag.class);
        tagClassByTagName.put(TagNameConstants.SUB, Sub.class);
        tagClassByTagName.put(TagNameConstants.SUMMARY, Summary.class);
        tagClassByTagName.put(TagNameConstants.SUP, Sup.class);
        tagClassByTagName.put(TagNameConstants.SVG, Svg.class);
        tagClassByTagName.put(TagNameConstants.TABLE, Table.class);
        tagClassByTagName.put(TagNameConstants.TBODY, TBody.class);
        tagClassByTagName.put(TagNameConstants.TD, Td.class);
        tagClassByTagName.put(TagNameConstants.TEMPLATE, Template.class);
        tagClassByTagName.put(TagNameConstants.TEXTAREA, TextArea.class);
        tagClassByTagName.put(TagNameConstants.TFOOT, TFoot.class);
        tagClassByTagName.put(TagNameConstants.TH, Th.class);
        tagClassByTagName.put(TagNameConstants.THEAD, THead.class);
        tagClassByTagName.put(TagNameConstants.TIME, Time.class);
        tagClassByTagName.put(TagNameConstants.TITLE_TAG, TitleTag.class);
        tagClassByTagName.put(TagNameConstants.TR, Tr.class);
        tagClassByTagName.put(TagNameConstants.TRACK, Track.class);
        tagClassByTagName.put(TagNameConstants.U, U.class);
        tagClassByTagName.put(TagNameConstants.UL, Ul.class);
        tagClassByTagName.put(TagNameConstants.VAR, Var.class);
        tagClassByTagName.put(TagNameConstants.VIDEO, Video.class);
        tagClassByTagName.put(TagNameConstants.WBR, Wbr.class);
        tagClassByTagName.put(TagNameConstants.H6, H6.class);
        tagClassByTagName.put(TagNameConstants.PICTURE, Picture.class);
        tagClassByTagName.put(TagNameConstants.RECT, Rect.class);
        tagClassByTagName.put(TagNameConstants.CIRCLE, Circle.class);
        tagClassByTagName.put(TagNameConstants.ELLIPSE, Ellipse.class);
        tagClassByTagName.put(TagNameConstants.LINE, Line.class);
        tagClassByTagName.put(TagNameConstants.POLYGON, Polygon.class);
        tagClassByTagName.put(TagNameConstants.POLYLINE, Polyline.class);
        tagClassByTagName.put(TagNameConstants.PATH, Path.class);
        tagClassByTagName.put(TagNameConstants.TEXT, Text.class);

        TAG_CLASS_BY_TAG_NAME = Collections.unmodifiableMap(tagClassByTagName);

        tagClassByTagNameTmp.putAll(tagClassByTagName);

        final Map<String, String> tagClassNameByTagName = new ConcurrentHashMap<>();

        for (final Entry<String, Class<?>> entry : tagClassByTagName
                .entrySet()) {
            tagClassNameByTagName.put(entry.getKey(),
                    entry.getValue().getSimpleName());
        }

        TAG_CLASS_NAME_BY_TAG_NAME = Collections
                .unmodifiableMap(tagClassNameByTagName);

        TAG_NAMES = new ArrayList<>(initialCapacity);
        TAG_NAMES_SET = Collections
                .newSetFromMap(new ConcurrentHashMap<>(initialCapacity));

        TAG_NAMES_SET.addAll(TAG_CLASS_NAME_BY_TAG_NAME.keySet());
        TAG_NAMES.addAll(TAG_NAMES_SET);

        for (final Field field : fields) {
            try {
                final String tagName = field.get(null).toString();
                TAG_NAMES_SET.add(tagName);
            } catch (final Exception e) {
                if (LOGGER.isLoggable(Level.SEVERE)) {
                    LOGGER.log(Level.SEVERE, e.getMessage(), e);
                }
            }
        }

        TAG_NAMES.addAll(TAG_NAMES_SET);

        Collections.sort(TAG_NAMES, (o1, o2) -> {

            final Integer length1 = o1.length();
            final Integer length2 = o2.length();

            return length1.compareTo(length2);
        });
    }

    /**
     * @return the list of tag names sorted in the ascending order of its length
     * @since 1.1.3
     * @author WFF
     */
    public static List<String> getTagNames() {
        return new ArrayList<>(TAG_NAMES);
    }

    /**
     *
     * @param tagNamesToRegister
     *                               the tag names to register , eg:-
     *                               register("new-tag1", "new-tag2")
     * @since 1.1.3
     * @author WFF
     */
    public static void register(final String... tagNamesToRegister) {

        final Set<String> tagNamesWithoutDuplicates = new HashSet<>(
                tagNamesToRegister.length);
        Collections.addAll(tagNamesWithoutDuplicates, tagNamesToRegister);

        TAG_NAMES_SET.addAll(tagNamesWithoutDuplicates);

        TAG_NAMES.clear();
        TAG_NAMES.addAll(TAG_NAMES_SET);

        Collections.sort(TAG_NAMES, (o1, o2) -> {

            final Integer length1 = o1.length();
            final Integer length2 = o2.length();

            return length1.compareTo(length2);
        });

    }

    /**
     * @return a map containing tag name as key and value as tag class name
     *         without package name
     * @since 1.1.3
     * @author WFF
     */
    public static Map<String, String> getTagClassNameByTagName() {
        return TAG_CLASS_NAME_BY_TAG_NAME;
    }

    /**
     * @return
     * @since 3.0.2
     */
    static Map<String, Class<?>> getTagClassByTagName() {
        return TAG_CLASS_BY_TAG_NAME;
    }

    /**
     * Loads all tag classes.
     *
     * @since 2.1.13
     * @author WFF
     */
    public static void loadAllTagClasses() {

        final Map<String, Class<?>> unloadedClasses = new HashMap<>();

        for (final Entry<String, Class<?>> entry : tagClassByTagNameTmp
                .entrySet()) {
            try {

                Class.forName(entry.getValue().getName());

            } catch (final ClassNotFoundException e) {
                unloadedClasses.put(entry.getKey(), entry.getValue());
                if (LOGGER.isLoggable(Level.WARNING)) {
                    LOGGER.warning("Could not load tag class "
                            + entry.getValue().getName());
                }

            }
        }
        tagClassByTagNameTmp.clear();
        if (unloadedClasses.size() > 0) {
            tagClassByTagNameTmp.putAll(unloadedClasses);
        } else {
            tagClassByTagNameTmp = null;
        }
    }

    /**
     * @param tagName
     *                    name of tag in lower case
     * @return the new instance of given tag name without any parent tag and
     *         attributes
     * @since 3.0.2
     * @throws InvalidValueException
     */
    public static AbstractHtml getNewTagInstance(final String tagName) {
        return getNewTagInstance(tagName, null, new AbstractAttribute[0]);
    }

    /**
     * @param tagName
     *                       name of tag in lower case
     * @param parent
     * @param attributes
     * @return
     * @since 3.0.2
     * @throws InvalidValueException
     */
    public static AbstractHtml getNewTagInstance(final String tagName,
            final AbstractHtml parent, final AbstractAttribute... attributes) {

        final Class<?> tagClass = TAG_CLASS_BY_TAG_NAME.get(tagName);

        if (tagClass == null) {
            return null;
        }

        try {

            final AbstractHtml tag = (AbstractHtml) tagClass
                    .getConstructor(AbstractHtml.class,
                            AbstractAttribute[].class)
                    .newInstance(parent, attributes);

            return tag;
        } catch (InstantiationException | IllegalAccessException
                | IllegalArgumentException | InvocationTargetException
                | NoSuchMethodException | SecurityException e) {
            throw new InvalidValueException(
                    "Unable to create instance for " + tagClass, e);
        }
    }

    /**
     * @param tagName
     *                       name of tag in lower case
     * @param parent
     * @param attributes
     * @return new instance or null if failed
     * @since 3.0.2
     */
    public static AbstractHtml getNewTagInstanceOrNullIfFailed(
            final String tagName, final AbstractHtml parent,
            final AbstractAttribute... attributes) {

        final Class<?> tagClass = TAG_CLASS_BY_TAG_NAME.get(tagName);

        if (tagClass == null) {
            return null;
        }

        try {

            final AbstractHtml tag = (AbstractHtml) tagClass
                    .getConstructor(AbstractHtml.class,
                            AbstractAttribute[].class)
                    .newInstance(parent, attributes);

            return tag;
        } catch (InstantiationException | IllegalAccessException
                | IllegalArgumentException | InvocationTargetException
                | NoSuchMethodException | SecurityException e) {
            // NOP
        }
        return null;
    }

    // only for testing purpose
    static void test() throws InstantiationException, IllegalAccessException,
            NoSuchFieldException, SecurityException, IllegalArgumentException,
            InvocationTargetException, NoSuchMethodException,
            InvalidValueException {
        for (final Entry<String, Class<?>> each : TAG_CLASS_BY_TAG_NAME
                .entrySet()) {
            final String expectedTagName = each.getKey();
            final Class<?> tagClass = each.getValue();
            final AbstractHtml newInstance = (AbstractHtml) tagClass
                    .getConstructor(AbstractHtml.class,
                            AbstractAttribute[].class)
                    .newInstance(null, new AbstractAttribute[0]);
            final String actualTagName = newInstance.getTagName();

            if (!expectedTagName.equals(actualTagName)) {
                throw new InvalidValueException("expectedTagName: "
                        + expectedTagName + " actualTagName: " + actualTagName);
            }
        }
    }

}


/*
 * Copyright 2014-2019 Web Firm Framework
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * @author WFF
 */
package com.webfirmframework.wffweb.css.css3;

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

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.css.core.CssProperty;
import com.webfirmframework.wffweb.informer.StateChangeInformer;
import com.webfirmframework.wffweb.util.StringUtil;
import com.webfirmframework.wffweb.util.TagStringUtil;

/**
 *

 *
 * -moz-flex: -moz-flex-grow -moz-flex-shrink -moz-flex-basis|auto|initial|inherit;
 *
 * The flex property specifies the length of the item, relative to the rest of the flexible items inside the same container.
 *
 * The flex property is a shorthand for the -moz-flex-grow, -moz-flex-shrink, and the -moz-flex-basis properties.
 *
 * Note: If the element is not a flexible item, the flex property has no effect.
 * Default value:  0 1 auto
 * Inherited:      no
 * Animatable:     yes
 * Version:        CSS3
 * JavaScript syntax:      object.style.mozFlex="1"
 *
 *
 * The undefined value for -moz-flex-grow, -moz-flex-shrink and -moz-flex-basis in -moz-flex property is 1, 1 and 0% respectively.
 *
 *

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

    private static final long serialVersionUID = 1_0_0L;

    // NB : it should not override equals and hashcode methods as its objects
    // are equalized by Objects.equals(obj1, obj2) method in some places.

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

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

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

    private String cssValue;

    private MozFlexGrow mozFlexGrow;

    private MozFlexShrink mozFlexShrink;

    private MozFlexBasis mozFlexBasis;

    /**
     * The value 0 1 auto will be assigned as the cssValue.
     *
     * @author WFF
     * @since 1.0.0
     */
    public MozFlex() {
        setCssValue("0 1 auto");
    }

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

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

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

    /*
     * (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
     *                     1 1 auto or 1 1 15px.
     *                     {@code null} is considered as an invalid value and it
     *                     will throw {@code NullValueException}.
     * @since 1.0.0
     * @author WFF
     */
    @Override
    public MozFlex 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 \"0 1 auto\" 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 \"0 1 auto\" or initial/inherit.");
        }

        final String[] cssValueParts = StringUtil.splitBySpace(trimmedCssValue);

        if (cssValueParts.length > 1) {
            if (trimmedCssValue.contains(MozFlexBasis.INITIAL)) {
                throw new InvalidValueException(
                        "The string 'initial' makes the given cssValue invalid, please remove 'initial' from it and try.");
            }
            if (trimmedCssValue.contains(MozFlexBasis.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 (mozFlexGrow != null) {
                    mozFlexGrow.setAlreadyInUse(false);
                    mozFlexGrow = null;
                }
                if (mozFlexShrink != null) {
                    mozFlexShrink.setAlreadyInUse(false);
                    mozFlexShrink = null;
                }
                if (mozFlexBasis != null) {
                    mozFlexBasis.setAlreadyInUse(false);
                    mozFlexBasis = null;
                }

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

        MozFlexGrow mozFlexGrow = null;
        MozFlexShrink mozFlexShrink = null;
        MozFlexBasis mozFlexBasis = null;

        for (final String eachPart : cssValueParts) {
            if (mozFlexGrow == null && MozFlexGrow.isValid(eachPart)) {
                if (this.mozFlexGrow == null) {
                    mozFlexGrow = new MozFlexGrow(eachPart);
                    mozFlexGrow.setStateChangeInformer(this);
                    mozFlexGrow.setAlreadyInUse(true);
                } else {
                    this.mozFlexGrow.setCssValue(eachPart);
                    mozFlexGrow = this.mozFlexGrow;
                }
            } else if (mozFlexShrink == null
                    && MozFlexShrink.isValid(eachPart)) {
                if (this.mozFlexGrow == null) {
                    mozFlexShrink = new MozFlexShrink(eachPart);
                    mozFlexShrink.setStateChangeInformer(this);
                    mozFlexShrink.setAlreadyInUse(true);
                } else {
                    this.mozFlexShrink.setCssValue(eachPart);
                    mozFlexShrink = this.mozFlexShrink;
                }
            } else if (mozFlexBasis == null && MozFlexBasis.isValid(eachPart)) {
                if (this.mozFlexBasis == null) {
                    mozFlexBasis = new MozFlexBasis(eachPart);
                    mozFlexBasis.setStateChangeInformer(this);
                    mozFlexBasis.setAlreadyInUse(true);
                } else {
                    this.mozFlexBasis.setCssValue(eachPart);
                    mozFlexBasis = this.mozFlexBasis;
                }
            }
        }

        final StringBuilder cssValueBuilder = new StringBuilder();
        boolean invalid = true;
        if (mozFlexGrow == null) {
            if (this.mozFlexGrow == null) {
                mozFlexGrow = new MozFlexGrow(1);
                mozFlexGrow.setStateChangeInformer(this);
                mozFlexGrow.setAlreadyInUse(true);
            } else {
                this.mozFlexGrow.setValue(0);
                mozFlexGrow = this.mozFlexGrow;
            }
        } else {
            invalid = false;
        }

        cssValueBuilder.append(mozFlexGrow.getCssValue()).append(' ');

        if (mozFlexShrink == null) {
            if (this.mozFlexShrink == null) {
                mozFlexShrink = new MozFlexShrink(1);
                mozFlexShrink.setStateChangeInformer(this);
                mozFlexShrink.setAlreadyInUse(true);
            } else {
                this.mozFlexShrink.setValue(1);
                mozFlexShrink = this.mozFlexShrink;
            }
        } else {
            invalid = false;
        }

        cssValueBuilder.append(mozFlexShrink.getCssValue()).append(' ');

        if (mozFlexBasis == null) {
            if (cssValueParts.length == 3) {
                throw new InvalidValueException(
                        "the given cssValue contains invalid flex-basis value.");
            }
            if (this.mozFlexBasis == null) {
                mozFlexBasis = new MozFlexBasis(0);
                mozFlexBasis.setStateChangeInformer(this);
                mozFlexBasis.setAlreadyInUse(true);
            } else {
                this.mozFlexBasis.setAsAuto();
                mozFlexBasis = this.mozFlexBasis;
            }
        } else {
            invalid = false;
        }

        cssValueBuilder.append(mozFlexBasis.getCssValue());

        if (invalid) {
            throw new InvalidValueException(cssValue
                    + " is an invalid value. The value format should be as for example \"0 1 auto\" or initial/inherit.");
        }
        this.cssValue = cssValueBuilder.toString();

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

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

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

        this.mozFlexGrow = mozFlexGrow;
        this.mozFlexShrink = mozFlexShrink;
        this.mozFlexBasis = mozFlexBasis;

        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 String[] cssValueParts = StringUtil.splitBySpace(cssValue);

        MozFlexGrow mozFlexGrow = null;
        MozFlexShrink mozFlexShrink = null;
        MozFlexBasis mozFlexBasis = null;

        for (final String eachPart : cssValueParts) {
            boolean invalid = true;

            if (mozFlexGrow == null && MozFlexGrow.isValid(eachPart)) {
                mozFlexGrow = new MozFlexGrow(eachPart);
                invalid = false;
            } else if (mozFlexShrink == null
                    && MozFlexShrink.isValid(eachPart)) {
                mozFlexShrink = new MozFlexShrink(eachPart);
                invalid = false;
            } else if (mozFlexBasis == null && MozFlexBasis.isValid(eachPart)) {
                mozFlexBasis = new MozFlexBasis(eachPart);
                invalid = false;
            }
            if (invalid) {
                return false;
            }
        }

        return mozFlexGrow != null || mozFlexShrink != null
                || mozFlexBasis != 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);
    }

    /**
     * sets as auto
     *
     * @since 1.0.0
     * @author WFF
     */
    public void setAsAuto() {
        setCssValue(AUTO);
    }

    public MozFlexBasis getMozFlexBasis() {
        return mozFlexBasis;
    }

    public MozFlexShrink getMozFlexShrink() {
        return mozFlexShrink;
    }

    public MozFlexGrow getMozFlexGrow() {
        return mozFlexGrow;
    }

    public MozFlex setMozFlexGrow(final MozFlexGrow mozFlexGrow) {
        if (mozFlexGrow == null) {
            throw new InvalidValueException("mozFlexGrow cannot be null");
        }

        if (Objects.equals(this.mozFlexGrow, mozFlexGrow)) {
            return this;
        }

        final StringBuilder cssValueBuilder = new StringBuilder();

        if (this.mozFlexGrow != null) {
            this.mozFlexGrow.setCssValue(mozFlexGrow.getCssValue());
            if (LOGGER.isLoggable(Level.WARNING)) {
                LOGGER.warning(
                        "copied the cssValue from the given mozFlexGrow to the existing mozFlexGrow object.");
            }
        } else {
            if (mozFlexGrow.isAlreadyInUse()) {
                this.mozFlexGrow = new MozFlexGrow(mozFlexGrow);
                this.mozFlexGrow.setAlreadyInUse(true);
                this.mozFlexGrow.setStateChangeInformer(this);
                if (LOGGER.isLoggable(Level.WARNING)) {
                    LOGGER.warning(
                            "created a new object of MozFlexGrow as the given mozFlexGrow object is already used by another object");
                }
            } else {
                this.mozFlexGrow = mozFlexGrow;
                this.mozFlexGrow.setAlreadyInUse(true);
                this.mozFlexGrow.setStateChangeInformer(this);
            }
        }

        cssValueBuilder.append(mozFlexGrow.getCssValue()).append(' ');

        if (mozFlexShrink == null) {
            mozFlexShrink = new MozFlexShrink(1);
            mozFlexShrink.setStateChangeInformer(this);
            mozFlexShrink.setAlreadyInUse(true);
        }

        cssValueBuilder.append(mozFlexShrink.getCssValue()).append(' ');

        if (mozFlexBasis == null) {
            mozFlexBasis = new MozFlexBasis(0);
            mozFlexBasis.setStateChangeInformer(this);
            mozFlexBasis.setAlreadyInUse(true);
        }

        cssValueBuilder.append(mozFlexBasis.getCssValue());

        cssValue = cssValueBuilder.toString();

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

    public MozFlex setMozFlexShrink(final MozFlexShrink mozFlexShrink) {
        if (mozFlexShrink == null) {
            throw new InvalidValueException("mozFlexShrink cannot be null");
        }

        if (Objects.equals(this.mozFlexShrink, mozFlexShrink)) {
            return this;
        }

        final StringBuilder cssValueBuilder = new StringBuilder();

        if (mozFlexGrow == null) {
            mozFlexGrow = new MozFlexGrow(1);
            mozFlexGrow.setStateChangeInformer(this);
            mozFlexGrow.setAlreadyInUse(true);
        }

        cssValueBuilder.append(mozFlexGrow.getCssValue()).append(' ');

        if (this.mozFlexShrink != null) {
            this.mozFlexShrink.setCssValue(mozFlexShrink.getCssValue());
            if (LOGGER.isLoggable(Level.WARNING)) {
                LOGGER.warning(
                        "copied the cssValue from the given mozFlexShrink to the existing mozFlexShrink object.");
            }
        } else {
            if (mozFlexShrink.isAlreadyInUse()) {
                this.mozFlexShrink = new MozFlexShrink(mozFlexShrink);
                this.mozFlexShrink.setAlreadyInUse(true);
                this.mozFlexShrink.setStateChangeInformer(this);
                if (LOGGER.isLoggable(Level.WARNING)) {
                    LOGGER.warning(
                            "created a new object of MozFlexShrink as the given mozFlexShrink object is already used by another object");
                }
            } else {
                this.mozFlexShrink = mozFlexShrink;
                this.mozFlexShrink.setAlreadyInUse(true);
                this.mozFlexShrink.setStateChangeInformer(this);
            }
        }

        cssValueBuilder.append(mozFlexShrink.getCssValue()).append(' ');

        if (mozFlexBasis == null) {
            mozFlexBasis = new MozFlexBasis(0);
            mozFlexBasis.setStateChangeInformer(this);
            mozFlexBasis.setAlreadyInUse(true);
        }

        cssValueBuilder.append(mozFlexBasis.getCssValue());

        cssValue = cssValueBuilder.toString();

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

    public MozFlex setMozFlexBasis(final MozFlexBasis mozFlexBasis) {
        if (mozFlexBasis == null) {
            throw new InvalidValueException("mozFlexBasis cannot be null");
        }

        if (Objects.equals(this.mozFlexBasis, mozFlexBasis)) {
            return this;
        }

        final StringBuilder cssValueBuilder = new StringBuilder();

        if (mozFlexGrow == null) {
            mozFlexGrow = new MozFlexGrow(1);
            mozFlexGrow.setStateChangeInformer(this);
            mozFlexGrow.setAlreadyInUse(true);
        }

        cssValueBuilder.append(mozFlexGrow.getCssValue()).append(' ');

        if (mozFlexShrink == null) {
            mozFlexShrink = new MozFlexShrink(1);
            mozFlexShrink.setStateChangeInformer(this);
            mozFlexShrink.setAlreadyInUse(true);
        }

        cssValueBuilder.append(mozFlexShrink.getCssValue()).append(' ');

        if (this.mozFlexBasis != null) {
            this.mozFlexBasis.setCssValue(mozFlexBasis.getCssValue());
            if (LOGGER.isLoggable(Level.WARNING)) {
                LOGGER.warning(
                        "copied the cssValue from the given mozFlexBasis to the existing mozFlexBasis object.");
            }
        } else {
            if (mozFlexBasis.isAlreadyInUse()) {
                this.mozFlexBasis = new MozFlexBasis(mozFlexBasis);
                this.mozFlexBasis.setAlreadyInUse(true);
                this.mozFlexBasis.setStateChangeInformer(this);
                if (LOGGER.isLoggable(Level.WARNING)) {
                    LOGGER.warning(
                            "created a new object of MozFlexBasis as the given mozFlexBasis object is already used by another object");
                }
            } else {
                this.mozFlexBasis = mozFlexBasis;
                this.mozFlexBasis.setAlreadyInUse(true);
                this.mozFlexBasis.setStateChangeInformer(this);
            }
        }

        cssValueBuilder.append(mozFlexBasis.getCssValue());

        cssValue = cssValueBuilder.toString();

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

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

            final MozFlexGrow mozFlexGrow = (MozFlexGrow) stateChangedObject;

            final String cssValue = mozFlexGrow.getCssValue();

            if (MozFlexGrow.INITIAL.equals(cssValue)
                    || MozFlexGrow.INHERIT.equals(cssValue)) {
                throw new InvalidValueException(
                        "initial/inherit cannot be set as mozFlexGrow cssValue");
            }
            this.cssValue = new StringBuilder(mozFlexGrow.getCssValue())
                    .append(' ').append(mozFlexShrink.getCssValue()).append(' ')
                    .append(mozFlexBasis.getCssValue()).toString();
        } else if (stateChangedObject instanceof MozFlexShrink) {

            final MozFlexShrink mozFlexShrink = (MozFlexShrink) stateChangedObject;

            final String cssValue = mozFlexShrink.getCssValue();

            if (MozFlexShrink.INITIAL.equals(cssValue)
                    || MozFlexShrink.INHERIT.equals(cssValue)) {
                throw new InvalidValueException(
                        "initial/inherit cannot be set as mozFlexShrink cssValue");
            }
            this.cssValue = new StringBuilder(mozFlexGrow.getCssValue())
                    .append(' ').append(mozFlexShrink.getCssValue()).append(' ')
                    .append(mozFlexBasis.getCssValue()).toString();

        } else if (stateChangedObject instanceof MozFlexBasis) {

            final MozFlexBasis mozFlexBasis = (MozFlexBasis) stateChangedObject;

            final String cssValue = mozFlexBasis.getCssValue();

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

            this.cssValue = new StringBuilder(mozFlexGrow.getCssValue())
                    .append(' ').append(mozFlexShrink.getCssValue()).append(' ')
                    .append(mozFlexBasis.getCssValue()).toString();
        }
    }

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