/*
 * Copyright (c) 2010-2013 the original author or authors
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */
package org.jmxtrans.agent;

import org.jmxtrans.agent.util.StringUtils2;
import org.jmxtrans.agent.util.logging.Logger;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.management.ObjectName;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;

/**
 * Build a {@linkplain org.jmxtrans.agent.QueryResult#name} from a collected metric ({@linkplain org.jmxtrans.agent.Query}).
 *
 * Build name must be escaped to be compatible with all {@linkplain org.jmxtrans.agent.OutputWriter}.
 * The approach is to escape non alpha-numeric chars.
 *
 * Expressions support '#' based keywords (e.g. #hostname#) and with '%' based variables mapped to objectname properties.
 *
 * Supported '#' based 'functions':
 *

 *

 * Function
 * Description
 * Sample
 *

 *

 * #hostname#
 * localhost - hostname {@link java.net.InetAddress#getHostName()}
 *
 *

 *

 * #reversed_hostname#
 * reversed localhost - hostname {@link java.net.InetAddress#getHostName()}
 *
 *

 *

 * #escaped_hostname#
 * localhost - hostname {@link java.net.InetAddress#getHostName()} with '.' replaced by '_'
 *
 *

 *

 * #canonical_hostname#
 * localhost - canonical hostname {@link java.net.InetAddress#getCanonicalHostName()}
 * server1.ecommerce.mycompany.com
 *

 *

 * #reversed_canonical_hostname#
 * reversed localhost - canonical hostname {@link java.net.InetAddress#getCanonicalHostName()}
 * com.mycompany.ecommerce.server1
 *

 *

 * #escaped_canonical_hostname#
 * localhost - canonical hostname {@link java.net.InetAddress#getCanonicalHostName()} with '.' replaced by '_'
 * server1_ecommerce_mycompany_com
 *

 *

 * #hostaddress#
 * localhost - hostaddress {@link java.net.InetAddress#getHostAddress()}
 *
 *

 *

 * #escaped_hostname#
 * localhost - hostaddress {@link java.net.InetAddress#getHostAddress()} with '.' replaced by '_'
 *
 *

 *

 *
 * @author Cyrille Le Clerc
 */
public class ResultNameStrategyImpl implements ResultNameStrategy {

    protected final Logger logger = Logger.getLogger(getClass().getName());

    private ExpressionLanguageEngine expressionLanguageEngine;

    /**
     * Create with default expression language engine.
     */
    public ResultNameStrategyImpl() {
        expressionLanguageEngine = new ExpressionLanguageEngineImpl();
	}

    public ResultNameStrategyImpl(ExpressionLanguageEngine expressionLanguageEngine) {
        this.expressionLanguageEngine = expressionLanguageEngine;
	}

    @Nonnull
    @Override
    public String getResultName(@Nonnull Query query, @Nonnull ObjectName objectName, @Nonnull String attribute, @Nullable String compositeDataKey, @Nullable Integer position) {
        String result;
        if (query.getResultAlias() == null) {
            result = escapeObjectName(objectName);
            result += "." + attribute;
            if(compositeDataKey != null) {
                result+= "." + compositeDataKey;
            }
            if(position != null) {
                result += "_" + position;
            }
        } else {
            result = expressionLanguageEngine.resolveExpression(query.getResultAlias(), objectName, attribute, compositeDataKey, position);
        }
        return result;
    }

    /**
     * Transforms an {@linkplain javax.management.ObjectName} into a plain {@linkplain String} only composed of ('a' to 'Z', 'A' to 'Z', '_').
     *
     * '_' is the escape char for not compliant chars.
     */
    protected String escapeObjectName(@Nonnull ObjectName objectName) {
        StringBuilder result = new StringBuilder();
        StringUtils2.appendEscapedNonAlphaNumericChars(objectName.getDomain(), result);
        result.append('.');
        List<String> keys = Collections.list(objectName.getKeyPropertyList().keys());
        Collections.sort(keys);
        for (Iterator<String> it = keys.iterator(); it.hasNext(); ) {
            String key = it.next();
            StringUtils2.appendEscapedNonAlphaNumericChars(key, result);
            result.append("__");
            StringUtils2.appendEscapedNonAlphaNumericChars(objectName.getKeyProperty(key), result);
            if (it.hasNext()) {
                result.append('.');
            }
        }
        if (logger.isLoggable(Level.FINEST))
            logger.log(Level.FINEST, "escapeObjectName(" + objectName + "): " + result);
        return result.toString();
    }

    public ExpressionLanguageEngine getExpressionLanguageEngine() {
        return expressionLanguageEngine;
    }

    public void setExpressionLanguageEngine(ExpressionLanguageEngine expressionLanguageEngine) {
        this.expressionLanguageEngine = expressionLanguageEngine;
    }

    public void postConstruct(@Nonnull Map<String, String> settings) {

    }
}


/*******************************************************************************
 * Copyright (c) 2013, 2015 EclipseSource.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 ******************************************************************************/
package org.jmxtrans.agent.util.json;

import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

/**
 * Represents a JSON array, an ordered collection of JSON values.
 *

 * Elements can be added using the add(...) methods which accept instances of
 * {@link org.jmxtrans.agent.util.json.JsonValue}, strings, primitive numbers, and boolean values. To replace an element of an
 * array, use the set(int, ...) methods.
 *

 *

 * Elements can be accessed by their index using {@link #get(int)}. This class also supports
 * iterating over the elements in document order using an {@link #iterator()} or an enhanced for
 * loop:
 *

 *

 * for (JsonValue value : jsonArray) {
 *   ...
 * }
 *

 *

 * An equivalent {@link List} can be obtained from the method {@link #values()}.
 *

 *

 * Note that this class is not thread-safe. If multiple threads access a
 * JsonArray instance concurrently, while at least one of these threads modifies the
 * contents of this array, access to the instance must be synchronized externally. Failure to do so
 * may lead to an inconsistent state.
 *

 *

 * This class is not supposed to be extended by clients.
 *

 */
@SuppressWarnings("serial") // use default serial UID
public class JsonArray extends org.jmxtrans.agent.util.json.JsonValue implements Iterable<org.jmxtrans.agent.util.json.JsonValue> {

  private final List<org.jmxtrans.agent.util.json.JsonValue> values;

  /**
   * Creates a new empty JsonArray.
   */
  public JsonArray() {
    values = new ArrayList<org.jmxtrans.agent.util.json.JsonValue>();
  }

  /**
   * Creates a new JsonArray with the contents of the specified JSON array.
   *
   * @param array
   *          the JsonArray to get the initial contents from, must not be null
   */
  public JsonArray(JsonArray array) {
    this(array, false);
  }

  private JsonArray(JsonArray array, boolean unmodifiable) {
    if (array == null) {
      throw new NullPointerException("array is null");
    }
    if (unmodifiable) {
      values = Collections.unmodifiableList(array.values);
    } else {
      values = new ArrayList<org.jmxtrans.agent.util.json.JsonValue>(array.values);
    }
  }

  /**
   * Reads a JSON array from the given reader.
   *

   * Characters are read in chunks and buffered internally, therefore wrapping an existing reader in
   * an additional BufferedReader does not improve reading
   * performance.
   *

   *
   * @param reader
   *          the reader to read the JSON array from
   * @return the JSON array that has been read
   * @throws IOException
   *           if an I/O error occurs in the reader
   * @throws ParseException
   *           if the input is not valid JSON
   * @throws UnsupportedOperationException
   *           if the input does not contain a JSON array
   * @deprecated Use {@link Json#parse(Reader)}{@link org.jmxtrans.agent.util.json.JsonValue#asArray() .asArray()} instead
   */
  @Deprecated
  public static JsonArray readFrom(Reader reader) throws IOException {
    return org.jmxtrans.agent.util.json.JsonValue.readFrom(reader).asArray();
  }

  /**
   * Reads a JSON array from the given string.
   *
   * @param string
   *          the string that contains the JSON array
   * @return the JSON array that has been read
   * @throws ParseException
   *           if the input is not valid JSON
   * @throws UnsupportedOperationException
   *           if the input does not contain a JSON array
   * @deprecated Use {@link Json#parse(String)}{@link org.jmxtrans.agent.util.json.JsonValue#asArray() .asArray()} instead
   */
  @Deprecated
  public static JsonArray readFrom(String string) {
    return org.jmxtrans.agent.util.json.JsonValue.readFrom(string).asArray();
  }

  /**
   * Returns an unmodifiable wrapper for the specified JsonArray. This method allows to provide
   * read-only access to a JsonArray.
   *

   * The returned JsonArray is backed by the given array and reflects subsequent changes. Attempts
   * to modify the returned JsonArray result in an UnsupportedOperationException.
   *

   *
   * @param array
   *          the JsonArray for which an unmodifiable JsonArray is to be returned
   * @return an unmodifiable view of the specified JsonArray
   */
  public static JsonArray unmodifiableArray(JsonArray array) {
    return new JsonArray(array, true);
  }

  /**
   * Appends the JSON representation of the specified int value to the end of this
   * array.
   *
   * @param value
   *          the value to add to the array
   * @return the array itself, to enable method chaining
   */
  public JsonArray add(int value) {
    values.add(Json.value(value));
    return this;
  }

  /**
   * Appends the JSON representation of the specified long value to the end of this
   * array.
   *
   * @param value
   *          the value to add to the array
   * @return the array itself, to enable method chaining
   */
  public JsonArray add(long value) {
    values.add(Json.value(value));
    return this;
  }

  /**
   * Appends the JSON representation of the specified float value to the end of this
   * array.
   *
   * @param value
   *          the value to add to the array
   * @return the array itself, to enable method chaining
   */
  public JsonArray add(float value) {
    values.add(Json.value(value));
    return this;
  }

  /**
   * Appends the JSON representation of the specified double value to the end of this
   * array.
   *
   * @param value
   *          the value to add to the array
   * @return the array itself, to enable method chaining
   */
  public JsonArray add(double value) {
    values.add(Json.value(value));
    return this;
  }

  /**
   * Appends the JSON representation of the specified boolean value to the end of this
   * array.
   *
   * @param value
   *          the value to add to the array
   * @return the array itself, to enable method chaining
   */
  public JsonArray add(boolean value) {
    values.add(Json.value(value));
    return this;
  }

  /**
   * Appends the JSON representation of the specified string to the end of this array.
   *
   * @param value
   *          the string to add to the array
   * @return the array itself, to enable method chaining
   */
  public JsonArray add(String value) {
    values.add(Json.value(value));
    return this;
  }

  /**
   * Appends the specified JSON value to the end of this array.
   *
   * @param value
   *          the JsonValue to add to the array, must not be null
   * @return the array itself, to enable method chaining
   */
  public JsonArray add(org.jmxtrans.agent.util.json.JsonValue value) {
    if (value == null) {
      throw new NullPointerException("value is null");
    }
    values.add(value);
    return this;
  }

  /**
   * Replaces the element at the specified position in this array with the JSON representation of
   * the specified int value.
   *
   * @param index
   *          the index of the array element to replace
   * @param value
   *          the value to be stored at the specified array position
   * @return the array itself, to enable method chaining
   * @throws IndexOutOfBoundsException
   *           if the index is out of range, i.e. index < 0 or
   *           index >= size
   */
  public JsonArray set(int index, int value) {
    values.set(index, Json.value(value));
    return this;
  }

  /**
   * Replaces the element at the specified position in this array with the JSON representation of
   * the specified long value.
   *
   * @param index
   *          the index of the array element to replace
   * @param value
   *          the value to be stored at the specified array position
   * @return the array itself, to enable method chaining
   * @throws IndexOutOfBoundsException
   *           if the index is out of range, i.e. index < 0 or
   *           index >= size
   */
  public JsonArray set(int index, long value) {
    values.set(index, Json.value(value));
    return this;
  }

  /**
   * Replaces the element at the specified position in this array with the JSON representation of
   * the specified float value.
   *
   * @param index
   *          the index of the array element to replace
   * @param value
   *          the value to be stored at the specified array position
   * @return the array itself, to enable method chaining
   * @throws IndexOutOfBoundsException
   *           if the index is out of range, i.e. index < 0 or
   *           index >= size
   */
  public JsonArray set(int index, float value) {
    values.set(index, Json.value(value));
    return this;
  }

  /**
   * Replaces the element at the specified position in this array with the JSON representation of
   * the specified double value.
   *
   * @param index
   *          the index of the array element to replace
   * @param value
   *          the value to be stored at the specified array position
   * @return the array itself, to enable method chaining
   * @throws IndexOutOfBoundsException
   *           if the index is out of range, i.e. index < 0 or
   *           index >= size
   */
  public JsonArray set(int index, double value) {
    values.set(index, Json.value(value));
    return this;
  }

  /**
   * Replaces the element at the specified position in this array with the JSON representation of
   * the specified boolean value.
   *
   * @param index
   *          the index of the array element to replace
   * @param value
   *          the value to be stored at the specified array position
   * @return the array itself, to enable method chaining
   * @throws IndexOutOfBoundsException
   *           if the index is out of range, i.e. index < 0 or
   *           index >= size
   */
  public JsonArray set(int index, boolean value) {
    values.set(index, Json.value(value));
    return this;
  }

  /**
   * Replaces the element at the specified position in this array with the JSON representation of
   * the specified string.
   *
   * @param index
   *          the index of the array element to replace
   * @param value
   *          the string to be stored at the specified array position
   * @return the array itself, to enable method chaining
   * @throws IndexOutOfBoundsException
   *           if the index is out of range, i.e. index < 0 or
   *           index >= size
   */
  public JsonArray set(int index, String value) {
    values.set(index, Json.value(value));
    return this;
  }

  /**
   * Replaces the element at the specified position in this array with the specified JSON value.
   *
   * @param index
   *          the index of the array element to replace
   * @param value
   *          the value to be stored at the specified array position, must not be null
   * @return the array itself, to enable method chaining
   * @throws IndexOutOfBoundsException
   *           if the index is out of range, i.e. index < 0 or
   *           index >= size
   */
  public JsonArray set(int index, org.jmxtrans.agent.util.json.JsonValue value) {
    if (value == null) {
      throw new NullPointerException("value is null");
    }
    values.set(index, value);
    return this;
  }

  /**
   * Removes the element at the specified index from this array.
   *
   * @param index
   *          the index of the element to remove
   * @return the array itself, to enable method chaining
   * @throws IndexOutOfBoundsException
   *           if the index is out of range, i.e. index < 0 or
   *           index >= size
   */
  public JsonArray remove(int index) {
    values.remove(index);
    return this;
  }

  /**
   * Returns the number of elements in this array.
   *
   * @return the number of elements in this array
   */
  public int size() {
    return values.size();
  }

  /**
   * Returns true if this array contains no elements.
   *
   * @return true if this array contains no elements
   */
  public boolean isEmpty() {
    return values.isEmpty();
  }

  /**
   * Returns the value of the element at the specified position in this array.
   *
   * @param index
   *          the index of the array element to return
   * @return the value of the element at the specified position
   * @throws IndexOutOfBoundsException
   *           if the index is out of range, i.e. index < 0 or
   *           index >= size
   */
  public org.jmxtrans.agent.util.json.JsonValue get(int index) {
    return values.get(index);
  }

  /**
   * Returns a list of the values in this array in document order. The returned list is backed by
   * this array and will reflect subsequent changes. It cannot be used to modify this array.
   * Attempts to modify the returned list will result in an exception.
   *
   * @return a list of the values in this array
   */
  public List<org.jmxtrans.agent.util.json.JsonValue> values() {
    return Collections.unmodifiableList(values);
  }

  /**
   * Returns an iterator over the values of this array in document order. The returned iterator
   * cannot be used to modify this array.
   *
   * @return an iterator over the values of this array
   */
  public Iterator<org.jmxtrans.agent.util.json.JsonValue> iterator() {
    final Iterator<org.jmxtrans.agent.util.json.JsonValue> iterator = values.iterator();
    return new Iterator<org.jmxtrans.agent.util.json.JsonValue>() {

      public boolean hasNext() {
        return iterator.hasNext();
      }

      public org.jmxtrans.agent.util.json.JsonValue next() {
        return iterator.next();
      }

      public void remove() {
        throw new UnsupportedOperationException();
      }
    };
  }

  @Override
  void write(org.jmxtrans.agent.util.json.JsonWriter writer) throws IOException {
    writer.writeArrayOpen();
    Iterator<org.jmxtrans.agent.util.json.JsonValue> iterator = iterator();
    if (iterator.hasNext()) {
      iterator.next().write(writer);
      while (iterator.hasNext()) {
        writer.writeArraySeparator();
        iterator.next().write(writer);
      }
    }
    writer.writeArrayClose();
  }

  @Override
  public boolean isArray() {
    return true;
  }

  @Override
  public JsonArray asArray() {
    return this;
  }

  @Override
  public int hashCode() {
    return values.hashCode();
  }

  /**
   * Indicates whether a given object is "equal to" this JsonArray. An object is considered equal
   * if it is also a JsonArray and both arrays contain the same list of values.
   *

   * If two JsonArrays are equal, they will also produce the same JSON output.
   *

   *
   * @param object
   *          the object to be compared with this JsonArray
   * @return <tt>true</tt> if the specified object is equal to this JsonArray, false
   *         otherwise
   */
  @Override
  public boolean equals(Object object) {
    if (this == object) {
      return true;
    }
    if (object == null) {
      return false;
    }
    if (getClass() != object.getClass()) {
      return false;
    }
    JsonArray other = (JsonArray)object;
    return values.equals(other.values);
  }

}


/*
    Copyright 2011, Strategic Gains, Inc.

	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.strategicgains.eventing.local;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import com.strategicgains.eventing.EventHandler;

/**
 * A thread that receives published events and sends them to subscribers.
 * Registered event handlers will be called for whatever event types each can process.
 *
 * EventHandlers are called using an Executor pool that grows dynamically as needed, so
 * they are run asynchronously.
 *
 * @author toddf
 * @since May 17, 2011
 */
public class EventMonitor
extends Thread
{
	// SECTION: CONSTANTS

	private static final Executor EVENT_EXECUTOR = Executors.newCachedThreadPool();

	// SECTION: INSTANCE METHODS

	private Map<Class<?>, List<EventHandler>> handlersByEvent = new ConcurrentHashMap<Class<?>, List<EventHandler>>();
	private Set<EventHandler> handlers = new LinkedHashSet<EventHandler>();
	private boolean shouldShutDown = false;
	private boolean shouldReRaiseOnError = true;
	private LocalEventTransport eventQueue;
	private long delay;

	// SECTION: CONSTRUCTORS

	public EventMonitor(LocalEventTransport queue, long pollDelayMillis)
	{
		super();
		setDaemon(true);
		this.delay = pollDelayMillis;
		this.eventQueue = queue;
	}

	// SECTION: INSTANCE METHODS

	public synchronized boolean register(EventHandler handler)
	{
		boolean result = handlers.add(handler);
		handlersByEvent.clear();
		return result;
	}

	public synchronized boolean unregister(EventHandler handler)
	{
		if (handlers.remove(handler))
		{
			handlersByEvent.clear();
			return true;
		}

		return false;
	}

	public void shutdown()
	{
		shouldShutDown = true;
		System.out.println("Event monitor notified for shutdown.");

		synchronized(eventQueue)
		{
			eventQueue.notifyAll();
		}
	}

	public void setReRaiseOnError(boolean value)
	{
		this.shouldReRaiseOnError = value;
	}

	// SECTION: RUNNABLE/THREAD

	@Override
	public void run()
	{
		System.out.println("Event monitor starting...");

		while(!shouldShutDown)
		{
			try
			{
				synchronized (eventQueue)
				{
					if (eventQueue.isEmpty())
					{
						eventQueue.wait(delay);		// Support wake-up via eventQueue.notify()
					}
				}
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
				System.err.println("Interrupted (use shutdown() to terminate).  Continuing...");
				continue;
			}

			Object event = null;

			while ((event = eventQueue.poll()) != null)
			{
				processEvent(event);
			}
		}

		System.out.println("Event monitor exiting...");
		clearAllHandlers();
	}

	/**
	 * Runs each appropriate EventHandler in an Executor.
	 *
	 * @param event
	 */
	private void processEvent(final Object event)
    {
	    System.out.println("Processing event: " + event.toString());
	    for (final EventHandler handler : getConsumersFor(event.getClass()))
	    {
    		EVENT_EXECUTOR.execute(new Runnable(){
				@Override
                public void run()
                {
			    	try
			    	{
			    		handler.handle(event);
			    	}
			    	catch(Exception e)
			    	{
			    		e.printStackTrace();

			    		if (shouldReRaiseOnError)
			    		{
			    			System.out.println("Event handler failed. Re-publishing event: " + event.toString());
			    			eventQueue.publish(event);
			    		}
			    	}
                }
    		});
	    }
    }

	// SECTION: UTILITY - PRIVATE

	private void clearAllHandlers()
    {
	    handlers.clear();
		handlersByEvent.clear();
    }

	private synchronized List<EventHandler> getConsumersFor(Class<?> eventClass)
	{
		List<EventHandler> result = handlersByEvent.get(eventClass);

		if (result == null)
		{
			result = new ArrayList<EventHandler>();
			handlersByEvent.put(eventClass, result);

			for (EventHandler consumer : handlers)
			{
				if (consumer.handles(eventClass))
				{
					result.add(consumer);
				}
			}
		}

		return result;
	}
}


/*
    Copyright 2011, Strategic Gains, Inc.

	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.strategicgains.eventing;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * DomainEvents defines a static public interface for raising and handling domain events.
 * Raising an event places it in an in-memory queue that is then handled asynchronously
 * by an EventMonitor.
 *

 * Domain events are publish-subscribe, where when an event is raised, all appropriate
 * EventHandler instances are notified of an event, if they have subscribed and handles() returns
 * true for the given event class.
 *
 * All raised DomainEvent instances are handled asynchronously.  However, they may NOT be published
 * asynchronously, depending on the underlying transport implementation.
 *
 * @author toddf
 * @since May 12, 2011
 */
public class DomainEvents
{
	// SECTION: CONSTANTS

	private static final DomainEvents INSTANCE = new DomainEvents();

	// SECTION: INSTANCE VARIABLES

	private Map<String, EventBus> eventBusses = new LinkedHashMap<String, EventBus>();

	// SECTION: CONSTRUCTOR

	private DomainEvents()
	{
		super();
	}

	// SECTION: STATIC METHODS

	/**
	 * Get the Singleton instance of DomainEvents.
	 */
	public static DomainEvents instance()
	{
		return INSTANCE;
	}

	/**
	 * Publish an event to a named event bus.
	 * The name of the bus is assigned during a call to addBus(String, EventBus).
	 *

	 * Event publishing can only occur after event busses are setup.
	 *
	 * @param name the name of a specific event bus.
	 * @param event the Object as an event to publish.
	 */
	public static void publish(String name, Object event)
	{
		instance().publishEvent(name, event);
	}

	/**
	 * Publish an event, passing it to applicable consumers asynchronously.
	 *

	 * Event publishing can only occur after event busses are setup.
	 *
	 * @param event the Object as an event to publish.
	 */
	public static void publish(Object event)
	{
		instance().publishEvent(event);
	}

	/**
	 * Register an event bus with the DomainEvents manager.
	 *
	 * @param name the event bus name.  Must be unique within the DomainEvents manager.
	 * @param bus a newly-constructed EventBus instance.
	 * @return true if the name is unique and the event bus was added.  Otherwise, false.
	 */
	public static boolean addBus(String name, EventBus bus)
	{
		return instance().addEventBus(name, bus);
	}

	/**
	 * Get an event bus by name.
	 *
	 * @param name the name of an event bus given at the time of calling addBus(String, EventBus).
	 * @return an EventBus instance, or null if 'name' not found.
	 */
	public static EventBus getBus(String name)
	{
		return instance().getEventBus(name);
	}

	/**
	 * Shutdown all the even busses, releasing their resources cleanly.
	 *

	 * shutdown() should be called at application termination to cleanly release
	 * all consumed resources.
	 */
	public static void shutdown()
	{
		instance().shutdownEventBusses();
	}

	// SECTION: INSTANCE METHODS

	private boolean addEventBus(String name, EventBus bus)
	{
		if (!eventBusses.containsKey(name))
		{
			eventBusses.put(name, bus);
			return true;
		}

		return false;
	}

	private EventBus getEventBus(String name)
	{
		return eventBusses.get(name);
	}

	private boolean hasEventBusses()
	{
		return (eventBusses != null);
	}

	/**
	 * Raise an event on all event busses, passing it to applicable consumers asynchronously.
	 *
	 * @param event
	 */
	private void publishEvent(Object event)
	{
		assert(hasEventBusses());

		for (EventBus eventBus : eventBusses.values())
		{
			eventBus.publish(event);
		}
	}

	/**
	 * Raise an event on a named event bus, passing it to applicable consumers asynchronously.
	 *
	 * @param name the name of an event bus, assigned during calls to addEventBus(String, EventBus).
	 * @param event the event to publish.
	 */
	private void publishEvent(String name, Object event)
	{
		assert(hasEventBusses());
		EventBus eventBus = getEventBus(name);

		if (eventBus == null)
		{
			throw new RuntimeException("Unknown event bus name: " + name);
		}

		eventBus.publish(event);
	}

	private void shutdownEventBusses()
	{
		for (EventBus eventBus : eventBusses.values())
		{
			eventBus.shutdown();
		}

		eventBusses.clear();
	}
}


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

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

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

/**
 *

 * The padding shorthand property sets all the padding properties in one declaration. This property can have from one to four values.
 *
 * Examples:
 *
 *     padding:10px 5px 15px 20px;
 *         top padding is 10px
 *         right padding is 5px
 *         bottom padding is 15px
 *         left padding is 20px
 *
 *     padding:10px 5px 15px;
 *         top padding is 10px
 *         right and left padding are 5px
 *         bottom padding is 15px
 *
 *     padding:10px 5px;
 *         top and bottom padding are 10px
 *         right and left padding are 5px
 *
 *     padding:10px;
 *         all four paddings are 10px
 *
 * Note: Negative values are not allowed.
 * Default value:  0
 * Inherited:      no
 * Animatable:     yes
 *
 * Version:        CSS1
 * JavaScript syntax:      object.style.padding="100px 20px"
 *

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

    private static final long serialVersionUID = 1_0_0L;

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

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

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

    private String cssValue;

    private PaddingTop paddingTop;
    private PaddingRight paddingRight;
    private PaddingBottom paddingBottom;
    private PaddingLeft paddingLeft;

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

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

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

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

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

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

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

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

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

            if (PREDEFINED_CONSTANTS.contains(trimmedCssValue)) {
                this.cssValue = trimmedCssValue;
                if (paddingTop != null) {
                    paddingTop.setAlreadyInUse(false);
                    paddingTop = null;
                }
                if (paddingRight != null) {
                    paddingRight.setAlreadyInUse(false);
                    paddingRight = null;
                }
                if (paddingBottom != null) {
                    paddingBottom.setAlreadyInUse(false);
                    paddingBottom = null;
                }
                if (paddingLeft != null) {
                    paddingLeft.setAlreadyInUse(false);
                    paddingLeft = null;
                }
                if (getStateChangeInformer() != null) {
                    getStateChangeInformer().stateChanged(this);
                }

                return this;
            }

            final String paddingString = StringUtil
                    .convertToSingleSpace(trimmedCssValue);

            final String[] extractedPaddings = StringUtil
                    .splitBySpace(paddingString);

            if (extractedPaddings.length == 1) {
                if (paddingTop == null) {
                    paddingTop = new PaddingTop(extractedPaddings[0]);
                    paddingTop.setStateChangeInformer(this);
                    paddingTop.setAlreadyInUse(true);
                } else {
                    paddingTop.setCssValue(extractedPaddings[0]);
                }
                if (paddingRight == null) {
                    paddingRight = new PaddingRight(extractedPaddings[0]);
                    paddingRight.setStateChangeInformer(this);
                    paddingRight.setAlreadyInUse(true);
                } else {
                    paddingRight.setCssValue(extractedPaddings[0]);
                }
                if (paddingBottom == null) {
                    paddingBottom = new PaddingBottom(extractedPaddings[0]);
                    paddingBottom.setStateChangeInformer(this);
                    paddingBottom.setAlreadyInUse(true);
                } else {
                    paddingBottom.setCssValue(extractedPaddings[0]);
                }
                if (paddingLeft == null) {
                    paddingLeft = new PaddingLeft(extractedPaddings[0]);
                    paddingLeft.setStateChangeInformer(this);
                    paddingLeft.setAlreadyInUse(true);
                } else {
                    paddingLeft.setCssValue(extractedPaddings[0]);
                }
            } else if (extractedPaddings.length == 2) {
                if (paddingTop == null) {
                    paddingTop = new PaddingTop(extractedPaddings[0]);
                    paddingTop.setStateChangeInformer(this);
                    paddingTop.setAlreadyInUse(true);
                } else {
                    paddingTop.setCssValue(extractedPaddings[0]);
                }
                if (paddingBottom == null) {
                    paddingBottom = new PaddingBottom(extractedPaddings[0]);
                    paddingBottom.setStateChangeInformer(this);
                    paddingBottom.setAlreadyInUse(true);
                } else {
                    paddingBottom.setCssValue(extractedPaddings[0]);
                }
                if (paddingRight == null) {
                    paddingRight = new PaddingRight(extractedPaddings[1]);
                    paddingRight.setStateChangeInformer(this);
                    paddingRight.setAlreadyInUse(true);
                } else {
                    paddingRight.setCssValue(extractedPaddings[1]);
                }
                if (paddingLeft == null) {
                    paddingLeft = new PaddingLeft(extractedPaddings[1]);
                    paddingLeft.setStateChangeInformer(this);
                    paddingLeft.setAlreadyInUse(true);
                } else {
                    paddingLeft.setCssValue(extractedPaddings[1]);
                }
            } else if (extractedPaddings.length == 3) {
                if (paddingTop == null) {
                    paddingTop = new PaddingTop(extractedPaddings[0]);
                    paddingTop.setStateChangeInformer(this);
                    paddingTop.setAlreadyInUse(true);
                } else {
                    paddingTop.setCssValue(extractedPaddings[0]);
                }
                if (paddingRight == null) {
                    paddingRight = new PaddingRight(extractedPaddings[1]);
                    paddingRight.setStateChangeInformer(this);
                    paddingRight.setAlreadyInUse(true);
                } else {
                    paddingRight.setCssValue(extractedPaddings[1]);
                }
                if (paddingLeft == null) {
                    paddingLeft = new PaddingLeft(extractedPaddings[1]);
                    paddingLeft.setStateChangeInformer(this);
                    paddingLeft.setAlreadyInUse(true);
                } else {
                    paddingLeft.setCssValue(extractedPaddings[1]);
                }
                if (paddingBottom == null) {
                    paddingBottom = new PaddingBottom(extractedPaddings[2]);
                    paddingBottom.setStateChangeInformer(this);
                    paddingBottom.setAlreadyInUse(true);
                } else {
                    paddingBottom.setCssValue(extractedPaddings[2]);
                }
            } else if (extractedPaddings.length == 4) {
                if (paddingTop == null) {
                    paddingTop = new PaddingTop(extractedPaddings[0]);
                    paddingTop.setStateChangeInformer(this);
                    paddingTop.setAlreadyInUse(true);
                } else {
                    paddingTop.setCssValue(extractedPaddings[0]);
                }
                if (paddingRight == null) {
                    paddingRight = new PaddingRight(extractedPaddings[1]);
                    paddingRight.setStateChangeInformer(this);
                    paddingRight.setAlreadyInUse(true);
                } else {
                    paddingRight.setCssValue(extractedPaddings[1]);
                }
                if (paddingBottom == null) {
                    paddingBottom = new PaddingBottom(extractedPaddings[2]);
                    paddingBottom.setStateChangeInformer(this);
                    paddingBottom.setAlreadyInUse(true);
                } else {
                    paddingBottom.setCssValue(extractedPaddings[2]);
                }
                if (paddingLeft == null) {
                    paddingLeft = new PaddingLeft(extractedPaddings[3]);
                    paddingLeft.setStateChangeInformer(this);
                    paddingLeft.setAlreadyInUse(true);
                } else {
                    paddingLeft.setCssValue(extractedPaddings[3]);
                }
            } else {
                throw new InvalidValueException(
                        "the given cssValue is invalid");
            }

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

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

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

    /**
     * @return the paddingTop
     * @author WFF
     * @since 1.0.0
     */
    public PaddingTop getPaddingTop() {
        return paddingTop;
    }

    /**
     * sets the top, right, bottom and left width in {@code Padding}. If the
     * given argument is already used by another object, then the
     * existing/cloned object will be used. And throws
     * {@code NullValueException} if any of the given argument is null.
     *
     * @param paddingTop
     * @param paddingRight
     * @param paddingBottom
     * @param paddingLeft
     * @author WFF
     * @since 1.0.0
     */
    public void setPadding(final PaddingTop paddingTop,
            final PaddingRight paddingRight, final PaddingBottom paddingBottom,
            final PaddingLeft paddingLeft) {

        if (paddingTop != null && paddingRight != null && paddingBottom != null
                && paddingLeft != null) {

            if (PaddingTop.INITIAL.equals(paddingTop.getCssValue())
                    || PaddingTop.INHERIT.equals(paddingTop.getCssValue())
                    || PaddingRight.INITIAL.equals(paddingRight.getCssValue())
                    || PaddingRight.INHERIT.equals(paddingRight.getCssValue())
                    || PaddingBottom.INITIAL.equals(paddingBottom.getCssValue())
                    || PaddingBottom.INHERIT.equals(paddingBottom.getCssValue())
                    || PaddingLeft.INITIAL.equals(paddingLeft.getCssValue())
                    || PaddingLeft.INHERIT.equals(paddingLeft.getCssValue())) {
                throw new InvalidValueException(
                        "Any or all of the given arguments have initial/inherit constant value as its cssValue");
            }

            try {
                final PaddingTop paddingTopTemp;
                final PaddingRight paddingRightTemp;
                final PaddingBottom paddingBottomTemp;
                final PaddingLeft paddingLeftTemp;

                if (this.paddingTop != null) {
                    this.paddingTop.setAlreadyInUse(false);
                    this.paddingRight.setAlreadyInUse(false);
                    this.paddingBottom.setAlreadyInUse(false);
                    this.paddingLeft.setAlreadyInUse(false);
                }

                if (paddingTop.isAlreadyInUse()
                        && this.paddingTop != paddingTop) {
                    if (this.paddingTop != null) {
                        paddingTopTemp = this.paddingTop
                                .setCssValue(paddingTop.getCssValue());
                        if (LOGGER.isLoggable(Level.WARNING)) {
                            LOGGER.warning(
                                    "the given paddingTop is already used by another object so the existing object is used");
                        }
                    } else {
                        paddingTopTemp = paddingTop.clone();
                        if (LOGGER.isLoggable(Level.WARNING)) {
                            LOGGER.warning(
                                    "the given paddingTop is already used by another object so its clone is assigned");
                        }
                    }
                } else {
                    paddingTopTemp = paddingTop;
                }

                if (paddingRight.isAlreadyInUse()
                        && this.paddingRight != paddingRight) {
                    if (this.paddingRight != null) {
                        paddingRightTemp = this.paddingRight
                                .setCssValue(paddingTop.getCssValue());
                        if (LOGGER.isLoggable(Level.WARNING)) {
                            LOGGER.warning(
                                    "the given paddingRight is already used by another object so the existing object is used");
                        }
                    } else {
                        paddingRightTemp = paddingRight.clone();
                        if (LOGGER.isLoggable(Level.WARNING)) {
                            LOGGER.warning(
                                    "the given paddingRight is already used by another object so its clone is assigned");
                        }
                    }

                } else {
                    paddingRightTemp = paddingRight;
                }

                if (paddingBottom.isAlreadyInUse()
                        && this.paddingBottom != paddingBottom) {
                    if (this.paddingBottom != null) {
                        paddingBottomTemp = this.paddingBottom
                                .setCssValue(paddingTop.getCssValue());
                        if (LOGGER.isLoggable(Level.WARNING)) {
                            LOGGER.warning(
                                    "the given paddingBottom is already used by another object so the existing object is used");
                        }
                    } else {
                        paddingBottomTemp = paddingBottom.clone();
                        if (LOGGER.isLoggable(Level.WARNING)) {
                            LOGGER.warning(
                                    "the given paddingBottom is already used by another object so its clone is assigned");
                        }
                    }

                } else {
                    paddingBottomTemp = paddingBottom;
                }

                if (paddingLeft.isAlreadyInUse()
                        && this.paddingLeft != paddingLeft) {
                    if (this.paddingLeft != null) {
                        paddingLeftTemp = this.paddingLeft
                                .setCssValue(paddingTop.getCssValue());
                        if (LOGGER.isLoggable(Level.WARNING)) {
                            LOGGER.warning(
                                    "the given paddingLeft is already used by another object so the existing object is used");
                        }
                    } else {
                        paddingLeftTemp = paddingLeft.clone();
                        if (LOGGER.isLoggable(Level.WARNING)) {
                            LOGGER.warning(
                                    "the given paddingLeft is already used by another object so its clone is assigned");
                        }
                    }

                } else {
                    paddingLeftTemp = paddingLeft;
                }

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

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

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

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

                assignProducedCssValue(paddingTop, paddingRight, paddingBottom,
                        paddingLeft);

                this.paddingTop = paddingTopTemp;
                this.paddingRight = paddingRightTemp;
                this.paddingBottom = paddingBottomTemp;
                this.paddingLeft = paddingLeftTemp;

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

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

    }

    /**
     * @return the paddingRight
     * @author WFF
     * @since 1.0.0
     */
    public PaddingRight getPaddingRight() {
        return paddingRight;
    }

    /**
     * @return the paddingBottom
     * @author WFF
     * @since 1.0.0
     */
    public PaddingBottom getPaddingBottom() {
        return paddingBottom;
    }

    /**
     * @return the paddingLeft
     * @author WFF
     * @since 1.0.0
     */
    public PaddingLeft getPaddingLeft() {
        return paddingLeft;
    }

    /**
     *
     * @author WFF
     * @since 1.0.0
     */
    private void assignProducedCssValue(final PaddingTop paddingTop,
            final PaddingRight paddingRight, final PaddingBottom paddingBottom,
            final PaddingLeft paddingLeft) {

        final String paddingTopCssValue = paddingTop.getCssValue();
        final String paddingRightCssValue = paddingRight.getCssValue();
        final String paddingBottomCssValue = paddingBottom.getCssValue();
        final String paddingLeftCssValue = paddingLeft.getCssValue();

        if (paddingTopCssValue.equals(paddingRightCssValue)
                && paddingRightCssValue.equals(paddingBottomCssValue)
                && paddingBottomCssValue.equals(paddingLeftCssValue)) {

            cssValue = paddingTopCssValue;

            final StateChangeInformer<CssProperty> stateChangeInformer = getStateChangeInformer();
            if (stateChangeInformer != null) {
                stateChangeInformer.stateChanged(this);
            }
        } else if (paddingTopCssValue.equals(paddingBottomCssValue)
                && paddingRightCssValue.equals(paddingLeftCssValue)) {

            final StringBuilder cssValueBuilder = new StringBuilder(
                    paddingTopCssValue);
            cssValueBuilder.append(' ').append(paddingRightCssValue);

            cssValue = cssValueBuilder.toString();

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

        } else if (paddingRightCssValue.equals(paddingLeftCssValue)) {
            cssValue = new StringBuilder(paddingTopCssValue).append(' ')
                    .append(paddingRightCssValue).append(' ')
                    .append(paddingBottomCssValue).toString();

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

            cssValue = new StringBuilder(paddingTopCssValue).append(' ')
                    .append(paddingRightCssValue).append(' ')
                    .append(paddingBottomCssValue).append(' ')
                    .append(paddingLeftCssValue).toString();

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

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

        if (stateChangedObject instanceof PaddingTop) {
            final PaddingTop paddingTop = (PaddingTop) stateChangedObject;
            if (PaddingTop.INITIAL.equals(paddingTop.getCssValue())
                    || PaddingTop.INHERIT.equals(paddingTop.getCssValue())) {
                throw new InvalidValueException(
                        "paddingTop cannot have initial/inherit as its cssValue");
            }
        } else if (stateChangedObject instanceof PaddingRight) {
            final PaddingRight paddingRight = (PaddingRight) stateChangedObject;
            if (PaddingRight.INITIAL.equals(paddingRight.getCssValue())
                    || PaddingRight.INHERIT
                            .equals(paddingRight.getCssValue())) {
                throw new InvalidValueException(
                        "paddingRight cannot have initial/inherit as its cssValue");
            }
        } else if (stateChangedObject instanceof PaddingBottom) {
            final PaddingBottom paddingBottom = (PaddingBottom) stateChangedObject;
            if (PaddingBottom.INITIAL.equals(paddingBottom.getCssValue())
                    || PaddingBottom.INHERIT
                            .equals(paddingBottom.getCssValue())) {
                throw new InvalidValueException(
                        "paddingBottom cannot have initial/inherit as its cssValue");
            }
        } else if (stateChangedObject instanceof PaddingLeft) {
            final PaddingLeft paddingLeft = (PaddingLeft) stateChangedObject;
            if (PaddingLeft.INITIAL.equals(paddingLeft.getCssValue())
                    || PaddingLeft.INHERIT.equals(paddingLeft.getCssValue())) {
                throw new InvalidValueException(
                        "paddingLeft cannot have initial/inherit as its cssValue");
            }
        }

        assignProducedCssValue(paddingTop, paddingRight, paddingBottom,
                paddingLeft);

    }

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

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

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

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

        return true;
    }
}


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

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import com.webfirmframework.wffweb.InvalidValueException;
import com.webfirmframework.wffweb.NullValueException;

/**
 *
 * @author WFF
 * @since 1.0.0
 */
public final class CssValueUtil {

    private static final Map<String, String> CSS_VALUE_PART_START_END_VALUES;

    static {
        CSS_VALUE_PART_START_END_VALUES = new HashMap<>();
        CSS_VALUE_PART_START_END_VALUES.put("rgb(", ")");
        CSS_VALUE_PART_START_END_VALUES.put("rgba(", ")");
        CSS_VALUE_PART_START_END_VALUES.put("hsl(", ")");
        CSS_VALUE_PART_START_END_VALUES.put("hsla(", ")");
    }

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

    /**
     * The given value is used by Web Firm Framework Index Based Extraction
     * algorithm to extract css value parts, i.e. the
     * {@code CssValueUtil#split(String)} is extracting values based on the
     * given values.

     * Eg :-
     *
     *

     * addCssValuePartStartEndValue("rgb(", ")");
     *

     *
     * And the following "rgb(", ")", "rgba(", ")",
     * "hsl(", ")", "hsla(", ")" values are already
     * added.
     *
     * @param start
     * @param end
     * @author WFF
     * @since 1.0.0
     */
    public void addCssValuePartStartEndValue(final String start,
            final String end) {
        CSS_VALUE_PART_START_END_VALUES.put(start, end);
    }

    /**
     * splits each cssValue part based on the css rule.

     * This extraction is done based on the Web Firm Framework Index Based
     * Extraction algorithm. It will throw InvalidValueException if the given
     * cssValue contains no space in between any valid cssValue part, eg:-
     * rgb(1, 2, 5)rgb(11, 12, 15). And, it doesn't validate the
     * extracted cssValue parts so the extracted cssValue parts (i.e. the
     * returned {@code List<String>}) may contain invalid cssValue part.

     *

     * Sample code to test
     *
     *

     * final List<String> splitTest = CssValueUtil.split("hsla(7, 8, 9, 1) red rgb(3, 5, 6) rgb(7, 8, 9) rgba(7, 8, 9, 1) middle hsl(10, 11, 12) green   blue");
     * for (final String each : splitTest) {
     *     System.out.println(each);
     * }
     *
     * gives out as :
     *
     * hsla(7, 8, 9, 1)
     * red
     * rgb(3, 5, 6)
     * rgb(7, 8, 9)
     * rgba(7, 8, 9, 1)
     * middle
     * hsl(10, 11, 12)
     * green
     * blue
     *

     *
     *
     * @param cssValue
     *                     the value from which the css parts will be extracted
     * @return the list containing cssValue parts
     * @author WFF
     * @since 1.0.0
     */
    public static List<String> split(final String cssValue) {
        try {

            final Map<Integer, int[]> startAndEndIndexes = new TreeMap<>();

            for (final Entry<String, String> entry : CSS_VALUE_PART_START_END_VALUES
                    .entrySet()) {

                final int[][] rgbStartAndEndIndexesOf = StringUtil
                        .startAndEndIndexesOf(cssValue, entry.getKey(),
                                entry.getValue());
                for (final int[] each : rgbStartAndEndIndexesOf) {
                    startAndEndIndexes.put(each[0], each);
                }
            }

            if (startAndEndIndexes.size() == 0) {
                return Arrays.asList(StringUtil.splitBySpace(
                        StringUtil.convertToSingleSpace(cssValue)));
            }

            final List<String> cssValueParts = new ArrayList<>();

            int count = 0;
            final int startAndEndIndexesSize = startAndEndIndexes.size();

            int[] previousStartEndIndex = null;

            for (final Entry<Integer, int[]> entry : startAndEndIndexes
                    .entrySet()) {

                count++;

                final int[] currentStartEndIndex = entry.getValue();

                final int zerothSlotCurrent = currentStartEndIndex[0];
                final int firstSlotCurrent = currentStartEndIndex[1];

                if (previousStartEndIndex != null) {

                    final int firstSlotOfPrevious = previousStartEndIndex[1];

                    // the difference should be more than two, 1 for at least
                    // one char (css value having length one) + one for space
                    // char.
                    if ((zerothSlotCurrent - firstSlotOfPrevious) > 2) {

                        final String cssValuePart2 = cssValue.substring(
                                firstSlotOfPrevious + 1, zerothSlotCurrent);

                        final String trimmedCssValuePart2 = StringUtil
                                .strip(cssValuePart2);

                        if (StringUtil.startsWithWhitespace(cssValuePart2)
                                && StringUtil
                                        .endsWithWhitespace(cssValuePart2)) {
                            if (!trimmedCssValuePart2.isEmpty()) {
                                for (final String each : StringUtil
                                        .splitBySpace(trimmedCssValuePart2)) {
                                    final String trimmed;
                                    if (!(trimmed = StringUtil.strip(each))
                                            .isEmpty()) {
                                        cssValueParts.add(trimmed);
                                    }
                                }
                            }
                        } else {
                            throw new InvalidValueException(
                                    "there must be a space between each part of cssValue, a space is expected before and/or after '"
                                            + cssValuePart2 + "'");
                        }

                    } else if ((zerothSlotCurrent - firstSlotOfPrevious) == 1) {
                        throw new InvalidValueException(
                                "there must be a space between each part of cssValue, a space is expected before '"
                                        + cssValue.substring(zerothSlotCurrent)
                                        + "'");
                    }

                    cssValueParts
                            .add(cssValue.substring(currentStartEndIndex[0],
                                    currentStartEndIndex[1] + 1));

                } else {

                    // to check whether the starting cssValue part has at least
                    // two chars, 1 for at least one char (css value having
                    // length one) + one for space char.
                    // greater than 1 is enough since zerothSlotCurrent is an
                    // index number.

                    if ((zerothSlotCurrent) > 1) {
                        final String cssValuePart2 = cssValue.substring(0,
                                zerothSlotCurrent);

                        final String trimmedCssValuePart2 = StringUtil
                                .strip(cssValuePart2);

                        if (StringUtil.endsWithWhitespace(cssValuePart2)) {
                            for (final String each : StringUtil
                                    .splitBySpace(trimmedCssValuePart2)) {
                                final String trimmed;
                                if (!(trimmed = StringUtil.strip(each))
                                        .isEmpty()) {
                                    cssValueParts.add(trimmed);
                                }
                            }

                        } else {
                            throw new InvalidValueException(
                                    "there must be a space between each part of cssValue, a space is expected after '"
                                            + cssValuePart2 + "'");
                        }

                    }

                    cssValueParts
                            .add(cssValue.substring(currentStartEndIndex[0],
                                    currentStartEndIndex[1] + 1));

                }

                final int cssValueLength = cssValue.length();
                if (count == startAndEndIndexesSize
                        && (firstSlotCurrent < (cssValueLength - 1))) {

                    final String cssValuePart2 = cssValue
                            .substring(firstSlotCurrent + 1);

                    final String trimmedCssValuePart2 = StringUtil
                            .strip(cssValuePart2);

                    if (StringUtil.startsWithWhitespace(cssValuePart2)) {
                        for (final String each : StringUtil
                                .splitBySpace(trimmedCssValuePart2)) {
                            if (!each.isEmpty()) {
                                cssValueParts.add(each);
                            }
                        }

                    } else {
                        throw new InvalidValueException(
                                "there must be a space between each part of cssValue, a space is expected before '"
                                        + cssValuePart2 + "'");
                    }
                }

                previousStartEndIndex = currentStartEndIndex;

            }

            return cssValueParts;
        } catch (final RuntimeException e) {
            throw new InvalidValueException(e);
        }
    }

    /**
     * This method throws {@code NullValueException} if the given
     * {@code cssValue} is null or {@code InvalidValueException} if the given
     * {@code cssValue} is blank.
     *
     * @param cssValue
     * @throws NullValueException
     * @throws InvalidValueException
     * @author WFF
     * @since 1.0.0
     */
    public static void throwExceptionForInvalidValue(final String cssValue)
            throws NullValueException, InvalidValueException {
        if (cssValue == null) {
            throw new NullValueException("the cssValue should not be null.");
        } else if (StringUtil.isBlank(cssValue)) {
            throw new InvalidValueException(
                    "the cssValue should not be blank.");
        }
    }

}


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

import java.util.Arrays;
import java.util.List;

import com.webfirmframework.wffweb.InvalidValueException;
import com.webfirmframework.wffweb.NullValueException;
import com.webfirmframework.wffweb.css.CssNameConstants;
import com.webfirmframework.wffweb.css.core.AbstractCssProperty;
import com.webfirmframework.wffweb.util.StringUtil;
import com.webfirmframework.wffweb.util.TagStringUtil;

/**
 *

 *  column-count: number|auto|initial|inherit;
 *
 * The column-count property specifies the number of columns an element should be divided into.
 * Default value:  auto
 * Inherited:      no
 * Animatable:     yes
 * Version:        CSS3
 * JavaScript syntax:      object.style.columnCount=3
 *

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

    private static final long serialVersionUID = 1_0_0L;

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

    /**
     * The default value 1 will be set as the cssValue.
     *
     * @author WFF
     * @since 1.0.0
     */
    public ColumnCount() {
        cssValue = AUTO;
    }

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

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

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

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

    /*
     * (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 columnCount in {@code Integer} value.
     *
     * @return the value in int or null if the cssValue is initial
     *         or inherit.
     * @since 1.0.0
     * @author WFF
     */
    public Integer getValue() {
        return value;
    }

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

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

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

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

            }
            if (getStateChangeInformer() != null) {
                getStateChangeInformer().stateChanged(this);
            }
        } catch (final InvalidValueException e) {
            this.cssValue = previousCssValue;
            throw 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 auto}
     *
     * @since 1.0.0
     * @author WFF
     */
    public void setAsAuto() {
        setCssValue(AUTO);
    }

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

            return !(parsedValue == 0
                    && (StringUtil.containsMinus(trimmedCssValue)
                            || StringUtil.containsPlus(trimmedCssValue)));
        } catch (final NumberFormatException e) {
        }

        return PREDEFINED_CONSTANTS.contains(trimmedCssValue);
    }
}


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

import java.util.Collection;
import java.util.List;

import com.webfirmframework.wffweb.tag.html.AbstractHtml;

/**
 * It's a tag which makes child content without any opening closing tag.

 *

 * NB: Nesting NoTag is not recommended and it may cause strange behavior. We
 * recommend TagContent enum for NoTag insertion in a tag instead of creating
 * new NoTag object.
 *
 * @author WFF
 * @since 1.0.0
 */
public class NoTag extends AbstractHtml {

    // TODO This class needs to be tested properly

    private static final long serialVersionUID = 1_0_0L;

    {
        init();
    }

    /**
     *
     * @param base
     *                     i.e. parent tag of this tag
     * @param children
     *                     An array of {@code AbstractHtml}
     *
     * @since 1.0.0
     */
    public NoTag(final AbstractHtml base, final AbstractHtml... children) {
        super(base, children);
    }

    /**
     *
     * @param base
     *                     i.e. parent tag of this tag
     * @param children
     *                     An array of {@code AbstractHtml}
     *
     * @since 1.0.0
     */
    public NoTag(final AbstractHtml base,
            final Collection<? extends AbstractHtml> children) {
        super(base, children);
    }

    /**
     *
     * @param base
     *                         i.e. parent tag of this tag
     * @param childContent
     *
     * @since 1.0.0
     */
    public NoTag(final AbstractHtml base, final String childContent) {
        super(base, childContent, false);
    }

    /**
     *
     * @param base
     *                            i.e. parent tag of this tag
     * @param childContent
     * @param contentTypeHtml
     *                            true if the given childContent is HTML. by
     *                            default it is false.
     * @since 3.0.2
     */
    public NoTag(final AbstractHtml base, final String childContent,
            final boolean contentTypeHtml) {
        super(base, childContent, contentTypeHtml);
    }

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

    /**
     * adds {@code AbstractHtml}s as children.
     *
     * @param children
     * @since 1.0.0
     * @author WFF
     */
    public void addChildren(final List<AbstractHtml> children) {
        super.appendChildren(children);
    }

    /**
     * adds {@code AbstractHtml}s as children.
     *
     * @param child
     * @since 1.0.0
     * @author WFF
     */
    public void addChild(final AbstractHtml child) {
        super.appendChild(child);
    }

    /**
     * adds {@code AbstractHtml}s as children.
     *
     * @param children
     * @since 1.0.0
     * @author WFF
     */
    public void removeChildren(final List<AbstractHtml> children) {
        super.removeChildren(children);
    }

    /**
     * adds {@code AbstractHtml}s as children.
     *
     * @param child
     * @since 1.0.0
     * @author WFF
     */
    @Override
    public boolean removeChild(final AbstractHtml child) {
        return super.removeChild(child);
    }

    /**
     * removes the the child content.
     *
     * @param child
     * @since 1.0.0
     * @author WFF
     */
    public void removeChild(final String child) {
        final StringBuilder htmlMiddleSB = getHtmlMiddleSB();
        final String sb = htmlMiddleSB.toString();
        final String replaced = sb.replace(child, "");
        final int lastIndex = htmlMiddleSB.length() - 1;
        htmlMiddleSB.delete(0, lastIndex);
        htmlMiddleSB.append(replaced);
    }

    /**
     * adds {@code AbstractHtml}s as children.
     *
     * @param child
     * @since 1.0.0
     * @author WFF
     */
    public void addChild(final String child) {
        super.getChildren().add(new NoTag(this, child));
    }

    /**
     * adds {@code AbstractHtml}s as children.
     *
     * @return
     * @since 1.0.0
     * @author WFF
     */
    public String getChildContent() {
        return getHtmlMiddleSB().toString();
    }

    /**
     * @return true if the child content is considered to be HTML
     * @since 3.0.2
     */
    public boolean isChildContentTypeHtml() {
        return noTagContentTypeHtml;
    }
}


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

/**
 *

 * background-size: auto|length|cover|contain|initial|inherit;
 *
 * The background-size property specifies the size of the background images.
 * Default value:  auto
 * Inherited:      no
 * Animatable:     yes
 * Version:        CSS3
 * JavaScript syntax:      object.style.backgroundSize="60px 120px"
 *

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

    private static final long serialVersionUID = 1_0_0L;

    public static final String INITIAL = "initial";
    public static final String INHERIT = "inherit";
    public static final String AUTO = "auto";
    public static final String COVER = "cover";
    public static final String CONTAIN = "contain";

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

    private String cssValue;
    private Float width;
    private CssLengthUnit widthLengthUnit;

    private Float height;
    private CssLengthUnit heightLengthUnit;

    /**
     * The {@code 0px} will be set as the value
     */
    public BackgroundSize() {
        setCssValue("0px");
    }

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

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

    /**
     * @param widthHeight
     *                          the value to set.
     * @param cssLengthUnit
     *                          the value unit to set.
     * @since 1.0.0
     * @author WFF
     */
    public BackgroundSize(final float widthHeight,
            final CssLengthUnit cssLengthUnit) {

        width = height = Float.valueOf(widthHeight);
        widthLengthUnit = heightLengthUnit = cssLengthUnit;

        cssValue = String.valueOf(widthHeight) + cssLengthUnit;
    }

    /**
     * @param width
     * @param widthLengthUnit
     * @param height
     * @param heightLengthUnit
     * @author WFF
     * @since 1.0.0
     */
    public BackgroundSize(final float width,
            final CssLengthUnit widthLengthUnit, final float height,
            final CssLengthUnit heightLengthUnit) {

        this.width = Float.valueOf(width);
        this.height = Float.valueOf(height);

        this.widthLengthUnit = widthLengthUnit;
        this.heightLengthUnit = heightLengthUnit;

        if (width == height
                && Objects.equals(widthLengthUnit, heightLengthUnit)) {
            cssValue = String.valueOf(width) + widthLengthUnit;
        } else {
            cssValue = new StringBuilder().append(width).append(widthLengthUnit)
                    .append(' ').append(height).append(heightLengthUnit)
                    .toString();

        }
    }

    /**
     * @param width
     * @param widthLengthUnit
     * @param height
     * @param heightLengthUnit
     * @return
     * @author WFF
     * @since 1.0.0
     */
    public BackgroundSize setValue(final float width,
            final CssLengthUnit widthLengthUnit, final float height,
            final CssLengthUnit heightLengthUnit) {

        this.width = Float.valueOf(width);
        this.height = Float.valueOf(height);

        this.widthLengthUnit = widthLengthUnit;
        this.heightLengthUnit = heightLengthUnit;

        if (width == height
                && Objects.equals(widthLengthUnit, heightLengthUnit)) {
            cssValue = String.valueOf(width) + widthLengthUnit;
        } else {
            cssValue = new StringBuilder().append(width).append(widthLengthUnit)
                    .append(' ').append(height).append(heightLengthUnit)
                    .toString();

        }

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

    /**
     * @param widthHeight
     * @param cssLengthUnit
     * @return the current object
     * @since 1.0.0
     * @author WFF
     */
    public BackgroundSize setValue(final float widthHeight,
            final CssLengthUnit cssLengthUnit) {

        width = height = Float.valueOf(widthHeight);
        widthLengthUnit = heightLengthUnit = cssLengthUnit;

        cssValue = String.valueOf(widthHeight) + cssLengthUnit;

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

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

    /*
     * (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 horizontal value in float value.
     * {@code BackgroundSize#getWidthUnit()} should be used to get the
     * cssLengthUnit for this value.
     *
     * @return the horizontal value in float or {@code null} if the value is any
     *         inbuilt value like {@code inherit}.
     * @since 1.0.0
     * @author WFF
     */
    public Float getWidth() {
        return width;
    }

    /**
     * gets the vertical value in float value.
     * {@code BackgroundSize#getHeightUnit()} should be used to get the
     * cssLengthUnit for this value.
     *
     * @return the vertical value in float or {@code null} if the value is any
     *         inbuilt value like {@code inherit}.
     * @since 1.0.0
     * @author WFF
     */
    public Float getHeight() {
        return height;
    }

    /**
     * @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 getWidthUnit() {
        return widthLengthUnit;
    }

    /**
     * @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 getHeightUnit() {
        return heightLengthUnit;
    }

    /**
     * @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 BackgroundSize 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 = TagStringUtil
                        .toLowerCase(StringUtil.strip(cssValue));
                boolean invalidValue = true;

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

                if (lengthValues.length == 1) {
                    if (trimmedCssValue.equalsIgnoreCase(INITIAL)
                            || trimmedCssValue.equalsIgnoreCase(INHERIT)) {
                        this.cssValue = trimmedCssValue;
                        width = null;
                        height = null;
                        widthLengthUnit = null;
                        heightLengthUnit = null;
                        invalidValue = false;
                    } else {
                        final Object[] lengthValueAndUnitHorizontalVertical = CssLengthUtil
                                .getLengthValueAndUnit(lengthValues[0]);

                        if (lengthValueAndUnitHorizontalVertical.length == 2) {
                            width = height = (Float) lengthValueAndUnitHorizontalVertical[0];
                            widthLengthUnit = heightLengthUnit = (CssLengthUnit) lengthValueAndUnitHorizontalVertical[1];
                            this.cssValue = trimmedCssValue;
                            invalidValue = false;
                        }
                    }
                } else if (lengthValues.length == 2) {
                    final Object[] lengthValueAndUnitHorizontal = CssLengthUtil
                            .getLengthValueAndUnit(lengthValues[0]);

                    final Object[] lengthValueAndUnitVertical = CssLengthUtil
                            .getLengthValueAndUnit(lengthValues[1]);

                    if (lengthValueAndUnitHorizontal.length == 2
                            && lengthValueAndUnitVertical.length == 2) {
                        width = (Float) lengthValueAndUnitHorizontal[0];
                        widthLengthUnit = (CssLengthUnit) lengthValueAndUnitHorizontal[1];
                        height = (Float) lengthValueAndUnitVertical[0];
                        heightLengthUnit = (CssLengthUnit) lengthValueAndUnitVertical[1];

                        // nano optimized way to check if both width
                        // and height are equal.
                        if (Float.floatToIntBits(width.floatValue()) == Float
                                .floatToIntBits(width.floatValue())
                                && Objects.equals(widthLengthUnit,
                                        heightLengthUnit)) {
                            this.cssValue = String.valueOf(width)
                                    + heightLengthUnit;
                        } else {
                            this.cssValue = trimmedCssValue;
                        }
                        invalidValue = false;
                    }
                } else {
                    throw new InvalidValueException(
                            "The given cssValue should not contain more that 2 length values.");
                }

                if (invalidValue) {
                    throw new InvalidValueException(cssValue
                            + " is an invalid value. The value format should be as for example 75px, 85%, 125px 10px, 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);
    }

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

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

        boolean valid = false;
        for (final String each : cssValueParts) {
            final Object[] lengthValueAndUnit = CssLengthUtil
                    .getLengthValueAsPremitiveAndUnit(each);
            if (lengthValueAndUnit.length != 2) {
                return false;
            }
            valid = true;
        }
        return valid;
    }
}