// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.joy.page;

import jodd.db.oom.DbOomQuery;
import jodd.db.oom.sqlgen.DbSqlBuilder;
import jodd.util.StringUtil;

import java.util.List;
import java.util.Map;

import static jodd.db.oom.DbOomQuery.query;
import static jodd.db.oom.sqlgen.DbSqlBuilder.sql;

/**
 * Database pager.
 */
public abstract class DbPager {

	/**
	 * Returns default page request when passed one is null.
	 * This usually happens on initial page view, when no page request is created.
	 * Returned PageRequest defines global defaults.
	 */
	protected PageRequest getDefaultPageRequest() {
		return new PageRequest();
	}

	/**
	 * Performs the pagination with given {@link jodd.joy.page.PageRequest}.
	 *
	 * @param pageRequest page request, may be null, then the {@link #getDefaultPageRequest() default page request} will be used
	 * @param sql SQL query that lists all items
	 * @param params SQL query parameters or null
	 * @param sortColumns array of all column names
	 * @param target db entities for mapping (as usual in DbOom)
	 *
	 * @see #page(String, java.util.Map, int, int, String, boolean, Class[])
	 */
	public <T> PageData<T> page(PageRequest pageRequest, final String sql, final Map params, final String[] sortColumns, final Class[] target) {
		if (pageRequest == null) {
			pageRequest = getDefaultPageRequest();
		}

		// check sort

		String sortColumName = null;
		boolean ascending = true;

		int sort = pageRequest.getSort();
		if (sort != 0) {
			ascending = sort > 0;
			if (!ascending) {
				sort = -sort;
			}
			int index = sort - 1;

			if (index >= sortColumns.length) {
				index = 1;
			}
			sortColumName = sortColumns[index];
		}

		// page

		int page = pageRequest.getPage();
		int pageSize = pageRequest.getSize();

		PageData<T> pageData = page(sql, params, page, pageSize, sortColumName, ascending, target);

		// fix the out-of-bounds

		if (pageData.getItems().isEmpty() && pageData.currentPage != 0) {
			if (pageData.currentPage != page) {
				// out of bounds
				int newPage = pageData.getCurrentPage();
				pageData = page(sql, params, newPage, pageSize, sortColumName, ascending, target);
			}
		}

		return pageData;
	}

	/**
	 * Pages given page.
	 *
	 * @param sql sql query that lists all items
	 * @param params map of SQL parameters
	 * @param page current page to show
	 * @param pageSize number of items to show
	 * @param sortColumnName name of sorting column, null for no sorting
	 * @param ascending true for ascending order
	 * @param target db entities for mapping (sa usual in DbOom)
	 */
	protected <T> PageData<T> page(String sql, final Map params, final int page, final int pageSize, final String sortColumnName, final boolean ascending, final Class[] target) {
		if (sortColumnName != null) {
			sql = buildOrderSql(sql, sortColumnName, ascending);
		}

		int from = (page - 1) * pageSize;

		String pageSql = buildPageSql(sql, from, pageSize);
		DbSqlBuilder dbsql = sql(pageSql);

		DbOomQuery query = query(dbsql);
		query.setMaxRows(pageSize);
		query.setFetchSize(pageSize);
		query.setMap(params);

		List<T> list = query.list(pageSize, target);
		query.close();

		String countSql = buildCountSql(sql);
		dbsql = sql(countSql);
		query = query(dbsql);
		query.setMap(params);
		long count = query.executeCount();
		query.close();

		return new PageData<>(page, (int) count, pageSize, list);
	}

	// ---------------------------------------------------------------- abstract

	/**
	 * Builds order SQL string.
	 * Invoked before all other SQL modification.
	 */
	protected abstract String buildOrderSql(String sql, String column, boolean ascending);

	/**
	 * Builds page SQL string.
	 * Returned SQL string may return more than pageSize elements,
	 * but only pageSize will be consumed.
	 */
	protected abstract String buildPageSql(String sql, int from, int pageSize);

	/**
	 * Builds SQL for retrieving total number of results.
	 */
	protected abstract String buildCountSql(String sql);

	// ---------------------------------------------------------------- sql manipulation

	/**
	 * Removes the first 'select' from the sql query.
	 */
	protected String removeSelect(String sql) {
		int ndx = StringUtil.indexOfIgnoreCase(sql, "select");
		if (ndx != -1) {
			sql = sql.substring(ndx + 6);	// select.length()
		}
		return sql;
	}

	/**
	 * Removes the first part of the sql up to the relevant 'from'.
	 * Tries to detect sub-queries in the 'select' part.
	 */
	protected String removeToFrom(String sql) {
		int from = 0;
		int fromCount = 1;
		int selectCount = 0;
		int lastNdx = 0;
		while (true) {
			int ndx = StringUtil.indexOfIgnoreCase(sql, "from", from);
			if (ndx == -1) {
				break;
			}

			// count selects in left part
			String left = sql.substring(lastNdx, ndx);
			selectCount += StringUtil.countIgnoreCase(left, "select");

			if (fromCount >= selectCount) {
				sql = sql.substring(ndx);
				break;
			}

			// find next 'from'
			lastNdx = ndx;
			from = ndx + 4;
			fromCount++;
		}
		return sql;
	}

	/**
	 * Removes everything from last "order by".
	 */
	protected String removeLastOrderBy(String sql) {
		int ndx = StringUtil.lastIndexOfIgnoreCase(sql, "order by");
		if (ndx != -1) {
			int ndx2 = sql.lastIndexOf(sql, ')');
			if (ndx > ndx2) {
				sql = sql.substring(0, ndx);
			}
		}
		return sql;
	}

}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.net;

import jodd.io.StreamUtil;
import jodd.util.BinarySearchBase;
import jodd.util.CharUtil;
import jodd.util.StringUtil;

import java.io.InputStream;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * HTML decoder.
 */
public class HtmlDecoder {

	private static final Map<String, char[]> ENTITY_MAP;
	private static final char[][] ENTITY_NAMES;

	static {
		Properties entityReferences = new Properties();

		String propertiesName = HtmlDecoder.class.getSimpleName() + ".properties";

		InputStream is = HtmlDecoder.class.getResourceAsStream(propertiesName);

		try {
			entityReferences.load(is);
		}
		catch (Exception ex) {
			throw new IllegalStateException("Can't load properties", ex);
		} finally {
			StreamUtil.close(is);
		}

		ENTITY_MAP = new HashMap<>(entityReferences.size());

		Enumeration keys = entityReferences.propertyNames();
		while (keys.hasMoreElements()) {
			String name = (String) keys.nextElement();
			String values = entityReferences.getProperty(name);
			String[] array = StringUtil.splitc(values, ',');

			char[] chars;

			String hex = array[0];
			char value = (char) Integer.parseInt(hex, 16);

			if (array.length == 2) {
				String hex2 = array[1];
				char value2 = (char) Integer.parseInt(hex2, 16);

				chars = new char[]{value, value2};
			} else {
				chars = new char[]{value};
			}

			ENTITY_MAP.put(name, chars);
		}

		// create sorted list of entry names

		ENTITY_NAMES = new char[ENTITY_MAP.size()][];

		int i = 0;
		for (String name : ENTITY_MAP.keySet()) {
			ENTITY_NAMES[i++] = name.toCharArray();
		}

		Arrays.sort(ENTITY_NAMES, Comparator.comparing(String::new));
	}

	/**
	 * Decodes HTML text. Assumes that all character references are properly closed with semi-colon.
	 */
	public static String decode(final String html) {

		int ndx = html.indexOf('&');
		if (ndx == -1) {
			return html;
		}

		StringBuilder result = new StringBuilder(html.length());

		int lastIndex = 0;
		int len = html.length();
mainloop:
		while (ndx != -1) {
			result.append(html.substring(lastIndex, ndx));

			lastIndex = ndx;
			while (html.charAt(lastIndex) != ';') {
				lastIndex++;
				if (lastIndex == len) {
					lastIndex = ndx;
					break mainloop;
				}
			}

			if (html.charAt(ndx + 1) == '#') {
				// decimal/hex
				char c = html.charAt(ndx + 2);
				int radix;
				if ((c == 'x') || (c == 'X')) {
					radix = 16;
					ndx += 3;
				} else {
					radix = 10;
					ndx += 2;
				}

				String number = html.substring(ndx, lastIndex);
				int i = Integer.parseInt(number, radix);
				result.append((char) i);
				lastIndex++;
			} else {
				// token
				String encodeToken = html.substring(ndx + 1, lastIndex);

				char[] replacement = ENTITY_MAP.get(encodeToken);
				if (replacement == null) {
					result.append('&');
					lastIndex = ndx + 1;
				} else {
					result.append(replacement);
					lastIndex++;
				}
			}
			ndx = html.indexOf('&', lastIndex);
		}
		result.append(html.substring(lastIndex));
		return result.toString();
	}

	private static final class Ptr {
		public int offset;
		public char c;
	}

	/**
	 * Detects the longest character reference name on given position in char array.
	 */
	public static String detectName(final char[] input, int ndx) {
		final Ptr ptr = new Ptr();

		int firstIndex = 0;
		int lastIndex = ENTITY_NAMES.length - 1;
		int len = input.length;
		char[] lastName = null;

		final BinarySearchBase binarySearch = new BinarySearchBase() {
			@Override
			protected int compare(final int index) {
				char[] name = ENTITY_NAMES[index];

				if (ptr.offset >= name.length) {
					return -1;
				}

				return name[ptr.offset] - ptr.c;
			}
		};

		while (true) {
			ptr.c = input[ndx];

			if (!CharUtil.isAlphaOrDigit(ptr.c)) {
				return lastName != null ? new String(lastName) : null;
			}

			firstIndex = binarySearch.findFirst(firstIndex, lastIndex);
			if (firstIndex < 0) {
				return lastName != null ? new String(lastName) : null;
			}

			char[] element = ENTITY_NAMES[firstIndex];

			if (element.length == ptr.offset + 1) {
				// total match, remember position, continue for finding the longer name
				lastName = ENTITY_NAMES[firstIndex];
			}

			lastIndex = binarySearch.findLast(firstIndex, lastIndex);

			if (firstIndex == lastIndex) {
				// only one element found, check the rest
				for (int i = ptr.offset; i < element.length; i++) {
					if (element[i] != input[ndx]) {
						return lastName != null ? new String(lastName) : null;
					}
					ndx++;
				}
				return new String(element);
			}

			ptr.offset++;

			ndx++;
			if (ndx == len) {
				return lastName != null ? new String(lastName) : null;
			}
		}
	}

	/**
	 * Returns replacement chars for given character reference.
	 */
	public static char[] lookup(final String name) {
		return ENTITY_MAP.get(name);
	}

}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.db.oom.sqlgen.chunks;

import jodd.db.oom.DbEntityColumnDescriptor;
import jodd.db.oom.DbEntityManager;

/**
 * {@link ValueChunk Value} for the <b>last</b> column.
 */
public class ColumnValueChunk extends ValueChunk {

	public ColumnValueChunk(final DbEntityManager dbEntityManager, final String name, final Object value) {
		this(dbEntityManager, name, value, null);
	}

	public ColumnValueChunk(final DbEntityManager dbEntityManager, final String objReference) {
		this(dbEntityManager, null, null, objReference);
	}

	private ColumnValueChunk(final DbEntityManager dbEntityManager, final String name, final Object value, final String objReference) {
		super(dbEntityManager, name, value, objReference);
	}

	// ---------------------------------------------------------------- define
	@Override
	protected void defineParameter(final StringBuilder query, final String name, final Object value, DbEntityColumnDescriptor dec) {
		if (dec == null) {
			dec = templateData.lastColumnDec;
		}
		super.defineParameter(query, name, value, dec);
	}

}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.joy.page.db;

import jodd.joy.page.DbPager;

/**
 * HSQLDB database pager.
 */
public class HsqlDbPager extends DbPager {

	/**
	 * Appends ORDER BY keyword.
	 */
	@Override
	protected String buildOrderSql(String sql, final String column, final boolean ascending) {
		sql += " order by " + column;
		if (!ascending) {
			sql += " desc";
		}
		return sql;
	}

	/**
	 * Builds page sql using LIMIT keyword after the SELECT.
	 */
	@Override
	protected String buildPageSql(String sql, final int from, final int pageSize) {
		sql = removeSelect(sql);
		return "select LIMIT " + from + ' ' + pageSize + sql;
	}

	/**
	 * Builds count sql using COUNT(*).
	 */
	@Override
	protected String buildCountSql(String sql) {
		sql = removeToFrom(sql);
		sql = removeLastOrderBy(sql);
		return "select count(*) " + sql;
	}
}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.db.oom;

import jodd.bean.BeanUtil;
import jodd.db.oom.naming.ColumnNamingStrategy;
import jodd.db.oom.naming.TableNamingStrategy;
import jodd.introspector.ClassIntrospector;
import jodd.introspector.PropertyDescriptor;
import jodd.util.StringPool;
import jodd.util.StringUtil;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;

/**
 * Holds all information about some entity type, such as table name and {@link DbEntityColumnDescriptor columns data}.
 */
public class DbEntityDescriptor<E> {

	public DbEntityDescriptor(
			final Class<E> type,
			final String schemaName,
			final TableNamingStrategy tableNamingStrategy,
			final ColumnNamingStrategy columnNamingStrategy) {

		this.type = type;
		this.entityName = type.getSimpleName();
		this.isAnnotated = DbMetaUtil.resolveIsAnnotated(type);
		this.schemaName = DbMetaUtil.resolveSchemaName(type, schemaName);
		this.columnNamingStrategy = columnNamingStrategy;
		this.mappedTypes = DbMetaUtil.resolveMappedTypes(type);

		this.tableNameForQuery = DbMetaUtil.resolveTableName(type, tableNamingStrategy);

		if (StringUtil.detectQuoteChar(tableNameForQuery) != 0) {
			this.tableName = StringUtil.substring(tableNameForQuery, 1, -1);
		} else {
			this.tableName = tableNameForQuery;
		}
	}

	// ---------------------------------------------------------------- type and table

	private final Class<E> type;
	private final String entityName;
	private final boolean isAnnotated;
	private final String tableName;
	private final String tableNameForQuery;
	private final String schemaName;
	private final ColumnNamingStrategy columnNamingStrategy;
	private final Class[] mappedTypes;

	/**
	 * Returns entity type.
	 */
	public Class<E> getType() {
		return type;
	}

	/**
	 * Returns true if type is annotated with {@link jodd.db.oom.meta.DbTable}.
	 */
	public boolean isAnnotated() {
		return isAnnotated;
	}

	/**
	 * Returns table name to which the entity is mapped.
	 */
	public String getTableName() {
		return tableName;
	}

	/**
	 * Returns table name to be used when generating queries.
	 */
	public String getTableNameForQuery() {
		return tableNameForQuery;
	}

	/**
	 * Returns type name.
	 */
	public String getEntityName() {
		return entityName;
	}

	/**
	 * Returns schema name or null if not available.
	 */
	public String getSchemaName() {
		return schemaName;
	}

	/**
	 * Returns mapped types.
	 */
	public Class[] getMappedTypes() {
		return mappedTypes;
	}

	// ---------------------------------------------------------------- columns and fields

	private DbEntityColumnDescriptor[] columnDescriptors;
	private DbEntityColumnDescriptor[] idColumnDescriptors;

	/**
	 * Returns the array of column descriptors.
	 */
	public DbEntityColumnDescriptor[] getColumnDescriptors() {
		init();
		return columnDescriptors;
	}

	// ---------------------------------------------------------------- initialization

	/**
	 * Lazy initialization of descriptor.
	 */
	protected void init() {
		if (columnDescriptors == null) {
			resolveColumnsAndProperties(type);
		}
	}

	/**
	 * Resolves list of all columns and properties.
	 */
	private void resolveColumnsAndProperties(final Class type) {
		PropertyDescriptor[] allProperties = ClassIntrospector.get().lookup(type).getAllPropertyDescriptors();
		List<DbEntityColumnDescriptor> decList = new ArrayList<>(allProperties.length);
		int idcount = 0;

		HashSet<String> names = new HashSet<>(allProperties.length);

		for (PropertyDescriptor propertyDescriptor : allProperties) {

			DbEntityColumnDescriptor dec =
					DbMetaUtil.resolveColumnDescriptors(this, propertyDescriptor, isAnnotated, columnNamingStrategy);

			if (dec != null) {
				if (!names.add(dec.getColumnName())) {
					throw new DbOomException("Duplicate column name: " + dec.getColumnName());
				}

				decList.add(dec);

				if (dec.isId) {
					idcount++;
				}
			}
		}
		if (decList.isEmpty()) {
			throw new DbOomException("No column mappings in entity: " + type);
		}
		columnDescriptors = decList.toArray(new DbEntityColumnDescriptor[0]);
		Arrays.sort(columnDescriptors);

		// extract ids from sorted list
		if (idcount > 0) {
			idColumnDescriptors = new DbEntityColumnDescriptor[idcount];
			idcount = 0;
			for (DbEntityColumnDescriptor dec : columnDescriptors) {
				if (dec.isId) {
					idColumnDescriptors[idcount++] = dec;
				}
			}
		}
	}

	// ---------------------------------------------------------------- finders

	/**
	 * Finds column descriptor by column name. Case is ignored.
	 */
	public DbEntityColumnDescriptor findByColumnName(final String columnName) {
		if (columnName == null) {
			return null;
		}
		init();
		for (DbEntityColumnDescriptor columnDescriptor : columnDescriptors) {
			if (columnDescriptor.columnName.equalsIgnoreCase(columnName)) {
				return columnDescriptor;
			}
		}
		return null;
	}
	/**
	 * Finds column descriptor by property name.
	 */
	public DbEntityColumnDescriptor findByPropertyName(final String propertyName) {
		if (propertyName == null) {
			return null;
		}
		init();
		for (DbEntityColumnDescriptor columnDescriptor : columnDescriptors) {
			if (columnDescriptor.propertyName.equals(propertyName)) {
				return columnDescriptor;
			}
		}
		return null;
	}

	/**
	 * Returns property name for specified column name.
	 */
	public String getPropertyName(final String columnName) {
		DbEntityColumnDescriptor dec = findByColumnName(columnName);
		return dec == null ? null : dec.propertyName;
	}

	/**
	 * Returns column name for specified property name..
	 */
	public String getColumnName(final String propertyName) {
		DbEntityColumnDescriptor dec = findByPropertyName(propertyName);
		return dec == null ? null : dec.columnName;
	}

	// ---------------------------------------------------------------- column work

	/**
	 * Returns total number of columns.
	 */
	public int getColumnsCount() {
		init();
		return columnDescriptors.length;
	}

	/**
	 * Returns total number of identity columns.
	 */
	public int getIdColumnsCount() {
		init();
		return idColumnDescriptors == null ? 0 : idColumnDescriptors.length;
	}

	/**
	 * Returns true if entity has one ID column.
	 */
	public boolean hasIdColumn() {
		return getIdColumnsCount() == 1;
	}

	private void ensureSingleIdColumn() {
		init();
		if (idColumnDescriptors == null) {
			throw new DbOomException("No identity column in entity: " + entityName);
		} else if (idColumnDescriptors.length > 1) {
			throw new DbOomException("More then one identity column in entity: " + entityName);
		}
	}

	/**
	 * Returns the identity column name of column marked as identity.
	 * Throws an exception if table has composite primary key.
	 */
	public String getIdColumnName() {
		ensureSingleIdColumn();
		return idColumnDescriptors[0].getColumnName();
	}

	/**
	 * Returns the first property name of column marked as identity.
	 * Throws an exception if table has composite primary key.
	 */
	public String getIdPropertyName() {
		ensureSingleIdColumn();
		return idColumnDescriptors[0].getPropertyName();
	}

	/**
	 * Returns ID value for given entity instance.
	 */
	public Object getIdValue(final E object) {
		final String propertyName = getIdPropertyName();
		return BeanUtil.declared.getProperty(object, propertyName);
	}

	/**
	 * Sets ID value for given entity.
	 */
	public void setIdValue(final E object, final Object value) {
		final String propertyName = getIdPropertyName();
		BeanUtil.declared.setProperty(object, propertyName, value);
	}

	/**
	 * Returns unique key for this entity. Returned key
	 * is built from entity class and id value.
	 */
	public String getKeyValue(final E object) {
		Object idValue = getIdValue(object);

		String idValueString = idValue == null ?  StringPool.NULL : idValue.toString();

		return type.getName().concat(StringPool.COLON).concat(idValueString);
	}

	// ---------------------------------------------------------------- toString

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("DbEntity: ");
		if (schemaName != null) {
			sb.append(schemaName);
			sb.append('.');
		}
		sb.append(tableName);
		return sb.toString();
	}

}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.util;

/**
 * StringBand is a faster alternative to StringBuilder.
 * Instead of adding strings, they are stored in an internal array. Only at the
 * end of concatenation, when toString() is invoked, strings are
 * joined together in a very fast manner.
 *

 * To make StringBand even faster, predict the number of joined
 * strings (and not the final string size)!
 */
public class StringBand {

	private static final int DEFAULT_ARRAY_CAPACITY = 16;

	private String[] array;
	private int index;
	private int length;

	/**
	 * Creates an empty StringBand.
	 */
	public StringBand() {
		array = new String[DEFAULT_ARRAY_CAPACITY];
	}

	/**
	 * Creates an empty StringBand with provided capacity.
	 * Capacity refers to internal string array (i.e. number of
	 * joins) and not the total string size.
	 */
	public StringBand(final int initialCapacity) {
		array = new String[initialCapacity];
	}

	/**
	 * Creates StringBand with provided content.
	 */
	public StringBand(final String s) {
		this();
		array[0] = s;
		index = 1;
		length = s.length();
	}

	public StringBand(final Object o) {
		this(String.valueOf(o));
	}

	// ---------------------------------------------------------------- append

	/**
	 * Appends boolean value.
	 */
	public StringBand append(final boolean b) {
		return append(b ? StringPool.TRUE : StringPool.FALSE);
	}

	/**
	 * Appends double value.
	 */
	public StringBand append(final double d) {
		return append(Double.toString(d));
	}

	/**
	 * Appends float value.
	 */
	public StringBand append(final float f) {
		return append(Float.toString(f));
	}

	/**
	 * Appends int value.
	 */
	public StringBand append(final int i) {
		return append(Integer.toString(i));
	}

	/**
	 * Appends long value.
	 */
	public StringBand append(final long l) {
		return append(Long.toString(l));
	}

	/**
	 * Appends short value.
	 */
	public StringBand append(final short s) {
		return append(Short.toString(s));
	}

	/**
	 * Appends a character. This is not efficient
	 * as in StringBuilder, since new string is created.
	 */
	public StringBand append(final char c) {
		return append(String.valueOf(c));
	}

	/**
	 * Appends byte value.
	 */
	public StringBand append(final byte b) {
		return append(Byte.toString(b));
	}

	/**
	 * Appends string representation of an object.
	 * If null, the 'null' string
	 * will be appended.
	 */
	public StringBand append(final Object obj) {
		return append(String.valueOf(obj));
	}

	/**
	 * Appends a string.
	 */
	public StringBand append(String s) {
		if (s == null) {
			s = StringPool.NULL;
		}

		if (index >= array.length) {
			expandCapacity();
		}

		array[index++] = s;
		length += s.length();

		return this;
	}

	// ---------------------------------------------------------------- size

	/**
	 * Returns array capacity.
	 */
	public int capacity() {
		return array.length;
	}

	/**
	 * Returns total string length.
	 */
	public int length() {
		return length;
	}

	/**
	 * Returns current index of string array.
	 */
	public int index() {
		return index;
	}

	/**
	 * Specifies the new index.
	 */
	public void setIndex(final int newIndex) {
		if (newIndex < 0) {
			throw new ArrayIndexOutOfBoundsException(newIndex);
		}

		if (newIndex > array.length) {
			String[] newArray = new String[newIndex];
			System.arraycopy(array, 0, newArray, 0, index);
			array = newArray;
		}

		if (newIndex > index) {
			for (int i = index; i < newIndex; i++) {
				array[i] = StringPool.EMPTY;
			}
		} else if (newIndex < index) {
			for (int i = newIndex; i < index; i++) {
				array[i] = null;
			}
		}

		index = newIndex;
		length = calculateLength();
	}

	// ---------------------------------------------------------------- values

	/**
	 * Returns char at given position.
	 * This method is not fast as it calculates
	 * the right string array element and the offset!
	 */
	public char charAt(final int pos) {
		int len = 0;
		for (int i = 0; i < index; i++) {
			int newlen = len + array[i].length();
			if (pos < newlen) {
				return array[i].charAt(pos - len);
			}
			len = newlen;
		}
		throw new IllegalArgumentException("Invalid char index");
	}

	/**
	 * Returns string at given position.
	 */
	public String stringAt(final int index) {
		if (index >= this.index) {
			throw new ArrayIndexOutOfBoundsException();
		}
		return array[index];
	}

	/**
	 * Joins together all strings into one.
	 */
	@Override
	public String toString() {

		// special cases
		switch (index) {
			case 0:
				return StringPool.EMPTY;
			case 1:
				return array[0];
			case 2:
				return array[0] + array[1];
		}

		// join strings
		char[] destination = new char[length];
		int start = 0;
		for (int i = 0; i < index; i++) {
			String s = array[i];

			int len = s.length();
			s.getChars(0, len, destination, start);

			start += len;
		}

		return new String(destination);
	}

	// ---------------------------------------------------------------- utils

	/**
	 * Expands internal string array by multiplying its size by 2.
	 */
	protected void expandCapacity() {
		String[] newArray = new String[array.length << 1];
		System.arraycopy(array, 0, newArray, 0, index);
		array = newArray;
	}

	/**
	 * Calculates string length.
	 */
	protected int calculateLength() {
		int len = 0;
		for (int i = 0; i < index; i++) {
			len += array[i].length();
		}
		return len;
	}

}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.petite;

import jodd.cache.TypeCache;
import jodd.log.Logger;
import jodd.log.LoggerFactory;
import jodd.petite.proxetta.ProxettaBeanDefinition;
import jodd.petite.scope.Scope;
import jodd.proxetta.Proxetta;
import jodd.proxetta.ProxyAspect;
import jodd.proxetta.impl.ProxyProxetta;
import jodd.proxetta.impl.ProxyProxettaFactory;
import jodd.proxetta.pointcuts.AllMethodsPointcut;
import jodd.util.ArraysUtil;
import jodd.util.ClassUtil;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**
 * Manager for mixing scopes.
 * 'Mixed scopes' is the situation when 'smaller' scope
 * is injected into the 'bigger' scope. Trivial example
 * is when prototype scoped bean is injected into
 * singleton bean.
 *

 * Mixed scopes are handled using smart factory/proxy-wrapper singletons
 * instead of 'smaller' scoped beans. On each method call of
 * wrapped factory, smaller scoped bean will be looked up and
 * that instance will be used for passing by the method call.
 *

 * Manager also has to determine if scope combination is candidate for
 * mixed scopes.
 */
public class ScopedProxyManager {

	private static final Logger log = LoggerFactory.getLogger(ScopedProxyManager.class);

	protected ProxyAspect aspect = new ProxyAspect(ScopedProxyAdvice.class, new AllMethodsPointcut());

	protected TypeCache<Class> proxyClasses = TypeCache.createDefault();
	protected Map<String, Object> proxies = new HashMap<>();

	public ScopedProxyManager() {
		log.debug("ScopedProxyManager created");
	}

	/**
	 * Returns scoped proxy bean if injection scopes are mixed on some injection point.
	 * May return null if mixing scopes is not detected.
	 */
	public Object lookupValue(final PetiteContainer petiteContainer, final BeanDefinition targetBeanDefinition, final BeanDefinition refBeanDefinition) {
		Scope targetScope = targetBeanDefinition.scope;
		Scope refBeanScope = refBeanDefinition.scope;

		boolean detectMixedScopes = petiteContainer.config().isDetectMixedScopes();
		boolean wireScopedProxy = petiteContainer.config().isWireScopedProxy();

		// when target scope is null then all beans can be injected into it
		// similar to prototype scope
		if (targetScope != null && !targetScope.accept(refBeanScope)) {

			if (!wireScopedProxy) {
				if (detectMixedScopes) {
					throw new PetiteException(createMixingMessage(targetBeanDefinition, refBeanDefinition));
				}
				return null;
			}

			if (detectMixedScopes) {
				if (log.isWarnEnabled()) {
					log.warn(createMixingMessage(targetBeanDefinition, refBeanDefinition));
				}
			} else {
				if (log.isDebugEnabled()) {
					log.debug(createMixingMessage(targetBeanDefinition, refBeanDefinition));
				}
			}

			String scopedProxyBeanName = refBeanDefinition.name;

			Object proxy = proxies.get(scopedProxyBeanName);

			if (proxy == null) {
				proxy = createScopedProxyBean(petiteContainer, refBeanDefinition);

				proxies.put(scopedProxyBeanName, proxy);
			}

			return proxy;
		}

		return null;
	}

	/**
	 * Creates mixed scope message.
	 */
	protected String createMixingMessage(final BeanDefinition targetBeanDefinition, final BeanDefinition refBeanDefinition) {
		return "Scopes mixing detected: " +
				refBeanDefinition.name + "@" + refBeanDefinition.scope.getClass().getSimpleName() + " -> " +
				targetBeanDefinition.name + "@" + targetBeanDefinition.scope.getClass().getSimpleName();
	}

	/**
	 * Creates scoped proxy bean for given bean definition.
	 */
	protected Object createScopedProxyBean(final PetiteContainer petiteContainer, final BeanDefinition refBeanDefinition) {

		Class beanType = refBeanDefinition.type;

		Class proxyClass = proxyClasses.get(beanType);

		if (proxyClass == null) {
			// create proxy class only once

			if (refBeanDefinition instanceof ProxettaBeanDefinition) {
				// special case, double proxy!

				ProxettaBeanDefinition pbd =
					(ProxettaBeanDefinition) refBeanDefinition;

				ProxyProxetta proxetta = Proxetta.proxyProxetta().withAspects(ArraysUtil.insert(pbd.proxyAspects, aspect, 0));

				proxetta.setClassNameSuffix("$ScopedProxy");
				proxetta.setVariableClassName(true);

				ProxyProxettaFactory builder = proxetta.proxy().setTarget(pbd.originalTarget);

				proxyClass = builder.define();

				proxyClasses.put(beanType, proxyClass);
			}
			else {
				ProxyProxetta proxetta = Proxetta.proxyProxetta().withAspect(aspect);

				proxetta.setClassNameSuffix("$ScopedProxy");
				proxetta.setVariableClassName(true);

				ProxyProxettaFactory builder = proxetta.proxy().setTarget(beanType);

				proxyClass = builder.define();

				proxyClasses.put(beanType, proxyClass);
			}
		}

		Object proxy;

		try {
			proxy = ClassUtil.newInstance(proxyClass);

			Field field = proxyClass.getField("$__petiteContainer$0");

			field.set(proxy, petiteContainer);

			field = proxyClass.getField("$__name$0");

			field.set(proxy, refBeanDefinition.name);
		} catch (Exception ex) {
			throw new PetiteException(ex);
		}

		return proxy;
	}

}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.servlet.tag;

import jodd.util.LoopIterator;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;

/**
 * Support for looping tags.
 */
public abstract class LoopingTagSupport extends SimpleTagSupport {

	protected int start;
	protected int end;
	protected int step = 1;
	protected String status;
	protected int modulus = 2;

	// ---------------------------------------------------------------- attributes

	/**
	 * Sets the first loop value (inclusive).
	 */
	public void setStart(final int start) {
		this.start = start;
	}

	/**
	 * Sets the end loop value (inclusive).
	 */
	public void setEnd(final int end) {
		this.end = end;
	}

	/**
	 * Specifies the loop step. If step is 0, it will be set to +1 or -1,
	 * depending on start and end values.
	 */
	public void setStep(final int step) {
		this.step = step;
	}

	/**
	 * Specifies {@link jodd.util.LoopIterator status} variable name. If omitted, status will not be used.
	 */
	public void setStatus(final String status) {
		this.status = status;
	}

	/**
	 * Specifies modulus value for the looping status.
	 */
	public void setModulus(final int modulus) {
		this.modulus = modulus;
	}

	// ---------------------------------------------------------------- helper methods

	/**
	 * Shorter variant of {@link #prepareStepDirection(boolean, boolean)}.
	 */
	protected void prepareStepDirection() {
		if (step == 0) {
			step = (start <= end) ? 1 : -1;
		}
	}

	/**
	 * Prepares step value. If step is 0, it will be set to +1 or -1, depending on start and end value.
	 *

	 * If autoDirection flag is true then it is assumed that step is positive,
	 * and that direction (step sign) should be detected from start and end value.
	 *

	 * If checkDirection flag is true than it checks loop direction (step sign) based on
	 * start and end value. Throws an exception if direction is invalid.
	 * If autoDirection is set, direction checking is skipped.
	 */
	protected void prepareStepDirection(final boolean autoDirection, final boolean checkDirection) {
		if (step == 0) {
			step = (start <= end) ? 1 : -1;
			return;
		}
		if (autoDirection) {
			if (step < 0) {
				throw new IllegalArgumentException("Step value can't be negative: " + step);
			}
			if (start > end) {
				step = -step;
			}
			return;
		}
		if (checkDirection) {
			if (start < end) {
				if (step < 0) {
					throw new IllegalArgumentException("Negative step value for increasing loop");
				}
				return;
			}
			if (start > end) {
				if (step > 0) {
					throw new IllegalArgumentException("Positive step value for decreasing loop");
				}
			}
		}
	}

	/**
	 * Loops body.
	 */
	protected void loopBody() throws JspException {
		JspFragment body = getJspBody();
		if (body == null) {
			return;
		}

		LoopIterator loopIterator = new LoopIterator(start, end, step, modulus);
		if (status != null) {
			getJspContext().setAttribute(status, loopIterator);
		}
		while (loopIterator.next()) {
			TagUtil.invokeBody(body);
		}
		if (status != null) {
			getJspContext().removeAttribute(status);
		}
	}
}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.io.watch;

import jodd.io.FileUtil;
import jodd.mutable.MutableLong;
import jodd.util.StringPool;
import jodd.util.Wildcard;
import jodd.util.function.Consumers;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Timer;
import java.util.TimerTask;
import java.util.function.Consumer;

public class DirWatcher {

	protected final File dir;
	protected HashMap<File, MutableLong> map = new HashMap<>();
	protected int filesCount;
	protected Consumers<DirWatcherEvent> listeners = Consumers.empty();
	protected String[] patterns;

	/**
	 * Creates new watcher on specified directory.
	 * You can set file patterns {@link #monitor(String...) later}.
	 */
	public DirWatcher(final String dir) {
		this(dir, null);
	}

	/**
	 * Creates new watched on specified directory with given set of
	 * wildcard patterns for file names.
	 */
	public DirWatcher(final String dirName, final String... patterns) {
		this.dir = new File(dirName);

		if (!dir.exists() || !dir.isDirectory()) {
			throw new DirWatcherException("Invalid watch dir: " + dirName);
		}

		this.patterns = patterns;
	}

	/**
	 * Initializes dir watcher by reading all files
	 * from watched folder.
	 */
	protected void init() {
		File[] filesArray = dir.listFiles();

		filesCount = 0;

		if (filesArray != null) {
			filesCount = filesArray.length;

			for (File file : filesArray) {
				if (!acceptFile(file)) {
					continue;
				}

				map.put(file, new MutableLong(file.lastModified()));
			}
		}
	}

	// ---------------------------------------------------------------- flags

	protected boolean ignoreDotFiles = true;
	protected boolean startBlank = false;

	/**
	 * Enables or disables if dot files should be watched.
	 */
	public DirWatcher ignoreDotFiles(final boolean ignoreDotFiles) {
		this.ignoreDotFiles = ignoreDotFiles;
		return this;
	}

	/**
	 * Defines if watcher should start blank and consider all present
	 * files as {@link jodd.io.watch.DirWatcherEvent.Type#CREATED created}.
	 * By default all existing files will consider as existing ones.
	 */
	public DirWatcher startBlank(final boolean startBlank) {
		this.startBlank = startBlank;
		return this;
	}

	/**
	 * Defines patterns to scan.
	 */
	public DirWatcher monitor(final String... patterns) {
		this.patterns = patterns;
		return this;
	}

	// ---------------------------------------------------------------- accept

	/**
	 * Accepts if a file is going to be watched.
	 */
	protected boolean acceptFile(final File file) {
		if (!file.isFile()) {
			return false;			// ignore non-files
		}

		String fileName = file.getName();

		if (ignoreDotFiles) {
			if (fileName.startsWith(StringPool.DOT)) {
				return false;        // ignore hidden files
			}
		}

		if (patterns == null) {
			return true;
		}

		return Wildcard.matchOne(fileName, patterns) != -1;
	}

	// ---------------------------------------------------------------- watch file

	protected File watchFile;
	protected long watchFileLastAccessTime;

	/**
	 * Enables usage of default watch file (".watch.ready").
	 */
	public DirWatcher useWatchFile() {
		return useWatchFile(".watch.ready");
	}

	/**
	 * Enables usage of provided watch file.
	 */
	public DirWatcher useWatchFile(final String name) {
		watchFile = new File(dir, name);

		if (!watchFile.isFile() || !watchFile.exists()) {
			try {
				FileUtil.touch(watchFile);
			} catch (IOException ioex) {
				throw new DirWatcherException("Invalid watch file: " + name, ioex);
			}
		}

		watchFileLastAccessTime = watchFile.lastModified();

		return this;
	}

	// ---------------------------------------------------------------- timer

	protected Timer timer;

	/**
	 * Starts the watcher.
	 */
	public void start(final long pollingInterval) {
		if (timer == null) {
			if (!startBlank) {
				init();
			}
			timer = new Timer(true);
			timer.schedule(new WatchTask(), 0, pollingInterval);
		}
	}
	/**
	 * Stops the watcher.
	 */
	public void stop() {
		if (timer != null) {
			timer.cancel();
			timer = null;
		}
	}

	// ---------------------------------------------------------------- timer

	public class WatchTask extends TimerTask {

		protected boolean running;

		@Override
		public final void run() {
			if (running) {
				// if one task takes too long, don't fire another one
				return;
			}
			running = true;

			if (watchFile != null) {
				// wait for watch file changes
				long last = watchFile.lastModified();

				if (last <= watchFileLastAccessTime) {
					running = false;
					return;
				}
				watchFileLastAccessTime = last;
			}

			// scan!

			File[] filesArray = dir.listFiles();

			if (filesArray == null) {
				running = false;
				return;
			}

			HashSet<File> deletedFiles = null;

			// check if there might be a delete file
			if (filesArray.length < filesCount) {
				deletedFiles = new HashSet<>(map.keySet());
			}

			filesCount = filesArray.length;

			// scan the files and check for modification/addition
			for (File file : filesArray) {
				if (!acceptFile(file)) {
					continue;
				}

				MutableLong currentTime = map.get(file);

				if (deletedFiles != null) {
					deletedFiles.remove(file);
				}

				long lastModified = file.lastModified();

				if (currentTime == null) {
					// new file
					map.put(file, new MutableLong(lastModified));
					onChange(DirWatcherEvent.Type.CREATED, file);
				}
				else if (currentTime.longValue() != lastModified) {
					// modified file
					currentTime.set(lastModified);
					onChange(DirWatcherEvent.Type.MODIFIED, file);
				}
			}

			// check for deleted files
			if (deletedFiles != null) {
				for (File deletedFile : deletedFiles) {
					map.remove(deletedFile);
					onChange(DirWatcherEvent.Type.DELETED, deletedFile);
				}
			}

			// stop running
			running = false;
		}
	}

	/**
	 * Triggers listeners on file change.
	 */
	protected void onChange(final DirWatcherEvent.Type type, final File file) {
		listeners.accept(new DirWatcherEvent(type, file));
	}

	// ---------------------------------------------------------------- listeners

	/**
	 * Registers {@link jodd.io.watch.DirWatcherEvent consumer}.
	 */
	public void register(final Consumer<DirWatcherEvent> dirWatcherListener) {
		listeners.add(dirWatcherListener);
	}

	/**
	 * Removes registered {@link jodd.io.watch.DirWatcherEvent consumer}.
	 */
	public void remove(final Consumer<DirWatcherEvent> dirWatcherListener) {
		listeners.remove(dirWatcherListener);
	}

	/**
	 * Removes all event consumers..
	 */
	public void clear() {
		listeners.clear();
	}

}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.util.collection;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.NoSuchElementException;

/**
  * Enumeration that combines multiple enumerations.
  */
public class CompositeEnumeration<T> implements Enumeration<T> {

	protected final List<Enumeration<T>> allEnumerations = new ArrayList<>();

	/**
	 * Creates new composite enumeration.
	 * Enumerations may be added using the {@link #add(Enumeration)} method.
	 */
	public CompositeEnumeration() {
	}

	/**
	 * Creates new composite enumeration with provided enumerations.
	 */
	public CompositeEnumeration(final Enumeration<T>... enumerations) {
		for (Enumeration<T> enumeration : enumerations) {
			add(enumeration);
		}
	}

	/**
	 * Adds an enumeration to this composite.
	 */
	public void add(final Enumeration<T> enumeration) {
		if (allEnumerations.contains(enumeration)) {
			throw new IllegalArgumentException("Duplicate enumeration");
		}
		allEnumerations.add(enumeration);
	}

	// ---------------------------------------------------------------- interface

	protected int currentEnumeration = -1;

	/**
	 * Returns true if composite has more elements.
	 */
	public boolean hasMoreElements() {
		if (currentEnumeration == -1) {
			currentEnumeration = 0;
		}
		for (int i = currentEnumeration; i < allEnumerations.size(); i++) {
			Enumeration enumeration = allEnumerations.get(i);
			if (enumeration.hasMoreElements()) {
				currentEnumeration = i;
				return true;
			}
		}
		return false;
	}

	/**
	 * {@inheritDoc}
	 */
	public T nextElement() {
		if (!hasMoreElements()) {
			throw new NoSuchElementException();
		}

		return allEnumerations.get(currentEnumeration).nextElement();
	}

}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.typeconverter.impl;

import jodd.typeconverter.TypeConverter;
import jodd.typeconverter.TypeConverterManager;
import jodd.util.StringUtil;

import java.util.ArrayList;
import java.util.Collection;

/**
 * Converts given object to double[].
 */
public class DoubleArrayConverter implements TypeConverter<double[]> {

	protected final TypeConverterManager typeConverterManager;

	public DoubleArrayConverter(final TypeConverterManager typeConverterManager) {
		this.typeConverterManager = typeConverterManager;
	}

	@Override
	public double[] convert(final Object value) {
		if (value == null) {
			return null;
		}

		final Class valueClass = value.getClass();

		if (!valueClass.isArray()) {
			// source is not an array
			return convertValueToArray(value);
		}

		// source is an array
		return convertArrayToArray(value);
	}

	/**
	 * Converts type using type converter manager.
	 */
	protected double convertType(final Object value) {
		return typeConverterManager.convertType(value, double.class).doubleValue();
	}

	/**
	 * Creates an array with single element.
	 */
	protected double[] convertToSingleElementArray(final Object value) {
		return new double[] {convertType(value)};
	}

	/**
	 * Converts non-array value to array. Detects various
	 * collection types and iterates them to make conversion
	 * and to create target array.
 	 */
	protected double[] convertValueToArray(final Object value) {
		if (value instanceof Collection) {
			final Collection collection = (Collection) value;
			final double[] target = new double[collection.size()];

			int i = 0;
			for (final Object element : collection) {
				target[i] = convertType(element);
				i++;
			}

			return target;
		}

		if (value instanceof Iterable) {
			final Iterable iterable = (Iterable) value;

			final ArrayList<Double> doubleArrayList = new ArrayList<>();

			for (final Object element : iterable) {
				final double convertedValue = convertType(element);
				doubleArrayList.add(Double.valueOf(convertedValue));
			}

			final double[] array = new double[doubleArrayList.size()];

			for (int i = 0; i < doubleArrayList.size(); i++) {
				final Double d = doubleArrayList.get(i);
				array[i] = d.doubleValue();
			}

			return array;
		}

		if (value instanceof CharSequence) {
			final String[] strings = StringUtil.splitc(value.toString(), ArrayConverter.NUMBER_DELIMITERS);
			return convertArrayToArray(strings);
		}

		// everything else:
		return convertToSingleElementArray(value);
	}

	/**
	 * Converts array value to array.
	 */
	protected double[] convertArrayToArray(final Object value) {
		final Class valueComponentType = value.getClass().getComponentType();

		final double[] result;

		if (valueComponentType.isPrimitive()) {
			result = convertPrimitiveArrayToArray(value, valueComponentType);
		} else {
			// convert object array to target array
			final Object[] array = (Object[]) value;
			result = new double[array.length];

			for (int i = 0; i < array.length; i++) {
				result[i] = convertType(array[i]);
			}
		}

		return result;
	}

	/**
	 * Converts primitive array to target array.
	 */
	protected double[] convertPrimitiveArrayToArray(final Object value, final Class primitiveComponentType) {
		double[] result = null;

		if (primitiveComponentType == double.class) {
			return (double[]) value;
		}

		if (primitiveComponentType == int.class) {
			final int[] array = (int[]) value;
			result = new double[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = array[i];
			}
		}
		else if (primitiveComponentType == long.class) {
			final long[] array = (long[]) value;
			result = new double[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = array[i];
			}
		}
		else if (primitiveComponentType == float.class) {
			final float[] array = (float[]) value;
			result = new double[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = array[i];
			}
		}
		else if (primitiveComponentType == short.class) {
			final short[] array = (short[]) value;
			result = new double[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = array[i];
			}
		}
		else if (primitiveComponentType == byte.class) {
			final byte[] array = (byte[]) value;
			result = new double[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = array[i];
			}
		}
		else if (primitiveComponentType == char.class) {
			final char[] array = (char[]) value;
			result = new double[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = array[i];
			}
		}
		else if (primitiveComponentType == boolean.class) {
			final boolean[] array = (boolean[]) value;
			result = new double[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = array[i] ? 1 : 0;
			}
		}
		return result;
	}

}


// ASM: a very small and fast Java bytecode manipulation framework
// Copyright (c) 2000-2011 INRIA, France Telecom
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
// 3. Neither the name of the copyright holders nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
package jodd.asm7;

/**
 * A {@link FieldVisitor} that generates a corresponding 'field_info' structure, as defined in the
 * Java Virtual Machine Specification (JVMS).
 *
 * @see JVMS
 *     4.5
 * @author Eric Bruneton
 */
final class FieldWriter extends FieldVisitor {

  /** Where the constants used in this FieldWriter must be stored. */
  private final SymbolTable symbolTable;

  // Note: fields are ordered as in the field_info structure, and those related to attributes are
  // ordered as in Section 4.7 of the JVMS.

  /**
   * The access_flags field of the field_info JVMS structure. This field can contain ASM specific
   * access flags, such as {@link Opcodes#ACC_DEPRECATED}, which are removed when generating the
   * ClassFile structure.
   */
  private final int accessFlags;

  /** The name_index field of the field_info JVMS structure. */
  private final int nameIndex;

  /** The descriptor_index field of the field_info JVMS structure. */
  private final int descriptorIndex;

  /**
   * The signature_index field of the Signature attribute of this field_info, or 0 if there is no
   * Signature attribute.
   */
  private int signatureIndex;

  /**
   * The constantvalue_index field of the ConstantValue attribute of this field_info, or 0 if there
   * is no ConstantValue attribute.
   */
  private int constantValueIndex;

  /**
   * The last runtime visible annotation of this field. The previous ones can be accessed with the
   * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
   */
  private AnnotationWriter lastRuntimeVisibleAnnotation;

  /**
   * The last runtime invisible annotation of this field. The previous ones can be accessed with the
   * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
   */
  private AnnotationWriter lastRuntimeInvisibleAnnotation;

  /**
   * The last runtime visible type annotation of this field. The previous ones can be accessed with
   * the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
   */
  private AnnotationWriter lastRuntimeVisibleTypeAnnotation;

  /**
   * The last runtime invisible type annotation of this field. The previous ones can be accessed
   * with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
   */
  private AnnotationWriter lastRuntimeInvisibleTypeAnnotation;

  /**
   * The first non standard attribute of this field. The next ones can be accessed with the {@link
   * Attribute#nextAttribute} field. May be {@literal null}.
   *
   *
WARNING: this list stores the attributes in the reverse order of their visit.
   * firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link
   * #putFieldInfo} method writes the attributes in the order defined by this list, i.e. in the
   * reverse order specified by the user.
   */
  private Attribute firstAttribute;

  // -----------------------------------------------------------------------------------------------
  // Constructor
  // -----------------------------------------------------------------------------------------------

  /**
   * Constructs a new {@link FieldWriter}.
   *
   * @param symbolTable where the constants used in this FieldWriter must be stored.
   * @param access the field's access flags (see {@link Opcodes}).
   * @param name the field's name.
   * @param descriptor the field's descriptor (see {@link Type}).
   * @param signature the field's signature. May be {@literal null}.
   * @param constantValue the field's constant value. May be {@literal null}.
   */
  FieldWriter(
      final SymbolTable symbolTable,
      final int access,
      final String name,
      final String descriptor,
      final String signature,
      final Object constantValue) {
    super(Opcodes.ASM7);
    this.symbolTable = symbolTable;
    this.accessFlags = access;
    this.nameIndex = symbolTable.addConstantUtf8(name);
    this.descriptorIndex = symbolTable.addConstantUtf8(descriptor);
    if (signature != null) {
      this.signatureIndex = symbolTable.addConstantUtf8(signature);
    }
    if (constantValue != null) {
      this.constantValueIndex = symbolTable.addConstant(constantValue).index;
    }
  }

  // -----------------------------------------------------------------------------------------------
  // Implementation of the FieldVisitor abstract class
  // -----------------------------------------------------------------------------------------------

  @Override
  public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
    // Create a ByteVector to hold an 'annotation' JVMS structure.
    // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.
    ByteVector annotation = new ByteVector();
    // Write type_index and reserve space for num_element_value_pairs.
    annotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0);
    if (visible) {
      return lastRuntimeVisibleAnnotation =
          new AnnotationWriter(symbolTable, annotation, lastRuntimeVisibleAnnotation);
    } else {
      return lastRuntimeInvisibleAnnotation =
          new AnnotationWriter(symbolTable, annotation, lastRuntimeInvisibleAnnotation);
    }
  }

  @Override
  public AnnotationVisitor visitTypeAnnotation(
      final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
    // Create a ByteVector to hold a 'type_annotation' JVMS structure.
    // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20.
    ByteVector typeAnnotation = new ByteVector();
    // Write target_type, target_info, and target_path.
    TypeReference.putTarget(typeRef, typeAnnotation);
    TypePath.put(typePath, typeAnnotation);
    // Write type_index and reserve space for num_element_value_pairs.
    typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0);
    if (visible) {
      return lastRuntimeVisibleTypeAnnotation =
          new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeVisibleTypeAnnotation);
    } else {
      return lastRuntimeInvisibleTypeAnnotation =
          new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeInvisibleTypeAnnotation);
    }
  }

  @Override
  public void visitAttribute(final Attribute attribute) {
    // Store the attributes in the reverse order of their visit by this method.
    attribute.nextAttribute = firstAttribute;
    firstAttribute = attribute;
  }

  @Override
  public void visitEnd() {
    // Nothing to do.
  }

  // -----------------------------------------------------------------------------------------------
  // Utility methods
  // -----------------------------------------------------------------------------------------------

  /**
   * Returns the size of the field_info JVMS structure generated by this FieldWriter. Also adds the
   * names of the attributes of this field in the constant pool.
   *
   * @return the size in bytes of the field_info JVMS structure.
   */
  int computeFieldInfoSize() {
    // The access_flags, name_index, descriptor_index and attributes_count fields use 8 bytes.
    int size = 8;
    // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
    if (constantValueIndex != 0) {
      // ConstantValue attributes always use 8 bytes.
      symbolTable.addConstantUtf8(Constants.CONSTANT_VALUE);
      size += 8;
    }
    // Before Java 1.5, synthetic fields are represented with a Synthetic attribute.
    if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0
        && symbolTable.getMajorVersion() < Opcodes.V1_5) {
      // Synthetic attributes always use 6 bytes.
      symbolTable.addConstantUtf8(Constants.SYNTHETIC);
      size += 6;
    }
    if (signatureIndex != 0) {
      // Signature attributes always use 8 bytes.
      symbolTable.addConstantUtf8(Constants.SIGNATURE);
      size += 8;
    }
    // ACC_DEPRECATED is ASM specific, the ClassFile format uses a Deprecated attribute instead.
    if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) {
      // Deprecated attributes always use 6 bytes.
      symbolTable.addConstantUtf8(Constants.DEPRECATED);
      size += 6;
    }
    if (lastRuntimeVisibleAnnotation != null) {
      size +=
          lastRuntimeVisibleAnnotation.computeAnnotationsSize(
              Constants.RUNTIME_VISIBLE_ANNOTATIONS);
    }
    if (lastRuntimeInvisibleAnnotation != null) {
      size +=
          lastRuntimeInvisibleAnnotation.computeAnnotationsSize(
              Constants.RUNTIME_INVISIBLE_ANNOTATIONS);
    }
    if (lastRuntimeVisibleTypeAnnotation != null) {
      size +=
          lastRuntimeVisibleTypeAnnotation.computeAnnotationsSize(
              Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
    }
    if (lastRuntimeInvisibleTypeAnnotation != null) {
      size +=
          lastRuntimeInvisibleTypeAnnotation.computeAnnotationsSize(
              Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
    }
    if (firstAttribute != null) {
      size += firstAttribute.computeAttributesSize(symbolTable);
    }
    return size;
  }

  /**
   * Puts the content of the field_info JVMS structure generated by this FieldWriter into the given
   * ByteVector.
   *
   * @param output where the field_info structure must be put.
   */
  void putFieldInfo(final ByteVector output) {
    boolean useSyntheticAttribute = symbolTable.getMajorVersion() < Opcodes.V1_5;
    // Put the access_flags, name_index and descriptor_index fields.
    int mask = useSyntheticAttribute ? Opcodes.ACC_SYNTHETIC : 0;
    output.putShort(accessFlags & ~mask).putShort(nameIndex).putShort(descriptorIndex);
    // Compute and put the attributes_count field.
    // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
    int attributesCount = 0;
    if (constantValueIndex != 0) {
      ++attributesCount;
    }
    if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && useSyntheticAttribute) {
      ++attributesCount;
    }
    if (signatureIndex != 0) {
      ++attributesCount;
    }
    if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) {
      ++attributesCount;
    }
    if (lastRuntimeVisibleAnnotation != null) {
      ++attributesCount;
    }
    if (lastRuntimeInvisibleAnnotation != null) {
      ++attributesCount;
    }
    if (lastRuntimeVisibleTypeAnnotation != null) {
      ++attributesCount;
    }
    if (lastRuntimeInvisibleTypeAnnotation != null) {
      ++attributesCount;
    }
    if (firstAttribute != null) {
      attributesCount += firstAttribute.getAttributeCount();
    }
    output.putShort(attributesCount);
    // Put the field_info attributes.
    // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
    if (constantValueIndex != 0) {
      output
          .putShort(symbolTable.addConstantUtf8(Constants.CONSTANT_VALUE))
          .putInt(2)
          .putShort(constantValueIndex);
    }
    if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && useSyntheticAttribute) {
      output.putShort(symbolTable.addConstantUtf8(Constants.SYNTHETIC)).putInt(0);
    }
    if (signatureIndex != 0) {
      output
          .putShort(symbolTable.addConstantUtf8(Constants.SIGNATURE))
          .putInt(2)
          .putShort(signatureIndex);
    }
    if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) {
      output.putShort(symbolTable.addConstantUtf8(Constants.DEPRECATED)).putInt(0);
    }
    if (lastRuntimeVisibleAnnotation != null) {
      lastRuntimeVisibleAnnotation.putAnnotations(
          symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_ANNOTATIONS), output);
    }
    if (lastRuntimeInvisibleAnnotation != null) {
      lastRuntimeInvisibleAnnotation.putAnnotations(
          symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_ANNOTATIONS), output);
    }
    if (lastRuntimeVisibleTypeAnnotation != null) {
      lastRuntimeVisibleTypeAnnotation.putAnnotations(
          symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS), output);
    }
    if (lastRuntimeInvisibleTypeAnnotation != null) {
      lastRuntimeInvisibleTypeAnnotation.putAnnotations(
          symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS), output);
    }
    if (firstAttribute != null) {
      firstAttribute.putAttributes(symbolTable, output);
    }
  }

  /**
   * Collects the attributes of this field into the given set of attribute prototypes.
   *
   * @param attributePrototypes a set of attribute prototypes.
   */
  final void collectAttributePrototypes(final Attribute.Set attributePrototypes) {
    attributePrototypes.addAttributes(firstAttribute);
  }
}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.lagarto.dom;

import jodd.util.StringUtil;

/**
 * Validates conditional comments expressions.
 */
public class HtmlCCommentExpressionMatcher {

	/**
	 * Matches conditional comment expression with current mode.
	 * Returns true it conditional comment expression is positive,
	 * otherwise returns false.
	 */
	public boolean match(final float ieVersion, String expression) {
		expression = StringUtil.removeChars(expression, "()");
		expression = expression.substring(3);

		String[] andChunks = StringUtil.splitc(expression, '&');

		boolean valid = true;

		for (String andChunk : andChunks) {
			String[] orChunks = StringUtil.splitc(andChunk, '|');

			boolean innerValid = false;

			for (String orChunk : orChunks) {
				orChunk = orChunk.trim();

				if (orChunk.startsWith("IE ")) {
					String value = orChunk.substring(3);
					float number = Float.parseFloat(value);

					if (versionToCompare(ieVersion, number) == number) {
						innerValid = true;
						break;
					}
					continue;
				}
				if (orChunk.startsWith("!IE ")) {
					String value = orChunk.substring(4);
					float number = Float.parseFloat(value);

					if (versionToCompare(ieVersion, number) != number) {
						innerValid = true;
						break;
					}
					continue;
				}
				if (orChunk.startsWith("lt IE ")) {
					String value = orChunk.substring(6);
					float number = Float.parseFloat(value);

					if (ieVersion < number) {
						innerValid = true;
						break;
					}
					continue;
				}
				if (orChunk.startsWith("lte IE ")) {
					String value = orChunk.substring(7);
					float number = Float.parseFloat(value);

					if (versionToCompare(ieVersion, number) <= number) {
						innerValid = true;
						break;
					}
					continue;
				}
				if (orChunk.startsWith("gt IE ")) {
					String value = orChunk.substring(6);
					float number = Float.parseFloat(value);

					if (versionToCompare(ieVersion, number) > number) {
						innerValid = true;
						break;
					}
					continue;
				}
				if (orChunk.startsWith("gte IE ")) {
					String value = orChunk.substring(7);
					float number = Float.parseFloat(value);

					if (ieVersion >= number) {
						innerValid = true;
						break;
					}
					continue;
				}
			}

			valid = valid && innerValid;
		}

		return valid;
	}

	// If in expression IE version is represented as a natural number
	// we should compare only major number
	private float versionToCompare(final float ieVersion, final float number) {
		return (int) number == number ? (int) ieVersion : ieVersion;
	}
}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.buffer;

import java.util.Arrays;

/**
 * Faster {@code byte} buffer. Works faster for smaller buffer sizes.
 * After eg. length of 2048 the performances are practically the same.
 */
public class FastByteBuffer {

	private byte[] buffer;
	private int offset;

	/**
	 * Creates a new {@code byte} buffer. The buffer capacity is
	 * initially 64 bytes, though its size increases if necessary.
	 */
	public FastByteBuffer() {
		this.buffer = new byte[64];
	}

	/**
	 * Creates a new {@code byte} buffer, with a buffer capacity of
	 * the specified size.
	 *
	 * @param size the initial size.
	 * @throws IllegalArgumentException if size is negative.
	 */
	public FastByteBuffer(final int size) {
		this.buffer = new byte[size];
	}

	/**
	 * Grows the buffer.
	 */
	private void grow(final int minCapacity) {
		final int oldCapacity = buffer.length;
		int newCapacity = oldCapacity << 1;
		if (newCapacity - minCapacity < 0) {
			// special case, min capacity is larger then a grow
			newCapacity = minCapacity + 512;
		}
		buffer = Arrays.copyOf(buffer, newCapacity);
	}

	/**
	 * Appends single {@code byte} to buffer.
	 */
	public void append(final byte element) {
		if (offset - buffer.length >= 0) {
			grow(offset);
		}

		buffer[offset++] = element;
	}

	/**
	 * Appends {@code byte} array to buffer.
	 */
	public FastByteBuffer append(final byte[] array, final int off, final int len) {
		if (offset + len - buffer.length > 0) {
			grow(offset + len);
		}

		System.arraycopy(array, off, buffer, offset, len);
		offset += len;
		return this;
	}

	/**
	 * Appends {@code byte} array to buffer.
	 */
	public FastByteBuffer append(final byte[] array) {
		return append(array, 0, array.length);
	}

	/**
	 * Appends another fast buffer to this one.
	 */
	public FastByteBuffer append(final FastByteBuffer buff) {
		if (buff.offset == 0) {
			return this;
		}
		append(buff.buffer, 0, buff.offset);
		return this;
	}

	/**
	 * Returns buffer size.
	 */
	public int size() {
		return offset;
	}

	/**
	 * Tests if this buffer has no elements.
	 */
	public boolean isEmpty() {
		return offset == 0;
	}

	/**
	 * Resets the buffer content.
	 */
	public void clear() {
		offset = 0;
	}

	/**
	 * Creates {@code byte} array from buffered content.
	 */
	public byte[] toArray() {
		return Arrays.copyOf(buffer, offset);
	}

    /**
     * Creates {@code byte} subarray from buffered content.
     */
	public byte[] toArray(final int start, final int len) {
		final byte[] array = new byte[len];

		if (len == 0) {
			return array;
		}

		System.arraycopy(buffer, start, array, 0, len);

		return array;
	}

	/**
	 * Returns {@code byte} element at given index.
	 */
	public byte get(final int index) {
		if (index >= offset) {
			throw new IndexOutOfBoundsException();
		}
		return buffer[index];
	}

}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.madvoc.config;

import jodd.bean.BeanUtil;
import jodd.introspector.MapperFunction;
import jodd.madvoc.MadvocException;
import jodd.typeconverter.TypeConverterManager;
import jodd.util.ClassUtil;

import java.util.function.Function;

/**
 * Wrapper of a target for IN/OUT operations. It wraps one of these:
 *

 *
action object

 *
method parameter

 *
third-party object

 *

 *
 * Target provides a common interface to write or read certain property from a wrapped value.
 */
public class Target {

	private static final Function<Class, Object> VALUE_INSTANCE_CREATOR = type -> {
		try {
			return ClassUtil.newInstance(type);
		} catch (Exception ex) {
			throw new MadvocException(ex);
		}
	};

	/**
	 * Creates a common target over a value, with known scope data.
	 */
	public static Target ofValue(final Object value, final ScopeData scopeData) {
		return new Target(value, null, scopeData, null, VALUE_INSTANCE_CREATOR);
	}
	/**
	 * Creates a common target over a method param.
	 */
	public static Target ofMethodParam(final MethodParam methodParam, final Object object) {
		return new Target(object, methodParam.type(), methodParam.scopeData(), methodParam.mapperFunction(), VALUE_INSTANCE_CREATOR);
	}
	/**
	 * Creates a common target over a method param.
	 */
	public static Target ofMethodParam(final MethodParam methodParam, final Function<Class, Object> valueInstanceCreator) {
		return new Target(null, methodParam.type(), methodParam.scopeData(), methodParam.mapperFunction(), valueInstanceCreator);
	}

	private final Class type;
	private final ScopeData scopeData;
	private Object value;
	private final Function<Class, Object> valueInstanceCreator;
	private final MapperFunction mapperFunction;

	private Target(final Object value, final Class type, final ScopeData scopeData, final MapperFunction mapperFunction, final Function<Class, Object> valueInstanceCreator) {
		this.value = value;
		this.type = type;
		this.scopeData = scopeData;
		this.valueInstanceCreator = valueInstanceCreator;
		this.mapperFunction = mapperFunction;
	}

	/**
	 * Returns targets type, if specified.
	 * @see #resolveType()
	 */
	public Class type() {
		return type;
	}

	/**
	 * Resolves target type: either using {@link #type() provided type}
	 * or type of the {@link #value() value}.
	 */
	public Class resolveType() {
		if (type != null) {
			return type;
		}
		return value.getClass();
	}

	/**
	 * Returns target value, if specified.
	 */
	public Object value() {
		return value;
	}

	/**
	 * Returns associated scope data.
	 */
	public ScopeData scopeData() {
		return scopeData;
	}

	// ---------------------------------------------------------------- read

	/**
	 * Reads value from the target. If something goes wrong, exception
	 * is thrown. We assume that outjection is controlled by developer
	 * and that each reading of a value must be a successful operation.
	 */
	public Object readValue(final InjectionPoint injectionPoint) {
		return readValue(injectionPoint.targetName());
	}
	public Object readValue(final String name) {
		String propertyName = name;

		if (type != null) {
			final int dotNdx = propertyName.indexOf('.');

			if (dotNdx == -1) {
				return value;
			}

			propertyName = propertyName.substring(dotNdx + 1);
		}

		return BeanUtil.declared.getProperty(value, propertyName);
	}

	// ---------------------------------------------------------------- write

	/**
	 * Writes value to this target. Depending on a flag, writing the value can be
	 * completely silent, when no exception is thrown and with top performances.
	 * Otherwise, an exception is thrown on a failure.
	 */
	public void writeValue(final InjectionPoint injectionPoint, final Object propertyValue, final boolean silent) {
		writeValue(injectionPoint.targetName(), propertyValue, silent);
	}
	public void writeValue(final String name, final Object propertyValue, final boolean silent) {
		String propertyName = name;

		if (type != null) {
			// target type specified, save into target value!

			int dotNdx = propertyName.indexOf('.');

			if (dotNdx == -1) {

				if (mapperFunction != null) {
					value = mapperFunction.apply(propertyValue);
				}
				else {
					value = TypeConverterManager.get().convertType(propertyValue, type);
				}
				return;
			}

			if (value == null) {
				value = valueInstanceCreator.apply(type);
			}

			propertyName = propertyName.substring(dotNdx + 1);
		}

		// inject into target value

		if (silent) {
			BeanUtil.declaredForcedSilent.setProperty(value, propertyName, propertyValue);
		}
		else {
			BeanUtil.declaredForced.setProperty(value, propertyName, propertyValue);
		}
	}

}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.decora.parser;

import jodd.lagarto.LagartoParser;

import java.io.IOException;
import java.io.Writer;

/**
 * Decora parser takes page content and puts it into decorator template, to produce the output.
 * Important: Decora templates do not support raw tags, since Decora tags may be placed inside.
 * Decora parser does not depend on servlets.
 */
public class DecoraParser {

	/**
	 * Decorates page content with decorator template and outputs the result.
	 */
	public void decorate(final Writer writer, final char[] pageContent, final char[] decoraContent) throws IOException {
		DecoraTag[] decoraTags = parseDecorator(decoraContent);

		parsePage(pageContent, decoraTags);

		writeDecoratedPage(writer, decoraContent, pageContent, decoraTags);
	}

	/**
	 * Parses decorator file and collects {@link jodd.decora.parser.DecoraTag Decora tags}
	 * used in template. Returned Decora tags have start and end index set,
	 * but their region is not set.
	 */
	protected DecoraTag[] parseDecorator(final char[] decoraContent) {
		LagartoParser lagartoParser = new LagartoParser(decoraContent);
		lagartoParser.getConfig().setEnableRawTextModes(false);

		DecoratorTagVisitor visitor = new DecoratorTagVisitor();
		lagartoParser.parse(visitor);
		return visitor.getDecoraTags();
	}

	/**
	 * Parses target page and extracts Decora regions for replacements.
	 */
	protected void parsePage(final char[] pageContent, final DecoraTag[] decoraTags) {
		LagartoParser lagartoParser = new LagartoParser(pageContent);
		PageRegionExtractor writer = new PageRegionExtractor(decoraTags);
		lagartoParser.parse(writer);
	}

	/**
	 * Writes decorated content.
	 */
	protected void writeDecoratedPage(final Writer out, final char[] decoratorContent, final char[] pageContent, final DecoraTag[] decoraTags) throws IOException {
		int ndx = 0;

		for (DecoraTag decoraTag : decoraTags) {
			// [1] just copy content before the Decora tag
			int decoratorLen = decoraTag.getStartIndex() - ndx;
			if (decoratorLen <= 0) {
				continue;
			}
			out.write(decoratorContent, ndx, decoratorLen);

			ndx = decoraTag.getEndIndex();

			// [2] now write region at the place of Decora tag
			int regionLen = decoraTag.getRegionLength();

			if (regionLen == 0) {
				if (decoraTag.hasDefaultValue()) {
					out.write(decoratorContent, decoraTag.getDefaultValueStart(), decoraTag.getDefaultValueLength());
				}
			} else {
				writeRegion(out, pageContent, decoraTag, decoraTags);
			}

		}

		// write remaining content
		out.write(decoratorContent, ndx, decoratorContent.length - ndx);
	}

	/**
	 * Writes region to output, but extracts all inner regions.
	 */
	protected void writeRegion(final Writer out, final char[] pageContent, final DecoraTag decoraTag, final DecoraTag[] decoraTags) throws IOException {
		int regionStart = decoraTag.getRegionStart();
		int regionLen = decoraTag.getRegionLength();
		int regionEnd = regionStart + regionLen;

		for (DecoraTag innerDecoraTag : decoraTags) {
			if (decoraTag == innerDecoraTag) {
				continue;
			}

			if (decoraTag.isRegionUndefined()) {
				continue;
			}

			if (innerDecoraTag.isInsideOtherTagRegion(decoraTag)) {
				// write everything from region start to the inner Decora tag
				out.write(pageContent, regionStart, innerDecoraTag.getRegionTagStart() - regionStart);

				regionStart = innerDecoraTag.getRegionTagEnd();
			}
		}

		// write remaining content of the region
		out.write(pageContent, regionStart, regionEnd - regionStart);
	}

}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.lagarto.dom;

import jodd.util.StringUtil;
import jodd.net.HtmlDecoder;
import jodd.net.HtmlEncoder;

/**
 * Text node. Text value is stored as node value in decoded, readable form.
 * There is also an option to get and set html content in
 * raw, html form.
 */
public class Text extends Node {

	protected String encodedText;

	public Text(final Document ownerDocument, final String text) {
		super(ownerDocument, NodeType.TEXT, null);
		this.nodeValue = text;
		this.encodedText = null;
	}

	@Override
	public Text clone() {
		return cloneTo(new Text(ownerDocument, nodeValue));
	}

	protected Boolean blank;

	/**
	 * Returns true if text content is blank.
	 */
	public boolean isBlank() {
		if (blank == null) {
			blank = Boolean.valueOf(StringUtil.isBlank(nodeValue));
		}
		return blank.booleanValue();
	}

	/**
	 * Sets the plain text as node value.
	 */
	@Override
	public void setNodeValue(final String value) {
		encodedText = null;
		super.setNodeValue(value);
	}

	/**
	 * Sets HTML text, but decodes it first.
	 */
	public void setTextValue(final String text) {
		encodedText = text;
		nodeValue = HtmlDecoder.decode(text);
	}

	/**
	 * Returns encoded HTML text.
	 */
	public String getTextValue() {
		if (encodedText == null) {
			encodedText = HtmlEncoder.text(nodeValue);
		}
		return encodedText;
	}

	@Override
	protected void visitNode(final NodeVisitor nodeVisitor) {
		nodeVisitor.text(this);
	}
}