text:
package br.com.dlp.jazzomr.parser;

import ij.IJ;
import ij.ImagePlus;
import ij.measure.Measurements;
import ij.measure.ResultsTable;
import ij.plugin.filter.ParticleAnalyzer;
import ij.process.ImageConverter;

import java.awt.Color;
import java.awt.Composite;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.imageio.ImageIO;

import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import br.com.dlp.framework.gof.strategy.Strategy;
import br.com.dlp.jazzomr.application.result.CriterionResultVO;
import br.com.dlp.jazzomr.application.result.QuestionResultVO;
import br.com.dlp.jazzomr.empresa.RelatorioVO;
import br.com.dlp.jazzomr.event.EventVO;
import br.com.dlp.jazzomr.event.ParticipationVO;
import br.com.dlp.jazzomr.exam.ExamOMRMetadataVO;
import br.com.dlp.jazzomr.exam.coordinate.OMRMark;
import br.com.dlp.jazzomr.exceptions.JazzOMRException;
import br.com.dlp.jazzomr.exceptions.JazzOMRFixRotationException;
import br.com.dlp.jazzomr.exceptions.JazzOMRIdentityException;
import br.com.dlp.jazzomr.exceptions.RefParticleNotFoundException;
import br.com.dlp.jazzomr.omr.EParticleArea;
import br.com.dlp.jazzomr.omr.EParticlePosition;
import br.com.dlp.jazzomr.omr.ImageDocPK;
import br.com.dlp.jazzomr.omr.ImageDocVO;
import br.com.dlp.jazzomr.omr.ParticleTemplateVO;
import br.com.dlp.jazzomr.omr.ParticleVO;
import br.com.dlp.jazzomr.particle.counting.RefParticleFinder;
import br.com.dlp.jazzomr.particle.counting.ThreeRefParticleFinder;
import br.com.dlp.jazzomr.particle.counting.TwoRefParticleFinder;
import br.com.dlp.jazzomr.question.AlternativeScore;
import br.com.dlp.jazzomr.question.QuestionVO;

/**
 *
 * @author darcio
 */
@Component
public class JazzOMRImageParser {

	private Logger log = LoggerFactory.getLogger(this.getClass());
	private ImageDocLogger imageDocLogger = new ImageDocLogger();

	//private static final double MAX_IMAGE_AREA = 2000 * 3000;
	private static final double MAX_IMAGE_AREA = 400 * 600;
	private static final double RAIZ_MAX_AREA = Math.sqrt(MAX_IMAGE_AREA);

	private static final String THRESHOLD_HUANG = "Huang";
	private static final String THRESHOLD_DEFAULT = "Default";

	private static final int CRITIC_FONT_SIZE = 8;
	private static final String IMG_EXT = "gif";

	private static final int DEF_INTERPOLATION_TYPE = AffineTransformOp.TYPE_BICUBIC;
	private static final int MAX_CIRCULARITY = 1;
	private static final int MEDIDAS_PADRAO = Measurements.SHAPE_DESCRIPTORS + Measurements.CENTER_OF_MASS + Measurements.AREA + Measurements.PERIMETER + Measurements.CENTROID;

	private static final double bulletMinAreaFactor = 0.07;
	private static final double bulletMaxAreaFactor = 0.95;
	private static final double bulletMinCirc = 0.62;// ajuste de sensibilidade de reconhecimento de marcas

	private List<Strategy<QRCodeReaderParam, String>> strategies;

	@Autowired
	@Qualifier("alternativeResultBullet")
	public Comparator<? super CriterionResultVO> altComparator;

	/**
	 * Cria DTO contendo os parametros para funcionamento do parseador OMR de imagens
	 *
	 * @return
	 */
	private ParticleAnalyzerParamDTO createParticleAnalyzerParams() {
		// tODO: PARAMETRIZAR EM BANCO DE DADOS
		List<ParticleTemplateVO> particleTemplates = getParticleTemplates();

		IdentityRegion identityRegion = new IdentityRegion(0.747, 0.015, 0.185, 0.118);

		ParticleAnalyzerParamDTO analyzerParams = new ParticleAnalyzerParamDTO(particleTemplates);
		analyzerParams.setIdentityRegion(identityRegion);
		return analyzerParams;
	}

	/**
	 * Constroi modelo padrao de particulas que sera esperado pelo image parser
	 *
	 * @return
	 */
	private static List<ParticleTemplateVO> getParticleTemplates() {
		// TODO: PARAMETRIZAR MODELOS DE PARTICULAS
		List<ParticleTemplateVO> particleTemplatesList = new ArrayList<ParticleTemplateVO>(2);

		particleTemplatesList.add(ParticleTemplateVO.getInstance(EParticleArea.PEQUENA, EParticlePosition.BOTTOM_LEFT));
		particleTemplatesList.add(ParticleTemplateVO.getInstance(EParticleArea.PEQUENA, EParticlePosition.TOP_RIGHT));

		return particleTemplatesList;
	}

	/**
	 * @param eventVO
	 * @param images
	 * @return
	 * @throws JazzOMRException
	 */
	public List<QuestionResultVO> parseImages(EventVO eventVO, List<InputStream> images) throws JazzOMRException {
		List<QuestionResultVO> resultsTotal = new ArrayList<QuestionResultVO>();

		for (InputStream inputStream : images) {
			ImageDocVO docVO = new ImageDocVO(inputStream);
			List<QuestionResultVO> questions = parseImage(docVO,eventVO);

			resultsTotal.addAll(questions);
		}
		return resultsTotal;
	}

	/**
	 * Processa a imagem informada no construtor
	 *
	 * @param relatorioVO
	 * @return
	 *
	 * @throws RefParticleNotFoundException
	 * @throws JazzOMRException
	 * @throws JazzOMRException
	 */
	public List<QuestionResultVO> parseImage(ImageDocVO imageDocVO, EventVO eventVO) throws JazzOMRException {
		// recupera modelo de particulas do sistema
		ParticleAnalyzerParamDTO particleAnalyzerParamDTO = createParticleAnalyzerParams();

		// ajusta imagem inicial
		adjustImage(imageDocVO, particleAnalyzerParamDTO);

		// encontra as particulas candidatas a particulas de referencia
		// Analise de particulas pura e simples. padrao imagej
		findParticles(imageDocVO, particleAnalyzerParamDTO);

		// Determinar, dentre as particulas encontradas, quais sao as particulas de referencia
		findRefParticles(imageDocVO, particleAnalyzerParamDTO);

		// Ajusta rotacao da imagem original.
		// So e possivel ser executada quando se sabe a posicao das particulas de referencia
		fixImageRotation(imageDocVO, particleAnalyzerParamDTO);

		// Determina onde esta a regiao da imagem com os codigos de barra ou QRCode com a identificacao do documento.
		findIdentityRegion(imageDocVO, particleAnalyzerParamDTO, eventVO);

		//processa a imagem tratada e identificada previamente e gera colecao de resultados.
		List<QuestionResultVO> questionResults = processResults(imageDocVO, eventVO);

		imageDocLogger.closeGraphics2D();

		return questionResults;

	}

	/**
	 * @param imageDocVO
	 * @param eventVO
	 * @return
	 * @throws JazzOMRException
	 */
	private List<QuestionResultVO> processResults(ImageDocVO imageDocVO, EventVO eventVO) throws JazzOMRException {
		Long participationId = imageDocVO.getImageDocPK().getParticipation();
		ParticipationVO participationVO = eventVO.getParticipations().get(participationId);
		Integer examVarId = participationVO.getExamVariant();
		Integer page = imageDocVO.getImageDocPK().getPage();

		//localiza todas as questoes contidas na imagem enviada
		Set<QuestionVO> questions = eventVO.findQuestions(examVarId, page);

		//cria arraylist para conter resultados
		List<QuestionResultVO> questionResults = new ArrayList<QuestionResultVO>(questions.size());

		for (QuestionVO questionVO : questions) {

			// encontra a referencia de todas as marcas que podem estar na página para cada questao
			Set<OMRMark> markCoordinates = eventVO.findOMRMarks(questionVO, page, examVarId);

			// recorta todas as marcas da imagem tratada
			Set<CriterionResultVO> critResults = recortarRespostas(imageDocVO, markCoordinates, eventVO, examVarId, questionVO, participationVO);

			//determina quais foram os bullets selecionados
			determineSelectedBullets(critResults, imageDocVO, questionVO, eventVO);

			//cria o objeto de resultado para a questao da vez
			QuestionResultVO questionResultVO = createQuestionResult(questionVO, critResults, examVarId, participationVO, page);

			//adiciona questionResult à lista de resposta
			questionResults.add(questionResultVO);

		}
		return questionResults;
	}

	/**
	 * @param questionVO
	 * @param critResults
	 * @param examVarId
	 * @param participationVO.getPK()
	 * @param page
	 * @param examVarId2
	 * @return
	 */
	private QuestionResultVO createQuestionResult(QuestionVO questionVO, Set<CriterionResultVO> critResults, Integer examVarId, ParticipationVO participationVO, Integer page) {
		QuestionResultVO questionResultVO = new QuestionResultVO();

		questionResultVO.setPage(page);
		questionResultVO.setParticipation(participationVO.getPK());
		questionResultVO.setPessoaPK(participationVO.getPessoaVO().getPK());

		questionResultVO.setQuestionPK(questionVO.getPK());
		questionResultVO.setCriterionResultVOs(critResults);
		questionResultVO.setExamVar(examVarId);
		return questionResultVO;
	}

	/**
	 * Analisa os resultados de analise de particulas das imagens e determina qual foi a resposta escolhida
	 *
	 * @param questionResultVO
	 * @param examVO
	 * @param imageDocVO
	 * @param eventVO
	 * @throws JazzOMRException
	 */
	private void determineSelectedBullets(Set<CriterionResultVO> allCriterions, ImageDocVO imageDocVO, QuestionVO questionVO, EventVO eventVO) throws JazzOMRException {

		// cria nova colecao com as alternativas para preservar a original
		List<CriterionResultVO> setAlternatives = new ArrayList<CriterionResultVO>(allCriterions.size());
		setAlternatives.addAll(allCriterions);

		// filtra apenas os candidatos mais provaveis
		CollectionUtils.filter(setAlternatives, (Predicate) this.altComparator);

		// apenas formaliza a passagem da colecao para uma lista
		ArrayList<CriterionResultVO> criterions = new ArrayList<CriterionResultVO>(setAlternatives.size());
		criterions.addAll(setAlternatives);

		// ordena as alternativas de pela maior probabilidade de terem sido as escolhidas
		Collections.sort(criterions, this.altComparator);

		// recupera quantas sao as questoes que serao assinaladas. normalmente 1 apenas
		Integer corrects = correctOptions(questionVO, criterions);

		// recupera lista de repostas assinaladas
		List<CriterionResultVO> selectedAlternatives = criterions.subList(0, corrects);

		// marca como checked os mais provaveis candidatos
		for (CriterionResultVO criterionResultVO : selectedAlternatives) {
			criterionResultVO.setChecked(true);
		}

		// loga os bullets nao checados
		logUncheckedAlternatives(allCriterions, eventVO.getRelatorioVO(), imageDocVO);

	}

	/**
	 * Loga imagens de bullets não checados. para depuração de codigo
	 *
	 * @param allAlternatives
	 * @param selectedAlternatives
	 * @param examVO
	 * @param imageDocVO
	 */
	private void logUncheckedAlternatives(Collection<CriterionResultVO> allAlternatives, RelatorioVO relatorioVO, ImageDocVO imageDocVO) {
		for (CriterionResultVO alternativeVO : allAlternatives) {
			logCheckedOption(alternativeVO,relatorioVO,imageDocVO);
			imageDocLogger.logBullet(alternativeVO);
		}
	}

	/**
	 * @param questionResultVO
	 * @param alternatives
	 * @return
	 */
	private Integer correctOptions(QuestionVO questionVO, ArrayList<CriterionResultVO> alternatives) {
		Integer correctAlternatives = questionVO.countCorrectScore();
		correctAlternatives = correctAlternatives == null ? 1 : correctAlternatives;

		if (alternatives.size() < correctAlternatives) {
			correctAlternatives = alternatives.size();
		}

		return correctAlternatives;
	}

	/**
	 *
	 * @param imageDocPK
	 * @param eventVO
	 * @return
	 * @throws JazzOMRException
	 */
	private ParticipationVO findParticipationVO(ImageDocPK imageDocPK, EventVO eventVO) throws JazzOMRException {

		if (imageDocPK == null || imageDocPK.getParticipation() == null) {
			throw new IllegalArgumentException("Erro ao tentar ler numero de participacao contido em ImageDocPK (null)");
		}

		ParticipationVO participationVO = getParticipacao(imageDocPK, eventVO);

		return participationVO;

	}

	private ParticipationVO getParticipacao(ImageDocPK imageDocPK, EventVO eventVO) {

		return eventVO.getParticipations().get(imageDocPK.getParticipation());
	}

	/**
	 * Marca com um X vermelho a posicao esperada das particulas de referencia. Deve sobrepor exatamente as cruzetas verdes das particulas de referencia encontradas
	 *
	 * @param imageDocVO
	 * @param examVO
	 * @param widthAdjFactor
	 * @param heigthAdjFactor
	 * @throws JazzOMRException
	 */
	private void logPosicaoEsperada(ImageDocVO imageDocVO, RelatorioVO relatorioVO) throws JazzOMRException {
		if (!this.imageDocLogger.isOpenedGraphics2D()) {
			return;
		}

		// fator de ajuste: ajustar coordenadas de acordo com o tamanho da imagem
		double widthAdjFactor = calculateWidthAdjustFactor(imageDocVO, relatorioVO);
		double heigthAdjFactor = calculateHeigthAdjustFactor(imageDocVO, relatorioVO);

		imageDocLogger.setTransparency(1);

		Graphics2D graphics2d = imageDocLogger.getOpenedGraphics2D();

		Stroke origStroke = graphics2d.getStroke();

		// set log pattern
		ImageDocLogger.setLogStroke(graphics2d, ImageDocLogger.WIDTH_REF_MARKS);

		Map<String, ExamOMRMetadataVO> mapExamOMR = relatorioVO.getExamOMRMetadataVO();
		Set<String> keys = mapExamOMR.keySet();

		for (String key : keys) {

			ExamOMRMetadataVO examOMRMetadataVO = mapExamOMR.get(key);

			Point2D.Double tlXMLReport = relatorioVO.determineTopLeftCorner();

			Point2D.Double tlImagem = imageDocVO.determineTopLeftCorner();
			int d = 8;

			Double xXML = examOMRMetadataVO.getX();
			Double yXML = examOMRMetadataVO.getY();

			int x = adjustXValue(widthAdjFactor, tlXMLReport, tlImagem, xXML);
			int y = adjustYValue(heigthAdjFactor, tlXMLReport, tlImagem, yXML);

			graphics2d.drawLine(x - d, y - d, x + d, y + d);
			graphics2d.drawLine(x - d, y + d, x + d, y - d);
		}

		graphics2d.setStroke(origStroke);
	}

	/**
	 * Faz ajustes necessários na imagem inicial
	 *
	 * @param imageDocVO
	 * @param particleAnalyzerParamDTO
	 * @throws JazzOMRException
	 */
	private void adjustImage(ImageDocVO imageDocVO, ParticleAnalyzerParamDTO particleAnalyzerParamDTO) throws JazzOMRException {
		// tratamento de null basico
		if (imageDocVO == null || imageDocVO.getBufferedImage() == null) {
			throw new JazzOMRException("A imagem informada nao e valida (null)!");
		}

		// recupera área da imagem
		BufferedImage bi = imageDocVO.getBufferedImage();
		double imgArea = bi.getHeight() * bi.getWidth();

		// testa se a imagem eh maior que o maximo necessario
		if (imgArea > MAX_IMAGE_AREA) {
			// reduz imagem para melhorar performance de processamento
			reduzTamanhoImagem(imageDocVO, bi, imgArea);
		}
	}

	/**
	 * Reduz imagem para melhorar performance de processamento. Não é necessário que uma imagem tenha area maior que MAX_IMAGE_AREA
	 *
	 * @param imageDocVO
	 * @param bi
	 * @param imgArea
	 */
	private void reduzTamanhoImagem(ImageDocVO imageDocVO, BufferedImage bi, double imgArea) {
		double raizImgArea = Math.sqrt(imgArea);

		double adjustFactor = RAIZ_MAX_AREA / raizImgArea;

		AffineTransform scaleTransform = AffineTransform.getScaleInstance(adjustFactor, adjustFactor);

		AffineTransformOp affineTransformOp = new AffineTransformOp(scaleTransform, null);
		bi = affineTransformOp.filter(bi, null);

		imageDocVO.setBufferedImage(bi);
	}

	/**
	 * @param heigthAdjFactor
	 * @param tlXMLReport
	 * @param tlImagem
	 * @param yXML
	 * @return
	 */
	private int adjustYValue(double heigthAdjFactor, Point2D.Double tlXMLReport, Point2D.Double tlImagem, Double yXML) {
		double y = yXML;
		y = y - tlXMLReport.y;
		y = y / heigthAdjFactor;
		y = y + tlImagem.y;
		return new Double(Math.floor(y)).intValue();
	}

	/**
	 * @param heigthAdjFactor
	 * @param tlXMLReport
	 * @param tlImagem
	 * @param yXML
	 * @return
	 */
	private int adjustYValue(double heigthAdjFactor, Point2D.Double tlXMLReport, Point2D.Double tlImagem, Integer yXML) {

		return adjustYValue(heigthAdjFactor, tlXMLReport, tlImagem, yXML.doubleValue());
	}

	/**
	 * @param widthAdjFactor
	 * @param tlXMLReport
	 * @param tlImagem
	 * @param xXML
	 * @return
	 */
	private int adjustXValue(double widthAdjFactor, Point2D.Double tlXMLReport, Point2D.Double tlImagem, Double xXML) {
		double x = xXML;
		x = x - tlXMLReport.x;
		x = x / widthAdjFactor;
		x = x + tlImagem.x;
		return new Double(Math.floor(x)).intValue();
	}

	/**
	 * @param widthAdjFactor
	 * @param tlXMLReport
	 * @param tlImagem
	 * @param xXML
	 * @return
	 */
	private int adjustXValue(double widthAdjFactor, Point2D.Double tlXMLReport, Point2D.Double tlImagem, Integer xXML) {
		return adjustXValue(widthAdjFactor, tlXMLReport, tlImagem, xXML.doubleValue());
	}

	/**
	 * @param imageDocVO
	 * @param eventVO
	 * @param participationVO
	 * @param questionCoordinateVO
	 * @param examVO
	 * @param questionResult
	 * @return
	 * @return
	 * @throws JazzOMRException
	 */
	private Set<CriterionResultVO> recortarRespostas(ImageDocVO imageDocVO, Set<OMRMark> omrMarks, EventVO eventVO, Integer examVar, QuestionVO questionVO, ParticipationVO participationVO) throws JazzOMRException {

		Set<CriterionResultVO> critQuestResults = new HashSet<CriterionResultVO>(omrMarks.size());

		for (OMRMark omrMark : omrMarks) {

			BufferedImage imgAnswer = cropOMRMark(imageDocVO, eventVO.getRelatorioVO(), omrMark, imageDocVO.getBufferedImage());

			ResultsTable resultsTable = particleAnaylis(imgAnswer);

			CriterionResultVO critResVO = new CriterionResultVO();

			critResVO.setChave(omrMark.getChave());
			critResVO.setCriterion(omrMark.getCriterion());
			critResVO.setCritType(omrMark.getCritType());

			critResVO.setOmrMark(omrMark);

			critResVO.setPergunta(questionVO.getPK());

			critResVO.setTransientResultAnalysis(resultsTable);
			critResVO.setCroppedImage(imgAnswer);

			critQuestResults.add(critResVO);

		}

		return critQuestResults;

	}

	/**
	 * @param graphics2d
	 * @param intW
	 * @param intH
	 * @param intX
	 * @param intY
	 */
	private void drawSetaCritic(Graphics2D graphics2d, int intW, int intH, int intX, int intY) {
		intX = intX - (intW / 2);
		graphics2d.fillPolygon(new int[] { intX, intX + intW, intX + intW + (intW / 2), intX + intW, intX }, new int[] { intY, intY, intY + (intH / 2), intY + intH, intY + intH }, 5);
	}

	/**
	 * @param widthAdjFactor
	 * @param graphics2d
	 */
	private void setCriticFont(double widthAdjFactor, Graphics2D graphics2d) {
		int fontSize = (int) ((double) JazzOMRImageParser.CRITIC_FONT_SIZE / widthAdjFactor);
		Font font = new Font("Arial", Font.BOLD, fontSize);
		graphics2d.setFont(font);
	}

	/**
	 * @param omrMark
	 * @param graphics2d
	 */
	private void setLogColor(OMRMark omrMark, Graphics2D graphics2d) {

		if ("A".equals(omrMark.getCritType())) {

			Object score = AlternativeScore.CORRECT;
			if (score != null && score.equals(AlternativeScore.CORRECT)) {
				graphics2d.setColor(ImageDocLogger.COR_LOG_CHECKED_CORRECT);

			} else if (score == null || score.equals(AlternativeScore.WRONG)) {
				graphics2d.setColor(ImageDocLogger.COR_LOG_CHECKED_WRONG);

			}
		}
	}

	/**
	 * Realiza uma analise de particulas na imagem do bullet. Estes resultados serao analizados juntamente com as outras imagens de bullets de alternativas.
	 *
	 * @param bi
	 * @return
	 */
	private ResultsTable particleAnaylis(BufferedImage bi) {

		// wrapper de imagem do ImageJ para analise
		ImagePlus ip = new ImagePlus("bullet", bi);

		// prepara imagem para analise
		contrastImageThresholds(ip, JazzOMRImageParser.THRESHOLD_HUANG);

		// cria tabela de resultados
		ResultsTable rt = new ResultsTable();

		// determina parametros de analise
		double area = bi.getHeight() * bi.getWidth();
		double minArea = area * bulletMinAreaFactor;
		double maxArea = area * bulletMaxAreaFactor;

		/*
		 * private double bulletMinAreaFactor = 0.10; private double bulletMaxAreaFactor = 0.95; private double bulletMinCirc = 0.30;
		 */
		// cria analizador de particulas para bullets
		ParticleAnalyzer jpa = new ParticleAnalyzer(0, Measurements.AREA + Measurements.CIRCULARITY, rt, minArea, maxArea, bulletMinCirc, JazzOMRImageParser.MAX_CIRCULARITY);

		// executa analise
		jpa.analyze(ip);

		// testa se foram encotradas particulas
		boolean checked = rt.getCounter() > 0;

		if (log.isDebugEnabled()) {
			imageDocLogger.logBullet(checked, bi, rt);
		}

		return rt;

	}

	/**
	 *
	 * @param examVO
	 * @param omrMark
	 * @param bufferedImage
	 * @param imageDocPK
	 * @return
	 * @throws JazzOMRException
	 */
	private BufferedImage cropOMRMark(ImageDocVO imageDocVO, RelatorioVO relatorioVO, OMRMark omrMark, BufferedImage bufferedImage) throws JazzOMRException {

		Point2D.Double tlCornerExam = relatorioVO.determineTopLeftCorner();
		Point2D.Double tlCornerImag = imageDocVO.determineTopLeftCorner();

		// calculos dos fatores de ajuste das coordenadas armazenadas para efetuar os cortes na imagem
		double widthAdjFactor = calculateWidthAdjustFactor(imageDocVO, relatorioVO);
		double heigthAdjFactor = calculateHeigthAdjustFactor(imageDocVO, relatorioVO);

		BufferedImage imageQuestion = cropOMRMark(bufferedImage, omrMark, tlCornerExam, tlCornerImag, widthAdjFactor, heigthAdjFactor);

		return imageQuestion;
	}

	/**
	 * @param imageDocVO
	 * @param omrMark
	 * @param tlCornerExam
	 * @param tlCornerImag
	 * @param widthAdjFactor
	 * @param heigthAdjFactor
	 * @return
	 * @throws JazzOMRException
	 */
	private BufferedImage cropOMRMark(BufferedImage bufferedImage, OMRMark omrMark, Point2D.Double tlCornerExam, Point2D.Double tlCornerImag, double widthAdjFactor, double heigthAdjFactor) throws JazzOMRException {

		// calcula a posição (x,y) e tamanho dos cortes (w,h)
		// lembrando que as coordenadas da questão nao é relativa aos discos pretos de referencia
		// o trecho de calculo "omrMark.getX() - tlCornerExam.x" determina as coordenadas a partir dos discos

		double qcRefW = omrMark.getW() / widthAdjFactor;
		int intX = adjustXValue(widthAdjFactor, tlCornerExam, tlCornerImag, omrMark.getX());
		int intW = new Double(Math.floor(qcRefW)).intValue();
		int xBorder = ((int) (qcRefW));
		intX = intX - xBorder;
		intW = intW + (2 * xBorder);

		double qcRefH = omrMark.getH() / heigthAdjFactor;
		final int intYOrig = adjustYValue(heigthAdjFactor, tlCornerExam, tlCornerImag, omrMark.getY());
		final int intHOrig = new Double(Math.floor(qcRefH)).intValue();

		int yBorder = ((int) (qcRefH / 3.5d));
		int intY = intYOrig - yBorder;
		int intH = intHOrig + (2 * yBorder);

		int wImagem = bufferedImage.getWidth();

		if (isDissertativa(omrMark)) {
			// se for dissertativo é toda a largura da imagem
			intX = 0;
			intW = wImagem;

			intY = intYOrig;
			intH = intHOrig;

		} else {
			// se for alternativa é apenas o bullet
			int wMax = intW + intX;
			if (wMax > wImagem) {
				intW = wImagem - intX;
			}
			if (intX < 0) {
				intX = 0;
			}
		}

		BufferedImage imageQuestion = bufferedImage.getSubimage(intX, intY, intW, intH);
		// BufferedImage imageQuestion = bufferedImage.getSubimage(3, intY, wImagem-10, intH);

		// loga posicao dos cortes na imagem
		logImageCropPosition(omrMark, intW, intH, intX, intY);
		// logImageCropPosition(omrMark, wImagem, intH, 0, intY);

		return imageQuestion;
	}

	/**
	 * @param omrMark
	 * @return
	 */
	private Boolean isDissertativa(OMRMark omrMark) {
		return omrMark != null && "D".equals(omrMark.getCritType());
	}

	/**
	 * @param omrMark
	 * @param intW
	 * @param intH
	 * @param intX
	 * @param intY
	 * @throws JazzOMRException
	 */
	private void logImageCropPosition(OMRMark omrMark, int intW, int intH, int intX, int intY) throws JazzOMRException {
		if (this.imageDocLogger.isOpenedGraphics2D()) {
		}

		Graphics2D graphics2d = this.imageDocLogger.getOpenedGraphics2D();
		Color cOrig = graphics2d.getColor();
		Stroke origStroke = graphics2d.getStroke();
		Composite compositeOrig = graphics2d.getComposite();

		ImageDocLogger.setLogStroke(graphics2d, 0.5f);
		ImageDocLogger.setTransparency(graphics2d, 0.5f);

		// determina a cor do log, de acordo com o score de omrMark (questao ou alternativa)
		// setLogColor(omrMark, graphics2d);

		if (isDissertativa(omrMark)) {
			graphics2d.drawRect(intX, intY, intW, intH);
		} else { // para evitar confusoes nao vou logar a regiao do bullet
			graphics2d.drawRect(intX - 2, intY, 3, intH);
		}

		/*
		 * graphics2d.setStroke(origStroke); graphics2d.setColor(cOrig); graphics2d.setComposite(compositeOrig);
		 */
	}

	/**
	 * @param imageDocVO
	 * @param examVO
	 * @return
	 */
	private double calculateHeigthAdjustFactor(ImageDocVO imageDocVO, RelatorioVO relatorioVO) {
		// busca tamanhos de referencia para o padrao do exame, a partir dos discos pretos nas quinas do relatorio
		Double examHeigth = relatorioVO.getExamRefHeigth();

		// busca tamanhos de referencia encontrados na imagem, a partir dos discos pretos nas quinas da imagem
		Double imgHeigth = imageDocVO.getImgRefHeigth();

		// determina um coeficiente de ajuste das coordenadas
		// este coeficiente será utilizado para ajustar os cortes que serão feitos na imagem
		double heigthAdjFactor = examHeigth / imgHeigth;
		return heigthAdjFactor;
	}

	/**
	 * @param imageDocVO
	 * @param examVO
	 * @return
	 */
	private double calculateWidthAdjustFactor(ImageDocVO imageDocVO, RelatorioVO relatorioVO) {

		// busca tamanhos de referencia para o padrao do exame, a partir dos discos pretos nas quinas do relatorio
		Double examWidth = relatorioVO.getExamRefWidth();

		// busca tamanhos de referencia encontrados na imagem, a partir dos discos pretos nas quinas da imagem
		Double imgWidth = imageDocVO.getImgRefWidth();

		// determina um coeficiente de ajuste das coordenadas
		// este coeficiente será utilizado para ajustar os cortes que serão feitos na imagem
		double widthAdjFactor = examWidth / imgWidth;
		return widthAdjFactor;
	}

	/**
	 * Ajusta rotacao da imagem original. so e possivel ser executada quando se sabe a posicao das particulas de referencia
	 *
	 * @param refParticles
	 * @param params
	 * @throws JazzOMRException
	 */
	private void fixImageRotation(ImageDocVO imageDocVO, ParticleAnalyzerParamDTO params) throws JazzOMRException {
		// determina o angulo de ajuste da rotacao da imagem. Angulo atual menos o angulo esperado
		double anguloAjuste = anguloAjuste(imageDocVO, params);

		// cria um affine transform com o angulo de ajuste
		AffineTransform affineRotateTransform = prepareRotateTransformations(anguloAjuste);

		// aplica as transformacoes nos pontos primeiro
		applyParticleTransformations(imageDocVO, affineRotateTransform);

		// de posse na nova posicacao dos pontos, aplica as transformacoes necessarias na imagem
		applyImageTransformations(imageDocVO, affineRotateTransform, params);

		// Apenas loga processamento da imagem. Faz uma copia da imagem no estado atual
		imageDocLogger.logImageProcessing(imageDocVO, params, "3-FixedRotation", "Rotacao da Imagem Corrigida");

	}

	/**
	 * Determina o angulo de ajuste, para que a imagem scaneada
	 *
	 * @param imageDocVO
	 * @param params
	 * @return
	 * @throws JazzOMRFixRotationException
	 */
	private double anguloAjuste(ImageDocVO imageDocVO, ParticleAnalyzerParamDTO params) throws JazzOMRFixRotationException {
		// encontra particulas que sao referencia do angulo/rotacao da imagem
		List<ParticleVO> angularRef = findAngularRefParticles(imageDocVO.getParticles());
		imageDocVO.setParticles(angularRef);

		// angulo esperado vindo do ParticleAnalyzerParamDTO
		double refParticleAngleParam = params.getParticlesAngle();

		// angulo atual das particulas de referencia
		double refParticleAngle = anguloHipotenusa(angularRef);

		// angulo para de ajuste
		double anguloAjuste = refParticleAngleParam - refParticleAngle;
		return anguloAjuste;
	}

	/**
	 * Aplica as transformacoes na imagem scaneada
	 *
	 * @param imageDocVO
	 * @param rotationTransform
	 * @param params
	 */
	private void applyImageTransformations(ImageDocVO imageDocVO, AffineTransform rotationTransform, ParticleAnalyzerParamDTO params) {

		// calcula a translacao para trazer a quina superior esquerda do documento até a borda da imagem
		AffineTransform translation = determineTranslation(imageDocVO, params);

		// aplica as translacao nos pontos primeiro
		applyParticleTransformations(imageDocVO, translation);

		// soma a translacao aa rotacao da imagem
		translation.concatenate(rotationTransform);

		// aplica a concatenacao da translacao e rotacao aa imagem
		BufferedImage buffImgDest = applyImageTransformation(imageDocVO, translation);

		// recorta as sobras inferiores da imagem
		buffImgDest = recortaSobrasDaImagem(imageDocVO, params, buffImgDest);

		// atualiza o buffered image
		imageDocVO.setBufferedImage(buffImgDest);
	}

	/**
	 * @param imageDocVO
	 * @param translation
	 * @return
	 */
	private BufferedImage applyImageTransformation(ImageDocVO imageDocVO, AffineTransform translation) {
		AffineTransformOp affOp = new AffineTransformOp(translation, JazzOMRImageParser.DEF_INTERPOLATION_TYPE);
		BufferedImage buffImgDest = affOp.filter(imageDocVO.getBufferedImage(), null);
		return buffImgDest;
	}

	/**
	 * Determina o limite inferior direito util da imagem e recorta as sobras.
	 *
	 * @param imageDocVO
	 * @param params
	 * @param buffImgDest
	 * @return
	 */
	private BufferedImage recortaSobrasDaImagem(ImageDocVO imageDocVO, ParticleAnalyzerParamDTO params, BufferedImage buffImgDest) {
		Point2D.Double bottRight = imageDocVO.determineBottomRightCorner();

		// raio medio das particulas. é adicionado aos calculos de corner para nao cortar as quinas da imagem no meio das marcas de referencia
		double avgParticleRadius = params.getAvgParticleRadius(imageDocVO.getParticles());

		int xLimit = new Long(Math.round(bottRight.x + avgParticleRadius)).intValue();
		int yLimit = new Long(Math.round(bottRight.y + avgParticleRadius)).intValue();

		// protege conta possiveis erros de calculo
		if (xLimit > buffImgDest.getWidth()) {
			xLimit = buffImgDest.getWidth();
		}

		// protege conta possiveis erros de calculo
		if (yLimit > buffImgDest.getHeight()) {
			yLimit = buffImgDest.getHeight();
		}

		buffImgDest = buffImgDest.getSubimage(0, 0, xLimit, yLimit);
		return buffImgDest;
	}

	/**
	 * Determina translacao que alinhara a area util registrada na imagem ao canto superior esquerdo da imagem
	 *
	 * @param imageDocVO
	 * @param params
	 * @return
	 */
	private AffineTransform determineTranslation(ImageDocVO imageDocVO, ParticleAnalyzerParamDTO params) {
		Point2D.Double particleTopLeft = imageDocVO.determineTopLeftCorner();
		Point2D.Double pointTopLeftDest = determineTopLeftCornerDest(imageDocVO, params);

		// faz translacao da figura
		AffineTransform translation = new AffineTransform();
		translation.translate(pointTopLeftDest.x - particleTopLeft.x, pointTopLeftDest.y - particleTopLeft.y);
		return translation;
	}

	/**
	 * Calcula o ponto da imagem que devera corresponder ao ponto 0,0 da imagem.
	 *
	 * Usado no calculo da translacao da imagem e das particulas
	 *
	 * @param imageDocVO
	 * @param params
	 * @return
	 */
	private Point2D.Double determineTopLeftCornerDest(ImageDocVO imageDocVO, ParticleAnalyzerParamDTO params) {

		double particleRadius = params.getAvgParticleRadius(imageDocVO.getParticles());

		Point2D.Double ptDest = new Point2D.Double(particleRadius, particleRadius);

		return ptDest;
	}

	/**
	 * Aplica as transformacoes 2D no conjunto de particulas encontradas. O conjunto allParticles contem o subset de particulas de referencia (refParticlesSubSet)
	 *
	 * @param imageDocVO
	 * @param affineTransform
	 */
	private void applyParticleTransformations(ImageDocVO imageDocVO, AffineTransform affineTransform) {
		List<ParticleVO> particles = imageDocVO.getParticles();

		applyParticleTransformations(affineTransform, particles);
	}

	/**
	 * Aplica as transformacoes 2D na colecao de particulas informada
	 *
	 * @param affineTransform
	 * @param refParticles
	 */
	private void applyParticleTransformations(AffineTransform affineTransform, List<ParticleVO> refParticles) {
		for (ParticleVO particleVO : refParticles) {
			if (log.isDebugEnabled()) {
				log.debug("Origem  X=" + particleVO.getX() + " Y=" + particleVO.getY());
			}

			// aplica das mudancas 2D acumuladas no affinetransform: rotacao, translacao, escala etc
			affineTransform.transform(particleVO, particleVO);

			if (log.isDebugEnabled()) {
				log.debug("Destino X=" + particleVO.getX() + " Y=" + particleVO.getY() + "\n\n");
			}

		}
	}

	/**
	 * Determina as transformacoes 2D a serem realizadas a partir do angulo informado (rotacao, translacao e crop).
	 *
	 * @param anguloRadians
	 * @return
	 */
	private AffineTransform prepareRotateTransformations(double anguloRadians) {
		AffineTransform affineTransform = new AffineTransform();
		affineTransform.rotate(anguloRadians);

		return affineTransform;
	}

	/**
	 * Retorna o Angulo formado pela hipotenusa formada pelas particulas 1 e 2
	 *
	 * @param angularRef
	 * @return
	 */
	private double anguloHipotenusa(List<ParticleVO> angularRef) {
		ParticleVO particle1 = angularRef.get(0);
		ParticleVO particle2 = angularRef.get(1);

		double x1 = particle1.getX();
		double x2 = particle2.getX();

		double y1 = particle1.getY();
		double y2 = particle2.getY();

		double deltaX = x1 - x2;
		double deltaY = y1 - y2;

		double tangente = deltaY / deltaX;

		double arcoTangente = Math.atan(tangente);

		return arcoTangente;
	}

	/**
	 * A partir da lista de particulas de referencia, filtra as particulas que sao referencias do angulo (rotacao) da imagem
	 *
	 * @param refParticles
	 * @return
	 * @throws JazzOMRFixRotationException
	 */
	private List<ParticleVO> findAngularRefParticles(List<ParticleVO> refParticles) throws JazzOMRFixRotationException {
		if (refParticles.size() < 2) {
			throw new JazzOMRFixRotationException("Duas, e apenas duas, particulas de referência angular devem ser informadas para ajuste da rotacao da imagem !");
		}

		List<ParticleVO> angularReference = new ArrayList<ParticleVO>(2);

		for (ParticleVO particleVO : refParticles) {
			if (particleVO.isAngularReference()) {
				angularReference.add(particleVO);
			}
		}

		if (angularReference.size() != 2) {
			throw new JazzOMRFixRotationException("Duas, e apenas duas, particulas de referência angular devem ser informadas para ajuste da rotacao da imagem !");
		}

		return angularReference;
	}

	/**
	 * Determina onde esta a regiao da imagem com os codigos de barra ou QRCode com a identificacao do documento.
	 *
	 * @param particleAnalyzerParamDTO
	 * @param eventVO
	 *
	 * @param rotatedImage
	 * @param refDots
	 * @return
	 * @throws JazzOMRException
	 * @throws JazzOMRException
	 */
	private void findIdentityRegion(ImageDocVO imageDocVO, ParticleAnalyzerParamDTO particleAnalyzerParamDTO, EventVO eventVO) throws JazzOMRException {

		ImageDocPK imageDocPK;

		// TODO IMPLEMENTAR ESTE MESMO ALGORITMO EM STRATEGY E CHAIN OF RESPONSABILITY

		openGraphics2D(imageDocVO, particleAnalyzerParamDTO);
		try {
			// executa diversas tentativas de encontrar o QRCode identificador da imagem
			imageDocPK = getImageDocPK(imageDocVO, particleAnalyzerParamDTO);
		} catch (JazzOMRIdentityException e) {

			/*
			if (log.isDebugEnabled()) {
				// loga imagem onde deveria estar o qrcode
			}
			*/

			throw new JazzOMRIdentityException("Nao foi possivel encontrar o QRCode identificador da imagem mesmo depois de duas tentativas em HEAD_UP e UPSIDE_DOWN", e);

		}

		// atribui identificador da imagem
		imageDocVO.setImageDocPK(imageDocPK);

		// pesquisa e atribui registro de participacao referente aa imagem
		ParticipationVO participationVO = findParticipationVO(imageDocPK, eventVO);
		imageDocVO.setParticipationVO(participationVO);
		Integer examVar = participationVO.getExamVariant();

		// loga estato do processamento da imagem
		imageDocLogger.logImageProcessing(imageDocVO, particleAnalyzerParamDTO, "4-idRegionFinded", "Identificador QRCode Encontrado. ExamVar=" + examVar);

	}

	private void openGraphics2D(ImageDocVO imageDocVO, ParticleAnalyzerParamDTO particleAnalyzerParamDTO) throws JazzOMRException {
		imageDocLogger.openGraphics2D(imageDocVO, particleAnalyzerParamDTO, "XX-erroImgID_HEAD_UP", null);
		imageDocLogger.logaIdentificador(imageDocVO, particleAnalyzerParamDTO, imageDocLogger.getOpenedGraphics2D());
		// imageDocLogger.closeGraphics2D();
	}

	/**
	 * Detecta na imagem o qrCode de identificacao documento/pagina e cria o objeto chave ImageDocPK
	 *
	 * @param imageDocVO
	 * @param particleAnalyzerParamDTO
	 * @param imagePosition
	 * @return
	 * @throws JazzOMRException
	 */
	private ImageDocPK getImageDocPK(ImageDocVO imageDocVO, ParticleAnalyzerParamDTO particleAnalyzerParamDTO) throws JazzOMRException {

		QRCodeReaderParam parameterObject = new QRCodeReaderParam(imageDocVO, particleAnalyzerParamDTO);

		String strResult = readQRCode(parameterObject);

		// cria identificador da imagem a partir do codigo lido
		ImageDocPK imageDocPK = new ImageDocPK(strResult);

		if (log.isDebugEnabled()) {
			log.debug("Resultado leitura codigo de barras: " + strResult);
		}

		return imageDocPK;
	}

	/**
	 * Executa leitura do qrCDode contido na imagem. Utiliza diversas estrategias ate conseguir ler identificacao da imagem com sucesso
	 *
	 * @param identityRegion
	 * @return
	 * @throws JazzOMRException
	 */
	private String readQRCode(QRCodeReaderParam qrCodeReaderParam) throws JazzOMRException {

		// recupera estrategias de leitura de qrcode: qrCodeReaderDirect qrCodeReader180G qrCodeReaderScaled qrCodeReader180Scaled
		List<Strategy<QRCodeReaderParam, String>> strategies = getStrategies();
		String result = null;
		Exception lastError = null;

		// itera a sequencia de estrategias de leitura do qrCode (normal, 180g, escalado, 180g escalado ....)
		for (Strategy<QRCodeReaderParam, String> strategy : strategies) {
			try {
				result = strategy.execute(qrCodeReaderParam);

				if (isValidResult(result)) {
					break;
				}

			} catch (Exception e) {
				lastError = e;
				log.warn("Erro ao tentar interpretar imagem!", e);
			}
		}

		if (result == null) {
			// nao houve resultado satisfatorio
			throw new JazzOMRException("Erro ao tentar ler qrcode", lastError);
		}

		return result;
	}

	/**
	 * @param result
	 * @return
	 */
	private boolean isValidResult(String result) {
		return !StringUtils.isBlank(result);
	}

	/**
	 * Determinar pontos de referência para processamento das imagens com base na tabela de resultados de análise de partículas do imagej
	 *
	 * @param totalParticles
	 * @param particleAnalyzerParamDTO
	 *          TODO
	 * @return
	 * @throws JazzOMRException
	 */
	private void findRefParticles(ImageDocVO imageDocVO, ParticleAnalyzerParamDTO particleAnalyzerParamDTO) throws JazzOMRException {
		RefParticleFinder refParticleFinder = createRefParticleFinder(particleAnalyzerParamDTO);

		List<ParticleVO> refParticleSubSet = refParticleFinder.executeFind(imageDocVO.getParticles(), particleAnalyzerParamDTO);

		imageDocVO.setParticles(refParticleSubSet);

	}

	/**
	 * Fabrica Apurador de particulas de acordo com o modelo informado
	 *
	 * @param particleAnalyzerParamDTO
	 * @return
	 * @throws RefParticleNotFoundException
	 */
	private RefParticleFinder createRefParticleFinder(ParticleAnalyzerParamDTO particleAnalyzerParamDTO) throws RefParticleNotFoundException {

		int countParticles = particleAnalyzerParamDTO.getCount();

		switch (countParticles) {
		case 3:
			return new ThreeRefParticleFinder();
		case 2:
			return new TwoRefParticleFinder();

		default:
			throw new RefParticleNotFoundException("Nao foi possivel encontrar um analizador de particulas para o (count=" + countParticles + ")");
		}

	}

	/**
	 * @param particleTemplates
	 * @param imageDocVO.getImp()
	 * @return
	 * @throws RefParticleNotFoundException
	 * @throws JazzOMRException
	 * @throws IOException
	 */
	private void findParticles(ImageDocVO imageDocVO, ParticleAnalyzerParamDTO analyzerParams) throws JazzOMRException {

		imageDocLogger.logImageProcessing(imageDocVO, analyzerParams, "0-imagemOriginal", "Imagem Original");

		// destaca limites da imagem
		contrastImageThresholds(imageDocVO.getImagePlus(), JazzOMRImageParser.THRESHOLD_DEFAULT);

		// executa a analise de particulas
		List<ParticleVO> particles = findParticles(imageDocVO, 0, JazzOMRImageParser.MEDIDAS_PADRAO, analyzerParams);

		if (CollectionUtils.isEmpty(particles)) {
			throw new RefParticleNotFoundException("Não foram encontradas particulas de marcação na imagem informada");
		}

		imageDocVO.setParticles(particles);

		imageDocLogger.logImageProcessing(imageDocVO, analyzerParams, "1-particulasEncontradas", "Particulas Encontradas");

	}

	/**
	 * @param imageDoc
	 * @param fileSufix
	 * @param logDescription
	 * @param bi
	 * @throws JazzOMRException
	 */
	private void writeImageFileLog(ImageDocVO imageDoc, String fileSufix, String logDescription, BufferedImage bi) throws JazzOMRException {
		try {
			if (fileSufix != null) {
				fileSufix = "_" + fileSufix;

			} else {
				fileSufix = "";
			}

			ImageIO.write(bi, IMG_EXT, new File("result/" + imageDoc.hashCode() + fileSufix));
		} catch (IOException e) {
			throw new JazzOMRException("erro ao tentar logar estado de processamento de imagem (" + logDescription + ")", e);
		}
	}

	/**
	 * Contrasta a imagem destacando todos os shapes para a analise de particulas
	 *
	 * @param imp
	 * @param thresMethod
	 */
	private void contrastImageThresholds(ImagePlus imp, String thresMethod) {
		ImageConverter converter = new ImageConverter(imp);
		converter.convertToGray8();

		IJ.setAutoThreshold(imp, thresMethod);
	}

	/**
	 * Lista partículas encontradas na imagem de acordo com o template de particulas informado
	 *
	 * @param imp
	 * @param options
	 * @param medidas
	 * @param parameterObject
	 *          TODO
	 * @return Measurements.SHAPE_DESCRIPTORS + Measurements.CENTER_OF_MASS + Measurements.AREA + Measurements.PERIMETER + Measurements.CENTROID;
	 */
	private List<ParticleVO> findParticles(ImageDocVO data, int options, int medidas, ParticleAnalyzerParamDTO analyzerParams) {
		// int measurements=0;
		ResultsTable rt = new ResultsTable();

		double minArea = analyzerParams.getMinArea(data.getImgArea());
		double maxArea = analyzerParams.getMaxArea(data.getImgArea());
		double minCirc = analyzerParams.getMinCirc();

		ParticleAnalyzer jpa = new ParticleAnalyzer(options, medidas, rt, minArea, maxArea, minCirc, JazzOMRImageParser.MAX_CIRCULARITY);

		// executa analise
		jpa.analyze(data.getImagePlus());

		// itera resultados e preenche VOs
		List<ParticleVO> particles = fillParticleVOs(data, rt);

		return particles;
	}

	/**
	 * Apenas faz o trabalho bracal de preencher a lista de VOs
	 *
	 * @param rt
	 * @return
	 */
	private List<ParticleVO> fillParticleVOs(ImageDocVO data, ResultsTable rt) {
		int rowCount = rt.getCounter();
		List<ParticleVO> particles = new ArrayList<ParticleVO>(rowCount);

		if (rowCount > 0) {

			for (int row = 0; row < rowCount; row++) {

				ParticleVO particleVO = new ParticleVO();

				particleVO.setArea(getValue(rt, COLUMNS_PARTICLE.AREA, row));
				particleVO.setPerimeter(getValue(rt, COLUMNS_PARTICLE.PERIM, row));
				particleVO.setSolidity(getValue(rt, COLUMNS_PARTICLE.SOLIDITY, row));

				particleVO.setX(getValue(rt, COLUMNS_PARTICLE.X, row));
				particleVO.setY(getValue(rt, COLUMNS_PARTICLE.Y, row));

				particleVO.setCircularity(getValue(rt, COLUMNS_PARTICLE.CIRC, row));

				particles.add(particleVO);
			}

		}
		return particles;
	}

	/**
	 * Checa se coluna existe nos resultados. Se sim, retorna valor.
	 *
	 * Evita erros
	 *
	 * @param rt
	 * @param column
	 * @param row
	 * @return
	 */
	private Double getValue(ResultsTable rt, COLUMNS_PARTICLE column, int row) {

		if (rt.columnExists(column.col)) {
			return rt.getValueAsDouble(column.col, row);
		} else {
			return null;
		}

	}

	/**
	 * @param strategies
	 *          the strategies to set
	 */
	@SuppressWarnings("unchecked")
	@Autowired
	public void setStrategies(List<Strategy<QRCodeReaderParam, String>> strategies) {

		BeanComparator orderComparator = new BeanComparator("order");

		Collections.sort(strategies, orderComparator);

		this.strategies = strategies;
	}

	/**
	 * @return the strategies
	 */
	public List<Strategy<QRCodeReaderParam, String>> getStrategies() {
		return strategies;
	}

	/**
	 * @param altCoord
	 * @param widthAdjFactor
	 * @param heigthAdjFactor
	 * @param tlCornerExam
	 * @param tlCornerImag
	 * @throws JazzOMRException
	 */
	private void logCheckedOption(CriterionResultVO criterionResultVO, RelatorioVO relatorioVO, ImageDocVO imageDocVO) {

		if (!imageDocLogger.isOpenedGraphics2D() || !criterionResultVO.getChecked()) {
			return;
		}

		Point2D.Double tlCornerExam = relatorioVO.determineTopLeftCorner();
		Point2D.Double tlCornerImag = imageDocVO.determineTopLeftCorner();

		double widthAdjFactor = calculateWidthAdjustFactor(imageDocVO, relatorioVO);
		double heigthAdjFactor = calculateHeigthAdjustFactor(imageDocVO, relatorioVO);

		Graphics2D graphics2d = imageDocLogger.getOpenedGraphics2D();

		Color colorOrig = graphics2d.getColor();// backup color Font fontOrig = graphics2d.getFont();

		// aplica cor do log de acordo com score da alernativa CORRECT/WRONG setLogColor(altCoord, graphics2d);

		OMRMark omrMark = criterionResultVO.getOmrMark();

		double qcRefW = omrMark.getW() / widthAdjFactor;
		double qcRefH = omrMark.getH() / heigthAdjFactor;

		int intW = new Double(Math.floor(qcRefW)).intValue();
		int intH = new Double(Math.floor(qcRefH)).intValue();
		int intX = adjustXValue(widthAdjFactor, tlCornerExam, tlCornerImag, omrMark.getX());
		int intY = adjustYValue(heigthAdjFactor, tlCornerExam, tlCornerImag,

		omrMark.getY());

		intX = (int) (intX - (intW * 2.4));
		intX = intX <= 1 ? 1 : intX;

		setCriticFont(widthAdjFactor, graphics2d);

		int criticX = (int) (intX - (1.5 * (double) intW));
		criticX = criticX <= 1 ? 1 : criticX;

		graphics2d.drawString(criterionResultVO.getCritType(), criticX, (int) (intY + (intH * 0.93)));

		drawSetaCritic(graphics2d, intW, intH, intX, intY);

		graphics2d.setColor(colorOrig);
		//graphics2d.setFont(fontOrig);
	}

}


text:
//
// Copyright 2013-2014 Yummy Melon Software LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//  Author: Charles Y. Choi <charles.choi@yummymelon.com>
//

#import "DEABaseService.h"

/**
 TI SensorTag CoreBluetooth service definition for thermometer.
 */
@interface DEATemperatureService : DEABaseService

/**
 Inherited property of DEABaseService.
 Keys: @"ambientTemp", @"objectTemp".
 */
@property (nonatomic, strong, readonly) NSDictionary *sensorValues;

/// Ambient temperature
@property (nonatomic, strong, readonly) NSNumber *ambientTemp;

/// Object temperature
@property (nonatomic, strong, readonly) NSNumber *objectTemp;

@end

redpajama_set_name: RedPajamaGithub


text:
package cn.limw.summer.hibernate.cfg;

import org.hibernate.cfg.NamingStrategy;

import cn.limw.summer.util.Asserts;

/**
 * @author li
 * @version 1 2014年3月27日上午11:17:09
 */
public class NamingStrategyWrapper implements NamingStrategy {
    private NamingStrategy namingStrategy;

    public NamingStrategyWrapper() {}

    public NamingStrategyWrapper(NamingStrategy namingStrategy) {
        this.namingStrategy = namingStrategy;
    }

    public NamingStrategy getNamingStrategy() {
        return Asserts.noNull(namingStrategy);
    }

    public void setNamingStrategy(NamingStrategy namingStrategy) {
        this.namingStrategy = namingStrategy;
    }

    public String classToTableName(String className) {
        return getNamingStrategy().classToTableName(className);
    }

    public String propertyToColumnName(String propertyName) {
        return getNamingStrategy().propertyToColumnName(propertyName);
    }

    public String tableName(String tableName) {
        return getNamingStrategy().tableName(tableName);
    }

    public String columnName(String columnName) {
        return getNamingStrategy().columnName(columnName);
    }

    public String collectionTableName(String ownerEntity, String ownerEntityTable, String associatedEntity, String associatedEntityTable, String propertyName) {
        return getNamingStrategy().collectionTableName(ownerEntity, ownerEntityTable, associatedEntity, associatedEntityTable, propertyName);
    }

    public String joinKeyColumnName(String joinedColumn, String joinedTable) {
        return getNamingStrategy().joinKeyColumnName(joinedColumn, joinedTable);
    }

    public String foreignKeyColumnName(String propertyName, String propertyEntityName, String propertyTableName, String referencedColumnName) {
        return getNamingStrategy().foreignKeyColumnName(propertyName, propertyEntityName, propertyTableName, referencedColumnName);
    }

    public String logicalColumnName(String columnName, String propertyName) {
        return getNamingStrategy().logicalColumnName(columnName, propertyName);
    }

    public String logicalCollectionTableName(String tableName, String ownerEntityTable, String associatedEntityTable, String propertyName) {
        return getNamingStrategy().logicalCollectionTableName(tableName, ownerEntityTable, associatedEntityTable, propertyName);
    }

    public String logicalCollectionColumnName(String columnName, String propertyName, String referencedColumn) {
        return getNamingStrategy().logicalCollectionColumnName(columnName, propertyName, referencedColumn);
    }
}


text:
<?xml version="1.0" encoding="UTF-8"?>
<!--
  Licensed to the Apache Software Foundation (ASF) under one or more
  contributor license agreements.  See the NOTICE file distributed with
  this work for additional information regarding copyright ownership.
  The ASF licenses this file to You under the Apache License, Version 2.0
  (the "License"); you may not use this file except in compliance with
  the License.  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>org.apache.camel</groupId>
    <artifactId>tests</artifactId>
    <version>2.20.0-SNAPSHOT</version>
  </parent>

  <artifactId>camel-itest-osgi</artifactId>
  <name>Camel :: Integration Tests :: OSGi</name>
  <description>Performs OSGi compliance integration tests</description>

  <dependencies>
    <dependency>
      <groupId>org.apache.camel</groupId>
      <artifactId>camel-test-karaf</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.camel</groupId>
      <artifactId>camel-blueprint</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.camel</groupId>
      <artifactId>camel-jackson</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.camel</groupId>
      <artifactId>camel-cxf</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <artifactId>maven-surefire-plugin</artifactId>
        <!-- version 2.19.1 causes pax-exam to fail when testing the 2nd/3rd container when testing all  -->
        <!-- so we use an older version of surefire which works -->
        <version>2.16</version>
        <configuration>
          <!-- do not re-run these tests -->
          <!-- not supported by older version of surefire -->
          <!--<rerunFailingTestsCount>0</rerunFailingTestsCount>-->
          <systemPropertyVariables>
            <karafVersion>${karaf4-version}</karafVersion>
            <camelKarafFeatureVersion>${project.version}</camelKarafFeatureVersion>
          </systemPropertyVariables>
        </configuration>
      </plugin>
    </plugins>
  </build>

</project>


text:
package com.planet_ink.coffee_mud.Items.Weapons;
import com.planet_ink.coffee_mud.core.interfaces.*;
import com.planet_ink.coffee_mud.core.*;
import com.planet_ink.coffee_mud.core.collections.*;
import com.planet_ink.coffee_mud.Abilities.interfaces.*;
import com.planet_ink.coffee_mud.Areas.interfaces.*;
import com.planet_ink.coffee_mud.Behaviors.interfaces.*;
import com.planet_ink.coffee_mud.CharClasses.interfaces.*;
import com.planet_ink.coffee_mud.Commands.interfaces.*;
import com.planet_ink.coffee_mud.Common.interfaces.*;
import com.planet_ink.coffee_mud.Exits.interfaces.*;
import com.planet_ink.coffee_mud.Items.interfaces.*;
import com.planet_ink.coffee_mud.Libraries.interfaces.*;
import com.planet_ink.coffee_mud.Locales.interfaces.*;
import com.planet_ink.coffee_mud.MOBS.interfaces.*;
import com.planet_ink.coffee_mud.Races.interfaces.*;

public class Cestus extends StdWeapon
{
	@Override
	public String ID()
	{
		return "Cestus";
	}

	public Cestus()
	{
		super();

		setName("a mean looking cestus");
		setDisplayText("a cestus is on the gound.");
		setDescription("It\\`s a glove covered in long spikes and blades.");
		basePhyStats().setAbility(0);
		basePhyStats().setLevel(0);
		basePhyStats.setWeight(2);
		basePhyStats().setAttackAdjustment(0);
		basePhyStats().setDamage(4);
		material=RawMaterial.RESOURCE_LEATHER;
		baseGoldValue=5;
		recoverPhyStats();
		weaponDamageType=Weapon.TYPE_PIERCING;
		weaponClassification=Weapon.CLASS_EDGED;

	}

}


text:
            <form class="heading-form" action="#">

                        <input type="search" class="form-control" id="allSearch" placeholder="搜索">

            </form>

                信息公告
                5

                    信息公告

                            Drop the IE specific hacks for temporal inputs

4 minutes ago

                提醒信息
                3

                    提醒信息

                                Jeremy Victorino
                                22:48

                            But that would be extremely strained and suspicious...

                待办任务
                3

                    待办任务

                                Jeremy Victorino
                                22:48

                            But that would be extremely strained and suspicious...

                {{user.userName}}

 个人资料

 修改密码

 修改头像

 退出


text:
package com.microsoft.bingads.v12.api.test.entities.ad_extension.structuredSnippet.read;

import java.util.Arrays;
import java.util.Collection;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;

import com.microsoft.bingads.internal.functionalinterfaces.Function;
import com.microsoft.bingads.v12.api.test.entities.ad_extension.structuredSnippet.BulkStructuredSnippetAdExtensionTest;
import com.microsoft.bingads.v12.bulk.entities.BulkStructuredSnippetAdExtension;

@RunWith(Parameterized.class)
public class BulkStructuredSnippetAdExtensionReadFromRowValuesHeaderTest extends BulkStructuredSnippetAdExtensionTest {

    @Parameter(value = 1)
    public String expectedResult;

    /*
     * Test data generator.
     * This method is called the the JUnit parameterized test runner and
     * returns a Collection of Arrays.  For each Array in the Collection,
     * each array element corresponds to a parameter in the constructor.
     */
    @Parameters
    public static Collection<Object[]> data() {
        // In this example, the parameter generator returns a List of
        // arrays.  Each array has two elements: { datum, expected }.
        // These data are hard-coded into the class, but they could be
        // generated or loaded in any way you like.
        return Arrays.asList(new Object[][]{
                {"Header", "Header"},
                {"", ""},
                {null, null}
        });
    }

    @Test
    public void testRead() {
        this.testReadProperty("Structured Snippet Header", this.datum, this.expectedResult, new Function<BulkStructuredSnippetAdExtension, String>() {
            @Override
            public String apply(BulkStructuredSnippetAdExtension c) {
                return c.getStructuredSnippetAdExtension().getHeader();
            }
        });
    }
}


text:
from __future__ import absolute_import, unicode_literals
from gaegraph.business_base import NodeSearch, DeleteNode
from celula_app.commands import ListCelulaCommand, SaveCelulaCommand, UpdateCelulaCommand, \
    CelulaPublicForm, CelulaDetailForm, CelulaShortForm

def save_celula_cmd(**celula_properties):
    """
    Command to save Celula entity
    :param celula_properties: a dict of properties to save on model
    :return: a Command that save Celula, validating and localizing properties received as strings
    """
    return SaveCelulaCommand(**celula_properties)

def update_celula_cmd(celula_id, **celula_properties):
    """
    Command to update Celula entity with id equals 'celula_id'
    :param celula_properties: a dict of properties to update model
    :return: a Command that update Celula, validating and localizing properties received as strings
    """
    return UpdateCelulaCommand(celula_id, **celula_properties)

def list_celulas_cmd():
    """
    Command to list Celula entities ordered by their creation dates
    :return: a Command proceed the db operations when executed
    """
    return ListCelulaCommand()

def celula_detail_form(**kwargs):
    """
    Function to get Celula's detail form.
    :param kwargs: form properties
    :return: Form
    """
    return CelulaDetailForm(**kwargs)

def celula_short_form(**kwargs):
    """
    Function to get Celula's short form. just a subset of celula's properties
    :param kwargs: form properties
    :return: Form
    """
    return CelulaShortForm(**kwargs)

def celula_public_form(**kwargs):
    """
    Function to get Celula'spublic form. just a subset of celula's properties
    :param kwargs: form properties
    :return: Form
    """
    return CelulaPublicForm(**kwargs)

def get_celula_cmd(celula_id):
    """
    Find celula by her id
    :param celula_id: the celula id
    :return: Command
    """
    return NodeSearch(celula_id)

def delete_celula_cmd(celula_id):
    """
    Construct a command to delete a Celula
    :param celula_id: celula's id
    :return: Command
    """
    return DeleteNode(celula_id)


text:
package tryp
package core

import reflect.{ClassTag, classTag}

trait MetadataFunctions
{
  private[this] def removeDollar(s: String) = {
    """\$anon\$\d+""".r.replaceAllIn(s, "").stripSuffix("$")
  }

  def clsClassName(cls: Class[_]) = {
    def customSimple = {
      val s = cls.toString
      val tail = removeDollar(s).split('.')
        .lastOption
        .getOrElse(s)
      tail.split('$')
        .lastOption
        .getOrElse(tail)
    }
    val simple = try {
      cls.getSimpleName
    } catch {
      case e: java.lang.InternalError => customSimple
    }
    val n = removeDollar(simple)
    if(n.isEmpty) customSimple else n
  }

  implicit class `class name shortcut`[A](a: A) {
    def className = clsClassName(a.getClass)
  }

  implicit class `class name shortcut for Class`[A](a: Class[A]) {
    def className = clsClassName(a)
  }

  implicit class `class name shortcut for ClassTag`[A](a: ClassTag[A]) {
    def className = clsClassName(a.runtimeClass)
  }

  def className[A: ClassTag] = classTag[A].className
}


text:
const Fly = require('./fly');
const Web = require('./web');
const { v4: uuidv4 } = require('uuid');

// silence warning caused by starting many puppeteer
process.setMaxListeners(Infinity);

class Suite {
  constructor() {
    this.url = process.env.ATC_URL || 'http://localhost:8080';
    this.adminUsername = process.env.ATC_ADMIN_USERNAME || 'test';
    this.adminPassword = process.env.ATC_ADMIN_PASSWORD || 'test';
    this.guestUsername = process.env.ATC_GUEST_USERNAME || 'guest';
    this.guestPassword = process.env.ATC_GUEST_PASSWORD || 'guest';

    this.teamName = `watsjs-team-${uuidv4()}`;
    this.guestTeamName = `watsjs-non-main-team-${uuidv4()}`;
    this.teams = [];

    this.fly = new Fly(this.url, this.adminUsername, this.adminPassword, this.teamName);
    this.web = new Web(this.url, this.adminUsername, this.adminPassword);
  }

  async init(t) {
    await this.newTeam(this.adminUsername, this.teamName);
    await this.newTeam(this.guestUsername, this.guestTeamName);
    await this.fly.init();
    await this.web.init();
    await this.web.login(t);

    this.succeeded = false;
  }

  async newTeam(username = this.adminUsername, teamName) {
    if (!teamName) {
      teamName = `watsjs-team-${uuidv4()}`;
    }
    let fly = await Fly.build(this.url, this.adminUsername, this.adminPassword, 'main');

    await fly.newTeam(teamName, username);
    this.teams.push(teamName);

    return teamName;
  }

  async destroyTeams() {
    let fly = await Fly.build(this.url, this.adminUsername, this.adminPassword, 'main');

    var team;
    while (team = this.teams.pop()) {
      await fly.destroyTeam(team);
    }
  }

  passed(t) {
    this.succeeded = true;
  }

  async finish(t) {
    if (this.web.page && !this.succeeded) {
      await this.web.page.screenshot({path: 'failure.png'});
    }

    if (this.web.browser) {
      await this.web.browser.close();
    }

    await this.destroyTeams();
    await this.fly.cleanup();
  }
}

module.exports = Suite;


text:
package org.jboss.pnc.integration;

import com.jayway.restassured.http.ContentType;
import com.jayway.restassured.response.Response;
import org.apache.commons.lang.StringEscapeUtils;
import org.assertj.core.api.Assertions;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.junit.InSequence;
import org.jboss.pnc.auth.AuthenticationProvider;
import org.jboss.pnc.auth.ExternalAuthentication;
import org.jboss.pnc.common.Configuration;
import org.jboss.pnc.common.json.ConfigurationParseException;
import org.jboss.pnc.common.json.moduleconfig.AuthenticationModuleConfig;
import org.jboss.pnc.common.json.moduleprovider.PncConfigProvider;
import org.jboss.pnc.common.util.IoUtils;
import org.jboss.pnc.integration.deployments.Deployments;
import org.jboss.pnc.integration.matchers.JsonMatcher;
import org.jboss.pnc.integration.utils.AuthResource;
import org.jboss.pnc.integration.utils.JsonUtils;
import org.jboss.pnc.rest.restmodel.ProductRest;
import org.jboss.pnc.rest.restmodel.ProjectRest;
import org.jboss.pnc.test.category.ContainerTest;
import org.jboss.shrinkwrap.api.spec.EnterpriseArchive;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.lang.invoke.MethodHandles;

import static com.jayway.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import static org.jboss.pnc.integration.env.IntegrationTestEnv.getHttpPort;

@RunWith(Arquillian.class)
@Category(ContainerTest.class)
public class RestTest {

    public static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    private static int productId;
    private static int productVersionId;
    private static int projectId;
    private static int userId;

    private static Integer newProductId;
    private static Integer newProjectId;

    private static final String PRODUCT_REST_ENDPOINT = "/pnc-rest/rest/products/";
    private static final String PROJECT_REST_ENDPOINT = "/pnc-rest/rest/projects/";
    private static final String PROJECT_REST_ENDPOINT_SPECIFIC = PROJECT_REST_ENDPOINT + "%d";

    private static AuthenticationProvider authProvider;
    private static String access_token =  "no-auth";

    @Deployment(testable = false)
    public static EnterpriseArchive deploy() {
        EnterpriseArchive enterpriseArchive = Deployments.baseEar();
        logger.info(enterpriseArchive.toString(true));
        return enterpriseArchive;
    }

    @BeforeClass
    public static void setupAuth() throws IOException, ConfigurationParseException {
        if(AuthResource.authEnabled()) {
            Configuration configuration = new Configuration();
            AuthenticationModuleConfig config = configuration.getModuleConfig(new PncConfigProvider<AuthenticationModuleConfig>(AuthenticationModuleConfig.class));
            InputStream is = BuildRecordRestTest.class.getResourceAsStream("/keycloak.json");
            ExternalAuthentication ea = new ExternalAuthentication(is);
            authProvider = ea.authenticate(config.getUsername(), config.getPassword());
            access_token = authProvider.getTokenString();
        }
    }

    @Test
    @InSequence(0)
    public void shouldGetAllProducts() {
        given().header("Accept", "application/json").header("Authorization", "Bearer " + access_token)
                    .contentType(ContentType.JSON).port(getHttpPort()).when().get("/pnc-rest/rest/products").then().statusCode(200)
                .body(JsonMatcher.containsJsonAttribute("content[0].id", value -> productId = Integer.valueOf(value)));
    }

    @Test
    @InSequence(1)
    public void shouldGetSpecificProduct() {
        given().header("Accept", "application/json").header("Authorization", "Bearer " + access_token)
                    .contentType(ContentType.JSON).port(getHttpPort()).when()
                .get(String.format("/pnc-rest/rest/products/%d", productId)).then().statusCode(200)
                .body(JsonMatcher.containsJsonAttribute("content.id"));
    }

    @Test
    @InSequence(2)
    public void shouldGetAllProductsVersions() {
        given().header("Accept", "application/json").header("Authorization", "Bearer " + access_token)
                    .contentType(ContentType.JSON).port(getHttpPort()).when()
                .get("/pnc-rest/rest/product-versions/").then().statusCode(200)
                .body(JsonMatcher.containsJsonAttribute("content[0].id", value -> productVersionId = Integer.valueOf(value)));
    }

    @Test
    @InSequence(3)
    public void shouldSpecificProductsVersions() {
        given().header("Accept", "application/json").header("Authorization", "Bearer " + access_token)
                    .contentType(ContentType.JSON).port(getHttpPort()).when()
                .get(String.format("/pnc-rest/rest/product-versions/%d", productVersionId)).then().statusCode(200)
                .body(JsonMatcher.containsJsonAttribute("content.id"));
    }

    @Test
    @InSequence(4)
    public void shouldGetFirstProject() {
        given().header("Accept", "application/json").header("Authorization", "Bearer " + access_token)
                    .contentType(ContentType.JSON).port(getHttpPort()).when()
                .get("/pnc-rest/rest/projects/").then()
                .statusCode(200).body(JsonMatcher.containsJsonAttribute("content[0].id", value -> projectId = Integer.valueOf(value)));
    }

    @Test
    @InSequence(5)
    public void shouldGetSpecificProject() {
        given().header("Accept", "application/json").header("Authorization", "Bearer " + access_token)
                    .contentType(ContentType.JSON).port(getHttpPort()).when()
                .get(String.format("/pnc-rest/rest/projects/%d", projectId)).then().statusCode(200)
                .body(JsonMatcher.containsJsonAttribute("content.id"));
    }

    @Test
    @InSequence(5)
    public void shouldGetAllUsers() {
        final String toMatch = "{\"pageIndex\":0,\"pageSize\":50,\"totalPages\":1,\"content\":[{\"id\":1,\"email\":\"demo-user@pnc.com\",\"firstName\":\"Demo First Name\",\"lastName\":\"Demo Last Name\",\"username\":\"demo-user\"},{\"id\":2,\"email\":\"pnc-admin@pnc.com\",\"firstName\":\"pnc-admin\",\"lastName\":\"pnc-admin\",\"username\":\"pnc-admin\"}]}";

        given()
            .header("Accept", "application/json")
            .header("Authorization", "Bearer " + access_token)
            .contentType(ContentType.JSON).port(getHttpPort()).when().get("/pnc-rest/rest/users").then().assertThat()
            .body(equalTo(StringEscapeUtils.unescapeJava(toMatch)))
            .body(JsonMatcher.containsJsonAttribute("content[0].id", value -> userId = Integer.valueOf(value)));

    }

    @Test
    @InSequence(7)
    public void shouldGetSpecificUser() {
        given().header("Accept", "application/json").header("Authorization", "Bearer " + access_token)
                    .contentType(ContentType.JSON).port(getHttpPort()).when().get(String.format("/pnc-rest/rest/users/%d", userId))
                .then().statusCode(200).body(JsonMatcher.containsJsonAttribute("content.id"));
    }

    @Test
    @InSequence(8)
    public void shouldCreateNewUser() throws IOException {
        String rawJson = IoUtils.readFileOrResource("user", "user.json", getClass().getClassLoader());
        logger.info(rawJson);
        given().header("Accept", "application/json").header("Authorization", "Bearer " + access_token)
                .body(rawJson).contentType(ContentType.JSON).port(getHttpPort()).when().post("/pnc-rest/rest/users/").then()
                .statusCode(201);
    }

    @Test
    @InSequence(9)
    public void shouldCreateNewProduct() throws IOException {
        String rawJson = IoUtils.readFileOrResource("product", "product.json", getClass().getClassLoader());
        logger.info(rawJson);

        Response response = given().header("Accept", "application/json").header("Authorization", "Bearer " + access_token)
                .body(rawJson).contentType(ContentType.JSON).port(getHttpPort()).when()
                .post("/pnc-rest/rest/products/");
        Assertions.assertThat(response.statusCode()).isEqualTo(201);

        String location = response.getHeader("Location");
        logger.info("Found location in Response header: " + location);

        newProductId = Integer.valueOf(location.substring(location.lastIndexOf(PRODUCT_REST_ENDPOINT)
                + PRODUCT_REST_ENDPOINT.length()));

        logger.info("Created id of product: " + newProductId);
    }

    @Test
    @InSequence(10)
    public void shouldUpdateProduct() throws Exception {
        logger.info("### newProductId: " + newProductId);

        Response response = given().header("Accept", "application/json").header("Authorization", "Bearer " + access_token)
                    .contentType(ContentType.JSON).port(getHttpPort()).when()
                .get(String.format("/pnc-rest/rest/products/%d", newProductId));

        Assertions.assertThat(response.statusCode()).isEqualTo(200);

        ProductRest productRest = response.body().jsonPath().getObject("content", ProductRest.class);

        Assertions.assertThat(productRest.getId()).isEqualTo(newProductId);
        Assertions.assertThat(productRest.getName()).isEqualTo("JBoss Enterprise Application Platform 6");

        productRest.setName("JBoss Enterprise Application Platform 7");

        given().header("Accept", "application/json").header("Authorization", "Bearer " + access_token)
                    .body(JsonUtils.toJson(productRest)).contentType(ContentType.JSON).port(getHttpPort()).when()
                .put(String.format("/pnc-rest/rest/products/%d", newProductId)).then().statusCode(200);

        // Reading updated resource
        Response updateResponse = given().header("Accept", "application/json").header("Authorization", "Bearer " + access_token)
                    .contentType(ContentType.JSON).port(getHttpPort()).when()
                .get(String.format("/pnc-rest/rest/products/%d", newProductId));

        Assertions.assertThat(updateResponse.statusCode()).isEqualTo(200);
        Assertions.assertThat(updateResponse.body().jsonPath().getInt("content.id")).isEqualTo(newProductId);
        Assertions.assertThat(updateResponse.body().jsonPath().getString("content.name")).isEqualTo(
                "JBoss Enterprise Application Platform 7");

    }

    @Test
    @InSequence(11)
    public void shouldCreateNewProject() throws Exception {
        String rawJson = IoUtils.readFileOrResource("project", "project.json", getClass().getClassLoader());
        logger.info(rawJson);

        Response response = given()
                .header("Accept", "application/json")
                .header("Authorization", "Bearer " + access_token)
                .body(rawJson).contentType(ContentType.JSON).port(getHttpPort())
                .header("Content-Type", "application/json; charset=UTF-8").when().post(PROJECT_REST_ENDPOINT);
        Assertions.assertThat(response.statusCode()).isEqualTo(201);

        String location = response.getHeader("Location");
        logger.info("Found location in Response header: " + location);

        newProjectId = Integer.valueOf(location.substring(location.lastIndexOf(PROJECT_REST_ENDPOINT)
                + PROJECT_REST_ENDPOINT.length()));

        logger.info("Created id of project: " + newProjectId);
    }

    @Test
    @InSequence(12)
    public void shouldUpdateProject() throws Exception {
        logger.info("### newProjectId: " + newProjectId);

        Response response = given()
                .header("Accept", "application/json")
                .header("Authorization", "Bearer " + access_token)
                .contentType(ContentType.JSON).port(getHttpPort()).when()
                .get(String.format(PROJECT_REST_ENDPOINT_SPECIFIC, newProjectId));

        Assertions.assertThat(response.statusCode()).isEqualTo(200);

        ProjectRest projectRest = response.body().jsonPath().getObject("content", ProjectRest.class);

        Assertions.assertThat(projectRest.getId()).isEqualTo(newProjectId);
        Assertions.assertThat(projectRest.getName()).isEqualTo("New Project");

        projectRest.setName("New Awesome Project");

        given().header("Accept", "application/json").header("Authorization", "Bearer " + access_token)
                    .body(JsonUtils.toJson(projectRest)).contentType(ContentType.JSON).port(getHttpPort()).when()
                .put(String.format(PROJECT_REST_ENDPOINT_SPECIFIC, newProjectId)).then().statusCode(200);

        // Reading updated resource
        Response updateResponse = given().header("Accept", "application/json").header("Authorization", "Bearer " + access_token)
                    .contentType(ContentType.JSON).port(getHttpPort()).when()
                .get(String.format(PROJECT_REST_ENDPOINT_SPECIFIC, newProjectId));

        Assertions.assertThat(updateResponse.statusCode()).isEqualTo(200);
        Assertions.assertThat(updateResponse.body().jsonPath().getInt("content.id")).isEqualTo(newProjectId);
        Assertions.assertThat(updateResponse.body().jsonPath().getString("content.name")).isEqualTo("New Awesome Project");
    }
}