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

/**
 *

 * outline-width: medium|thin|thick|length|initial|inherit;
 *
 * An outline is a line that is drawn around elements (outside the borders) to make the element "stand out".
 *
 * The outline-width specifies the width of an outline.
 *
 * Note: Always declare the outline-style property before the outline-width property. An element must have an outline before you change the width of it.
 * Default value:  medium
 * Inherited:      no
 * Animatable:     yes, see individual properties
 * Version:        CSS2
 * JavaScript syntax:      object.style.outlineWidth="2px"
 *

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

    private static final long serialVersionUID = 1_0_0L;

    public static final String MEDIUM = "medium";
    public static final String THIN = "thin";
    public static final String THICK = "thick";
    public static final String INITIAL = "initial";
    public static final String INHERIT = "inherit";

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

    private String cssValue;
    private Float value;
    private CssLengthUnit cssLengthUnit;

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

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

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

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

    /**
     * @param value
     * @param cssLengthUnit
     */
    public OutlineWidth(final float value, final CssLengthUnit cssLengthUnit) {
        this.value = value;
        this.cssLengthUnit = cssLengthUnit;
        cssValue = String.valueOf(value) + cssLengthUnit;
    }

    /**
     * @param value
     * @param cssLengthUnit
     * @return the current object
     * @since 1.0.0
     * @author WFF
     */
    public OutlineWidth setValue(final float value,
            final CssLengthUnit cssLengthUnit) {
        this.value = value;
        this.cssLengthUnit = cssLengthUnit;
        cssValue = String.valueOf(value) + cssLengthUnit;
        if (getStateChangeInformer() != null) {
            getStateChangeInformer().stateChanged(this);
        }
        return this;
    }

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

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

    /*
     * (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 outline-width in float value. {@code OutlineWidth#getUnit()}
     * should be used to get the cssLengthUnit for this 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;
    }

    /**
     * @return the cssLengthUnit {@code PX}/{@code PER}, or {@code null} if the
     *         value is any inbuilt value like {@code inherit}.
     * @since 1.0.0
     * @author WFF
     */
    public CssLengthUnit getUnit() {
        return cssLengthUnit;
    }

    /**
     * @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 OutlineWidth setCssValue(final String cssValue) {
        try {
            if (cssValue == null) {
                throw new NullValueException(
                        "null is an invalid value. The value format should be as for example 75px or 85%. Or, initial/inherit.");
            } else {
                final String trimmedCssValue = StringUtil.strip(cssValue);
                boolean invalidValue = true;
                for (final CssLengthUnit cssLengthUnit : CssLengthUnit
                        .values()) {
                    final String unit = cssLengthUnit.getUnit();
                    if (trimmedCssValue.endsWith(unit)) {
                        final String valueOnly = cssValue.replaceFirst(unit,
                                "");
                        try {
                            value = Float.parseFloat(valueOnly);
                        } catch (final NumberFormatException e) {
                            break;
                        }
                        this.cssLengthUnit = cssLengthUnit;
                        this.cssValue = cssValue;
                        invalidValue = false;
                        break;
                    }
                }
                if (trimmedCssValue.equalsIgnoreCase(INITIAL)
                        || trimmedCssValue.equalsIgnoreCase(INHERIT)
                        || trimmedCssValue.equalsIgnoreCase(MEDIUM)
                        || trimmedCssValue.equalsIgnoreCase(THIN)
                        || trimmedCssValue.equalsIgnoreCase(THICK)) {
                    this.cssValue = trimmedCssValue.toLowerCase();
                    cssLengthUnit = null;
                    value = null;
                    invalidValue = false;
                }
                if (invalidValue) {
                    throw new InvalidValueException(cssValue
                            + " is an invalid value. The value format should be as for example 75px, 85%, initial, inherit etc..");
                }
            }
            if (getStateChangeInformer() != null) {
                getStateChangeInformer().stateChanged(this);
            }
        } catch (final NumberFormatException e) {
            throw new InvalidValueException(cssValue
                    + " is an invalid value. The value format should be as for example 75px or 85%. Or, initial/inherit.",
                    e);
        }
        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 medium}.
     *
     * @since 1.0.0
     * @author WFF
     */
    public void setAsMedium() {
        setCssValue(MEDIUM);
    }

    /**
     * sets as {@code thin}.
     *
     * @since 1.0.0
     * @author WFF
     */
    public void setAsThin() {
        setCssValue(THIN);
    }

    /**
     * sets as {@code thick}.
     *
     * @since 1.0.0
     * @author WFF
     */
    public void setAsThick() {
        setCssValue(THICK);
    }

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

        if (CssLengthUtil.getLengthValueAsPremitiveAndUnit(
                trimmedCssValue).length == 2) {
            return true;
        }

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

/**
 *

 *
 * -webkit-flex: -webkit-flex-grow -webkit-flex-shrink -webkit-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 -webkit-flex-grow, -webkit-flex-shrink, and the -webkit-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.webkitFlex="1"
 *
 *
 * The undefined value for -webkit-flex-grow, -webkit-flex-shrink and -webkit-flex-basis in -webkit-flex property is 1, 1 and 0% respectively.
 *
 *

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

    private static final long serialVersionUID = 1_0_0L;

    public static final Logger LOGGER = Logger
            .getLogger(WebkitFlex.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 WebkitFlexGrow webkitFlexGrow;

    private WebkitFlexShrink webkitFlexShrink;

    private WebkitFlexBasis webkitFlexBasis;

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

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

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

    /*
     * (non-Javadoc)
     *
     * @see com.webfirmframework.wffweb.css.CssProperty#getCssName()
     *
     * @since 1.0.0
     *
     * @author WFF
     */
    @Override
    public String getCssName() {
        return CssNameConstants.WEBKIT_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 WebkitFlex 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(WebkitFlexBasis.INITIAL)) {
                throw new InvalidValueException(
                        "The string 'initial' makes the given cssValue invalid, please remove 'initial' from it and try.");
            }
            if (trimmedCssValue.contains(WebkitFlexBasis.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 (webkitFlexGrow != null) {
                    webkitFlexGrow.setAlreadyInUse(false);
                    webkitFlexGrow = null;
                }
                if (webkitFlexShrink != null) {
                    webkitFlexShrink.setAlreadyInUse(false);
                    webkitFlexShrink = null;
                }
                if (webkitFlexBasis != null) {
                    webkitFlexBasis.setAlreadyInUse(false);
                    webkitFlexBasis = null;
                }

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

        WebkitFlexGrow webkitFlexGrow = null;
        WebkitFlexShrink webkitFlexShrink = null;
        WebkitFlexBasis webkitFlexBasis = null;

        for (final String eachPart : cssValueParts) {
            if (webkitFlexGrow == null && WebkitFlexGrow.isValid(eachPart)) {
                if (this.webkitFlexGrow == null) {
                    webkitFlexGrow = new WebkitFlexGrow(eachPart);
                    webkitFlexGrow.setStateChangeInformer(this);
                    webkitFlexGrow.setAlreadyInUse(true);
                } else {
                    this.webkitFlexGrow.setCssValue(eachPart);
                    webkitFlexGrow = this.webkitFlexGrow;
                }
            } else if (webkitFlexShrink == null
                    && WebkitFlexShrink.isValid(eachPart)) {
                if (this.webkitFlexGrow == null) {
                    webkitFlexShrink = new WebkitFlexShrink(eachPart);
                    webkitFlexShrink.setStateChangeInformer(this);
                    webkitFlexShrink.setAlreadyInUse(true);
                } else {
                    this.webkitFlexShrink.setCssValue(eachPart);
                    webkitFlexShrink = this.webkitFlexShrink;
                }
            } else if (webkitFlexBasis == null
                    && WebkitFlexBasis.isValid(eachPart)) {
                if (this.webkitFlexBasis == null) {
                    webkitFlexBasis = new WebkitFlexBasis(eachPart);
                    webkitFlexBasis.setStateChangeInformer(this);
                    webkitFlexBasis.setAlreadyInUse(true);
                } else {
                    this.webkitFlexBasis.setCssValue(eachPart);
                    webkitFlexBasis = this.webkitFlexBasis;
                }
            }
        }

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

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

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

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

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

        cssValueBuilder.append(webkitFlexBasis.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();

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

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

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

        this.webkitFlexGrow = webkitFlexGrow;
        this.webkitFlexShrink = webkitFlexShrink;
        this.webkitFlexBasis = webkitFlexBasis;

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

        WebkitFlexGrow webkitFlexGrow = null;
        WebkitFlexShrink webkitFlexShrink = null;
        WebkitFlexBasis webkitFlexBasis = null;

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

            if (webkitFlexGrow == null && WebkitFlexGrow.isValid(eachPart)) {
                webkitFlexGrow = new WebkitFlexGrow(eachPart);
                invalid = false;
            } else if (webkitFlexShrink == null
                    && WebkitFlexShrink.isValid(eachPart)) {
                webkitFlexShrink = new WebkitFlexShrink(eachPart);
                invalid = false;
            } else if (webkitFlexBasis == null
                    && WebkitFlexBasis.isValid(eachPart)) {
                webkitFlexBasis = new WebkitFlexBasis(eachPart);
                invalid = false;
            }
            if (invalid) {
                return false;
            }
        }

        return webkitFlexGrow != null || webkitFlexShrink != null
                || webkitFlexBasis != 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 WebkitFlexBasis getWebkitFlexBasis() {
        return webkitFlexBasis;
    }

    public WebkitFlexShrink getWebkitFlexShrink() {
        return webkitFlexShrink;
    }

    public WebkitFlexGrow getWebkitFlexGrow() {
        return webkitFlexGrow;
    }

    public WebkitFlex setWebkitFlexGrow(final WebkitFlexGrow webkitFlexGrow) {
        if (webkitFlexGrow == null) {
            throw new InvalidValueException("webkitFlexGrow cannot be null");
        }

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

        final StringBuilder cssValueBuilder = new StringBuilder();

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

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

        if (webkitFlexShrink == null) {
            webkitFlexShrink = new WebkitFlexShrink(1);
            webkitFlexShrink.setStateChangeInformer(this);
            webkitFlexShrink.setAlreadyInUse(true);
        }

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

        if (webkitFlexBasis == null) {
            webkitFlexBasis = new WebkitFlexBasis(0);
            webkitFlexBasis.setStateChangeInformer(this);
            webkitFlexBasis.setAlreadyInUse(true);
        }

        cssValueBuilder.append(webkitFlexBasis.getCssValue());

        cssValue = cssValueBuilder.toString();

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

    public WebkitFlex setWebkitFlexShrink(
            final WebkitFlexShrink webkitFlexShrink) {
        if (webkitFlexShrink == null) {
            throw new InvalidValueException("webkitFlexShrink cannot be null");
        }

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

        final StringBuilder cssValueBuilder = new StringBuilder();

        if (webkitFlexGrow == null) {
            webkitFlexGrow = new WebkitFlexGrow(1);
            webkitFlexGrow.setStateChangeInformer(this);
            webkitFlexGrow.setAlreadyInUse(true);
        }

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

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

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

        if (webkitFlexBasis == null) {
            webkitFlexBasis = new WebkitFlexBasis(0);
            webkitFlexBasis.setStateChangeInformer(this);
            webkitFlexBasis.setAlreadyInUse(true);
        }

        cssValueBuilder.append(webkitFlexBasis.getCssValue());

        cssValue = cssValueBuilder.toString();

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

    public WebkitFlex setWebkitFlexBasis(
            final WebkitFlexBasis webkitFlexBasis) {
        if (webkitFlexBasis == null) {
            throw new InvalidValueException("webkitFlexBasis cannot be null");
        }

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

        final StringBuilder cssValueBuilder = new StringBuilder();

        if (webkitFlexGrow == null) {
            webkitFlexGrow = new WebkitFlexGrow(1);
            webkitFlexGrow.setStateChangeInformer(this);
            webkitFlexGrow.setAlreadyInUse(true);
        }

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

        if (webkitFlexShrink == null) {
            webkitFlexShrink = new WebkitFlexShrink(1);
            webkitFlexShrink.setStateChangeInformer(this);
            webkitFlexShrink.setAlreadyInUse(true);
        }

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

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

        cssValueBuilder.append(webkitFlexBasis.getCssValue());

        cssValue = cssValueBuilder.toString();

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

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

            final WebkitFlexGrow webkitFlexGrow = (WebkitFlexGrow) stateChangedObject;

            final String cssValue = webkitFlexGrow.getCssValue();

            if (WebkitFlexGrow.INITIAL.equals(cssValue)
                    || WebkitFlexGrow.INHERIT.equals(cssValue)) {
                throw new InvalidValueException(
                        "initial/inherit cannot be set as webkitFlexGrow cssValue");
            }
            this.cssValue = new StringBuilder(webkitFlexGrow.getCssValue())
                    .append(' ').append(webkitFlexShrink.getCssValue())
                    .append(' ').append(webkitFlexBasis.getCssValue())
                    .toString();
        } else if (stateChangedObject instanceof WebkitFlexShrink) {

            final WebkitFlexShrink webkitFlexShrink = (WebkitFlexShrink) stateChangedObject;

            final String cssValue = webkitFlexShrink.getCssValue();

            if (WebkitFlexShrink.INITIAL.equals(cssValue)
                    || WebkitFlexShrink.INHERIT.equals(cssValue)) {
                throw new InvalidValueException(
                        "initial/inherit cannot be set as webkitFlexShrink cssValue");
            }
            this.cssValue = new StringBuilder(webkitFlexGrow.getCssValue())
                    .append(' ').append(webkitFlexShrink.getCssValue())
                    .append(' ').append(webkitFlexBasis.getCssValue())
                    .toString();

        } else if (stateChangedObject instanceof WebkitFlexBasis) {

            final WebkitFlexBasis webkitFlexBasis = (WebkitFlexBasis) stateChangedObject;

            final String cssValue = webkitFlexBasis.getCssValue();

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

            this.cssValue = new StringBuilder(webkitFlexGrow.getCssValue())
                    .append(' ').append(webkitFlexShrink.getCssValue())
                    .append(' ').append(webkitFlexBasis.getCssValue())
                    .toString();
        }
    }

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


/**
 * Copyright 2012-2017 the original author or authors.
 *
 * 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 ch.rasc.embeddedtc;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import javax.servlet.ServletException;

import org.apache.catalina.Context;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.Server;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.AprLifecycleListener;
import org.apache.catalina.core.JasperListener;
import org.apache.catalina.core.JreMemoryLeakPreventionListener;
import org.apache.catalina.core.StandardServer;
import org.apache.catalina.core.ThreadLocalLeakPreventionListener;
import org.apache.catalina.deploy.ApplicationParameter;
import org.apache.catalina.deploy.ContextEnvironment;
import org.apache.catalina.deploy.ContextResource;
import org.apache.catalina.deploy.NamingResources;
import org.apache.catalina.mbeans.GlobalResourcesLifecycleListener;
import org.apache.catalina.session.StandardManager;
import org.apache.catalina.startup.CatalinaProperties;
import org.apache.catalina.startup.Tomcat;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;

/**
 * Helper class to simplify setting up a Embedded Tomcat in a IDE and with a Maven web
 * project.
 *
 * @author Ralph Schaer
 */
public class EmbeddedTomcat {

	private static final Log log = LogFactory.getLog(EmbeddedTomcat.class);

	private String contextPath;

	private Integer httpPort;

	private Integer shutdownPort;

	private int secondsToWaitBeforePortBecomesAvailable;

	public int maxPostSize = 2097152;

	private int httpsPort;

	private String keyStoreFile;

	private String keyAlias;

	private String keyStorePass;

	private String sslProtocol;

	private String tempDirectory;

	private String contextDirectory;

	private String skipJarsDefaultJarScanner;

	private String skipJarsContextConfig;

	private String skipJarsTldConfig;

	private boolean privileged;

	private boolean silent;

	private boolean addDefaultListeners = false;

	private boolean useNio = false;

	private int compressionMinSize = -1;

	private String compressableMimeType;

	private boolean enableNaming = false;

	private final List<ContextEnvironment> contextEnvironments;

	private final List<ContextResource> contextResources;

	private final List<ApplicationParameter> contextInitializationParameters;

	private URL contextFileURL;

	private Tomcat tomcat;

	/**
	 * Starts a embedded Tomcat on port 8080 with context path "" and context directory
	 * current directory + /src/main/webapp
	 *
	 * @param args program arguments
	 */
	public static void main(String[] args) {
		new EmbeddedTomcat().startAndWait();
	}

	/**
	 * Convenient method to create a embedded Tomcat that listens on port 8080 and with a
	 * context path of ""
	 *
	 * @return EmbeddedTomcat the embedded tomcat
	 */
	public static EmbeddedTomcat create() {
		return new EmbeddedTomcat();
	}

	/**
	 * Creates an embedded Tomcat with context path "" and port 8080. Context directory
	 * points to current directory + /src/main/webapp Change context directory with the
	 * method setContextDirectory(String)
	 *
	 * @see EmbeddedTomcat#setContextDirectory(String)
	 */
	public EmbeddedTomcat() {
		this("", 8080);
	}

	/**
	 * Creates an embedded Tomcat with context path "" and specified port. Context
	 * directory points to current directory + /src/main/webapp Change context directory
	 * with the method setContextDirectory(String)
	 *
	 * @param port ip port the server is listening
	 *
	 * @see #setContextDirectory(String)
	 */
	public EmbeddedTomcat(int port) {
		this("", port);
	}

	/**
	 * Creates an embedded Tomcat with the specified context path and port 8080 Context
	 * directory points to current directory + /src/main/webapp Change context directory
	 * with the method setContextDirectory(String)
	 *
	 * @param contextPath The context path has to start with /
	 *
	 * @see #setContextDirectory(String)
	 */
	public EmbeddedTomcat(String contextPath) {
		this(contextPath, 8080);
	}

	/**
	 * Creates an embedded Tomcat with specified context path and specified port. Context
	 * directory points to current directory + /src/main/webapp Change context directory
	 * with the method setContextDirectory(String)
	 *
	 * @param contextPath has to start with /
	 * @param httpPort ip port the server is listening for http requests. Shutdown port is
	 * set to port + 1000
	 *
	 * @see EmbeddedTomcat#setContextDirectory(String)
	 */
	public EmbeddedTomcat(String contextPath, int httpPort) {
		this(contextPath, httpPort, 0);
	}

	/**
	 * Creates an embedded Tomcat with specified context path and specified ports. Context
	 * directory points to current directory + /src/main/webapp Change context directory
	 * with the method setContextDirectory(String)
	 *
	 * @param contextPath has to start with /
	 * @param httpPort ip port the server is listening for http requests. Shutdown port is
	 * set to port + 1000
	 * @param httpsPort ip port the server is listening for https requests.
	 *
	 * @see EmbeddedTomcat#setContextDirectory(String)
	 */
	public EmbeddedTomcat(String contextPath, int httpPort, int httpsPort) {
		this.tomcat = null;

		setContextPath(contextPath);
		setHttpPort(httpPort);
		setShutdownPort(httpPort + 1000);
		setHttpsPort(httpsPort);
		setSecondsToWaitBeforePortBecomesAvailable(10);
		setPrivileged(false);
		setSilent(false);
		setContextDirectory(null);

		this.tempDirectory = null;
		this.contextEnvironments = new ArrayList<ContextEnvironment>();
		this.contextResources = new ArrayList<ContextResource>();
		this.contextInitializationParameters = new ArrayList<ApplicationParameter>();
	}

	/**
	 * Sets the port the server is listening for http requests
	 *
	 * @param port The new port
	 * @return The embedded Tomcat
	 * @deprecated Use {@link #setHttpPort(int)} instead
	 */
	@Deprecated
	public EmbeddedTomcat setPort(int port) {
		return setHttpPort(port);
	}

	/**
	 * Sets the port the server is listening for http requests
	 *
	 * @param httpPort The new port
	 * @return The embedded Tomcat
	 */
	public EmbeddedTomcat setHttpPort(int httpPort) {
		this.httpPort = httpPort;
		return this;
	}

	/**
	 * The maximum size in bytes of the POST which will be handled by the container FORM
	 * URL parameter parsing. The limit can be disabled by setting this attribute to a
	 * value less than or equal to 0. If not specified, this attribute is set to 2097152
	 * (2 megabytes).
	 *
	 * @param maxPostSize maximum size in bytes for POST requests
	 * @return The embedded Tomcat
	 */
	public EmbeddedTomcat setMaxPostSize(int maxPostSize) {
		this.maxPostSize = maxPostSize;
		return this;
	}

	/**
	 * Sets the port the server is listening for https requests
	 *
	 * @param httpsPort The new port
	 * @return The embedded Tomcat
	 */
	public EmbeddedTomcat setHttpsPort(int httpsPort) {
		this.httpsPort = httpsPort;
		return this;
	}

	/**
	 * The pathname of the keystore file where you have stored the server certificate to
	 * be loaded. By default, the pathname is the file ".keystore" in the operating system
	 * home directory of the user that is running Tomcat.
	 *
	 * @param keyStoreFile The keystore pathname
	 * @return The embedded Tomcat
	 */
	public EmbeddedTomcat setKeyStoreFile(String keyStoreFile) {
		this.keyStoreFile = keyStoreFile;
		return this;
	}

	/**
	 * The alias used to for the server certificate in the keystore. If not specified the
	 * first key read in the keystore will be used.
	 *
	 * @param keyAlias The key alias
	 * @return The embedded Tomcat
	 */
	public EmbeddedTomcat setKeyAlias(String keyAlias) {
		this.keyAlias = keyAlias;
		return this;
	}

	/**
	 * The password used to access the server certificate from the specified keystore
	 * file. The default value is "changeit".
	 *
	 * @param keyStorePass The keystore password
	 * @return The embedded Tomcat
	 */
	public EmbeddedTomcat setKeyStorePass(String keyStorePass) {
		this.keyStorePass = keyStorePass;
		return this;
	}

	/**
	 * Sets the contextPath for the webapplication
	 *
	 * @param contextPath The new contextPath. Has to start with / or is the empty ""
	 * string
	 * @return The embedded Tomcat
	 */
	public EmbeddedTomcat setContextPath(String contextPath) {

		if (contextPath == null
				|| !contextPath.equals("") && !contextPath.startsWith("/")) {
			throw new IllegalArgumentException(
					"contextPath must be the empty string \"\" or a path starting with /");
		}

		this.contextPath = contextPath;
		return this;
	}

	/**
	 * Sets the context directory. Normally this point to the directory that hosts the
	 * WEB-INF directory. Default value is: current directory + /src/main/webapp This is
	 * the normal location of the webapplication directory in a Maven web app project.
	 *
	 * @param contextDirectory Path name to the directory that contains the web
	 * application.
	 * @return The embedded Tomcat
	 */
	public EmbeddedTomcat setContextDirectory(String contextDirectory) {
		this.contextDirectory = contextDirectory;
		return this;
	}

	/**
	 * List of JAR files that should not be scanned using the JarScanner functionality.
	 * This is typically used to scan JARs for configuration information. JARs that do not
	 * contain such information may be excluded from the scan to speed up the scanning
	 * process. JARs on this list are excluded from all scans.
	 *

	 * Scan specific lists (to exclude JARs from individual scans) see
	 * {@link #skipJarsContextConfig(String)} and {@link #skipJarsTldConfig(String)}.
	 *

	 * The list must be a comma separated list of JAR file names. Example:
	 * spring*.jar,cglib*.jar.
	 *

	 * This list is appended to the default list. The default list is located in the file
	 * CATALINA_HOME\conf\catalina.properties under the key
	 * tomcat.util.scan.DefaultJarScanner.jarsToSkip
	 *
	 * @param skipJars list of jars, comma separated
	 * @return The embedded Tomcat
	 */
	public EmbeddedTomcat skipJarsDefaultJarScanner(String skipJars) {
		this.skipJarsDefaultJarScanner = skipJars;
		return this;
	}

	/**
	 * Additional JARs (over and above the default JARs set with
	 * {@link #skipJarsDefaultJarScanner(String)}) to skip when scanning for Servlet 3.0
	 * pluggability features. These features include web fragments, annotations, SCIs and
	 * classes that match @HandlesTypes. The list must be a comma separated list of JAR
	 * file names.
	 *
	 * @param skipJars list of jars, comma separated
	 * @return The embedded Tomcat
	 */
	public EmbeddedTomcat skipJarsContextConfig(String skipJars) {
		this.skipJarsContextConfig = skipJars;
		return this;
	}

	/**
	 * Additional JARs (over and above the default JARs set with
	 * {@link #skipJarsDefaultJarScanner(String)}) to skip when scanning for TLDs. The
	 * list must be a comma separated list of JAR file names.
	 *
	 * @param skipJars list of jars, comma separated
	 * @return The embedded Tomcat
	 */
	public EmbeddedTomcat skipJarsTldConfig(String skipJars) {
		this.skipJarsTldConfig = skipJars;
		return this;
	}

	/**
	 * Sets the location of the temporary directory. Tomcat needs this for storing
	 * temporary files like compiled jsp files. Default value is
	 *

	 *
	 * target/tomcat. + port
	 *
	 *
	 * @param tempDirectory File object that represents the location of the temp directory
	 * @return The embedded Tomcat
	 */
	public EmbeddedTomcat setTempDirectory(File tempDirectory) {
		this.tempDirectory = tempDirectory.getAbsolutePath();
		return this;
	}

	/**
	 * Sets the temporary directory to a directory beneath the target directory

	 * The directory does not have to exists, Tomcat will create it automatically if
	 * necessary.
	 *
	 * @param name directory name
	 * @return The embedded Tomcat
	 *
	 * @see EmbeddedTomcat#setTempDirectory(File)
	 */
	public EmbeddedTomcat setTempDirectoryName(String name) {
		this.tempDirectory = new File(".", "target/" + name).getAbsolutePath();
		return this;
	}

	/**
	 * The EmbeddedTomcat listens per default for shutdown commands on port 8005 with the
	 * shutdown command SHUTDOWN. Calling this method disables adding the
	 * shutdown hook.
	 *
	 * @return The embedded Tomcat
	 */
	public EmbeddedTomcat dontAddShutdownHook() {
		this.shutdownPort = null;
		return this;
	}

	/**
	 * Before starting the embedded Tomcat the programm tries to stop a previous process
	 * by sendig the shutdown command to the shutdown port. It then waits for the port to
	 * become available. It checks this every second for the specified number of seconds
	 *
	 * @param seconds number of seconds
	 * @return The embedded Tomcat
	 */
	public EmbeddedTomcat setSecondsToWaitBeforePortBecomesAvailable(int seconds) {
		this.secondsToWaitBeforePortBecomesAvailable = seconds;
		return this;
	}

	/**
	 * Specifies the port the server is listen for the shutdown command. Default is port
	 * 8005
	 *
	 * @param shutdownPort the shutdown port
	 * @return The embedded Tomcat *
	 *
	 * @see EmbeddedTomcat#dontAddShutdownHook()
	 */
	public EmbeddedTomcat setShutdownPort(int shutdownPort) {
		this.shutdownPort = shutdownPort;
		return this;
	}

	/**
	 * Set the privileged flag for this web application.
	 *
	 * @param privileged The new privileged flag
	 * @return The embedded Tomcat
	 */
	public EmbeddedTomcat setPrivileged(boolean privileged) {
		this.privileged = privileged;
		return this;
	}

	/**
	 * Instructs the embedded tomcat to use the Non Blocking Connector
	 * (org.apache.coyote.http11.Http11NioProtocol) instead of the Blocking Connector
	 * (org.apache.coyote.http11.Http11Protocol)
	 *
	 * @return The embedded Tomcat
	 */
	public EmbeddedTomcat useNio() {
		this.useNio = true;
		return this;
	}

	@SuppressWarnings("hiding")
	public EmbeddedTomcat enableCompression(int compressionMinSize,
			String compressableMimeType) {
		this.compressionMinSize = compressionMinSize;
		this.compressableMimeType = compressableMimeType;
		return this;
	}

	/**
	 * Enables JNDI naming which is disabled by default.
	 *
	 * @return The embedded Tomcat
	 */
	public EmbeddedTomcat enableNaming() {
		this.enableNaming = true;
		return this;
	}

	/**
	 * Installs the default listeners AprLifecycleListener, JasperListener,
	 * JreMemoryLeakPreventionListener, GlobalResourcesLifecycleListener and
	 * ThreadLocalLeakPreventionListener during startup.
	 *
	 * @return The embedded Tomcat
	 */
	public EmbeddedTomcat addDefaultListeners() {
		this.addDefaultListeners = true;
		return this;
	}

	/**
	 * Set the silent flag of Tomcat. If true Tomcat no longer prints any log messages
	 *
	 * @param silent The new silent flag
	 * @return The embedded Tomcat
	 */
	public EmbeddedTomcat setSilent(boolean silent) {
		this.silent = silent;
		return this;
	}

	/**
	 * Adds a {@link ContextEnvironment} object to embedded Tomcat.
	 *

	 * Example:

	 * Tomcat context xml file
	 *
	 *

	 *    <Environment name="aparam"
	 *                 value="test"
	 *                 type="java.lang.String"
	 *                 override="false"/>
	 *

	 *
	 * A programmatic way to add this environment to the embedded Tomcat is by calling
	 * this method
	 *
	 *

	 * ContextEnvironment env = new ContextEnvironment();
	 * env.setType("java.lang.String");
	 * env.setName("aparam");
	 * env.setValue("test");
	 * embeddedTomcat.addContextEnvironment(env);
	 *

	 *
	 *
	 * @param env context environment variable
	 * @return The embedded Tomcat
	 *
	 * @see ContextEnvironment
	 * @see NamingResources#addEnvironment(ContextEnvironment)
	 * @see EmbeddedTomcat#addContextEnvironment(String, String, String)
	 * @see EmbeddedTomcat#addContextEnvironmentString(String, String)
	 */
	public EmbeddedTomcat addContextEnvironment(ContextEnvironment env) {
		this.contextEnvironments.add(env);
		return this;
	}

	/**
	 * Adds a {@link ContextResource} object to the list of resources in the embedded
	 * Tomcat.
	 *
	 *

	 * Example:

	 * Tomcat context xml file
	 *
	 *

	 *  <Resource name="jdbc/ds" auth="Container"
	 *     type="javax.sql.DataSource" username="sa" password=""
	 *     driverClassName="org.h2.Driver"
	 *     url="jdbc:h2:~/mydb"
	 *     maxActive="20" maxIdle="4" maxWait="10000"
	 *     defaultAutoCommit="false"/>
	 *

	 *
	 * Programmatic way:
	 *
	 *

	 * ContextResource res = new ContextResource();
	 * res.setName("jdbc/ds");
	 * res.setType("javax.sql.DataSource");
	 * res.setAuth("Container");
	 * res.setProperty("username", "sa");
	 * res.setProperty("password", "");
	 * res.setProperty("driverClassName", "org.h2.Driver");
	 * res.setProperty("url", "jdbc:h2:˜/mydb");
	 * res.setProperty("maxActive", "20");
	 * res.setProperty("maxIdle", "4");
	 * res.setProperty("maxWait", "10000");
	 * res.setProperty("defaultAutoCommit", "false");
	 *
	 * embeddedTomcat.addContextResource(res);
	 *

	 *
	 * @param res resource object
	 * @return The embedded Tomcat
	 *
	 * @see ContextResource
	 * @see NamingResources#addResource(ContextResource)
	 */
	public EmbeddedTomcat addContextResource(ContextResource res) {
		this.contextResources.add(res);
		return this;
	}

	public EmbeddedTomcat addContextInitializationParameter(String name, String value) {
		ApplicationParameter parameter = new ApplicationParameter();
		parameter.setName(name);
		parameter.setValue(value);
		this.contextInitializationParameters.add(parameter);
		return this;
	}

	/**
	 * Convenient method for adding a context environment to the embedded Tomcat. Creates
	 * a ContextEnvironment object and adds it to the list of the context
	 * environments.
	 *
	 *

	 * Example:

	 * Tomcat context xml file
	 *
	 *

	 *    <Environment name="aparam"
	 *                 value="test"
	 *                 type="java.lang.String"
	 *                 override="false"/>
	 *

	 *
	 * Programmatic way:
	 *
	 *

	 * embeddedTomcat.addContextEnvironment("aparam", "test", "java.lang.String");
	 *

	 *
	 * @param name name of the context environment
	 * @param value value of the context environment
	 * @param type type of the context environment
	 * @return The embedded Tomcat
	 *
	 * @see ContextEnvironment
	 * @see NamingResources#addEnvironment(ContextEnvironment)
	 * @see EmbeddedTomcat#addContextEnvironment(ContextEnvironment)
	 * @see EmbeddedTomcat#addContextEnvironmentString(String, String)
	 */
	public EmbeddedTomcat addContextEnvironment(String name, String value, String type) {
		final ContextEnvironment env = new ContextEnvironment();
		env.setType(type);
		env.setName(name);
		env.setValue(value);
		addContextEnvironment(env);
		return this;
	}

	/**
	 * Convenient method for adding a context environment with type java.lang.String to
	 * the embedded Tomcat.
	 *
	 *

	 * embeddedTomcat.addContextEnvironment("aparam", "test");
	 *

	 *
	 * @param name name of the context environment
	 * @param value value of the context environment
	 * @return The embedded Tomcat
	 *
	 * @see ContextEnvironment
	 * @see NamingResources#addEnvironment(ContextEnvironment)
	 * @see EmbeddedTomcat#addContextEnvironment(ContextEnvironment)
	 * @see EmbeddedTomcat#addContextEnvironment(String, String, String)
	 */
	public EmbeddedTomcat addContextEnvironmentString(String name, String value) {
		addContextEnvironment(name, value, "java.lang.String");
		return this;
	}

	/**
	 * Read ContextEnvironment and ContextResource definition from a text file.
	 *
	 * @param contextFile Location of the context file
	 * @return The embedded Tomcat
	 * @deprecated use {@link #setContextFile(URL)}
	 */
	@Deprecated
	public EmbeddedTomcat addContextEnvironmentAndResourceFromFile(File contextFile) {
		try {
			return setContextFile(contextFile.toURI().toURL());
		}
		catch (MalformedURLException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Sets the location of the context file that configures this web application
	 *
	 * @param contextFileURL Location of the context file
	 * @return The embedded Tomcat instance
	 */
	public EmbeddedTomcat setContextFile(URL contextFileURL) {
		this.contextFileURL = contextFileURL;
		return this;
	}

	/**
	 * Sets the location of the context file that configures this web application
	 *
	 * @param contextFile Location of the context file
	 * @return The embedded Tomcat instance
	 */
	public EmbeddedTomcat setContextFile(String contextFile) {
		try {
			this.contextFileURL = new File(contextFile).toURI().toURL();
		}
		catch (MalformedURLException e) {
			throw new RuntimeException(e);
		}
		return this;
	}

	/**
	 * Read ContextEnvironment and ContextResource definition from a text file.
	 *
	 * @param contextFile Location to a context file
	 * @return The embedded Tomcat
	 * @deprecated use {@link #setContextFile(String)}
	 */
	@Deprecated
	public EmbeddedTomcat addContextEnvironmentAndResourceFromFile(String contextFile) {
		try {
			setContextFile(new File(contextFile).toURI().toURL());
		}
		catch (MalformedURLException e) {
			throw new RuntimeException(e);
		}
		return this;
	}

	/**
	 * Starts the embedded Tomcat and do not wait for incoming requests. Returns
	 * immediately if the configured port is in use.
	 *
	 * @see EmbeddedTomcat#startAndWait()
	 */
	public void start() {
		start(false);
	}

	/**
	 * Starts the embedded Tomcat and waits for incoming requests. Returns immediately if
	 * the configured port is in use.
	 *
	 * @see EmbeddedTomcat#start()
	 */
	public void startAndWait() {
		start(true);
	}

	private void start(boolean await) {

		// try to shutdown a previous Tomcat
		sendShutdownCommand();

		try {
			final ServerSocket srv = new ServerSocket(this.httpPort);
			srv.close();
		}
		catch (IOException e) {
			log.error("PORT " + this.httpPort + " ALREADY IN USE");
			return;
		}

		// Read a dummy value. This triggers loading of the catalina.properties
		// file
		CatalinaProperties.getProperty("dummy");

		appendSkipJars("tomcat.util.scan.DefaultJarScanner.jarsToSkip",
				this.skipJarsDefaultJarScanner);
		appendSkipJars("org.apache.catalina.startup.ContextConfig.jarsToSkip",
				this.skipJarsContextConfig);
		appendSkipJars("org.apache.catalina.startup.TldConfig.jarsToSkip",
				this.skipJarsTldConfig);

		this.tomcat = new Tomcat();

		if (this.tempDirectory == null) {
			this.tempDirectory = new File(".", "/target/tomcat." + this.httpPort)
					.getAbsolutePath();
		}

		this.tomcat.setBaseDir(this.tempDirectory);

		if (this.silent) {
			this.tomcat.setSilent(true);
		}

		if (this.addDefaultListeners) {
			this.tomcat.getServer().addLifecycleListener(new AprLifecycleListener());
		}

		if (this.useNio) {
			Connector connector = new Connector(
					"org.apache.coyote.http11.Http11NioProtocol");
			connector.setPort(this.httpPort);
			connector.setMaxPostSize(this.maxPostSize);
			connector.setURIEncoding("UTF-8");
			this.tomcat.setConnector(connector);
			this.tomcat.getService().addConnector(connector);
		}
		else {
			this.tomcat.setPort(this.httpPort);
			this.tomcat.getConnector().setURIEncoding("UTF-8");
			this.tomcat.getConnector().setMaxPostSize(this.maxPostSize);
		}

		if (this.compressionMinSize >= 0) {
			this.tomcat.getConnector().setProperty("compression",
					String.valueOf(this.compressionMinSize));
			this.tomcat.getConnector().setProperty("compressableMimeType",
					this.compressableMimeType);
		}

		if (this.httpsPort != 0) {
			final Connector httpsConnector;
			if (this.useNio) {
				httpsConnector = new Connector(
						"org.apache.coyote.http11.Http11NioProtocol");
			}
			else {
				httpsConnector = new Connector("HTTP/1.1");
			}
			httpsConnector.setSecure(true);
			httpsConnector.setPort(this.httpsPort);
			httpsConnector.setMaxPostSize(this.maxPostSize);
			httpsConnector.setScheme("https");
			httpsConnector.setURIEncoding("UTF-8");

			httpsConnector.setProperty("SSLEnabled", "true");
			httpsConnector.setProperty("keyAlias", this.keyAlias);
			httpsConnector.setProperty("keystoreFile", this.keyStoreFile);
			httpsConnector.setProperty("keystorePass", this.keyStorePass);
			httpsConnector.setProperty("sslProtocol", this.sslProtocol);

			if (this.compressionMinSize >= 0) {
				httpsConnector.setProperty("compression",
						String.valueOf(this.compressionMinSize));
				httpsConnector.setProperty("compressableMimeType",
						this.compressableMimeType);
			}

			this.tomcat.getEngine().setDefaultHost("localhost");
			this.tomcat.getService().addConnector(httpsConnector);
		}

		if (this.shutdownPort != null) {
			this.tomcat.getServer().setPort(this.shutdownPort);
		}

		String contextDir = this.contextDirectory;
		if (contextDir == null) {
			contextDir = new File(".").getAbsolutePath() + "/src/main/webapp";
		}

		final Context ctx;
		try {

			if (!this.contextPath.equals("")) {
				File rootCtxDir = new File("./target/tcroot");
				if (!rootCtxDir.exists()) {
					rootCtxDir.mkdirs();
				}
				Context rootCtx = this.tomcat.addWebapp("", rootCtxDir.getAbsolutePath());
				rootCtx.setPrivileged(true);
				Tomcat.addServlet(rootCtx, "listContexts",
						new ListContextsServlet(rootCtx)).addMapping("/");
			}

			ctx = this.tomcat.addWebapp(this.contextPath, contextDir);
			ctx.setResources(new TargetClassesContext());
		}
		catch (ServletException e) {
			throw new RuntimeException(e);
		}

		if (this.privileged) {
			ctx.setPrivileged(true);
		}

		if (this.enableNaming || !this.contextEnvironments.isEmpty()
				|| !this.contextResources.isEmpty() || this.contextFileURL != null) {
			this.tomcat.enableNaming();

			if (this.addDefaultListeners) {
				this.tomcat.getServer()
						.addLifecycleListener(new GlobalResourcesLifecycleListener());
			}
		}

		if (this.addDefaultListeners) {
			Server server = this.tomcat.getServer();
			server.addLifecycleListener(new JasperListener());
			server.addLifecycleListener(new JreMemoryLeakPreventionListener());
			server.addLifecycleListener(new ThreadLocalLeakPreventionListener());
		}

		for (ContextEnvironment env : this.contextEnvironments) {
			ctx.getNamingResources().addEnvironment(env);
		}

		for (ContextResource res : this.contextResources) {
			ctx.getNamingResources().addResource(res);
		}

		for (ApplicationParameter param : this.contextInitializationParameters) {
			ctx.addApplicationParameter(param);
		}

		if (this.contextFileURL != null) {
			ctx.setConfigFile(this.contextFileURL);
		}

		// Shutdown tomcat if a failure occurs during startup
		ctx.addLifecycleListener(new LifecycleListener() {
			@Override
			public void lifecycleEvent(LifecycleEvent event) {
				if (event.getLifecycle().getState() == LifecycleState.FAILED) {
					((StandardServer) EmbeddedTomcat.this.tomcat.getServer()).stopAwait();

				}
			}
		});

		try {
			this.tomcat.start();
		}
		catch (LifecycleException e) {
			throw new RuntimeException(e);
		}

		((StandardManager) ctx.getManager()).setPathname(null);

		installSlf4jBridge();

		if (await) {
			this.tomcat.getServer().await();
			stop();
		}

	}

	/**
	 * Stops the embedded tomcat. Does nothing if it's not started
	 */
	public void stop() {
		if (this.tomcat != null) {
			try {
				this.tomcat.stop();
			}
			catch (LifecycleException e) {
				throw new RuntimeException(e);
			}
		}
	}

	private static void appendSkipJars(String systemPropertyKey, String skipJars) {
		if (skipJars != null && !skipJars.trim().isEmpty()) {
			String oldValue = System.getProperty(systemPropertyKey);
			String newValue;
			if (oldValue != null && !oldValue.trim().isEmpty()) {
				newValue = oldValue + "," + skipJars;
			}
			else {
				newValue = skipJars;
			}
			System.setProperty(systemPropertyKey, newValue);
		}
	}

	private void sendShutdownCommand() {
		if (this.shutdownPort != null) {
			try {
				final Socket socket = new Socket("localhost", this.shutdownPort);
				final OutputStream stream = socket.getOutputStream();

				for (int i = 0; i < "SHUTDOWN".length(); i++) {
					stream.write("SHUTDOWN".charAt(i));
				}

				stream.flush();
				stream.close();
				socket.close();
			}
			catch (UnknownHostException e) {
				if (!this.silent) {
					log.debug(e);
				}
				return;
			}
			catch (IOException e) {
				if (!this.silent) {
					log.debug(e);
				}
				return;
			}

			// try to wait the specified amount of seconds until port becomes
			// available
			int count = 0;
			while (count < this.secondsToWaitBeforePortBecomesAvailable * 2) {
				try {
					final ServerSocket srv = new ServerSocket(this.httpPort);
					srv.close();
					return;
				}
				catch (IOException e) {
					count++;
				}
				try {
					TimeUnit.MILLISECONDS.sleep(500);
				}
				catch (InterruptedException e) {
					return;
				}
			}
		}
	}

	private static void installSlf4jBridge() {
		try {
			// Check if slf4j bridge is available
			final Class<?> clazz = Class.forName("org.slf4j.bridge.SLF4JBridgeHandler");

			// Remove all JUL handlers
			java.util.logging.LogManager.getLogManager().reset();

			// Install slf4j bridge handler
			final Method method = clazz.getMethod("install", new Class<?>[0]);
			method.invoke(null);
		}
		catch (ClassNotFoundException e) {
			// do nothing
		}
		catch (IllegalArgumentException e) {
			throw new RuntimeException(e);
		}
		catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		}
		catch (InvocationTargetException e) {
			throw new RuntimeException(e);
		}
		catch (SecurityException e) {
			throw new RuntimeException(e);
		}
		catch (NoSuchMethodException e) {
			throw new RuntimeException(e);
		}
	}

}


package org.plumelib.bcelutil;

import java.util.ArrayList;
import java.util.List;
import org.checkerframework.checker.formatter.qual.FormatMethod;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
 * A logging class with the following features:
 *
 *

 *
Can be enabled and disabled (when disabled, all operations are no-ops),
 *
Can indent/exdent log output,
 *
Writes to standard output, and
 *
Can provide a stack trace.
 *

 */
public final class SimpleLog {

  /** If false, do no output. */
  public boolean enabled;

  /** The current indentation level. */
  private int indentLevel = 0;
  /** Indentation string for one level of indentation. */
  private final String INDENT_STR_ONE_LEVEL = "  ";
  /**
   * Cache for the current indentation string, or null if needs to be recomputed. Never access this
   * directly; always call {@link #getIndentString}.
   */
  private @Nullable String indentString = null;
  /** Cache of indentation strings that have been computed so far. */
  private List<String> indentStrings;

  /** Create a new SimpleLog object with logging enabled. */
  public SimpleLog() {
    this(true);
  }

  /**
   * Create a new SimpleLog object.
   *
   * @param enabled whether the logger starts out enabled
   */
  public SimpleLog(boolean enabled) {
    this.enabled = enabled;
    indentStrings = new ArrayList<String>();
    indentStrings.add("");
  }

  /**
   * Return whether logging is enabled.
   *
   * @return whether logging is enabled
   */
  public boolean enabled() {
    return enabled;
  }

  /**
   * Log a message to System.out. The message is prepended with the current indentation string. The
   * indentation is only applied at the start of the message, not for every line break within the
   * message.
   *
   * @param format format string for message
   * @param args values to be substituted into format
   */
  @FormatMethod
  public void log(String format, @Nullable Object... args) {
    if (enabled) {
      System.out.print(getIndentString());
      System.out.printf(format, args);
    }
  }

  /** Print a stack trace to System.out. */
  public void logStackTrace() {
    if (enabled) {
      Throwable t = new Throwable();
      t.fillInStackTrace();
      StackTraceElement[] ste_arr = t.getStackTrace();
      for (int ii = 2; ii < ste_arr.length; ii++) {
        StackTraceElement ste = ste_arr[ii];
        System.out.printf("%s  %s%n", getIndentString(), ste);
      }
    }
  }

  /** Return the current indentation string. */
  private String getIndentString() {
    assert enabled;
    if (indentString == null) {
      for (int i = indentStrings.size(); i <= indentLevel; i++) {
        indentStrings.add(indentStrings.get(i - 1) + INDENT_STR_ONE_LEVEL);
      }
      indentString = indentStrings.get(indentLevel);
    }
    return indentString;
  }

  /** Increases indentation by one level. */
  public void indent() {
    if (enabled) {
      indentLevel++;
      indentString = null;
    }
  }

  /** Decreases indentation by one level. */
  public void exdent() {
    if (enabled) {
      if (indentLevel == 0) {
        log("Called exdent when indentation level was 0.");
        logStackTrace();
      } else {
        indentLevel--;
        indentString = null;
      }
    }
  }

  /** Resets indentation to none. Has no effect if logging is disabled. */
  public void resetIndent() {
    if (enabled) {
      indentLevel = 0;
      indentString = "";
    }
  }
}


/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */
package org.plumelib.bcelutil;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Vector;
import org.apache.bcel.Const;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.JsrInstruction;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.RET;
import org.apache.bcel.generic.ReturnInstruction;
import org.apache.bcel.generic.ReturnaddressType;
import org.apache.bcel.generic.Type;
import org.apache.bcel.verifier.VerificationResult;
import org.apache.bcel.verifier.exc.AssertionViolatedException;
import org.apache.bcel.verifier.exc.StructuralCodeConstraintException;
import org.apache.bcel.verifier.exc.VerifierConstraintViolatedException;
import org.apache.bcel.verifier.structurals.ControlFlowGraph;
import org.apache.bcel.verifier.structurals.ExceptionHandler;
import org.apache.bcel.verifier.structurals.ExecutionVisitor;
import org.apache.bcel.verifier.structurals.Frame;
import org.apache.bcel.verifier.structurals.InstConstraintVisitor;
import org.apache.bcel.verifier.structurals.InstructionContext;
import org.apache.bcel.verifier.structurals.LocalVariables;
import org.apache.bcel.verifier.structurals.OperandStack;
import org.apache.bcel.verifier.structurals.UninitializedObjectType;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull;

/**
 * This is a slightly modified version of Pass3bVerifier from BCEL. It uses NoConstaintsVisitor as
 * InstConstraintVisitor appears to be quite out of date and incorrectly fails on many valid class
 * files. Hence, StackVer assumes the method is valid and is only interested in the result of the
 * symbolic execution in order to capture the state of the local variables and stack at the start of
 * each byte code instruction.
 *
 *
To see the diffs,
 * wget https://raw.githubusercontent.com/apache/commons-bcel/trunk/src/main/java/org/apache/bcel/verifier/structurals/Pass3bVerifier.java
 *  then run google-java-format on the downloaded file.
 *
 *
The original documentation follows.
 *
 *
This PassVerifier verifies a method of class file according to pass 3, so-called structural
 * verification as described in The Java Virtual Machine Specification, 2nd edition. More detailed
 * information is to be found at the do_verify() method's documentation.
 *
 * @version $Id$
 * @see #do_stack_ver
 */
@SuppressWarnings({"rawtypes", "nullness", "interning"}) // third-party code
public final class StackVer {
  /* TODO:    Throughout pass 3b, upper halves of LONG and DOUBLE
  are represented by Type.UNKNOWN. This should be changed
  in favour of LONG_Upper and DOUBLE_Upper as in pass 2. */

  /**
   * An InstructionContextQueue is a utility class that holds (InstructionContext, ArrayList) pairs
   * in a Queue data structure. This is used to hold information about InstructionContext objects
   * externally --- i.e. that information is not saved inside the InstructionContext object itself.
   * This is useful to save the execution path of the symbolic execution of the Pass3bVerifier -
   * this is not information that belongs into the InstructionContext object itself. Only at
   * "execute()"ing time, an InstructionContext object will get the current information we have
   * about its symbolic execution predecessors.
   */
  private static final class InstructionContextQueue {
    // The following two fields together represent the queue.
    /** The first elements from pairs in the queue. */
    private final List<InstructionContext> ics = new Vector<>();
    /** The second elements from pairs in the queue. */
    private final List<ArrayList<InstructionContext>> ecs = new Vector<>();
    /**
     * Add an (InstructionContext, ExecutionChain) pair to their respective queues.
     *
     * @param ic the InstructionContext
     * @param executionChain the ExecutionChain
     */
    public void add(
        final InstructionContext ic, final ArrayList<InstructionContext> executionChain) {
      ics.add(ic);
      ecs.add(executionChain);
    }
    /**
     * Test if InstructionContext queue is empty.
     *
     * @return true if the InstructionContext queue is empty.
     */
    public boolean isEmpty() {
      return ics.isEmpty();
    }

    /**
     * Remove a specific (InstructionContext, ExecutionChain) pair from their respective queues.
     *
     * @param i the index of the items to be removed
     */
    public void remove(final @NonNegative int i) {
      ics.remove(i);
      ecs.remove(i);
    }

    /**
     * Fetch a specific InstructionContext from the queue.
     *
     * @param i the index of the item to be fetched
     * @return the indicated InstructionContext
     */
    public InstructionContext getIC(final @NonNegative int i) {
      return ics.get(i);
    }

    /**
     * Fetch a specific ExecutionChain from the queue.
     *
     * @param i the index of the item to be fetched
     * @return the indicated ExecutionChain
     */
    public ArrayList<InstructionContext> getEC(final @NonNegative int i) {
      return ecs.get(i);
    }

    /**
     * Get the size of the InstructionContext queue.
     *
     * @return the size of the InstructionQueue
     */
    public int size() {
      return ics.size();
    }
  } // end Inner Class InstructionContextQueue

  /** In DEBUG mode, the verification algorithm is not randomized. */
  private static final boolean DEBUG = true;

  /** The Verifier that created this. */
  // private final Verifier myOwner;

  /** The types on the stack for each instruction by byte code offset. */
  // Set by do_stack_ver().
  private @MonotonicNonNull StackTypes stack_types;

  /**
   * This class should only be instantiated by a Verifier.
   *
   * @see org.apache.bcel.verifier.Verifier
   */
  public StackVer() {}

  /**
   * Return the types on the stack at each byte code offset. Only valid after do_stack_ver() is
   * called.
   *
   * @return the StackTypes object for the method being verified
   */
  public StackTypes get_stack_types() {
    return stack_types;
  }

  /**
   * Whenever the outgoing frame situation of an InstructionContext changes, all its successors are
   * put [back] into the queue [as if they were unvisited]. The proof of termination is about the
   * existence of a fix point of frame merging.
   */
  private void circulationPump(
      final MethodGen m,
      final ControlFlowGraph cfg,
      final InstructionContext start,
      final Frame vanillaFrame,
      final InstConstraintVisitor icv,
      final ExecutionVisitor ev) {
    final Random random = new Random();
    final InstructionContextQueue icq = new InstructionContextQueue();

    execute(start, vanillaFrame, new ArrayList<InstructionContext>(), icv, ev);
    // new ArrayList() <=>    no Instruction was executed before
    //                                    => Top-Level routine (no jsr call before)
    icq.add(start, new ArrayList<InstructionContext>());

    // LOOP!
    while (!icq.isEmpty()) {
      InstructionContext u;
      ArrayList<InstructionContext> ec;
      if (!DEBUG) {
        final int r = random.nextInt(icq.size());
        u = icq.getIC(r);
        ec = icq.getEC(r);
        icq.remove(r);
      } else {
        u = icq.getIC(0);
        ec = icq.getEC(0);
        icq.remove(0);
      }

      @SuppressWarnings("unchecked") // ec is of type ArrayList<InstructionContext>
      final ArrayList<InstructionContext> oldchain = (ArrayList<InstructionContext>) (ec.clone());
      @SuppressWarnings("unchecked") // ec is of type ArrayList<InstructionContext>
      final ArrayList<InstructionContext> newchain = (ArrayList<InstructionContext>) (ec.clone());
      newchain.add(u);

      if ((u.getInstruction().getInstruction()) instanceof RET) {
        // System.err.println(u);
        // We can only follow _one_ successor, the one after the
        // JSR that was recently executed.
        final RET ret = (RET) (u.getInstruction().getInstruction());
        final ReturnaddressType t =
            (ReturnaddressType) u.getOutFrame(oldchain).getLocals().get(ret.getIndex());
        final InstructionContext theSuccessor = cfg.contextOf(t.getTarget());

        // Sanity check
        InstructionContext lastJSR = null;
        int skip_jsr = 0;
        for (int ss = oldchain.size() - 1; ss >= 0; ss--) {
          if (skip_jsr < 0) {
            throw new AssertionViolatedException("More RET than JSR in execution chain?!");
          }
          // System.err.println("+"+oldchain.get(ss));
          if ((oldchain.get(ss)).getInstruction().getInstruction() instanceof JsrInstruction) {
            if (skip_jsr == 0) {
              lastJSR = oldchain.get(ss);
              break;
            }
            skip_jsr--;
          }
          if ((oldchain.get(ss)).getInstruction().getInstruction() instanceof RET) {
            skip_jsr++;
          }
        }
        if (lastJSR == null) {
          throw new AssertionViolatedException(
              "RET without a JSR before in ExecutionChain?! EC: '" + oldchain + "'.");
        }
        final JsrInstruction jsr = (JsrInstruction) (lastJSR.getInstruction().getInstruction());
        if (theSuccessor != (cfg.contextOf(jsr.physicalSuccessor()))) {
          throw new AssertionViolatedException(
              "RET '"
                  + u.getInstruction()
                  + "' info inconsistent: jump back to '"
                  + theSuccessor
                  + "' or '"
                  + cfg.contextOf(jsr.physicalSuccessor())
                  + "'?");
        }

        if (execute(theSuccessor, u.getOutFrame(oldchain), newchain, icv, ev)) {
          @SuppressWarnings(
              "unchecked") // newchain is already of type ArrayList<InstructionContext>
          final ArrayList<InstructionContext> newchainClone =
              (ArrayList<InstructionContext>) newchain.clone();
          icq.add(theSuccessor, newchainClone);
        }
      } else { // "not a ret"

        // Normal successors. Add them to the queue of successors.
        final InstructionContext[] succs = u.getSuccessors();
        for (final InstructionContext v : succs) {
          if (execute(v, u.getOutFrame(oldchain), newchain, icv, ev)) {
            @SuppressWarnings(
                "unchecked") // newchain is already of type ArrayList<InstructionContext>
            final ArrayList<InstructionContext> newchainClone =
                (ArrayList<InstructionContext>) newchain.clone();
            icq.add(v, newchainClone);
          }
        }
      } // end "not a ret"

      // Exception Handlers. Add them to the queue of successors.
      // [subroutines are never protected; mandated by JustIce]
      final ExceptionHandler[] exc_hds = u.getExceptionHandlers();
      for (final ExceptionHandler exc_hd : exc_hds) {
        final InstructionContext v = cfg.contextOf(exc_hd.getHandlerStart());
        // TODO: the "oldchain" and "newchain" is used to determine the subroutine
        // we're in (by searching for the last JSR) by the InstructionContext
        // implementation. Therefore, we should not use this chain mechanism
        // when dealing with exception handlers.
        // Example: a JSR with an exception handler as its successor does not
        // mean we're in a subroutine if we go to the exception handler.
        // We should address this problem later; by now we simply "cut" the chain
        // by using an empty chain for the exception handlers.
        // if (execute(v, new Frame(u.getOutFrame(oldchain).getLocals(),
        // new OperandStack (u.getOutFrame().getStack().maxStack(),
        // (exc_hds[s].getExceptionType()==null? Type.THROWABLE : exc_hds[s].getExceptionType())) ),
        // newchain), icv, ev) {
        // icq.add(v, (ArrayList) newchain.clone());
        if (execute(
            v,
            new Frame(
                u.getOutFrame(oldchain).getLocals(),
                new OperandStack(
                    u.getOutFrame(oldchain).getStack().maxStack(),
                    exc_hd.getExceptionType() == null
                        ? Type.THROWABLE
                        : exc_hd.getExceptionType())),
            new ArrayList<InstructionContext>(),
            icv,
            ev)) {
          icq.add(v, new ArrayList<InstructionContext>());
        }
      }
    } // while (!icq.isEmpty()) END

    InstructionHandle ih = start.getInstruction();
    do {
      if ((ih.getInstruction() instanceof ReturnInstruction) && (!(cfg.isDead(ih)))) {
        final InstructionContext ic = cfg.contextOf(ih);
        // TODO: This is buggy, we check only the top-level return instructions this way.
        // Maybe some maniac returns from a method when in a subroutine?
        final Frame f = ic.getOutFrame(new ArrayList<InstructionContext>());
        final LocalVariables lvs = f.getLocals();
        for (int i = 0; i < lvs.maxLocals(); i++) {
          if (lvs.get(i) instanceof UninitializedObjectType) {
            this.addMessage(
                "Warning: ReturnInstruction '"
                    + ic
                    + "' may leave method with an uninitialized object in the local variables array '"
                    + lvs
                    + "'.");
          }
        }
        final OperandStack os = f.getStack();
        for (int i = 0; i < os.size(); i++) {
          if (os.peek(i) instanceof UninitializedObjectType) {
            this.addMessage(
                "Warning: ReturnInstruction '"
                    + ic
                    + "' may leave method with an uninitialized object on the operand stack '"
                    + os
                    + "'.");
          }
        }
        /* This code from Pass3bVerifier incorrectly fails on some valid class files.
        // see JVM $4.8.2
        Type returnedType = null;
        final OperandStack inStack = ic.getInFrame().getStack();
        if (inStack.size() >= 1) {
          returnedType = inStack.peek();
        } else {
          returnedType = Type.VOID;
        }

        if (returnedType != null) {
          if (returnedType instanceof ReferenceType) {
            try {
              if (!((ReferenceType) returnedType).isCastableTo(m.getReturnType())) {
                invalidReturnTypeError(returnedType, m);
              }
            } catch (final ClassNotFoundException e) {
              // Don't know what do do now, so raise RuntimeException
              throw new RuntimeException(e);
            }
          } else if (!returnedType.equals(m.getReturnType().normalizeForStackOrLocal())) {
            invalidReturnTypeError(returnedType, m);
          }
        }
        */
      }
    } while ((ih = ih.getNext()) != null);
  }

  /**
   * Throws an exception indicating the returned type is not compatible with the return type of the
   * given method.
   *
   * @param returnedType the type of the returned expression
   * @param m the method we are processing
   * @throws StructuralCodeConstraintException always
   * @since 6.0
   */
  public void invalidReturnTypeError(final Type returnedType, final MethodGen m) {
    throw new StructuralCodeConstraintException(
        "Returned type "
            + returnedType
            + " does not match Method's return type "
            + m.getReturnType());
  }

  /**
   * Pass 3b implements the data flow analysis as described in the Java Virtual Machine
   * Specification, Second Edition. Later versions will use LocalVariablesInfo objects to verify if
   * the verifier-inferred types and the class file's debug information (LocalVariables attributes)
   * match [TODO].
   *
   * @param mg MethodGen for the method to be verified
   * @return the VerificationResult
   * @see org.apache.bcel.verifier.statics.LocalVariablesInfo
   * @see org.apache.bcel.verifier.statics.Pass2Verifier#getLocalVariablesInfo(int)
   */
  public VerificationResult do_stack_ver(MethodGen mg) {
    /* This code is not needed for StackVer.
    if (!myOwner.doPass3a(method_no).equals(VerificationResult.VR_OK)) {
      return VerificationResult.VR_NOTYET;
    }

    // Pass 3a ran before, so it's safe to assume the JavaClass object is
    // in the BCEL repository.
    JavaClass jc;
    try {
      jc = Repository.lookupClass(myOwner.getClassName());
    } catch (final ClassNotFoundException e) {
      // FIXME: maybe not the best way to handle this
      throw new AssertionViolatedException("Missing class: " + e, e);
    }
    */

    final ConstantPoolGen constantPoolGen = mg.getConstantPool();
    // Init Visitors
    final InstConstraintVisitor icv = new NoConstraintsVisitor();
    icv.setConstantPoolGen(constantPoolGen);

    final ExecutionVisitor ev = new ExecutionVisitor();
    ev.setConstantPoolGen(constantPoolGen);

    try {
      stack_types = new StackTypes(mg);

      icv.setMethodGen(mg);

      ////////////// DFA BEGINS HERE ////////////////
      if (!(mg.isAbstract() || mg.isNative())) { // IF mg HAS CODE (See pass 2)

        // false says don't check if jsr subroutine is covered by exception handler
        final ControlFlowGraph cfg = new ControlFlowGraph(mg, false);

        // Build the initial frame situation for this method.
        final Frame f = new Frame(mg.getMaxLocals(), mg.getMaxStack());
        if (!mg.isStatic()) {
          if (mg.getName().equals(Const.CONSTRUCTOR_NAME)) {
            Frame.setThis(new UninitializedObjectType(new ObjectType(mg.getClassName())));
            f.getLocals().set(0, Frame.getThis());
          } else {
            @SuppressWarnings("nullness") // unannotated: org.apache.bcel.verifier.structurals.Frame
            @NonNull UninitializedObjectType dummy = null;
            Frame.setThis(dummy);
            f.getLocals().set(0, new ObjectType(mg.getClassName()));
          }
        }
        final Type[] argtypes = mg.getArgumentTypes();
        int twoslotoffset = 0;
        for (int j = 0; j < argtypes.length; j++) {
          if (argtypes[j] == Type.SHORT
              || argtypes[j] == Type.BYTE
              || argtypes[j] == Type.CHAR
              || argtypes[j] == Type.BOOLEAN) {
            argtypes[j] = Type.INT;
          }
          f.getLocals().set(twoslotoffset + j + (mg.isStatic() ? 0 : 1), argtypes[j]);
          if (argtypes[j].getSize() == 2) {
            twoslotoffset++;
            f.getLocals().set(twoslotoffset + j + (mg.isStatic() ? 0 : 1), Type.UNKNOWN);
          }
        }
        circulationPump(mg, cfg, cfg.contextOf(mg.getInstructionList().getStart()), f, icv, ev);
      }
    } catch (final VerifierConstraintViolatedException ce) {
      ce.extendMessage("Constraint violated in method '" + mg + "':\n", "");
      return new VerificationResult(VerificationResult.VERIFIED_REJECTED, ce.getMessage());
    } catch (final RuntimeException re) {
      // These are internal errors

      final StringWriter sw = new StringWriter();
      final PrintWriter pw = new PrintWriter(sw);
      re.printStackTrace(pw);

      throw new AssertionViolatedException(
          "Some RuntimeException occured while verify()ing class '"
              + mg.getClassName()
              + "', method '"
              + mg
              + "'. Original RuntimeException's stack trace:\n---\n"
              + sw
              + "---\n",
          re);
    }
    return VerificationResult.VR_OK;
  }

  /** Like InstructionContext.execute, but also sets stack_types. */
  boolean execute(
      InstructionContext ic,
      Frame inFrame,
      ArrayList<InstructionContext> executionPredecessors,
      InstConstraintVisitor icv,
      ExecutionVisitor ev) {
    stack_types.set(ic.getInstruction().getPosition(), inFrame);
    return ic.execute(inFrame, executionPredecessors, icv, ev);
  }

  // Code from PassVerifier in BCEL so that we don't have to extend it

  /** The (warning) messages. */
  private ArrayList<String> messages = new ArrayList<>();

  /**
   * This method adds a (warning) message to the message pool of this PassVerifier.
   *
   * @param message the String containing the warning message
   */
  public void addMessage(String message) {
    messages.add(message);
  }
}