public class MapperMethod {
	private final Executor executor;
	private final ParameterProducer producer;
	private final MethodSignature methodSignature;
	private final MapperInterface mapperInterface;
	private final InfluxDBRepository repository;

	public MapperMethod(Class<?> mapperInterface, InfluxDBRepository repository, MethodSignature signature) {
		this.mapperInterface = new MapperInterface(mapperInterface);
		this.repository = repository;
		this.methodSignature = signature;
		this.executor = ExecutorFactory.getInstance().executorOf(signature, repository);

		final Collection<ParameterProducer> parameterProducers = new LinkedList<>();
		parameterProducers.add(new AnnotationParameterProducer(signature.getMethod()));
		parameterProducers.add(new J8ParameterProducer(signature.getMethod()));
		this.producer = new BatchParameterProducer(parameterProducers);
	}

	public Executor getExecutor() {
		return executor;
	}

	public ParameterProducer getProducer() {
		return producer;
	}

	public MethodSignature getMethodSignature() {
		return methodSignature;
	}

	public MapperInterface getMapperInterface() {
		return mapperInterface;
	}

	@CoberturaIgnore
	public InfluxDBRepository getRepository() {
		return repository;
	}

	@Override
	@CoberturaIgnore
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((methodSignature == null) ? 0 : methodSignature.hashCode());
		return result;
	}

	@Override
	@CoberturaIgnore
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		MapperMethod other = (MapperMethod) obj;
		if (methodSignature == null) {
			if (other.methodSignature != null)
				return false;
		} else if (!methodSignature.equals(other.methodSignature))
			return false;
		return true;
	}

} public class MethodSignature {
	private final boolean returnsMany;
	private final boolean returnsMap;
	private final boolean returnsVoid;
	private final boolean returnsBoolean;
	private final Class<?> returnType;
	private final Method method;
	private final Annotation[] annotations;
	private final Parameter[] parameters;

	public MethodSignature(Class<?> mapperInterface, Method method) {
		Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
		if (resolvedReturnType instanceof Class<?>) {
			this.returnType = (Class<?>) resolvedReturnType;
		} else if (resolvedReturnType instanceof ParameterizedType) {
			this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
		} else {
			this.returnType = method.getReturnType();
		}
		this.returnsVoid = void.class.isAssignableFrom(returnType);
		this.returnsMany = Collection.class.isAssignableFrom(returnType) || returnType.isArray();
		this.returnsMap = Map.class.isAssignableFrom(returnType);
		this.returnsBoolean = Boolean.class == returnType || boolean.class == returnType;
		this.method = method;
		annotations = method.getDeclaredAnnotations();
		parameters = method.getParameters();
	}

	public boolean returnsBoolean() {
		return returnsBoolean;
	}

	public boolean returnsMany() {
		return returnsMany;
	}

	public boolean returnsMap() {
		return returnsMap;
	}

	public boolean returnsVoid() {
		return returnsVoid;
	}

	public Class<?> getReturnType() {
		return returnType;
	}

	public Method getMethod() {
		return method;
	}

	public Annotation[] getAnnotations() {
		return annotations;
	}

	public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
		return method.getAnnotation(annotationClass);
	}

	public Parameter[] getParameters() {
		return parameters;
	}

	@Override
	@CoberturaIgnore
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((method == null) ? 0 : method.hashCode());
		return result;
	}

	@Override
	@CoberturaIgnore
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		MethodSignature other = (MethodSignature) obj;
		if (method == null) {
			if (other.method != null)
				return false;
		} else if (!method.equals(other.method))
			return false;
		return true;
	}

} public class ExecuteExecutor extends AnnotationExecutor<InfluxDBExecute> {

	public ExecuteExecutor(InfluxDBRepository repository, InfluxDBExecute annotation) {
		super(repository, annotation);
	}

	@Override
	public ResultContext execute(MapperMethod method, Map<String, ParameterValue> parameters) {
		String command = super.annotation.value();
		repository.execute(command, parameters);
		return ResultContext.VOID;
	}

} public class SelectExecutor extends AnnotationExecutor<InfluxDBSelect> {
	public SelectExecutor(InfluxDBRepository repository, InfluxDBSelect selectAnnotation) {
		super(repository, selectAnnotation);
	}

	@Override
	public ResultContext execute(MapperMethod method, Map<String, ParameterValue> parameters) {
		String command = super.annotation.value();
		return repository.query(command, parameters);
	}

} public class ParameterValue {
	private final Object value;
	private final ParameterSignature signature;

	public ParameterValue(Object value, ParameterSignature signature) {
		super();
		this.value = value;
		this.signature = signature;
	}

	public Object getValue() {
		return value;
	}

	public ParameterSignature getSignature() {
		return signature;
	}

} public class InfluxQLMapper {
	private static ResourceLoader resourceLoader = new DefaultResourceLoader();

	private Map<String, InfluxQLStatement> statementsMap = new HashMap<>();

	private static DocumentLoader documentLoader = new DefaultDocumentLoader();

	private static final Logger logger = Logger.getLogger(InfluxQLMapper.class.getName());

	private static final ErrorHandler DOCUMENT_BUILDER_ERROR_HANDLER = new ErrorHandler() {

		@Override
		public void warning(SAXParseException exception) throws SAXException {
			logger.log(Level.WARNING, exception.getMessage(), exception);
		}

		@Override
		public void fatalError(SAXParseException exception) throws SAXException {
			logger.log(Level.SEVERE, exception.getMessage(), exception);
		}

		@Override
		public void error(SAXParseException exception) throws SAXException {
			logger.log(Level.OFF, exception.getMessage(), exception);
		}
	};

	private InfluxQLMapper() {
	}

	public InfluxQLStatement getStatement(String key) {
		return statementsMap.get(key);
	}

	public InfluxQLMapper union(InfluxQLMapper other) {
		InfluxQLMapper newmapper = new InfluxQLMapper();
		newmapper.statementsMap.putAll(this.statementsMap);
		newmapper.statementsMap.putAll(other.statementsMap);
		return newmapper;
	}

	public static final InfluxQLMapper empty() {
		return new InfluxQLMapper();
	}

	public static final InfluxQLMapper parseFrom(String resourceLocation) throws InfluxDBException {
		Resource resource = resourceLoader.getResource(resourceLocation);
		return parseFrom(resource);
	}

	private static final InfluxQLMapper parseFrom(Resource resource) throws InfluxDBException {

		InputSource inputSource;
		try {
			inputSource = new InputSource(resource.getInputStream());
		} catch (IOException e) {
			throw new InfluxDBException(e.getMessage(), e);
		}
		Document document = null;
		try {
			document = documentLoader.loadDocument(inputSource, null, DOCUMENT_BUILDER_ERROR_HANDLER,
					XmlValidationModeDetector.VALIDATION_XSD, false);
		} catch (Exception e) {
			throw new InfluxDBException("XML parse failed: " + resource.getDescription(), e);
		}
		Element root = document.getDocumentElement();
		NodeList includeNodeList = root.getElementsByTagName("include");

		InfluxQLMapper mapper = new InfluxQLMapper();

		for (int i = 0; i < includeNodeList.getLength(); i++) {
			Node item = includeNodeList.item(i);
			String location = item.getAttributes().getNamedItem("path").getNodeValue();
			Resource includedResource = resourceLoader.getResource(location);
			InfluxQLMapper includedMapper = parseFrom(includedResource);
			mapper = mapper.union(includedMapper);
		}

		mapper.statementsMap.putAll(parseQLTag(root, "select"));
		mapper.statementsMap.putAll(parseQLTag(root, "execute"));

		return mapper;
	}

	private static Map<String, InfluxQLStatement> parseQLTag(Element root, String tagName) {
		NodeList nodeList = root.getElementsByTagName(tagName);
		Map<String, InfluxQLStatement> map = new HashMap<>();
		for (int i = 0; i < nodeList.getLength(); i++) {
			Node item = nodeList.item(i);
			String id = item.getAttributes().getNamedItem("id").getNodeValue();
			String ql = item.getTextContent().trim();
			if (map.containsKey(id)) {
				throw new InfluxDBException("Duplicate " + tagName + " ql id: " + id);
			}
			map.put(id, new InfluxQLStatement(ql, ACTION.valueOf(tagName.toUpperCase())));
		}
		return map;
	}

	public static final InfluxQLMapper parseFrom(InputStream stream) throws IOException {
		return parseFrom(new InputStreamResource(stream));
	}

} public class InfluxDBRepository {

	private static final Logger logger = Logger.getLogger(InfluxDBRepository.class.getCanonicalName());

	public static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.MILLISECONDS;

	private final InfluxDB influxDB;
	private final String databaseName;

	/**
	 * map<classname, metadata>
	 */
	private final Map<String, MeasurementClassMetadata> classmetadata;
	/**
	 * map<key{measurementName, retentionPolicy}, metadata>
	 */
	private final Map<MeasurementKey, MeasurementClassMetadata> classmetadataMapByKey;

	private final TypeConverterSupport typeConverter;

	private final InfluxQLMapper qlMapper;

	public InfluxDBRepository(final InfluxDB influxDB, final String databaseName, final Map<String, MeasurementClassMetadata> classmetadata,
			InfluxQLMapper qlMapper) {
		super();
		this.influxDB = influxDB;
		this.databaseName = databaseName;
		this.classmetadata = classmetadata;
		this.qlMapper = qlMapper;
		this.classmetadataMapByKey = new HashMap<>();
		this.typeConverter = new SimpleTypeConverter();

		this.typeConverter.registerCustomEditor(Date.class, new DatePropertyEditor());

		transform(classmetadata, classmetadataMapByKey);

		scanAndRegisterEnumTypeEditors(this.typeConverter, classmetadata);
	}

	private void scanAndRegisterEnumTypeEditors(TypeConverterSupport typeConverter, Map<String, MeasurementClassMetadata> classmetadatas) {
		for(MeasurementClassMetadata classMetadata: classmetadatas.values()) {
			checkAndRegisterEnumTypeEditors(typeConverter, classMetadata.getTagColumnDescriptors().values());
			checkAndRegisterEnumTypeEditors(typeConverter, classMetadata.getFieldColumnDescriptors().values());
		}
	}

	private void checkAndRegisterEnumTypeEditors(TypeConverterSupport typeConverter, Collection<PropertyDescriptor> values) {
		for(PropertyDescriptor dspr: values) {
			Class propertyType = dspr.getReadMethod().getReturnType();
			if(!EnumType.class.isAssignableFrom(propertyType)) {
				continue;
			}
			if(!typeConverter.hasCustomEditorForElement(propertyType, null)) {
				typeConverter.registerCustomEditor(propertyType, new EnumsValuePropertyEditor(propertyType));
			}
		}
	}


	public void execute(String command, final Map<String, ParameterValue> parameters) {
		command = CommandUtils.parseCommand(command, parameters);
		if (logger.isLoggable(Level.INFO)) {
			logger.info("execute → " + command);
		}
		final Query query = new Query(command, databaseName);
		influxDB.query(query, DEFAULT_TIME_UNIT);
	}

	public ResultContext query(String command, final Map<String, ParameterValue> parameters) {
		command = CommandUtils.parseCommand(command, parameters);
		if (logger.isLoggable(Level.INFO)) {
			logger.info("query → " + command);
		}
		final Query query = new Query(command, databaseName);
		final QueryResult queryResult = influxDB.query(query, DEFAULT_TIME_UNIT);
		return new NativeResultContext(queryResult);
	}

	public void insert(final Object entity) {
		if (entity == null) {
			return;
		}
		final String entityClassName = entity.getClass().getName();
		final MeasurementClassMetadata classMetadata = getClassMetadata(entityClassName);
		if (classMetadata == null) {
			throw new InfluxDBException("Class not mapped: " + entityClassName);
		}
		final Point point = buildPoint(entity, classMetadata);
		influxDB.write(databaseName, classMetadata.getRetentionPolicy(), point);
	}

	public void batchInsert(final Collection<Object> entities) {
		if (CollectionUtils.isEmpty(entities)) {
			return;
		}
		final Map<MeasurementKey, Collection<Object>> entitiesGrouped = new HashMap<>();
		for (final Object entity : entities) {
			if (entity == null) {
				return;
			}
			final String entityClassName = entity.getClass().getName();
			final MeasurementClassMetadata classMetadata = getClassMetadata(entityClassName);
			if (classMetadata == null) {
				throw new InfluxDBException("Class not mapped: " + entityClassName);
			}
			final MeasurementKey key = new MeasurementKey(classMetadata.getMeasurementName(), classMetadata.getRetentionPolicy());

			Collection<Object> gEntities = entitiesGrouped.get(key);
			if (gEntities == null) {
				gEntities = new LinkedList<>();
				entitiesGrouped.put(key, gEntities);
			}
			gEntities.add(entity);
		}
		for (final Map.Entry<MeasurementKey, Collection<Object>> entry : entitiesGrouped.entrySet()) {
			batchInsert(entry.getKey(), entry.getValue());
		}
	}

	private static void transform(final Map<String, MeasurementClassMetadata> classmetadata,
			final Map<MeasurementKey, MeasurementClassMetadata> classmetadataMapByKey) {
		for (final MeasurementClassMetadata metadata : classmetadata.values()) {
			classmetadataMapByKey.put(new MeasurementKey(metadata.getMeasurementName(), metadata.getRetentionPolicy()), metadata);
		}
	}

	private void batchInsert(final MeasurementKey key, final Collection<Object> entities) {
		if (CollectionUtils.isEmpty(entities)) {
			return;
		}
		final MeasurementClassMetadata classMetadata = getClassMetadata(key);

		final BatchPoints.Builder batchPointsBuilder = BatchPoints //
				.database(databaseName) //
				.retentionPolicy(key.retentionPolicy);
		for (final Object entity : entities) {
			if (entity == null) {
				return;
			}
			final Point point = buildPoint(entity, classMetadata);
			batchPointsBuilder.point(point);
		}
		influxDB.write(batchPointsBuilder.build());
	}

	private Point buildPoint(final Object entity, final MeasurementClassMetadata classMetadata) {
		final String measurementName = getShardingMeasurementName(classMetadata, entity);
		final Point.Builder pointBuilder = Point.measurement(measurementName);

		final Date time = readTimeField(classMetadata, entity);
		pointBuilder.time(DEFAULT_TIME_UNIT.toNanos(time.getTime()), TimeUnit.NANOSECONDS);

		resolveMeasurementTags(entity, classMetadata, pointBuilder);
		resolveMeasurementFields(entity, classMetadata, pointBuilder);
		final Point point = pointBuilder.build();
		return point;
	}

	private Date readTimeField(final MeasurementClassMetadata classMetadata, final Object entity) {
		final Method readMethod = classMetadata.getTimeFieldDescriptor().getReadMethod();
		try {
			final Object result = readMethod.invoke(entity);

			if (classMetadata.isJavaUtilDateTimeField()) {
				return (Date) result;
			} else if (classMetadata.isNumberTimeField()) {
				return new Date(((Number) result).longValue());
			} else if (classMetadata.isStringTimeField()) {
				return DateUtils.parseDate(result.toString(), classMetadata.getDatetimePatterns());
			}
			return null;
		} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | ParseException e) {
			throw new InfluxDBException("An error occurred while read time field value", e);
		}
	}

	private void resolveMeasurementFields(final Object entity, final MeasurementClassMetadata classMetadata, final Builder pointBuilder) {
		final Map<String, PropertyDescriptor> fieldColumnDescriptors = classMetadata.getFieldColumnDescriptors();
		Map<String, Object> fields = new HashMap<>(fieldColumnDescriptors.size());

		for (final Map.Entry<String, PropertyDescriptor> entry : fieldColumnDescriptors.entrySet()) {
			final String fieldName = entry.getKey();
			final PropertyDescriptor valueDescriptor = entry.getValue();
			final Method readMethod = valueDescriptor.getReadMethod();
			final Object value = valueOfField(readMethod, entity);

			Class<?> fieldJavaType = readMethod.getReturnType();

			Object convertedValue = convertFieldValue(value, fieldJavaType);

			fields.put(fieldName, convertedValue);
		}
		pointBuilder.fields(fields);
	}

	private Object convertFieldValue(Object value, Class<?> fieldJavaType) {
		if (value == null) {
			return null;
		}
		Object convertedValue = null;
		if (fieldJavaType.isPrimitive()) {
			convertedValue = value;
		} else if (Number.class.isAssignableFrom(fieldJavaType)) {
			convertedValue = ((Number) value).doubleValue();
		} else {
			convertedValue = this.typeConverter.convertIfNecessary(value, String.class);
		}
		if (convertedValue == null && value != null) {
			throw new InfluxDBException("cannot convert type \"" + fieldJavaType + "\" to String");
		}
		return convertedValue;
	}

	private void resolveMeasurementTags(final Object entity, final MeasurementClassMetadata classMetadata, final Builder pointBuilder) {
		final Map<String, PropertyDescriptor> tagColumnDescriptors = classMetadata.getTagColumnDescriptors();
		for (final Map.Entry<String, PropertyDescriptor> entry : tagColumnDescriptors.entrySet()) {
			final String tagName = entry.getKey();
			final PropertyDescriptor valueDescriptor = entry.getValue();
			final Method readMethod = valueDescriptor.getReadMethod();
			final Object value = valueOfTag(readMethod, entity);
			final String converted = this.typeConverter.convertIfNecessary(value, String.class);
			if (converted == null && value != null) {
				throw new InfluxDBException("cannot convert type \"" + readMethod.getReturnType() + "\" to String");
			}
			pointBuilder.tag(tagName, converted);
		}
	}

	private Object valueOfField(final Method readMethod, final Object entity) {
		try {
			return readMethod.invoke(entity);
		} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
			throw new InfluxDBException("An error occorred while reading field value", e);
		}
	}

	private Object valueOfTag(final Method readMethod, final Object entity) {
		try {
			return readMethod.invoke(entity);
		} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
			throw new InfluxDBException("An error occorred while reading tag field value", e);
		}
	}

	public String getShardingMeasurementName(final MeasurementClassMetadata metadata, final Object entity) {
		final PropertyDescriptor shardingFieldDescriptor = metadata.getShardingFieldDescriptor();
		if (shardingFieldDescriptor == null) {
			return metadata.getMeasurementName();
		}
		final Method readMethod = shardingFieldDescriptor.getReadMethod();
		try {
			final Object value = readMethod.invoke(entity);
			return new StringBuilder() //
					.append(metadata.getMeasurementName()) //
					.append('_') //
					.append(value) //
					.toString();
		} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
			throw new InfluxDBException("An error occurred while reading shard field value", e);
		}
	}

	public InfluxDB getInfluxDB() {
		return influxDB;
	}

	public InfluxQLStatement getStatement(String name) {
		return qlMapper.getStatement(name);
	}

	public MeasurementClassMetadata getClassMetadata(final String measurementClassName) {
		return classmetadata.get(measurementClassName);
	}

	public MeasurementClassMetadata getClassMetadata(final String measurementName, final String retentionPolicy) {
		return classmetadataMapByKey.get(new MeasurementKey(measurementName, retentionPolicy));
	}

	private MeasurementClassMetadata getClassMetadata(final MeasurementKey key) {
		return classmetadataMapByKey.get(key);
	}

	public MeasurementClassMetadata getClassMetadataByMeasurementName(final String measurementName) {
		return classmetadataMapByKey.get(new MeasurementKey(measurementName));
	}

	public MeasurementClassMetadata getClassMetadata(final Class<?> measurementClass) {
		return getClassMetadata(measurementClass.getName());
	}

	public Map<String, MeasurementClassMetadata> getAllClassMetadata() {
		return classmetadata;
	}

	public <T> T convert(Map<String, Object> map, Class<T> targetType) {
		MeasurementClassMetadata classMetadata = getClassMetadata(targetType);
		if (classMetadata != null) {
			return BeanConvertUtils.convert(map, classMetadata, this.typeConverter);
		}
		return BeanConvertUtils.convert(map, targetType, this.typeConverter);
	}

	public <T> T convert(Object source, Class<T> targetType) {
		return this.typeConverter.convertIfNecessary(source, targetType);
	}

	private static final class MeasurementKey {
		private final String measurementName;
		private final String retentionPolicy;
		private final int _hash;

		public MeasurementKey(final String measurementName, final String retentionPolicy) {
			super();
			this.measurementName = measurementName;
			this.retentionPolicy = retentionPolicy;
			this._hash = hash();
		}

		public MeasurementKey(final String measurementName) {
			this(measurementName, "autogen");
		}

		private int hash() {
			final int prime = 31;
			int result = 1;
			result = prime * result + ((measurementName == null) ? 0 : measurementName.hashCode());
			result = prime * result + ((retentionPolicy == null) ? 0 : retentionPolicy.hashCode());
			return result;
		}

		@Override
		public int hashCode() {
			return _hash;
		}

		@Override
		public boolean equals(final Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			final MeasurementKey other = (MeasurementKey) obj;
			if (measurementName == null) {
				if (other.measurementName != null)
					return false;
			} else if (!measurementName.equals(other.measurementName))
				return false;
			if (retentionPolicy == null) {
				if (other.retentionPolicy != null)
					return false;
			} else if (!retentionPolicy.equals(other.retentionPolicy))
				return false;
			return true;
		}

	}
} public class NativeResultContext implements ResultContext {

	private static final Map<String, List<Map<String, Object>>> EMPTY_SERIES_MAP = Collections.unmodifiableMap(new HashMap<>());
	private static final Set<String> EMPTY_SET = Collections.emptySet();

	private String errorMessage;
	private boolean hasResult;
	private Set<String> measurementNames;
	private Map<String, List<Map<String, Object>>> seriesMap;

	public NativeResultContext(QueryResult queryResult) {
		this.errorMessage = queryResult.getError();
		List<Result> results = queryResult.getResults();
		if (isNotEmpty(results)) {
			if (results.size() > 1) {
				throw new InfluxDBException("Multiple results is not suppported!");
			}

			Result result = results.get(0);
			if (StringUtils.isNotBlank(result.getError())) {
				errorMessage = result.getError();
				hasResult = false;
			} else if (isNotEmpty(result.getSeries())) {
				hasResult = true;
				seriesMap = transformResult(result);
			}
		}
		if (!hasResult) {
			measurementNames = EMPTY_SET;
			seriesMap = EMPTY_SERIES_MAP;
		}
	}

	public boolean hasResult() {
		return hasResult;
	}

	public boolean hasError() {
		return StringUtils.isNotBlank(errorMessage);
	}

	public String getErrorMessage() {
		return errorMessage;
	}

	public Object getValue() {
		return seriesMap;
	}

	public Set<String> getMeasurementNames() {
		return measurementNames;
	}

	private Map<String, List<Map<String, Object>>> transformResult(Result result) {
		List<Series> seriesList = result.getSeries();
		if (isNotEmpty(seriesList)) {
			Set<String> measurementNames = new HashSet<>();
			Map<String, List<Map<String, Object>>> seriesMap = new HashMap<>();
			for (Series series : seriesList) {
				measurementNames.add(series.getName());
				List<Map<String, Object>> dataList = seriesMap.get(series.getName());
				if (dataList == null) {
					dataList = new LinkedList<>();
					seriesMap.put(series.getName(), dataList);
				}
				transformTo(series, dataList);
			}
			return seriesMap;
		}
		return EMPTY_SERIES_MAP;
	}

	private void transformTo(Series series, List<Map<String, Object>> dataList) {
		List<String> columns = series.getColumns();
		List<List<Object>> valuesList = series.getValues();
		if (isNotEmpty(columns) && isNotEmpty(valuesList)) {
			int columnCount = columns.size();
			for (List<Object> values : valuesList) {
				Map<String, Object> data = new HashMap<String, Object>();
				for (int i = 0; i < columnCount; i++) {
					String columnName = columns.get(i);
					Object value = values.get(i);
					data.put(columnName, value);
				}
				dataList.add(data);
			}
		}
	}

	private static final boolean isNotEmpty(Collection<?> coll) {
		return !isEmpty(coll);
	}

	private static final boolean isEmpty(Collection<?> col) {
		return col == null || col.isEmpty();
	}
} public interface ResultContext {
	public static final ResultContext VOID = VoidResultContext.INSTANCE;

	public boolean hasResult();

	public boolean hasError();

	public String getErrorMessage();

	public Object getValue();
} public class WrapperResultContext implements ResultContext {

	private final Object value;

	private final boolean hasResult;

	public WrapperResultContext() {
		value = null;
		hasResult = false;
	}

	public WrapperResultContext(Object value) {
		this.value = value;
		hasResult = true;
	}

	@Override
	public boolean hasResult() {
		return hasResult;
	}

	@Override
	public boolean hasError() {
		return false;
	}

	@Override
	public String getErrorMessage() {
		return null;
	}

	@Override
	public Object getValue() {
		return value;
	}

}
import com.vgerbot.orm.influxdb.annotations.InfluxDBSelect;
import com.vgerbot.orm.influxdb.binding.MapperMethod;
import com.vgerbot.orm.influxdb.binding.MethodSignature;
import com.vgerbot.orm.influxdb.exec.ExecuteExecutor;
import com.vgerbot.orm.influxdb.exec.SelectExecutor;
import com.vgerbot.orm.influxdb.param.ParameterValue;
import com.vgerbot.orm.influxdb.ql.InfluxQLMapper;
import com.vgerbot.orm.influxdb.repo.InfluxDBRepository;
import com.vgerbot.orm.influxdb.result.NativeResultContext;
import com.vgerbot.orm.influxdb.result.ResultContext;
import com.vgerbot.orm.influxdb.result.WrapperResultContext;
import org.influxdb.InfluxDB;
import org.influxdb.dto.QueryResult;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import javax.management.Query;
import java.awt.event.WindowFocusListener;
import java.lang.reflect.Array;
import java.util.*;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import static org.mockito.Mockito.*;
package com.vgerbot.test.orm.influxdb.exec;



@RunWith(MockitoJUnitRunner.class)
public class SelectExecutorTest {
    @Mock
    private InfluxDB influxdb;
    @Mock
    private InfluxQLMapper mapper;
    @Spy
    private InfluxDBRepository repository = new InfluxDBRepository(influxdb, "test-db", new HashMap<>(), mapper);

    public interface QueryDao {
        @InfluxDBSelect("select * from users")
        public Map<String, Object> selectAllUsers();
    }


    @Test
    public void test() throws Exception {
        MethodSignature selectAllUsersMethodSignature = new MethodSignature(QueryDao.class, QueryDao.class.getMethod("selectAllUsers"));
        InfluxDBSelect selectAnnotation = selectAllUsersMethodSignature.getAnnotation(InfluxDBSelect.class);
        Map<String, ParameterValue> parameters = new HashMap<>();