// 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.interceptor;

import jodd.cache.TypeCache;
import jodd.introspector.ClassDescriptor;
import jodd.introspector.ClassIntrospector;
import jodd.introspector.PropertyDescriptor;
import jodd.madvoc.ActionRequest;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;

/**
 * Performs some operation on all annotated properties. Helpful with injection of
 * application context into action objects.
 */
public abstract class AnnotatedPropertyInterceptor implements ActionInterceptor {

	protected final Class<Annotation> annotations;

	protected AnnotatedPropertyInterceptor(final Class<Annotation> annotations) {
		this.annotations = annotations;
	}

	@Override
	public Object intercept(final ActionRequest actionRequest) throws Exception {
		Object action = actionRequest.getAction();
		Class actionType = action.getClass();

		PropertyDescriptor[] allProperties = lookupAnnotatedProperties(actionType);

		for (PropertyDescriptor propertyDescriptor : allProperties) {
			onAnnotatedProperty(actionRequest, propertyDescriptor);
		}
		return actionRequest.invoke();
	}

	/**
	 * Invoked on all annotated properties.
	 */
	protected abstract void onAnnotatedProperty(ActionRequest actionRequest, PropertyDescriptor propertyDescriptor);

	// ---------------------------------------------------------------- cache and lookup

	protected TypeCache<PropertyDescriptor[]> annotatedProperties = TypeCache.createDefault();
	protected static final PropertyDescriptor[] EMPTY = new PropertyDescriptor[0];

	/**
	 * Lookups for annotated properties. Caches all annotated properties on the first
	 * action class scan.
	 */
	protected PropertyDescriptor[] lookupAnnotatedProperties(final Class type) {
		PropertyDescriptor[] properties = annotatedProperties.get(type);

		if (properties != null) {
			return properties;
		}

		ClassDescriptor cd = ClassIntrospector.get().lookup(type);
		PropertyDescriptor[] allProperties = cd.getAllPropertyDescriptors();

		List<PropertyDescriptor> list = new ArrayList<>();

		for (PropertyDescriptor propertyDescriptor : allProperties) {

			Annotation ann = null;

			if (propertyDescriptor.getFieldDescriptor() != null) {
				ann = propertyDescriptor.getFieldDescriptor().getField().getAnnotation(annotations);
			}
			if (ann == null && propertyDescriptor.getWriteMethodDescriptor() != null) {
				ann = propertyDescriptor.getWriteMethodDescriptor().getMethod().getAnnotation(annotations);
			}
			if (ann == null && propertyDescriptor.getReadMethodDescriptor() != null) {
				ann = propertyDescriptor.getReadMethodDescriptor().getMethod().getAnnotation(annotations);
			}

			if (ann != null) {
				list.add(propertyDescriptor);
			}
		}

		if (list.isEmpty()) {
			properties = EMPTY;
		} else {
			properties = list.toArray(new PropertyDescriptor[0]);
		}

		annotatedProperties.put(type, properties);

		return properties;
	}

}


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

import jodd.db.connection.ConnectionProvider;
import jodd.db.oom.DbOomConfig;
import jodd.db.servers.Db2DbServer;
import jodd.db.servers.DbServer;
import jodd.db.servers.DerbyDbServer;
import jodd.db.servers.GenericDbServer;
import jodd.db.servers.HsqlDbServer;
import jodd.db.servers.InformixDbServer;
import jodd.db.servers.MySqlDbServer;
import jodd.db.servers.OracleDbServer;
import jodd.db.servers.PostgreSqlDbServer;
import jodd.db.servers.SQLiteDbServer;
import jodd.db.servers.SqlServerDbServer;
import jodd.db.servers.SybaseDbServer;
import jodd.log.Logger;
import jodd.log.LoggerFactory;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;

/**
 * Database detector and DbOom configurator.
 */
public class DbDetector {

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

	/**
	 * Detects database and configure DbOom engine.
	 */
	public DbServer detectDatabaseAndConfigureDbOom(
			final ConnectionProvider cp,
			final DbOomConfig dbOomConfig) {

		cp.init();

		final Connection connection = cp.getConnection();

		final DbServer dbServer = detectDatabase(connection);

		cp.closeConnection(connection);

		dbServer.accept(dbOomConfig);

		return dbServer;
	}

	/**
	 * Detects database and returns {@link DbServer}.
	 */
	public DbServer detectDatabase(final Connection connection) {
		final String dbName;
		final int dbMajorVersion;
		final String version;

		try {
			log.info("Detecting database...");

			DatabaseMetaData databaseMetaData = connection.getMetaData();
			dbName = databaseMetaData.getDatabaseProductName();
			dbMajorVersion = databaseMetaData.getDatabaseMajorVersion();
			final int dbMinorVersion = databaseMetaData.getDatabaseMinorVersion();
			version = dbMajorVersion + "." + dbMinorVersion;

			log.info("Database: " + dbName + " v" + dbMajorVersion + "." + dbMinorVersion);
		}
		catch (SQLException sex) {
			String msg = sex.getMessage();

			if (msg.contains("explicitly set for database: DB2")) {
				return new Db2DbServer();
			}

			throw new DbSqlException(sex);
		}

		if (dbName.equals("Apache Derby")) {
			return new DerbyDbServer(version);
		}
		if (dbName.startsWith("DB2/")) {
			return new Db2DbServer(version);
		}
		if (dbName.equals("HSQL Database Engine")) {
			return new HsqlDbServer(version);
		}
		if (dbName.equals("Informix Dynamic Server")) {
			return new InformixDbServer(version);
		}
		if (dbName.startsWith("Microsoft SQL Server")) {
			return new SqlServerDbServer(version);
		}
		if (dbName.equals("MySQL")) {
			return new MySqlDbServer(version);
		}
		if (dbName.equals("Oracle")) {
			return new OracleDbServer(version);
		}
		if (dbName.equals("PostgreSQL")) {
			return new PostgreSqlDbServer(version);
		}
		if (dbName.equals("Sybase SQL Server")) {
			return new SybaseDbServer(version);
		}
		if (dbName.equals("ASE") && (dbMajorVersion == 15)) {
			return new SybaseDbServer(version);
		}
		if (dbName.equals("SQLite")) {
			return new SQLiteDbServer(version);
		}

		return new GenericDbServer();
	}
}


// 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.filter;

import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.zip.GZIPOutputStream;

/**
 * GZIP response stream.
 */
public class GzipResponseStream extends ServletOutputStream {

	/**
	 * Constructs a servlet output stream associated with the specified Response.
	 */
	public GzipResponseStream(final HttpServletResponse response) throws IOException {
		super();
		closed = false;
		this.response = response;
		this.output = response.getOutputStream();
	}

	/**
	 * The threshold number which decides to compress or not.
	 */
	protected int compressionThreshold;

	/**
	 * The buffer through which all of our output bytes are passed.
	 */
	protected byte[] buffer;

	/**
	 * The number of data bytes currently in the buffer.
	 */
	protected int bufferCount;

	/**
	 * The underlying gzip output stream to which we should write data.
	 */
	protected GZIPOutputStream gzipstream;

	/**
	 * Has this stream been closed?
	 */
	protected boolean closed;

	/**
	 * The content length past which we will not write, or -1 if there is no
	 * defined content length.
	 */
	protected int length = -1;

	/**
	 * The response with which this servlet output stream is associated.
	 */
	protected HttpServletResponse response;

	/**
	 * The underlying servlet output stream to which we should write data.
	 */
	protected ServletOutputStream output;

	/**
	 * Sets the compressionThreshold number and create buffer for this size.
	 */
	protected void setBuffer(final int threshold) {
		compressionThreshold = threshold;
		buffer = new byte[compressionThreshold];
	}

	@Override
	public boolean isReady() {
		return output.isReady();
	}

	@Override
	public void setWriteListener(final WriteListener writeListener) {
		output.setWriteListener(writeListener);
	}

	/**
	 * Closes this output stream, causing any buffered data to be flushed and any
	 * further output data to throw an IOException.
	 */
	@Override
	public void close() throws IOException {
		if (closed) {
			return;
		}
		if (gzipstream != null) {
			flushToGZip();
			gzipstream.close();
			gzipstream = null;
		} else {
			if (bufferCount > 0) {
				output.write(buffer, 0, bufferCount);
				bufferCount = 0;
			}
		}
		output.close();
		closed = true;
	}

	/**
	 * Flushes any buffered data for this output stream, which also causes the
	 * response to be committed.
	 */
	@Override
	public void flush() throws IOException {

		if (closed) {
			return;
		}
		if (gzipstream != null) {
			gzipstream.flush();
		}

	}

	public void flushToGZip() throws IOException {
		if (bufferCount > 0) {
			writeToGZip(buffer, 0, bufferCount);
			bufferCount = 0;
		}
	}

	/**
	 * Writes the specified byte to our output stream.
	 */
	@Override
	public void write(final int b) throws IOException {

		if (closed) {
			throw new IOException("Cannot write to a closed output stream");
		}
		if (bufferCount >= buffer.length) {
			flushToGZip();
		}
		buffer[bufferCount++] = (byte) b;
	}

	/**
	 * Writes b.length bytes from the specified byte array to our
	 * output stream.
	 */
	@Override
	public void write(final byte[] b) throws IOException {
		write(b, 0, b.length);
	}

	/**
	 * Writes len bytes from the specified byte array, starting at
	 * the specified offset, to our output stream.
	 *
	 * @param b      byte array containing the bytes to be written
	 * @param off    zero-relative starting offset of the bytes to be written
	 * @param len    number of bytes to be written
	 */
	@Override
	public void write(final byte[] b, final int off, final int len) throws IOException {

		if (closed) {
			throw new IOException("Cannot write to a closed output stream");
		}

		if (len == 0) {
			return;
		}

		// Can we write into buffer ?
		if (len <= (buffer.length - bufferCount)) {
			System.arraycopy(b, off, buffer, bufferCount, len);
			bufferCount += len;
			return;
		}

		// There is not enough space in buffer. Flush it ...
		flushToGZip();

		// ... and try again. Note, that bufferCount = 0 here !
		if (len <= (buffer.length - bufferCount)) {
			System.arraycopy(b, off, buffer, bufferCount, len);
			bufferCount += len;
			return;
		}

		// write direct to gzip
		writeToGZip(b, off, len);
	}

	/**
	 * Writes byte array to gzip output stream. Creates new GZIPOutputStream
	 * if not created yet. Also sets the "Content-Encoding" header.
	 */
	public void writeToGZip(final byte[] b, final int off, final int len) throws IOException {
		if (gzipstream == null) {
			gzipstream = new GZIPOutputStream(output);
			response.setHeader("Content-Encoding", "gzip");
		}
		gzipstream.write(b, off, len);

	}

	/**
	 * Returns true if this response stream been closed.
	 */
	public boolean closed() {
		return(this.closed);
	}

}


// 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.filter;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * Protects from exposing session ids in URLs for security reasons.
 * Does the following:
 *

 *
invalidates session if session id is exposed in the URL

 *
removes session id from URLs.

 *

 */
public class RemoveSessionFromUrlFilter implements Filter {

	/**
	 * Filters requests to remove URL-based session identifiers.
	 */
	@Override
	public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
			throws IOException, ServletException {

		HttpServletRequest httpRequest = (HttpServletRequest) request;
		HttpServletResponse httpResponse = (HttpServletResponse) response;

		if (isRequestedSessionIdFromURL(httpRequest)) {
			HttpSession session = httpRequest.getSession(false);

			if (session != null) {
				session.invalidate();		// clear session if session id in URL
			}
		}

		// wrap response to remove URL encoding
		HttpServletResponseWrapper wrappedResponse = new HttpServletResponseWrapper(httpResponse) {
			@Override
			public String encodeRedirectUrl(final String url) {
				return url;
			}

			@Override
			public String encodeRedirectURL(final String url) {
				return url;
			}

			@Override
			public String encodeUrl(final String url) {
				return url;
			}

			@Override
			public String encodeURL(final String url) {
				return url;
			}
		};

		chain.doFilter(request, wrappedResponse);
	}

	/**
	 * Detects if session ID exist in the URL. It works more reliable
	 * than servletRequest.isRequestedSessionIdFromURL().
	 */
	protected boolean isRequestedSessionIdFromURL(final HttpServletRequest servletRequest) {
		if (servletRequest.isRequestedSessionIdFromURL()) {
			return true;
		}

		HttpSession session = servletRequest.getSession(false);
		if (session != null) {
			String sessionId = session.getId();
			StringBuffer requestUri = servletRequest.getRequestURL();

			return requestUri.indexOf(sessionId) != -1;
		}

		return false;
	}

	@Override
	public void init(final FilterConfig config) {
	}

	@Override
	public void destroy() {
	}
}


// 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.mail;

import jodd.io.FastByteArrayOutputStream;
import jodd.io.StreamUtil;

import javax.activation.DataSource;
import javax.mail.internet.MimeUtility;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;

/**
 * Email attachment.
 */
public class EmailAttachment<T extends DataSource> {

	/**
	 * {@link String} with file name.
	 */
	private final String name;

	/**
	 * Content ID of attachment.
	 */
	private String contentId;

	/**
	 * Whether the attachment is inline.
	 */
	private boolean isInline;

	/**
	 * {@link DataSource} of the attachment.
	 */
	private final T dataSource;

	/**
	 * Target {@link EmailMessage}.
	 */
	private EmailMessage targetMessage;

	// ---------------------------------------------------------------- constructor

	/**
	 * Returns new/empty {@link EmailAttachmentBuilder}.
	 *
	 * @return {@link EmailAttachmentBuilder}.
	 */
	public static EmailAttachmentBuilder with() {
		return new EmailAttachmentBuilder();
	}

	/**
	 * Creates new attachment with given name and content id for inline attachments.
	 *
	 * @param contentId Value may be {@code null} if attachment is not embedded.
	 * @param isInline  {@code true} if the attachment is inline.
	 * @param name      Email name may be {@code null} as well.
	 * @see MimeUtility#decodeText(String)
	 */
	protected EmailAttachment(final String name, final String contentId, final boolean isInline, final T dataSource) {
		if (name != null) {
			try {
				this.name = MimeUtility.decodeText(name);
			} catch (final UnsupportedEncodingException useexc) {
				throw new MailException(useexc);
			}
		} else {
			this.name = null;
		}
		this.contentId = contentId;
		this.isInline = isInline;
		this.dataSource = dataSource;
	}

	// ---------------------------------------------------------------- properties

	/**
	 * Returns attachment name.
	 *
	 * @return attachment name. Value may be {@code null}.
	 */
	public String getName() {
		return name;
	}

	/**
	 * Returns encoded attachment name.
	 *
	 * @return encoded attachment name. Value may be {@code null}.
	 */
	public String getEncodedName() {
		if (name == null) {
			return null;
		}
		try {
			return MimeUtility.encodeText(name);
		} catch (final UnsupportedEncodingException ueex) {
			throw new MailException(ueex);
		}
	}

	/**
	 * Returns content id for inline attachments.
	 *

	 * Value is {@code null} when attachment is not embedded.
	 *
	 * @return content id for inline attachments
	 * @see #isEmbedded()
	 */
	public String getContentId() {
		return contentId;
	}

	/**
	 * Returns {@code true} if the attachment is embedded.
	 *

	 * Embedded attachment is one when {@link #getContentId()} is not {@code null}.
	 *
	 * @return {@code true} if the attachment is embedded.
	 */
	public boolean isEmbedded() {
		return contentId != null;
	}

	/**
	 * Returns {@code true} if it is an inline attachment.
	 *
	 * @return {@code true} if it is an inline attachment.
	 */
	public boolean isInline() {
		return isInline;
	}

	/**
	 * Sets whether attachment is inline.
	 *
	 * @param isInline {@code true} for inline.
	 * @return this
	 */
	protected EmailAttachment<T> setInline(final boolean isInline) {
		this.isInline = isInline;
		return this;
	}

	/**
	 * Sets content ID.
	 *
	 * @param contentId content ID of {@link EmailAttachment}.
	 * @return this
	 */
	protected EmailAttachment<T> setContentId(final String contentId) {
		this.contentId = contentId;
		return this;
	}

	/**
	 * Sets target message for embedded attachments.
	 *
	 * @param emailMessage target {@link EmailMessage}.
	 */
	public EmailAttachment<T> setEmbeddedMessage(final EmailMessage emailMessage) {
		targetMessage = emailMessage;
		return this;
	}

	/**
	 * Returns {@code true} if attachment is embedded into provided message.
	 *
	 * @param emailMessage target {@link EmailMessage}.
	 * @return {@code true} if attachment is embedded into provided message.
	 */
	public boolean isEmbeddedInto(final EmailMessage emailMessage) {
		return targetMessage == emailMessage;
	}

	// ---------------------------------------------------------------- data source

	/**
	 * Returns {@link DataSource} implementation, depending on attachment source.
	 */
	public T getDataSource() {
		return dataSource;
	}

	/**
	 * Returns content type of {@link DataSource}.
	 *
	 * @return content type of {@link DataSource}.
	 */
	public String getContentType() {
		return dataSource.getContentType();
	}

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

	/**
	 * Size of attachment. Defaults to -1.
	 */
	private int size = -1;

	/**
	 * Returns size of attachment.
	 *
	 * @return size of attachment or -1 if not yet calculated from {@link DataSource}.
	 */
	public int getSize() {
		return size;
	}

	/**
	 * Sets size of attachment.
	 *
	 * @param size the size of the attachment.
	 * @return this
	 */
	protected EmailAttachment<T> setSize(final int size) {
		this.size = size;
		return this;
	}

	// ---------------------------------------------------------------- content methods

	/**
	 * Returns byte content of the attachment.
	 *
	 * @return byte array with content of the attachment.
	 */
	public byte[] toByteArray() {
		final FastByteArrayOutputStream out;
		if (size != -1) {
			out = new FastByteArrayOutputStream(size);
		} else {
			out = new FastByteArrayOutputStream();
		}
		writeToStream(out);
		return out.toByteArray();
	}

	/**
	 * Saves attachment to a file.
	 *
	 * @param destination The destination file to be written.
	 */
	public void writeToFile(final File destination) {
		InputStream input = null;
		final OutputStream output;
		try {
			input = getDataSource().getInputStream();
			output = new FileOutputStream(destination);

			StreamUtil.copy(input, output);
		}
		catch (final IOException ioex) {
			throw new MailException(ioex);
		}
		finally {
			StreamUtil.close(input);
		}
	}

	/**
	 * Saves attachment to the output stream.
	 *
	 * @param out OutputStream where attachment should be copied to.
	 */
	public void writeToStream(final OutputStream out) {
		InputStream input = null;
		try {
			input = getDataSource().getInputStream();

			StreamUtil.copy(input, out);
		}
		catch (final IOException ioex) {
			throw new MailException(ioex);
		}
		finally {
			StreamUtil.close(input);
		}
	}

}


// 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.type;

import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.PreparedStatement;

public class BigDecimalSqlType extends SqlType<BigDecimal> {
	/**
	 * {@inheritDoc}
	 */
	@Override
	public BigDecimal get(final ResultSet rs, final int index, final int dbSqlType) throws SQLException {
		return rs.getBigDecimal(index);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void set(final PreparedStatement st, final int index, final BigDecimal value, final int dbSqlType) throws SQLException {
		st.setBigDecimal(index, value);
	}

}


// 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.proxetta.asm;

import jodd.asm.AsmUtil;
import jodd.asm7.Label;
import jodd.asm7.MethodVisitor;
import jodd.asm7.Type;
import jodd.proxetta.InvokeAspect;
import jodd.proxetta.InvokeInfo;
import jodd.proxetta.InvokeReplacer;
import jodd.proxetta.MethodInfo;
import jodd.proxetta.ProxettaException;
import jodd.proxetta.ProxyTargetReplacement;
import jodd.util.StringPool;

import static jodd.asm7.Opcodes.ALOAD;
import static jodd.asm7.Opcodes.ASTORE;
import static jodd.asm7.Opcodes.DUP;
import static jodd.asm7.Opcodes.INVOKEINTERFACE;
import static jodd.asm7.Opcodes.INVOKESPECIAL;
import static jodd.asm7.Opcodes.INVOKESTATIC;
import static jodd.asm7.Opcodes.INVOKEVIRTUAL;
import static jodd.asm7.Opcodes.NEW;
import static jodd.asm7.Opcodes.POP;
import static jodd.proxetta.asm.ProxettaAsmUtil.INIT;
import static jodd.proxetta.asm.ProxettaAsmUtil.isArgumentMethod;
import static jodd.proxetta.asm.ProxettaAsmUtil.isArgumentTypeMethod;
import static jodd.proxetta.asm.ProxettaAsmUtil.isInfoMethod;
import static jodd.proxetta.asm.ProxettaAsmUtil.isTargetClassAnnotationMethod;
import static jodd.proxetta.asm.ProxettaAsmUtil.isTargetMethodAnnotationMethod;

/**
 * Invocation replacer method adapter.
 */
public class InvokeReplacerMethodAdapter extends HistoryMethodAdapter {

	protected final WorkData wd;
	protected final MethodInfo methodInfo;
	protected final InvokeAspect[] aspects;

	public InvokeReplacerMethodAdapter(final MethodVisitor mv, final MethodInfo methodInfo, final WorkData wd, final InvokeAspect[] aspects) {
		super(mv);
		this.wd = wd;
		this.aspects = aspects;
		this.methodInfo = methodInfo;
	}

	/**
	 * Detects super ctor invocation.
	 */
	protected boolean firstSuperCtorInitCalled;

	protected boolean proxyInfoRequested;

	/**
	 * New object creation matched.
	 */
	protected InvokeReplacer newInvokeReplacer;

	/**
	 * Invoked on INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC, INVOKEINTERFACE or INVOKEDYNAMIC.
	 */
	@Override
	public void visitMethodInsn(final int opcode, String owner, String name, String desc, final boolean isInterface) {

		// replace NEW.<init>
		if ((newInvokeReplacer != null) && (opcode == INVOKESPECIAL)) {
			String exOwner = owner;
			owner = newInvokeReplacer.getOwner();
			name = newInvokeReplacer.getMethodName();
			desc = changeReturnType(desc, 'L' + exOwner + ';');
			super.visitMethodInsn(INVOKESTATIC, owner, name, desc, isInterface);
			newInvokeReplacer = null;
			return;
		}

		InvokeInfo invokeInfo = new InvokeInfo(owner, name, desc);

		// [*]
		// creating FooClone.<init>; inside the FOO constructor
		// replace the very first invokespecial <init> call (SUB.<init>)
		// to targets subclass with target (FOO.<init>).
		if (methodInfo.getMethodName().equals(INIT)) {
			if (
					(!firstSuperCtorInitCalled) &&
							(opcode == INVOKESPECIAL) &&
							name.equals(INIT) &&
							owner.equals(wd.nextSupername)
					) {
				firstSuperCtorInitCalled = true;
				owner = wd.superReference;
				super.visitMethodInsn(opcode, owner, name, desc, isInterface);
				return;
			}
		}

		// detection of super calls
		if ((opcode == INVOKESPECIAL) && (owner.equals(wd.nextSupername) && (!name.equals(INIT)))) {
			throw new ProxettaException("Super call detected in class " + methodInfo.getClassname() + " method: " + methodInfo.getSignature() +
				"\nProxetta can't handle super calls due to VM limitations.");
		}

		InvokeReplacer ir = null;

		// find first matching aspect
		for (InvokeAspect aspect : aspects) {
			ir = aspect.pointcut(invokeInfo);
			if (ir != null) {
				break;
			}
		}

		if (ir == null || ir.isNone()) {

			if (ProxettaAsmUtil.isCreateArgumentsArrayMethod(name, desc)) {
				ProxyTargetReplacement.createArgumentsArray(mv, methodInfo);
				wd.proxyApplied = true;
				return;
			}

			if (ProxettaAsmUtil.isCreateArgumentsClassArrayMethod(name, desc)) {
				ProxyTargetReplacement.createArgumentsClassArray(mv, methodInfo);
				wd.proxyApplied = true;
				return;
			}

			if (ProxettaAsmUtil.isArgumentsCountMethod(name, desc)) {
				ProxyTargetReplacement.argumentsCount(mv, methodInfo);
				wd.proxyApplied = true;
				return;
			}

			if (ProxettaAsmUtil.isTargetMethodNameMethod(name, desc)) {
				ProxyTargetReplacement.targetMethodName(mv, methodInfo);
				wd.proxyApplied = true;
				return;
			}

			if (ProxettaAsmUtil.isTargetMethodDescriptionMethod(name, desc)) {
				ProxyTargetReplacement.targetMethodDescription(mv, methodInfo);
				wd.proxyApplied = true;
				return;
			}

			if (ProxettaAsmUtil.isTargetMethodSignatureMethod(name, desc)) {
				ProxyTargetReplacement.targetMethodSignature(mv, methodInfo);
				wd.proxyApplied = true;
				return;
			}

			if (ProxettaAsmUtil.isReturnTypeMethod(name, desc)) {
				ProxyTargetReplacement.returnType(mv, methodInfo);
				wd.proxyApplied = true;
				return;
			}

			if (ProxettaAsmUtil.isTargetClassMethod(name, desc)) {
				ProxyTargetReplacement.targetClass(mv, methodInfo);
				wd.proxyApplied = true;
				return;
			}

			if (isArgumentTypeMethod(name, desc)) {
				int argIndex = this.getArgumentIndex();
				ProxyTargetReplacement.argumentType(mv, methodInfo, argIndex);
				wd.proxyApplied = true;
				return;
			}

			if (isArgumentMethod(name, desc)) {
				int argIndex = this.getArgumentIndex();
				ProxyTargetReplacement.argument(mv, methodInfo, argIndex);
				wd.proxyApplied = true;
				return;
			}

			if (isInfoMethod(name, desc)) {
				proxyInfoRequested = true;
				// we are NOT calling the replacement here, as we would expect.
				// NO, we need to wait for the very next ASTORE method so we
				// can read the index and use it for replacement method!!!

				//ProxyTargetReplacement.info(mv, methodInfo);
				wd.proxyApplied = true;
				return;
			}

			if (isTargetMethodAnnotationMethod(name, desc)) {
				String[] args = getLastTwoStringArguments();

				// pop current two args
				mv.visitInsn(POP);
				mv.visitInsn(POP);

				ProxyTargetReplacement.targetMethodAnnotation(mv, methodInfo, args);
				wd.proxyApplied = true;
				return;
			}

			if (isTargetClassAnnotationMethod(name, desc)) {
				String[] args = getLastTwoStringArguments();

				// pop current two args
				mv.visitInsn(POP);
				mv.visitInsn(POP);

				ProxyTargetReplacement.targetClassAnnotation(mv, methodInfo.getClassInfo(), args);
				wd.proxyApplied = true;
				return;
			}

			super.visitMethodInsn(opcode, owner, name, desc, isInterface);
			return;
		}

		wd.proxyApplied = true;

		String exOwner = owner;
		owner = ir.getOwner();
		name = ir.getMethodName();

		switch (opcode) {
			case INVOKEINTERFACE:
				desc = prependArgument(desc, AsmUtil.L_SIGNATURE_JAVA_LANG_OBJECT);
				break;
			case INVOKEVIRTUAL:
				desc = prependArgument(desc, AsmUtil.L_SIGNATURE_JAVA_LANG_OBJECT);
				break;
			case INVOKESTATIC:
				break;
			default:
				throw new ProxettaException("Unsupported opcode: " + opcode);
		}

		// additional arguments
		if (ir.isPassOwnerName()) {
			desc = appendArgument(desc, AsmUtil.L_SIGNATURE_JAVA_LANG_STRING);
			super.visitLdcInsn(exOwner);
		}
		if (ir.isPassMethodName()) {
			desc = appendArgument(desc, AsmUtil.L_SIGNATURE_JAVA_LANG_STRING);
			super.visitLdcInsn(methodInfo.getMethodName());
		}
		if (ir.isPassMethodSignature()) {
			desc = appendArgument(desc, AsmUtil.L_SIGNATURE_JAVA_LANG_STRING);
			super.visitLdcInsn(methodInfo.getSignature());
		}
		if (ir.isPassTargetClass()) {
			desc = appendArgument(desc, AsmUtil.L_SIGNATURE_JAVA_LANG_CLASS);
			super.mv.visitLdcInsn(Type.getType('L' + wd.superReference + ';'));
		}
		if (ir.isPassThis()) {
			desc = appendArgument(desc, AsmUtil.L_SIGNATURE_JAVA_LANG_OBJECT);
			super.mv.visitVarInsn(ALOAD, 0);
		}

		super.visitMethodInsn(INVOKESTATIC, owner, name, desc, false);
	}

	@Override
	public void visitFieldInsn(final int opcode, String owner, final String name, final String desc) {
		// [*]
		// Fix all Foo.<field> to FooClone.<field>
		if (owner.equals(wd.superReference)) {
			owner = wd.thisReference;
		}
		super.visitFieldInsn(opcode, owner, name, desc);
	}

	@Override
	public void visitTypeInsn(final int opcode, final String type) {
		if (opcode == NEW) {
			InvokeInfo invokeInfo = new InvokeInfo(type, INIT, StringPool.EMPTY);
			for (InvokeAspect aspect : aspects) {
				InvokeReplacer ir = aspect.pointcut(invokeInfo);
				if (ir != null && !ir.isNone()) {
					newInvokeReplacer = ir;

					// new pointcut found, skip the new instruction and the following dup.
					// and then go to the invokespecial
					return;
				}
			}
		}
		super.visitTypeInsn(opcode, type);
	}

	@Override
	public void visitVarInsn(final int opcode, final int var) {
		if (proxyInfoRequested) {
			proxyInfoRequested = false;
			if (opcode == ASTORE) {
				ProxyTargetReplacement.info(mv, methodInfo, var);
			}
		}
		super.visitVarInsn(opcode, var);
	}

	@Override
	public void visitInsn(final int opcode) {
		if ((newInvokeReplacer != null) && (opcode == DUP)) {
			return;	// skip dup after new
		}
		super.visitInsn(opcode);
	}

	@Override
	public void visitLocalVariable(final String name, final String desc, final String signature, final Label start, final Label end, final int index) {
	}

	@Override
	public void visitLineNumber(final int line, final Label start) {
	}

	// ---------------------------------------------------------------- util

	/**
	 * Appends argument to the existing description.
	 */
	protected static String appendArgument(final String desc, final String type) {
		int ndx = desc.indexOf(')');
		return desc.substring(0, ndx) + type + desc.substring(ndx);
	}

	/**
	 * Prepends argument to the existing description.
	 */
	protected static String prependArgument(final String desc, final String type) {
		int ndx = desc.indexOf('(');
		ndx++;
		return desc.substring(0, ndx) + type + desc.substring(ndx);
	}

	/**
	 * Changes return type.
	 */
	protected static String changeReturnType(final String desc, final String type) {
		int ndx = desc.indexOf(')');
		return desc.substring(0, ndx + 1) + type;
	}

}


// 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.adapter;

import jodd.lagarto.Tag;
import jodd.lagarto.TagAdapter;
import jodd.lagarto.TagType;
import jodd.lagarto.TagVisitor;
import jodd.util.CharArraySequence;
import jodd.util.CharUtil;

import java.nio.CharBuffer;

/**
 * Strips all non-important characters from HTML.
 * Script and style blocks are not stripped, just HTML text blocks
 * and comments.
 */
public class StripHtmlTagAdapter extends TagAdapter {

	public StripHtmlTagAdapter(final TagVisitor target) {
		super(target);
	}

	protected int strippedCharsCount;
	protected boolean strip;

	@Override
	public void start() {
		strippedCharsCount = 0;
		strip = true;
		super.start();
	}

	/**
	 * Skips HTML comments.
	 */
	@Override
	public void comment(final CharSequence comment) {
		strippedCharsCount += comment.length() + 7;
	}

	private static CharSequence PRE = CharArraySequence.of('p', 'r', 'e');

	@Override
	public void tag(final Tag tag) {
		if (tag.getType() == TagType.START && tag.nameEquals(PRE)) {
			strip = false;
		}

		if (tag.getType() == TagType.END && tag.nameEquals(PRE)) {
			strip = true;
		}

		super.tag(tag);
	}

	/**
	 * Cleans unnecessary whitespaces.
	 */
	@Override
	public void text(final CharSequence text) {
		if (!strip) {
			super.text(text);
			return;
		}

		int textLength = text.length();

		char[] dest = new char[textLength];

		int ndx = 0;
		boolean regularChar = true;
		for (int i = 0; i < textLength; i++) {
			char c = text.charAt(i);

			if (CharUtil.isWhitespace(c)) {
				if (regularChar) {
					regularChar = false;
					c = ' ';
				} else {
					continue;
				}
			} else {
				regularChar = true;
			}

			dest[ndx] = c;
			ndx++;
		}

		if (regularChar || (ndx != 1)) {
			super.text(CharBuffer.wrap(dest, 0, ndx));
			strippedCharsCount += textLength - ndx;
		} else {
			strippedCharsCount += textLength;
		}
	}

	/**
	 * Returns total number of stripped chars.
	 */
	public int getStrippedCharsCount() {
		return strippedCharsCount;
	}
}


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

import jodd.cache.TypeCache;
import jodd.io.upload.FileUpload;
import jodd.mutable.MutableByte;
import jodd.mutable.MutableDouble;
import jodd.mutable.MutableFloat;
import jodd.mutable.MutableInteger;
import jodd.mutable.MutableLong;
import jodd.mutable.MutableShort;
import jodd.typeconverter.impl.ArrayConverter;
import jodd.typeconverter.impl.BigDecimalConverter;
import jodd.typeconverter.impl.BigIntegerConverter;
import jodd.typeconverter.impl.BooleanArrayConverter;
import jodd.typeconverter.impl.BooleanConverter;
import jodd.typeconverter.impl.ByteArrayConverter;
import jodd.typeconverter.impl.ByteConverter;
import jodd.typeconverter.impl.CalendarConverter;
import jodd.typeconverter.impl.CharacterArrayConverter;
import jodd.typeconverter.impl.CharacterConverter;
import jodd.typeconverter.impl.ClassArrayConverter;
import jodd.typeconverter.impl.ClassConverter;
import jodd.typeconverter.impl.CollectionConverter;
import jodd.typeconverter.impl.DateConverter;
import jodd.typeconverter.impl.DoubleArrayConverter;
import jodd.typeconverter.impl.DoubleConverter;
import jodd.typeconverter.impl.FileConverter;
import jodd.typeconverter.impl.FileUploadConverter;
import jodd.typeconverter.impl.FloatArrayConverter;
import jodd.typeconverter.impl.FloatConverter;
import jodd.typeconverter.impl.IntegerArrayConverter;
import jodd.typeconverter.impl.IntegerConverter;
import jodd.typeconverter.impl.LocalDateConverter;
import jodd.typeconverter.impl.LocalDateTimeConverter;
import jodd.typeconverter.impl.LocalTimeConverter;
import jodd.typeconverter.impl.LocaleConverter;
import jodd.typeconverter.impl.LongArrayConverter;
import jodd.typeconverter.impl.LongConverter;
import jodd.typeconverter.impl.MutableByteConverter;
import jodd.typeconverter.impl.MutableDoubleConverter;
import jodd.typeconverter.impl.MutableFloatConverter;
import jodd.typeconverter.impl.MutableIntegerConverter;
import jodd.typeconverter.impl.MutableLongConverter;
import jodd.typeconverter.impl.MutableShortConverter;
import jodd.typeconverter.impl.ShortArrayConverter;
import jodd.typeconverter.impl.ShortConverter;
import jodd.typeconverter.impl.SqlDateConverter;
import jodd.typeconverter.impl.SqlTimeConverter;
import jodd.typeconverter.impl.SqlTimestampConverter;
import jodd.typeconverter.impl.StringArrayConverter;
import jodd.typeconverter.impl.StringConverter;
import jodd.typeconverter.impl.TimeZoneConverter;
import jodd.typeconverter.impl.URIConverter;
import jodd.typeconverter.impl.URLConverter;
import jodd.typeconverter.impl.UUIDConverter;
import jodd.util.ClassUtil;

import java.io.File;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.net.URL;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Calendar;
import java.util.Collection;
import java.util.Locale;
import java.util.TimeZone;
import java.util.UUID;

/**
 * Provides dynamic object conversion to a type.
 * Contains a map of registered converters. User may add new converters.
 */
public class TypeConverterManager {

	private static final TypeConverterManager TYPE_CONVERTER_MANAGER = new TypeConverterManager();

	/**
	 * Returns default implementation.
	 */
	public static TypeConverterManager get() {
		return TYPE_CONVERTER_MANAGER;
	}

	private final TypeCache<TypeConverter> converters = TypeCache.createDefault();

	// ---------------------------------------------------------------- methods

	public TypeConverterManager() {
		registerDefaults();
	}

	/**
	 * Registers default set of converters.
	 */
	public void registerDefaults() {
		register(String.class, new StringConverter());
		register(String[].class, new StringArrayConverter(this));

		IntegerConverter integerConverter = new IntegerConverter();
		register(Integer.class, integerConverter);
		register(int.class, integerConverter);
		register(MutableInteger.class, new MutableIntegerConverter(this));

		ShortConverter shortConverter = new ShortConverter();
		register(Short.class, shortConverter);
		register(short.class, shortConverter);
		register(MutableShort.class, new MutableShortConverter(this));

		LongConverter longConverter = new LongConverter();
		register(Long.class, longConverter);
		register(long.class, longConverter);
		register(MutableLong.class, new MutableLongConverter(this));

		ByteConverter byteConverter = new ByteConverter();
		register(Byte.class, byteConverter);
		register(byte.class, byteConverter);
		register(MutableByte.class, new MutableByteConverter(this));

		FloatConverter floatConverter = new FloatConverter();
		register(Float.class, floatConverter);
		register(float.class, floatConverter);
		register(MutableFloat.class, new MutableFloatConverter(this));

		DoubleConverter doubleConverter = new DoubleConverter();
		register(Double.class, doubleConverter);
		register(double.class, doubleConverter);
		register(MutableDouble.class, new MutableDoubleConverter(this));

		BooleanConverter booleanConverter = new BooleanConverter();
		register(Boolean.class, booleanConverter);
		register(boolean.class, booleanConverter);

		CharacterConverter characterConverter = new CharacterConverter();
		register(Character.class, characterConverter);
		register(char.class, characterConverter);

		register(byte[].class, new ByteArrayConverter(this));
		register(short[].class, new ShortArrayConverter(this));
		register(int[].class, new IntegerArrayConverter(this));
		register(long[].class, new LongArrayConverter(this));
		register(float[].class, new FloatArrayConverter(this));
		register(double[].class, new DoubleArrayConverter(this));
		register(boolean[].class, new BooleanArrayConverter(this));
		register(char[].class, new CharacterArrayConverter(this));

		// we don't really need these, but converters will be cached and not created every time
		register(Integer[].class, new ArrayConverter<Integer>(this, Integer.class) {
			@Override
			protected Integer[] createArray(final int length) {
				return new Integer[length];
			}
		});
		register(Long[].class, new ArrayConverter<Long>(this, Long.class) {
			@Override
			protected Long[] createArray(final int length) {
				return new Long[length];
			}
		});
		register(Byte[].class, new ArrayConverter<Byte>(this, Byte.class) {
			@Override
			protected Byte[] createArray(final int length) {
				return new Byte[length];
			}
		});
		register(Short[].class, new ArrayConverter<Short>(this, Short.class) {
			@Override
			protected Short[] createArray(final int length) {
				return new Short[length];
			}
		});
		register(Float[].class, new ArrayConverter<Float>(this, Float.class) {
			@Override
			protected Float[] createArray(final int length) {
				return new Float[length];
			}
		});
		register(Double[].class, new ArrayConverter<Double>(this, Double.class) {
			@Override
			protected Double[] createArray(final int length) {
				return new Double[length];
			}
		});
		register(Boolean[].class, new ArrayConverter<Boolean>(this, Boolean.class) {
			@Override
			protected Boolean[] createArray(final int length) {
				return new Boolean[length];
			}
		});
		register(Character[].class, new ArrayConverter<Character>(this, Character.class) {
			@Override
			protected Character[] createArray(final int length) {
				return new Character[length];
			}
		});

		register(MutableInteger[].class, new ArrayConverter<>(this, MutableInteger.class));
		register(MutableLong[].class, new ArrayConverter<>(this, MutableLong.class));
		register(MutableByte[].class, new ArrayConverter<>(this, MutableByte.class));
		register(MutableShort[].class, new ArrayConverter<>(this, MutableShort.class));
		register(MutableFloat[].class, new ArrayConverter<>(this, MutableFloat.class));
		register(MutableDouble[].class, new ArrayConverter<>(this, MutableDouble.class));

		register(BigDecimal.class, new BigDecimalConverter());
		register(BigInteger.class, new BigIntegerConverter());
		register(BigDecimal[].class, new ArrayConverter<>(this, BigDecimal.class));
		register(BigInteger[].class, new ArrayConverter<>(this, BigInteger.class));

		register(java.util.Date.class, new DateConverter());
		register(java.sql.Date.class, new SqlDateConverter());
		register(Time.class, new SqlTimeConverter());
		register(Timestamp.class, new SqlTimestampConverter());
		register(Calendar.class, new CalendarConverter());
//		register(GregorianCalendar.class, new CalendarConverter());
		register(LocalDateTime.class, new LocalDateTimeConverter());
		register(LocalDate.class, new LocalDateConverter());
		register(LocalTime.class, new LocalTimeConverter());

		register(File.class, new FileConverter());
		register(FileUpload.class, new FileUploadConverter());

		register(Class.class, new ClassConverter());
		register(Class[].class, new ClassArrayConverter(this));

		register(URI.class, new URIConverter());
		register(URL.class, new URLConverter());

		register(Locale.class, new LocaleConverter());
		register(TimeZone.class, new TimeZoneConverter());

		register(UUID.class, new UUIDConverter());
	}

	/**
	 * Registers a converter for specified type.
	 * User must register converter for all super-classes as well.
	 *
	 * @param type		class that converter is for
	 * @param typeConverter	converter for provided class
	 */
	public <T> void register(final Class<T> type, final TypeConverter<T> typeConverter) {
		converters.put(type, typeConverter);
	}

	/**
	 * Un-registers converter for given type.
	 */
	public void unregister(final Class type) {
		converters.remove(type);
	}

	// ---------------------------------------------------------------- lookup

	/**
	 * Retrieves converter for provided type. Only registered types are matched,
	 * therefore subclasses must be also registered.
	 *
	 * @return founded converter or null
	 */
	public <T> TypeConverter<T> lookup(final Class<T> type) {
		return converters.get(type);
	}

	// ---------------------------------------------------------------- converter

	/**
	 * Converts an object to destination type. If type is registered, it's
	 * {@link TypeConverter} will be used. If not, it scans of destination is
	 * an array or enum, as those two cases are handled in a special way.
	 *

	 * If destination type is one of common types, consider using {@link Converter}
	 * instead for somewhat faster approach (no lookup).
	 */
	@SuppressWarnings({"unchecked"})
	public <T> T convertType(final Object value, final Class<T> destinationType) {
		if (destinationType == Object.class) {
			// no conversion :)
			return (T) value;
		}

		final TypeConverter converter = lookup(destinationType);

		if (converter != null) {
			return (T) converter.convert(value);
		}

		// no converter

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

		// check same instances
		if (ClassUtil.isInstanceOf(value, destinationType)) {
			return (T) value;
		}

		// handle destination arrays
		if (destinationType.isArray()) {
			ArrayConverter<T> arrayConverter = new ArrayConverter(this, destinationType.getComponentType());

			return (T) arrayConverter.convert(value);
		}

		// handle enums
		if (destinationType.isEnum()) {
			Object[] enums = destinationType.getEnumConstants();
			String valStr = value.toString();
			for (Object e : enums) {
				if (e.toString().equals(valStr)) {
					return (T) e;
				}
			}
		}

		// collection
		if (ClassUtil.isTypeOf(destinationType, Collection.class)) {
			// component type is unknown because of Java's type-erasure
			CollectionConverter<T> collectionConverter =
					new CollectionConverter(this, destinationType, Object.class);

			return (T) collectionConverter.convert(value);
		}

		// fail
		throw new TypeConversionException("Conversion failed of input type: " + value.getClass() + " into: " + destinationType.getName());
	}

	/**
	 * Special case of {@link #convertType(Object, Class)} when target is collection and
	 * when component type is known.
	 */
	@SuppressWarnings("unchecked")
	public <C extends Collection<T>, T> C convertToCollection(final Object value, final Class<? extends Collection> destinationType, final Class<T> componentType) {
		if (value == null) {
			return null;
		}

		// check same instances
		if (ClassUtil.isInstanceOf(value, destinationType)) {
			return (C) value;
		}

		final CollectionConverter collectionConverter;

		if (componentType == null) {
			collectionConverter = new CollectionConverter(destinationType, Object.class);
		}
		else {
			collectionConverter = new CollectionConverter(destinationType, componentType);
		}

		return (C) collectionConverter.convert(value);
	}

}


// 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.csselly.CSSelly;
import jodd.csselly.Combinator;
import jodd.csselly.CssSelector;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

/**
 * Node selector selects DOM nodes using {@link CSSelly CSS3 selectors}.
 * Group of queries are supported.
 */
public class NodeSelector {

	protected final Node rootNode;

	public NodeSelector(final Node rootNode) {
		this.rootNode = rootNode;
	}

	// ---------------------------------------------------------------- selector

	/**
	 * Selects nodes using CSS3 selector query.
	 */
	public List<Node> select(final String query) {
		Collection<List<CssSelector>> selectorsCollection = CSSelly.parse(query);
		return select(selectorsCollection);
	}

	/**
	 * Selected nodes using pre-parsed CSS selectors. Take in consideration
	 * collection type for results grouping order.
	 */
	public List<Node> select(final Collection<List<CssSelector>> selectorsCollection) {
		List<Node> results = new ArrayList<>();
		for (List<CssSelector> selectors : selectorsCollection) {
			processSelectors(results, selectors);
		}
		return results;
	}

	/**
	 * Process selectors and keep adding results.
	 */
	protected void processSelectors(final List<Node> results, final List<CssSelector> selectors) {
		List<Node> selectedNodes = select(rootNode, selectors);

		for (Node selectedNode : selectedNodes) {
			if (!results.contains(selectedNode)) {
				results.add(selectedNode);
			}
		}
	}

	/**
	 * Selects nodes using CSS3 selector query and returns the very first one.
	 */
	public Node selectFirst(final String query) {
		List<Node> selectedNodes = select(query);
		if (selectedNodes.isEmpty()) {
			return null;
		}
		return selectedNodes.get(0);
	}

	/**
	 * Selects nodes using {@link NodeFilter node filter}.
	 */
	public List<Node> select(final NodeFilter nodeFilter) {
		List<Node> nodes = new ArrayList<>();
		walk(rootNode, nodeFilter, nodes);
		return nodes;
	}

	/**
	 * Selects nodes using {@link NodeFilter node filter} and return the very first one.
	 */
	public Node selectFirst(final NodeFilter nodeFilter) {
		List<Node> selectedNodes = select(nodeFilter);
		if (selectedNodes.isEmpty()) {
			return null;
		}
		return selectedNodes.get(0);
	}

	// ---------------------------------------------------------------- internal

	protected void walk(final Node rootNode, final NodeFilter nodeFilter, final List<Node> result) {
		int childCount = rootNode.getChildNodesCount();
		for (int i = 0; i < childCount; i++) {
			Node node = rootNode.getChild(i);
			if (nodeFilter.accept(node)) {
				result.add(node);
			}
			walk(node, nodeFilter, result);
		}
	}

	protected List<Node> select(final Node rootNode, final List<CssSelector> selectors) {

		// start with the root node
		List<Node> nodes = new ArrayList<>();
		nodes.add(rootNode);

		// iterate all selectors
		for (CssSelector cssSelector : selectors) {

			// create new set of results for current css selector
			List<Node> selectedNodes = new ArrayList<>();
			for (Node node : nodes) {
				walk(node, cssSelector, selectedNodes);
			}

			// post-processing: filter out the results
			List<Node> resultNodes = new ArrayList<>();
			int index = 0;
			for (Node node : selectedNodes) {
				boolean match = filter(selectedNodes, node, cssSelector, index);
				if (match) {
					resultNodes.add(node);
				}
				index++;
			}

			// continue with results
			nodes = resultNodes;
		}

		return nodes;
	}

	/**
	 * Walks over the child notes, maintaining the tree order and not using recursion.
	 */
	protected void walkDescendantsIteratively(final LinkedList<Node> nodes, final CssSelector cssSelector, final List<Node> result) {
		while (!nodes.isEmpty()) {
			Node node = nodes.removeFirst();
			selectAndAdd(node, cssSelector, result);

			// append children in walking order to be processed right after this node
			int childCount = node.getChildNodesCount();
			for (int i = childCount - 1; i >= 0; i--) {
				nodes.addFirst(node.getChild(i));
			}
		}
	}

	/**
	 * Finds nodes in the tree that matches single selector.
	 */
	protected void walk(final Node rootNode, final CssSelector cssSelector, final List<Node> result) {

		// previous combinator determines the behavior
		CssSelector previousCssSelector = cssSelector.getPrevCssSelector();

		Combinator combinator = previousCssSelector != null ?
				previousCssSelector.getCombinator() :
				Combinator.DESCENDANT;

		switch (combinator) {
			case DESCENDANT:
				LinkedList<Node> nodes = new LinkedList<>();
				int childCount = rootNode.getChildNodesCount();
				for (int i = 0; i < childCount; i++) {
					nodes.add(rootNode.getChild(i));
					// recursive
//					selectAndAdd(node, cssSelector, result);
//					walk(node, cssSelector, result);
				}
				walkDescendantsIteratively(nodes, cssSelector, result);
				break;
			case CHILD:
				childCount = rootNode.getChildNodesCount();
				for (int i = 0; i < childCount; i++) {
					Node node = rootNode.getChild(i);
					selectAndAdd(node, cssSelector, result);
				}
				break;
			case ADJACENT_SIBLING:
				Node node = rootNode.getNextSiblingElement();
				if (node != null) {
					selectAndAdd(node, cssSelector, result);
				}
				break;
			case GENERAL_SIBLING:
				node = rootNode;
				while (true) {
					node = node.getNextSiblingElement();
					if (node == null) {
						break;
					}
					selectAndAdd(node, cssSelector, result);
				}
				break;
		}	}

	/**
	 * Selects single node for single selector and appends it to the results.
	 */
	protected void selectAndAdd(final Node node, final CssSelector cssSelector, final List<Node> result) {
		// ignore all nodes that are not elements
		if (node.getNodeType() != Node.NodeType.ELEMENT) {
			return;
		}
		boolean matched = cssSelector.accept(node);
		if (matched) {
			// check for duplicates
			if (result.contains(node)) {
				return;
			}
			// no duplicate found, add it to the results
			result.add(node);
		}
	}

	/**
	 * Filter nodes.
	 */
	protected boolean filter(final List<Node> currentResults, final Node node, final CssSelector cssSelector, final int index) {
		return cssSelector.accept(currentResults, node, 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.

/* The following code was generated by JFlex 1.6.1 */

package jodd.csselly;

import java.nio.CharBuffer;
import java.util.ArrayList;

/**
 * This class is a scanner generated by
 * JFlex 1.6.1
 * from the specification file <tt>csselly.flex</tt>
 */
final class CSSellyLexer {

  /** This character denotes the end of file */
  public static final int YYEOF = -1;

  /** initial size of the lookahead buffer */
  private static final int ZZ_BUFFERSIZE = 4096;

  /** lexical states */
  public static final int YYINITIAL = 0;
  public static final int SELECTOR = 2;
  public static final int ATTR = 4;
  public static final int COMBINATOR = 6;
  public static final int PSEUDO_FN = 8;

  /**
   * ZZ_LEXSTATE[l] is the state in the DFA for the lexical state l
   * ZZ_LEXSTATE[l+1] is the state in the DFA for the lexical state l
   *                  at the beginning of a line
   * l is of the form l = 2*k, k a non negative integer
   */
  private static final int ZZ_LEXSTATE[] = {
     0,  0,  1,  1,  2,  2,  3,  3,  4, 4
  };

  /**
   * Translates characters to character classes
   */
  private static final String ZZ_CMAP_PACKED =
    "\11\2\1\10\1\7\1\17\1\11\1\6\22\2\1\10\1\2\1\13"+
    "\1\22\1\27\2\2\1\14\1\24\1\15\1\16\1\32\1\2\1\1"+
    "\1\12\1\2\12\5\1\23\2\2\1\25\1\31\2\2\32\0\1\21"+
    "\1\3\1\30\1\27\1\0\1\2\6\4\24\0\1\2\1\27\1\2"+
    "\1\26\1\2\5\0\1\20\u1fa2\0\1\20\1\20\uffff\0\uffff\0\uffff\0\uffff\0\uffff\0\uffff\0\uffff\0\uffff\0\uffff\0\uffff\0\uffff\0\uffff\0\uffff\0\uffff\0\uffff\0\uffff\0\udfe6\0";

  /**
   * Translates characters to character classes
   */
  private static final char [] ZZ_CMAP = zzUnpackCMap(ZZ_CMAP_PACKED);

  /**
   * Translates DFA states to action switch labels.
   */
  private static final int [] ZZ_ACTION = zzUnpackAction();

  private static final String ZZ_ACTION_PACKED_0 =
    "\3\0\1\1\1\0\1\2\3\3\1\4\1\5\1\6"+
    "\2\7\1\10\2\7\1\11\3\6\1\12\1\13\1\1"+
    "\1\14\1\15\1\16\4\6\1\17\1\0\1\2\1\20"+
    "\2\0\1\21\1\0\1\22\4\0\1\11\2\0\1\11"+
    "\6\0\1\17\2\0\1\17\1\0\1\17\2\2\1\20"+
    "\1\21\1\23\1\22\1\11\4\0\2\11\2\0\1\17"+
    "\1\0\1\17\4\0\1\17\2\0\1\2\2\20\2\21"+
    "\2\22\2\11\2\0\1\11\3\0\1\17\7\0\1\2"+
    "\1\20\1\21\1\22\2\11\2\0\1\11\2\0\2\11"+
    "\7\0\1\2\1\20\1\21\1\22\1\11\2\0\1\11"+
    "\6\0\1\2\1\20\1\21\1\22\1\11\2\0\1\11"+
    "\6\0\1\20\1\21\1\22\1\11\10\0\1\11\11\0";

  private static int [] zzUnpackAction() {
    int [] result = new int[178];
    int offset = 0;
    offset = zzUnpackAction(ZZ_ACTION_PACKED_0, offset, result);
    return result;
  }

  private static int zzUnpackAction(final String packed, final int offset, final int [] result) {
    int i = 0;       /* index in packed string  */
    int j = offset;  /* index in unpacked array */
    int l = packed.length();
    while (i < l) {
      int count = packed.charAt(i++);
      int value = packed.charAt(i++);
      do result[j++] = value; while (--count > 0);
    }
    return j;
  }

  /**
   * Translates a state to a row index in the transition table
   */
  private static final int [] ZZ_ROWMAP = zzUnpackRowMap();

  private static final String ZZ_ROWMAP_PACKED_0 =
    "\0\0\0\33\0\66\0\121\0\154\0\207\0\242\0\275"+
    "\0\330\0\363\0\275\0\275\0\275\0\u010e\0\275\0\u0129"+
    "\0\u0144\0\u015f\0\u017a\0\u0195\0\u01b0\0\275\0\275\0\u01cb"+
    "\0\u01e6\0\u0201\0\u021c\0\u0237\0\u0252\0\u026d\0\u0288\0\275"+
    "\0\330\0\u02a3\0\u02be\0\u02d9\0\u02f4\0\u0129\0\u030f\0\u032a"+
    "\0\u0345\0\u0360\0\u037b\0\u0195\0\u0396\0\u03b1\0\u03cc\0\u03e7"+
    "\0\u017a\0\u01b0\0\u0237\0\u0252\0\u026d\0\u0288\0\u0237\0\u0402"+
    "\0\u041d\0\u0438\0\u0453\0\u046e\0\u0489\0\u04a4\0\u04bf\0\u04da"+
    "\0\275\0\u04f5\0\u0510\0\u052b\0\u0546\0\u0561\0\u057c\0\u0597"+
    "\0\u05b2\0\u05cd\0\u05e8\0\u026d\0\u0603\0\u061e\0\u0438\0\u0639"+
    "\0\u0654\0\u066f\0\u0288\0\u046e\0\u068a\0\u06a5\0\u06c0\0\u06db"+
    "\0\u06f6\0\u0711\0\u072c\0\u0747\0\u0762\0\u077d\0\u0798\0\u07b3"+
    "\0\u07ce\0\u07e9\0\u0804\0\u081f\0\u041d\0\u061e\0\u083a\0\u0855"+
    "\0\u0870\0\u088b\0\u08a6\0\u08c1\0\u08dc\0\u08f7\0\u0912\0\u092d"+
    "\0\u0948\0\u0963\0\u097e\0\u0999\0\u09b4\0\u09cf\0\u09ea\0\u0a05"+
    "\0\u0a20\0\u0a3b\0\u0a56\0\u0a71\0\u0a8c\0\u0aa7\0\u0ac2\0\u0add"+
    "\0\u0af8\0\u0b13\0\u0b2e\0\u0b49\0\u0b64\0\u0b7f\0\u0b9a\0\u0bb5"+
    "\0\u0bd0\0\u0beb\0\u0c06\0\u0c21\0\u0c3c\0\u0c57\0\u0c72\0\u0c8d"+
    "\0\u0ca8\0\u0cc3\0\u0cde\0\u0cf9\0\u0d14\0\u0d2f\0\u0d4a\0\u0d65"+
    "\0\u0d80\0\u0d9b\0\u0db6\0\u0dd1\0\u0dec\0\u0e07\0\u0e22\0\u0e3d"+
    "\0\u0e58\0\u0e73\0\u0e8e\0\u0ea9\0\u0ec4\0\u0edf\0\u0efa\0\u0f15"+
    "\0\u0f30\0\u0f4b\0\u0f66\0\u0f81\0\u0f9c\0\u0fb7\0\u0fd2\0\u0fed"+
    "\0\u1008\0\u1023";

  private static int [] zzUnpackRowMap() {
    int [] result = new int[178];
    int offset = 0;
    offset = zzUnpackRowMap(ZZ_ROWMAP_PACKED_0, offset, result);
    return result;
  }

  private static int zzUnpackRowMap(final String packed, final int offset, final int [] result) {
    int i = 0;  /* index in packed string  */
    int j = offset;  /* index in unpacked array */
    int l = packed.length();
    while (i < l) {
      int high = packed.charAt(i++) << 16;
      result[j++] = high | packed.charAt(i++);
    }
    return j;
  }

  /**
   * The transition table of the DFA
   */
  private static final int [] ZZ_TRANS = zzUnpackTrans();

  private static final String ZZ_TRANS_PACKED_0 =
    "\1\6\1\7\1\10\1\11\1\6\1\10\4\12\4\10"+
    "\1\13\1\14\1\6\12\10\6\15\2\14\1\15\1\14"+
    "\1\16\4\15\2\14\1\17\1\20\1\21\7\15\1\22"+
    "\1\23\1\14\1\24\1\22\1\14\4\25\6\14\1\22"+
    "\7\14\1\26\2\14\6\27\4\30\5\27\2\14\5\27"+
    "\1\31\2\27\1\32\1\33\3\34\1\35\7\34\1\36"+
    "\1\37\1\40\15\34\2\6\1\0\1\41\2\6\12\0"+
    "\1\6\12\0\1\6\2\0\1\41\1\6\13\0\1\6"+
    "\45\0\4\6\2\42\2\0\1\6\1\0\21\6\6\0"+
    "\4\12\21\0\1\43\1\44\1\0\1\45\1\43\13\0"+
    "\1\43\12\0\2\46\1\0\1\47\2\46\12\0\1\46"+
    "\12\0\1\50\1\51\1\0\1\52\1\50\13\0\1\50"+
    "\2\0\1\53\7\0\2\22\1\0\1\54\2\22\4\55"+
    "\4\0\1\56\1\0\1\22\4\0\1\57\2\56\3\0"+
    "\1\22\2\0\1\54\1\22\13\0\1\22\12\0\4\22"+
    "\2\60\2\0\1\22\1\0\22\22\1\61\1\0\1\54"+
    "\1\22\1\0\4\62\6\0\1\22\20\0\4\30\14\0"+
    "\1\31\2\0\1\32\1\33\6\0\4\31\27\0\4\32"+
    "\27\0\4\33\21\0\3\63\1\64\7\63\1\65\1\66"+
    "\1\40\20\63\1\64\7\63\1\65\1\66\1\67\15\63"+
    "\3\65\1\70\2\65\2\63\1\65\1\63\2\65\1\71"+
    "\1\72\15\65\3\66\1\73\2\66\2\63\1\66\1\63"+
    "\1\66\1\71\1\66\1\74\15\66\2\6\1\0\1\41"+
    "\2\75\1\76\3\6\6\0\1\6\12\0\2\43\1\0"+
    "\1\45\2\43\12\0\1\43\12\0\1\43\2\0\1\45"+
    "\1\43\13\0\1\43\12\0\4\43\2\77\2\0\1\43"+
    "\1\0\21\43\4\46\2\100\2\0\1\46\1\0\21\46"+
    "\2\50\1\0\1\52\2\50\12\0\1\50\3\0\1\101"+
    "\6\0\1\50\2\0\1\52\1\50\13\0\1\50\12\0"+
    "\4\50\2\102\2\0\1\50\1\0\22\50\1\51\1\0"+
    "\1\52\1\50\13\0\1\50\20\0\4\55\4\0\1\56"+
    "\6\0\1\57\2\56\30\0\1\57\5\0\1\103\1\104"+
    "\1\0\1\105\1\103\1\0\4\57\1\0\1\106\1\107"+
    "\3\0\1\103\12\0\2\22\1\0\1\54\2\110\1\111"+
    "\3\22\4\0\1\56\1\0\1\22\4\0\1\57\2\56"+
    "\3\0\3\65\1\70\2\112\1\113\5\65\1\71\1\114"+
    "\15\65\3\71\1\115\2\71\2\63\1\71\1\63\3\71"+
    "\1\116\15\71\3\117\1\120\2\117\2\0\1\117\1\0"+
    "\1\117\1\63\17\117\3\66\1\73\2\121\1\122\4\66"+
    "\1\71\1\66\1\123\15\66\3\124\1\125\2\124\2\0"+
    "\1\124\1\0\2\124\1\63\16\124\2\6\1\0\1\41"+
    "\2\126\1\76\3\6\6\0\1\6\12\0\2\6\1\0"+
    "\1\41\2\6\1\0\1\6\10\0\1\6\12\0\2\43"+
    "\1\0\1\45\2\127\1\130\3\43\6\0\1\43\12\0"+
    "\2\46\1\0\1\47\2\131\1\132\3\46\6\0\1\46"+
    "\12\0\2\50\1\0\1\52\2\133\1\134\3\50\6\0"+
    "\1\50\3\0\1\101\6\0\2\103\1\0\1\105\2\103"+
    "\4\135\6\0\1\103\12\0\1\103\2\0\1\105\1\103"+
    "\13\0\1\103\12\0\4\103\2\136\2\0\1\103\1\0"+
    "\21\103\3\106\1\137\2\106\2\0\1\106\1\0\1\106"+
    "\1\135\17\106\3\107\1\140\2\107\2\0\1\107\1\0"+
    "\2\107\1\135\16\107\2\22\1\0\1\54\2\141\1\111"+
    "\3\22\4\0\1\56\1\0\1\22\4\0\1\57\2\56"+
    "\3\0\2\22\1\0\1\54\2\22\1\55\1\22\2\55"+
    "\4\0\1\56\1\0\1\22\4\0\1\57\2\56\3\0"+
    "\3\65\1\70\2\142\1\113\5\65\1\71\1\72\20\65"+
    "\1\70\2\65\1\63\2\65\1\63\2\65\1\71\1\72"+
    "\15\65\3\71\1\115\2\143\1\144\6\71\1\145\15\71"+
    "\3\146\1\147\2\146\2\0\1\146\1\0\1\146\1\66"+
    "\1\65\16\146\3\117\1\120\2\150\1\151\4\117\1\65"+
    "\17\117\3\66\1\73\2\152\1\122\4\66\1\71\1\66"+
    "\1\74\20\66\1\73\2\66\1\63\2\66\1\63\1\66"+
    "\1\71\1\66\1\74\15\66\3\124\1\125\2\153\1\154"+
    "\5\124\1\66\16\124\2\6\1\0\1\41\2\155\1\76"+
    "\3\6\6\0\1\6\12\0\2\43\1\0\1\45\2\156"+
    "\1\130\3\43\6\0\1\43\12\0\2\43\1\0\1\45"+
    "\2\43\1\0\1\43\10\0\1\43\12\0\2\46\1\0"+
    "\1\47\2\157\1\132\3\46\6\0\1\46\12\0\2\46"+
    "\1\0\1\47\2\46\1\0\1\46\10\0\1\46\12\0"+
    "\2\50\1\0\1\52\2\160\1\134\3\50\6\0\1\50"+
    "\3\0\1\101\6\0\2\50\1\0\1\52\2\50\1\0"+
    "\1\50\10\0\1\50\3\0\1\101\14\0\4\135\21\0"+
    "\2\103\1\0\1\105\2\161\1\162\3\103\6\0\1\103"+
    "\12\0\3\106\1\137\2\163\1\164\4\106\1\165\17\106"+
    "\3\107\1\140\2\166\1\167\5\107\1\170\16\107\2\22"+
    "\1\0\1\54\2\171\1\111\3\22\4\0\1\56\1\0"+
    "\1\22\4\0\1\57\2\56\3\0\3\65\1\70\2\172"+
    "\1\113\5\65\1\71\1\72\15\65\3\71\1\115\2\173"+
    "\1\144\6\71\1\116\20\71\1\115\2\71\1\63\2\71"+
    "\1\63\3\71\1\116\15\71\3\146\1\147\2\174\1\175"+
    "\4\146\2\71\16\146\3\117\1\120\2\176\1\151\4\117"+
    "\1\63\22\117\1\120\2\117\1\0\2\117\1\0\1\117"+
    "\1\63\17\117\3\66\1\73\2\177\1\122\4\66\1\71"+
    "\1\66\1\74\15\66\3\124\1\125\2\200\1\154\5\124"+
    "\1\63\21\124\1\125\2\124\1\0\2\124\1\0\2\124"+
    "\1\63\16\124\2\6\1\0\1\41\2\201\1\76\3\6"+
    "\6\0\1\6\12\0\2\43\1\0\1\45\2\202\1\130"+
    "\3\43\6\0\1\43\12\0\2\46\1\0\1\47\2\203"+
    "\1\132\3\46\6\0\1\46\12\0\2\50\1\0\1\52"+
    "\2\204\1\134\3\50\6\0\1\50\3\0\1\101\6\0"+
    "\2\103\1\0\1\105\2\205\1\162\3\103\6\0\1\103"+
    "\12\0\2\103\1\0\1\105\2\103\1\135\1\103\2\135"+
    "\6\0\1\103\12\0\3\106\1\137\2\206\1\164\4\106"+
    "\1\135\22\106\1\137\2\106\1\0\2\106\1\0\1\106"+
    "\1\135\22\106\1\137\2\106\2\135\1\165\1\135\1\106"+
    "\1\135\17\106\3\107\1\140\2\207\1\167\5\107\1\135"+
    "\21\107\1\140\2\107\1\0\2\107\1\0\2\107\1\135"+
    "\21\107\1\140\2\107\2\135\1\170\1\135\2\107\1\135"+
    "\16\107\2\22\1\0\1\54\2\210\1\111\3\22\4\0"+
    "\1\56\1\0\1\22\4\0\1\57\2\56\3\0\3\65"+
    "\1\70\2\211\1\113\5\65\1\71\1\72\15\65\3\71"+
    "\1\115\2\212\1\144\6\71\1\116\15\71\3\146\1\147"+
    "\2\213\1\175\4\146\1\66\1\65\21\146\1\147\2\146"+
    "\1\0\2\146\1\0\1\146\1\66\1\65\16\146\3\117"+
    "\1\120\2\214\1\151\4\117\1\63\17\117\3\66\1\73"+
    "\2\215\1\122\4\66\1\71\1\66\1\74\15\66\3\124"+
    "\1\125\2\216\1\154\5\124\1\63\16\124\2\6\1\0"+
    "\1\41\2\217\1\76\3\6\6\0\1\6\12\0\2\43"+
    "\1\0\1\45\2\220\1\130\3\43\6\0\1\43\12\0"+
    "\2\46\1\0\1\47\2\221\1\132\3\46\6\0\1\46"+
    "\12\0\2\50\1\0\1\52\2\222\1\134\3\50\6\0"+
    "\1\50\3\0\1\101\6\0\2\103\1\0\1\105\2\223"+
    "\1\162\3\103\6\0\1\103\12\0\3\106\1\137\2\224"+
    "\1\164\4\106\1\135\17\106\3\107\1\140\2\225\1\167"+
    "\5\107\1\135\16\107\2\22\1\0\1\54\2\226\1\111"+
    "\3\22\4\0\1\56\1\0\1\22\4\0\1\57\2\56"+
    "\3\0\3\65\1\70\2\227\1\113\5\65\1\71\1\72"+
    "\15\65\3\71\1\115\2\230\1\144\6\71\1\116\15\71"+
    "\3\146\1\147\2\231\1\175\4\146\1\66\1\65\16\146"+
    "\3\117\1\120\2\232\1\151\4\117\1\63\17\117\3\66"+
    "\1\73\2\233\1\122\4\66\1\71\1\66\1\74\15\66"+
    "\3\124\1\125\2\234\1\154\5\124\1\63\16\124\2\6"+
    "\1\0\1\41\2\6\1\76\3\6\6\0\1\6\12\0"+
    "\2\43\1\0\1\45\2\235\1\130\3\43\6\0\1\43"+
    "\12\0\2\46\1\0\1\47\2\236\1\132\3\46\6\0"+
    "\1\46\12\0\2\50\1\0\1\52\2\237\1\134\3\50"+
    "\6\0\1\50\3\0\1\101\6\0\2\103\1\0\1\105"+
    "\2\240\1\162\3\103\6\0\1\103\12\0\3\106\1\137"+
    "\2\241\1\164\4\106\1\135\17\106\3\107\1\140\2\242"+
    "\1\167\5\107\1\135\16\107\2\22\1\0\1\54\2\22"+
    "\1\111\3\22\4\0\1\56\1\0\1\22\4\0\1\57"+
    "\2\56\3\0\3\65\1\70\2\243\1\113\5\65\1\71"+
    "\1\72\15\65\3\71\1\115\2\244\1\144\6\71\1\116"+
    "\15\71\3\146\1\147\2\245\1\175\4\146\1\66\1\65"+
    "\16\146\3\117\1\120\2\246\1\151\4\117\1\63\17\117"+
    "\3\66\1\73\2\247\1\122\4\66\1\71\1\66\1\74"+
    "\15\66\3\124\1\125\2\250\1\154\5\124\1\63\16\124"+
    "\2\43\1\0\1\45\2\43\1\130\3\43\6\0\1\43"+
    "\12\0\2\46\1\0\1\47\2\46\1\132\3\46\6\0"+
    "\1\46\12\0\2\50\1\0\1\52\2\50\1\134\3\50"+
    "\6\0\1\50\3\0\1\101\6\0\2\103\1\0\1\105"+
    "\2\251\1\162\3\103\6\0\1\103\12\0\3\106\1\137"+
    "\2\252\1\164\4\106\1\135\17\106\3\107\1\140\2\253"+
    "\1\167\5\107\1\135\16\107\3\65\1\70\2\65\1\113"+
    "\5\65\1\71\1\72\15\65\3\71\1\115\2\254\1\144"+
    "\6\71\1\116\15\71\3\146\1\147\2\255\1\175\4\146"+
    "\1\66\1\65\16\146\3\117\1\120\2\256\1\151\4\117"+
    "\1\63\17\117\3\66\1\73\2\66\1\122\4\66\1\71"+
    "\1\66\1\74\15\66\3\124\1\125\2\257\1\154\5\124"+
    "\1\63\16\124\2\103\1\0\1\105\2\103\1\162\3\103"+
    "\6\0\1\103\12\0\3\106\1\137\2\260\1\164\4\106"+
    "\1\135\17\106\3\107\1\140\2\261\1\167\5\107\1\135"+
    "\16\107\3\71\1\115\2\71\1\144\6\71\1\116\15\71"+
    "\3\146\1\147\2\262\1\175\4\146\1\66\1\65\16\146"+
    "\3\117\1\120\2\117\1\151\4\117\1\63\17\117\3\124"+
    "\1\125\2\124\1\154\5\124\1\63\16\124\3\106\1\137"+
    "\2\106\1\164\4\106\1\135\17\106\3\107\1\140\2\107"+
    "\1\167\5\107\1\135\16\107\3\146\1\147\2\146\1\175"+
    "\4\146\1\66\1\65\16\146";

  private static int [] zzUnpackTrans() {
    int [] result = new int[4158];
    int offset = 0;
    offset = zzUnpackTrans(ZZ_TRANS_PACKED_0, offset, result);
    return result;
  }

  private static int zzUnpackTrans(final String packed, final int offset, final int [] result) {
    int i = 0;       /* index in packed string  */
    int j = offset;  /* index in unpacked array */
    int l = packed.length();
    while (i < l) {
      int count = packed.charAt(i++);
      int value = packed.charAt(i++);
      value--;
      do result[j++] = value; while (--count > 0);
    }
    return j;
  }

  /* error codes */
  private static final int ZZ_UNKNOWN_ERROR = 0;
  private static final int ZZ_NO_MATCH = 1;
  private static final int ZZ_PUSHBACK_2BIG = 2;

  /* error messages for the codes above */
  private static final String ZZ_ERROR_MSG[] = {
    "Unknown internal scanner error",
    "Error: could not match input",
    "Error: pushback value was too large"
  };

  /**
   * ZZ_ATTRIBUTE[aState] contains the attributes of state aState
   */
  private static final int [] ZZ_ATTRIBUTE = zzUnpackAttribute();

  private static final String ZZ_ATTRIBUTE_PACKED_0 =
    "\3\0\1\1\1\0\2\1\1\11\2\1\3\11\1\1"+
    "\1\11\6\1\2\11\10\1\1\11\1\0\2\1\2\0"+
    "\1\1\1\0\1\1\4\0\1\1\2\0\1\1\6\0"+
    "\1\1\2\0\1\1\1\0\5\1\1\11\2\1\4\0"+
    "\2\1\2\0\1\1\1\0\1\1\4\0\1\1\2\0"+
    "\11\1\2\0\1\1\3\0\1\1\7\0\6\1\2\0"+
    "\1\1\2\0\2\1\7\0\5\1\2\0\1\1\6\0"+
    "\5\1\2\0\1\1\6\0\4\1\10\0\1\1\11\0";

  private static int [] zzUnpackAttribute() {
    int [] result = new int[178];
    int offset = 0;
    offset = zzUnpackAttribute(ZZ_ATTRIBUTE_PACKED_0, offset, result);
    return result;
  }

  private static int zzUnpackAttribute(final String packed, final int offset, final int [] result) {
    int i = 0;       /* index in packed string  */
    int j = offset;  /* index in unpacked array */
    int l = packed.length();
    while (i < l) {
      int count = packed.charAt(i++);
      int value = packed.charAt(i++);
      do result[j++] = value; while (--count > 0);
    }
    return j;
  }

  /** the input device */
  private char[] zzChars;

  /** the current state of the DFA */
  private int zzState;

  /** the current lexical state */
  private int zzLexicalState = YYINITIAL;

  /** this buffer contains the current text to be matched and is
      the source of the yytext() string */
  private char zzBuffer[];

  /** the textposition at the last accepting state */
  private int zzMarkedPos;

  /** the current text position in the buffer */
  private int zzCurrentPos;

  /** startRead marks the beginning of the yytext() string in the buffer */
  private int zzStartRead;

  /** endRead marks the last character in the buffer, that has been read
      from input */
  private int zzEndRead;

  /** number of newlines encountered up to the start of the matched text */
  private int yyline;

  /** the number of characters up to the start of the matched text */
  private int yychar;

  /**
   * the number of characters from the last newline up to the start of the
   * matched text
   */
  private int yycolumn;

  /**
   * zzAtBOL == true <=> the scanner is currently at the beginning of a line
   */
  private boolean zzAtBOL = true;

  /** zzAtEOF == true <=> the scanner is at the EOF */
  private boolean zzAtEOF;

  /** denotes if the user-EOF-code has already been executed */
  private boolean zzEOFDone;

  /**
   * The number of occupied positions in zzBuffer beyond zzEndRead.
   * When a lead/high surrogate has been read from the input stream
   * into the final zzBuffer position, this will have a value of 1;
   * otherwise, it will have a value of 0.
   */
  private int zzFinalHighSurrogate = 0;

  /* user code: */
	// position methods
	public int position() { return yychar; }
	public int length()   { return yylength(); }
	public int line()     { return -1; /*yyline;*/ }   	// for debugging
	public int column()   { return -1; /*yycolumn;*/ } 	// for debugging

	// state methods
	public void stateReset() 		{ yybegin(YYINITIAL); }
	public void stateSelector() 	{ yybegin(SELECTOR); }
	public void stateAttr()			{ yybegin(ATTR); }
	public void stateCombinator()	{ yybegin(COMBINATOR); }
	public void statePseudoFn()		{ yybegin(PSEUDO_FN); }

	// fast methods
	public final CharSequence xxtext() {
		return CharBuffer.wrap(zzBuffer, zzStartRead, zzMarkedPos - zzStartRead);
	}

	public final String yytext(int startIndex) {
		startIndex += zzStartRead;
		return new String(zzBuffer, startIndex, zzMarkedPos - startIndex);
	}
	public final String yytext(int startIndex, final int endIndexOffset) {
		startIndex += zzStartRead;
		return new String(zzBuffer, startIndex, zzMarkedPos - endIndexOffset - startIndex);
	}

	ArrayList<CssSelector> selectors = new ArrayList<>();
	CssSelector cssSelector;
	String pseudoFnName;

  /**
   * Unpacks the compressed character translation table.
   *
   * @param packed   the packed character translation table
   * @return         the unpacked character translation table
   */
  private static char [] zzUnpackCMap(final String packed) {
    char [] map = new char[0x110000];
    int i = 0;  /* index in packed string  */
    int j = 0;  /* index in unpacked array */
    while (i < 128) {
      int  count = packed.charAt(i++);
      char value = packed.charAt(i++);
      do map[j++] = value; while (--count > 0);
    }
    return map;
  }

  /**
   * Creates a new scanner.
   */
  CSSellyLexer(final char[] input) {
    this.zzChars = input;
  }

  /**
   * Refills the input buffer.
   *
   * @return      false, iff there was new input.
   */
  private boolean zzRefill() {
    if (zzBuffer == null) {
      zzBuffer = zzChars;
      zzEndRead += zzChars.length;
      return false;
    }
    return true;
  }

  /**
   * Closes the input stream.
   */
  public final void yyclose() {
    zzAtEOF = true;            /* indicate end of file */
    zzEndRead = zzStartRead;  /* invalidate buffer    */
  }

  /**
   * Returns the current lexical state.
   */
  public final int yystate() {
    return zzLexicalState;
  }

  /**
   * Enters a new lexical state
   *
   * @param newState the new lexical state
   */
  public final void yybegin(final int newState) {
    zzLexicalState = newState;
  }

  /**
   * Returns the text matched by the current regular expression.
   */
  public final String yytext() {
    return new String( zzBuffer, zzStartRead, zzMarkedPos-zzStartRead );
  }

  /**
   * Returns the character at position <tt>pos</tt> from the
   * matched text.
   *
   * It is equivalent to yytext().charAt(pos), but faster
   *
   * @param pos the position of the character to fetch.
   *            A value from 0 to yylength()-1.
   *
   * @return the character at position pos
   */
  public final char yycharat(final int pos) {
    return zzBuffer[zzStartRead+pos];
  }

  /**
   * Returns the length of the matched text region.
   */
  public final int yylength() {
    return zzMarkedPos-zzStartRead;
  }

  /**
   * Reports an error that occured while scanning.
   *
   * In a wellformed scanner (no or only correct usage of
   * yypushback(int) and a match-all fallback rule) this method
   * will only be called with things that "Can't Possibly Happen".
   * If this method is called, something is seriously wrong
   * (e.g. a JFlex bug producing a faulty scanner etc.).
   *
   * Usual syntax/scanner level error handling should be done
   * in error fallback rules.
   *
   * @param   errorCode  the code of the errormessage to display
   */
  private void zzScanError(final int errorCode) {
    String message;
    try {
      message = ZZ_ERROR_MSG[errorCode];
    }
    catch (ArrayIndexOutOfBoundsException e) {
      message = ZZ_ERROR_MSG[ZZ_UNKNOWN_ERROR];
    }

    throw new Error(message);
  }

  /**
   * Pushes the specified amount of characters back into the input stream.
   *
   * They will be read again by then next call of the scanning method
   *
   * @param number  the number of characters to be read again.
   *                This number must not be greater than yylength()!
   */
  public void yypushback(final int number)  {
    if ( number > yylength() )
      zzScanError(ZZ_PUSHBACK_2BIG);

    zzMarkedPos -= number;
  }

  /**
   * Contains user EOF-code, which will be executed exactly once,
   * when the end of file is reached
   */
  private void zzDoEOF() {
    if (!zzEOFDone) {
      zzEOFDone = true;
      yyclose();
    }
  }

  /**
   * Resumes scanning until the next regular expression is matched,
   * the end of input is encountered or an I/O-Error occurs.
   *
   * @return      the next token
   * @exception   java.io.IOException  if any I/O-Error occurs
   */
  public int yylex() throws java.io.IOException {
    int zzInput;
    int zzAction;

    // cached fields:
    int zzCurrentPosL;
    int zzMarkedPosL;
    int zzEndReadL = zzEndRead;
    char [] zzBufferL = zzBuffer;
    char [] zzCMapL = ZZ_CMAP;

    int [] zzTransL = ZZ_TRANS;
    int [] zzRowMapL = ZZ_ROWMAP;
    int [] zzAttrL = ZZ_ATTRIBUTE;

    while (true) {
      zzMarkedPosL = zzMarkedPos;

      yychar+= zzMarkedPosL-zzStartRead;

      zzAction = -1;

      zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL;

      zzState = ZZ_LEXSTATE[zzLexicalState];

      // set up zzAction for empty match case:
      int zzAttributes = zzAttrL[zzState];
      if ( (zzAttributes & 1) == 1 ) {
        zzAction = zzState;
      }

      zzForAction: {
        while (true) {

          if (zzCurrentPosL < zzEndReadL) {
            zzInput = Character.codePointAt(zzBufferL, zzCurrentPosL, zzEndReadL);
            zzCurrentPosL += Character.charCount(zzInput);
          }
          else if (zzAtEOF) {
            zzInput = YYEOF;
            break zzForAction;
          }
          else {
            // store back cached positions
            zzCurrentPos  = zzCurrentPosL;
            zzMarkedPos   = zzMarkedPosL;
            boolean eof = zzRefill();
            // get translated positions and possibly new buffer
            zzCurrentPosL  = zzCurrentPos;
            zzMarkedPosL   = zzMarkedPos;
            zzBufferL      = zzBuffer;
            zzEndReadL     = zzEndRead;
            if (eof) {
              zzInput = YYEOF;
              break zzForAction;
            }
            else {
              zzInput = Character.codePointAt(zzBufferL, zzCurrentPosL, zzEndReadL);
              zzCurrentPosL += Character.charCount(zzInput);
            }
          }
          int zzNext = zzTransL[ zzRowMapL[zzState] + zzCMapL[zzInput] ];
          if (zzNext == -1) break zzForAction;
          zzState = zzNext;

          zzAttributes = zzAttrL[zzState];
          if ( (zzAttributes & 1) == 1 ) {
            zzAction = zzState;
            zzMarkedPosL = zzCurrentPosL;
            if ( (zzAttributes & 8) == 8 ) break zzForAction;
          }

        }
      }

      // store back cached position
      zzMarkedPos = zzMarkedPosL;

      if (zzInput == YYEOF && zzStartRead == zzCurrentPos) {
        zzAtEOF = true;
            zzDoEOF();
          { return 0; }
      }
      else {
        switch (zzAction < 0 ? zzAction : ZZ_ACTION[zzAction]) {
          case 1:
            { cssSelector.setCombinator(Combinator.DESCENDANT); stateReset();
            }
          case 20: break;
          case 2:
            { cssSelector = new CssSelector(yytext()); selectors.add(cssSelector); stateSelector();
            }
          case 21: break;
          case 3:
            { cssSelector = new CssSelector(); selectors.add(cssSelector); yypushback(1); stateSelector();
            }
          case 22: break;
          case 4:
            { /* ignore whitespaces */
            }
          case 23: break;
          case 5:
            { cssSelector = new CssSelector(); selectors.add(cssSelector); stateSelector();
            }
          case 24: break;
          case 6:
            { throw new CSSellyException("Illegal character <"+ yytext() +">.", yystate(), line(), column());
            }
          case 25: break;
          case 7:
            { yypushback(1); stateCombinator();
            }
          case 26: break;
          case 8:
            { stateAttr();
            }
          case 27: break;
          case 9:
            { cssSelector.addAttributeSelector(yytext());
            }
          case 28: break;
          case 10:
            { stateSelector();
            }
          case 29: break;
          case 11:
            { throw new CSSellyException("Invalid combinator <"+ yytext() +">.", yystate(), line(), column());
            }
          case 30: break;
          case 12:
            { cssSelector.setCombinator(Combinator.GENERAL_SIBLING); stateReset();
            }
          case 31: break;
          case 13:
            { cssSelector.setCombinator(Combinator.CHILD); stateReset();
            }
          case 32: break;
          case 14:
            { cssSelector.setCombinator(Combinator.ADJACENT_SIBLING); stateReset();
            }
          case 33: break;
          case 15:
            { cssSelector.addPseudoFunctionSelector(pseudoFnName, yytext(0, 1)); stateSelector();
            }
          case 34: break;
          case 16:
            { cssSelector.addClassSelector(yytext(1));
            }
          case 35: break;
          case 17:
            { cssSelector.addIdSelector(yytext(1));
            }
          case 36: break;
          case 18:
            { cssSelector.addPseudoClassSelector(yytext( yycharat(1) == ':' ? 2 : 1 ));
            }
          case 37: break;
          case 19:
            { pseudoFnName = yytext(yycharat(1) == ':' ? 2 : 1,1); statePseudoFn();
            }
          case 38: break;
          default:
            zzScanError(ZZ_NO_MATCH);
        }
      }
    }
  }

}