001/*-----------------------------------------------------------------------+
002 | com.teamscale.ui
003 |                                                                       |
004   $Id$            
005 |                                                                       |
006 | Copyright (c)  2009-2013 CQSE GmbH                                 |
007 +-----------------------------------------------------------------------*/
008package org.conqat.lib.commons.diff;
009
010import java.util.ArrayList;
011import java.util.List;
012import java.util.regex.Matcher;
013import java.util.regex.Pattern;
014
015import org.conqat.lib.commons.region.Region;
016import org.conqat.lib.commons.string.LineOffsetConverter;
017
018/**
019 * Class for calculating diff based on given regions.
020 */
021public class RegionDiffer extends DifferBase<String> {
022
023        /**
024         * Pattern used to parse region. See {@link #RegionDiffer(String)} for
025         * details.
026         */
027        private static final Pattern REGION_PATTERN = Pattern.compile("(\\d+)-(\\d+):(\\d+)-(\\d+)");
028
029        /** The region in the left element. */
030        private final Region leftRegion;
031
032        /** The region in the right element. */
033        private final Region rightRegion;
034
035        /**
036         * Constructor.
037         * 
038         * @param regionDescription
039         *            description of the regions as a string, formatted as
040         *            "leftStart-leftEnd:rightStart-rightEnd", where all are
041         *            one-based inclusive line numbers.
042         * @throws IllegalArgumentException
043         *             if the regionDescription does not follow the required format.
044         */
045        public RegionDiffer(String regionDescription) throws IllegalArgumentException {
046                Matcher matcher = REGION_PATTERN.matcher(regionDescription);
047                if (!matcher.matches()) {
048                        throw new IllegalArgumentException("Invalid region description: " + regionDescription);
049                }
050
051                try {
052                        leftRegion = new Region(Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)));
053                        rightRegion = new Region(Integer.parseInt(matcher.group(3)), Integer.parseInt(matcher.group(4)));
054                } catch (NumberFormatException e) {
055                        throw new IllegalArgumentException("Invalid region description: " + regionDescription);
056                }
057        }
058
059        /** {@inheritDoc} */
060        @Override
061        protected String getElementText(String element) {
062                return element;
063        }
064
065        /** {@inheritDoc} */
066        @Override
067        protected String getDiffName() {
068                return "region-based";
069        }
070
071        /**
072         * This method return only three chunks. One for the text before the given
073         * region, the given region, and the text after. The comparison text is
074         * chosen such that the diff will be in the given region.
075         */
076        @Override
077        protected List<TextChunk> getChunks(String elementText, boolean isLeft) {
078
079                Region region = leftRegion;
080                if (!isLeft) {
081                        region = rightRegion;
082                }
083
084                LineOffsetConverter converter = new LineOffsetConverter(elementText);
085
086                List<TextChunk> result = new ArrayList<TextChunk>();
087
088                // both pre-regions get the same comparison text, hence are mapped to
089                // each other
090                result.add(new TextChunk(0, converter.getOffset(region.getStart()), 1, region.getStart(), "pre"));
091
092                // both main regions get different comparison text, hence are detected
093                // as change
094                result.add(new TextChunk(converter.getOffset(region.getStart()), converter.getOffset(region.getEnd() + 1),
095                                region.getStart(), region.getEnd() + 1, "content" + isLeft));
096
097                // both post-regions get the same comparison text, hence are mapped to
098                // each other
099                result.add(new TextChunk(converter.getOffset(region.getEnd() + 1), elementText.length(), region.getEnd() + 1,
100                                converter.getLine(elementText.length()) + 1, "post"));
101                return result;
102        }
103}