/******************************************************************************* * Copyright (c) 2006, 2015 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Christian Plesner Hansen (plesner@quenta.org) - initial API and implementation *******************************************************************************/ package org.eclipse.jface.text.tests; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.List; import org.junit.Test; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentExtension3; import org.eclipse.jface.text.IDocumentPartitioner; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextStore; import org.eclipse.jface.text.rules.FastPartitioner; import org.eclipse.jface.text.rules.IPredicateRule; import org.eclipse.jface.text.rules.RuleBasedPartitionScanner; import org.eclipse.jface.text.rules.SingleLineRule; import org.eclipse.jface.text.rules.Token; import org.eclipse.jface.text.source.DefaultCharacterPairMatcher; import org.eclipse.jface.text.source.ICharacterPairMatcher; import org.eclipse.jface.text.source.ICharacterPairMatcherExtension; /** * Generic test of simple character pair matchers * * @since 3.3 */ public abstract class AbstractPairMatcherTest { private final boolean fCaretEitherSideOfBracket; public AbstractPairMatcherTest(boolean caretEitherSideOfBracket) { fCaretEitherSideOfBracket= caretEitherSideOfBracket; } /** * Constructs a new character pair matcher. * * @param chars the characters to match * @return the character pair matcher */ protected ICharacterPairMatcher createMatcher(final String chars) { return new DefaultCharacterPairMatcher(chars.toCharArray(), getDocumentPartitioning(), fCaretEitherSideOfBracket); } /** * Returns the partitioning treated by the matcher. * * @return the partition */ protected String getDocumentPartitioning() { return IDocumentExtension3.DEFAULT_PARTITIONING; } /* --- T e s t s --- */ /** Tests that the test case reader works */ @Test public void testTestCaseReader() { performReaderTest("%( )#", 0, 3, "( )"); performReaderTest("#%", 0, 0, ""); } /** * Very simple checks. * * @throws BadLocationException test failure */ @Test public void testSimpleMatchSameMatcher() throws BadLocationException { final ICharacterPairMatcher matcher= createMatcher("()[]{}"); performMatch(matcher, "#( )%"); performMatch(matcher, "#[ ]%"); performMatch(matcher, "#{ }%"); performMatch(matcher, "(% )#"); performMatch(matcher, "[% ]#"); performMatch(matcher, "{% }#"); performMatch(matcher, "#( %)%"); performMatch(matcher, "#[ %]%"); performMatch(matcher, "#{ %}%"); performMatch(matcher, "%(% )#"); performMatch(matcher, "%[% ]#"); performMatch(matcher, "%{% }#"); performMatch(matcher, "#( % )#"); performMatch(matcher, "#[ % ]#"); performMatch(matcher, "#{ % }#"); performMatch(matcher, "#( % % )#"); performMatch(matcher, "#[ % % ]#"); performMatch(matcher, "#{ % % }#"); matcher.dispose(); } /** * Very simple checks. * * @throws BadLocationException test failure */ @Test public void testSimpleMatchDifferentMatchers() throws BadLocationException { performMatch("()[]{}", "#( )%"); performMatch("()[]{}", "#[ ]%"); performMatch("()[]{}", "#{ }%"); performMatch("()[]{}", "(% )#"); performMatch("()[]{}", "[% ]#"); performMatch("()[]{}", "{% }#"); performMatch("()[]{}", "#( % )#"); performMatch("()[]{}", "#[ % ]#"); performMatch("()[]{}", "#{ % }#"); } /** * Close matches. * * @throws BadLocationException test failure */ @Test public void testCloseMatches() throws BadLocationException { final ICharacterPairMatcher matcher= createMatcher("()[]{}"); performMatch(matcher, "(%)#"); performMatch(matcher, "#(())%"); performMatch(matcher, "(%())#"); performMatch(matcher, "((%)#)"); performMatch(matcher, "%(%)#"); performMatch(matcher, "#(()%)%"); performMatch(matcher, "%(%())#"); performMatch(matcher, "(%(%)#)"); performMatch(matcher, "#(%)#"); performMatch(matcher, "#(%())#"); performMatch(matcher, "#(% %)#"); performMatch(matcher, "#(% %())#"); matcher.dispose(); } /** * Checks of simple situations where no matches should be found. * * @throws BadLocationException test failure */ @Test public void testIncompleteMatch() throws BadLocationException { final ICharacterPairMatcher matcher= createMatcher("()[]{}"); performMatch(matcher, "(% "); performMatch(matcher, "( % )"); performMatch(matcher, "%"); matcher.dispose(); } /** * Test that it doesn't match across different partitions. * * @throws BadLocationException test failure */ @Test public void testPartitioned() throws BadLocationException { final ICharacterPairMatcher matcher= createMatcher("()[]{}"); performMatch(matcher, "(% |a a| )#"); performMatch(matcher, "#( |a a| )%"); performMatch(matcher, "|b #( )% b|"); performMatch(matcher, "( |b )% b|"); performMatch(matcher, "(% |b ) b|"); performMatch(matcher, "|a ( a| )%"); performMatch(matcher, "|a (% a| )"); performMatch(matcher, "|c #( c| ) ( |c )% c|"); performMatch(matcher, "|c (% c| ) ( |c )# c|"); performMatch(matcher, "(% |a ) a| |b ) b| |c ) c| )#"); performMatch(matcher, "#( % |a a| )#"); performMatch(matcher, "|b #( % )# b|"); performMatch(matcher, "|c #( % c| ) ( |c )# c|"); performMatch(matcher, "|c #( c| ) ( |c % )# c|"); performMatch(matcher, "#( % |a ) a| |b ) b| |c ) c| )#"); performMatch(matcher, "#( |a % a| )#"); performMatch(matcher, "( |a #( a| ( |a % a| ) |a )# a| )"); performMatch(matcher, "#( % % |a a| )#"); performMatch(matcher, "|b #( % % )# b|"); performMatch(matcher, "|c #( % % c| ) ( |c )# c|"); performMatch(matcher, "|c #( c| ) ( |c % % )# c|"); performMatch(matcher, "#( % % |a ) a| |b ) b| |c ) c| )#"); performMatch(matcher, " #( |c ( c| % % |c ) c| )#"); matcher.dispose(); } /** * Test that it works properly next to partition boundaries. * * @throws BadLocationException test failure */ @Test public void testTightPartitioned() throws BadLocationException { final ICharacterPairMatcher matcher= createMatcher("()[]{}"); performMatch(matcher, "(|b)%b|"); performMatch(matcher, "(%|b)b|"); performMatch(matcher, "|a(a|)%"); performMatch(matcher, "|a(%a|)"); performMatch(matcher, "|c#(c|)(|c)%c|"); performMatch(matcher, "|c(%c|)(|c)#c|"); performMatch(matcher, "(%|a)a||b)b||c)c|)#"); performMatch(matcher, "|c#(c|)(|%c)#c|"); performMatch(matcher, "|c#(c%|)(|c)#c|"); performMatch(matcher, "#(%|a)a||b)b||c)c|)#"); matcher.dispose(); } /** Test that nesting works properly */ @Test public void testNesting() { final ICharacterPairMatcher matcher= createMatcher("()[]{}"); performMatch(matcher, " ( #( ( ( ) ) ( ) )% ) "); performMatch(matcher, " ( (% ( ( ) ) ( ) )# ) "); performMatch(matcher, " ( #( { ( ) } [ ] )% ) "); performMatch(matcher, " ( (% { ( ) } [ ] )# ) "); performMatch(matcher, " ( ( #{ ( ) }% [ ] ) ) "); performMatch(matcher, " ( ( {% ( ) }# [ ] ) ) "); performMatch(matcher, "a(b#(c(d(e)f)g(h)i)%j)k"); performMatch(matcher, "a(b(%c(d(e)f)g(h)i)#j)k"); performMatch(matcher, "a(b#(c{d(e)f}g[h]i)%j)k"); performMatch(matcher, "a(b(%c{d(e)f}g[h]i)#j)k"); performMatch(matcher, "a(b(c#{d(e)f}%g[h]i)j)k"); performMatch(matcher, "a(b(c{%d(e)f}#g[h]i)j)k"); performMatch(matcher, " ( #( ( ( ) ) ( ) % )# ) "); performMatch(matcher, " ( #( % ( ( ) ) ( ) )# ) "); performMatch(matcher, " ( #( { ( ) } [ ] % )# ) "); performMatch(matcher, " ( #( % { ( ) } [ ] )# ) "); performMatch(matcher, " ( ( #{ ( ) % }# [ ] ) ) "); performMatch(matcher, " ( ( #{ % ( ) }# [ ] ) ) "); performMatch(matcher, "a(b#(c(d(e)f)g(h)i%)#j)k"); performMatch(matcher, "a(b#(%c(d(e)f)g(h)i)#j)k"); performMatch(matcher, "a(b#(c{d(e)f}g[h]i%)#j)k"); performMatch(matcher, "a(b#(%c{d(e)f}g[h]i)#j)k"); performMatch(matcher, "a(b(c#{d(e)f%}#g[h]i)j)k"); performMatch(matcher, "a(b(c#{%d(e)f}#g[h]i)j)k"); matcher.dispose(); } /** * Test a few boundary conditions. * * * @throws BadLocationException test failure */ @Test public void testBoundaries() throws BadLocationException { final ICharacterPairMatcher matcher= createMatcher("()[]{}"); final StringDocument doc= new StringDocument("abcdefghijkl"); assertNull(matcher.match(null, 0)); assertNull(matcher.match(doc, -1)); assertNull(matcher.match(doc, doc.getLength() + 1)); matcher.dispose(); } @Test public void testBug156426() { final ICharacterPairMatcher matcher= createMatcher("()[]{}<>"); performMatch(matcher, " #( a < b )% "); performMatch(matcher, " (% a < b )# "); performMatch(matcher, " #( a > b )% "); performMatch(matcher, " (% a > b )# "); matcher.dispose(); } @Test public void testBug377417() { final ICharacterPairMatcher matcher= createMatcher("()[]{}"); performMatch(matcher, "#( % )%#"); performMatch(matcher, "#[ % ]%#"); performMatch(matcher, "#{ % }%#"); matcher.dispose(); } /* --- U t i l i t i e s --- */ /** * Checks that the test case reader reads the test case as specified. * * @param testString the string to test * @param expectedPos the expected position * @param expectedMatch the expected match * @param expectedString the expected string */ protected void performReaderTest(String testString, int expectedPos, int expectedMatch, String expectedString) { TestCase t0= createTestCase(testString); assertEquals(expectedPos, t0.fPos1); assertEquals(expectedMatch, t0.fMatch2); assertEquals(expectedString, t0.fString); } /** * Checks that the given matcher matches the input as specified. * * @param matcher the matcher * @param testCase the test string */ protected void performMatch(final ICharacterPairMatcher matcher, final String testCase) { final TestCase test= createTestCase(testCase); matcher.clear(); final IRegion region; if (test.isSelectionTestCase()) { assertTrue((matcher instanceof ICharacterPairMatcherExtension)); ICharacterPairMatcherExtension matcherExtension= (ICharacterPairMatcherExtension)matcher; if (test.isEnclosingTestCase()) { region= matcherExtension.findEnclosingPeerCharacters(test.getDocument(), test.fPos1, test.fPos2 - test.fPos1); } else { region= matcherExtension.match(test.getDocument(), test.fPos1, test.fPos2 - test.fPos1); } } else { if (test.isEnclosingTestCase()) { assertTrue((matcher instanceof ICharacterPairMatcherExtension)); ICharacterPairMatcherExtension matcherExtension= (ICharacterPairMatcherExtension)matcher; region= matcherExtension.findEnclosingPeerCharacters(test.getDocument(), test.fPos1, test.fPos2 - test.fPos1); } else { region= matcher.match(test.getDocument(), test.fPos1); } } if (test.fMatch2 == -1) { // if no match point has been specified there should be no match if (region != null) System.out.println(region.getOffset()); assertNull(region); } else { assertNotNull(region); final boolean isBackward= test.isEnclosingTestCase() ? false : test.fPos1 > test.fMatch2; assertEquals(isBackward, matcher.getAnchor() == ICharacterPairMatcher.RIGHT); assertEquals(test.getLength(), region.getLength()); assertEquals(test.getOffset(), region.getOffset()); } } private void performMatch(final String delims, final String testCase) { final ICharacterPairMatcher matcher= createMatcher(delims); performMatch(matcher, testCase); matcher.dispose(); } /** * Creates a text case from a string. In the given string a '%' represents the position of the * cursor and a '#' represents the position of the expected matching character. * * @param str the string for which to create the test case * @return the created test case */ public TestCase createTestCase(String str) { int pos1= str.indexOf("%"); assertFalse(pos1 == -1); int pos2= str.lastIndexOf("%"); boolean selectionTest= pos1 != pos2; int match1= str.indexOf("#"); int match2= str.lastIndexOf("#"); boolean enclosingTest= match1 != match2; // account for the length of marker characters if (selectionTest) { if (!enclosingTest) { assertTrue(pos2 - pos1 == 2); if (match1 != -1 && match1 < pos1) { pos1-= 1; pos2-= 2; } if (pos1 < match1) { pos2-= 1; match1-= 2; } } else { pos1-= 1; pos2-= 2; match2-= 3; } } else { if (!enclosingTest) { if (match1 != -1 && match1 < pos1) pos1-= 1; if (pos1 < match1) match1-= 1; } else { pos1-= 1; match2-= 2; } pos2= pos1; } final String stripped= str.replaceAll("%", "").replaceAll("#", ""); if (enclosingTest) { return new TestCase(stripped, pos1, pos2, match1, match2); } else { if (selectionTest) { return new TestCase(stripped, pos1, pos2, pos1 < match1 ? pos1 : pos2, match1); } else { if (match1 == -1) return new TestCase(stripped, pos1, pos2, pos1, match1); match2= match1; match1= pos1; if (!selectionTest && !enclosingTest) { String chars= "()[]{}<>"; if (fCaretEitherSideOfBracket && pos1 < stripped.length()) { char ch= stripped.charAt(pos1); char prevCh= pos1 - 1 >= 0 ? stripped.charAt(pos1 - 1) : Character.MIN_VALUE; if (chars.indexOf(ch) % 2 == 1 && chars.indexOf(prevCh) % 2 != 0) { match1++; } } if (pos1 - 1 >= 0) { char ch= stripped.charAt(pos1 - 1); if (chars.indexOf(ch) % 2 == 0) { match1--; } } } return new TestCase(stripped, pos1, pos2, match1, match2); } } } private class TestCase { public final String fString; public final int fPos1, fPos2, fMatch1, fMatch2; public TestCase(String string, int pos1, int pos2, int match1, int match2) { fString= string; fPos1= pos1; fPos2= pos2; fMatch1= match1; fMatch2= match2; } public IDocument getDocument() { return new StringDocument(fString); } public int getLength() { return Math.abs(fMatch1 - fMatch2); } public int getOffset() { if (fMatch1 > fMatch2) return fMatch2; return fMatch1; } public boolean isEnclosingTestCase() { return fPos1 != fMatch1 && fPos2 != fMatch1; } public boolean isSelectionTestCase() { return fPos1 != fPos2; } } private class StringDocument extends Document { public StringDocument(String str) { this.setTextStore(new StringTextStore(str)); this.set(str); final IDocumentPartitioner part= createPartitioner(); this.setDocumentPartitioner(getDocumentPartitioning(), part); part.connect(this); } } private static class StringTextStore implements ITextStore { private String fString; public StringTextStore(final String str) { fString= str; } @Override public char get(int offset) { return fString.charAt(offset); } @Override public String get(int offset, int length) { return fString.substring(offset, offset + length); } @Override public int getLength() { return fString.length(); } @Override public void replace(int offset, int length, String text) { throw new UnsupportedOperationException(); } @Override public void set(String text) { fString= text; } } private static String DEFAULT_PARTITION= IDocument.DEFAULT_CONTENT_TYPE; private static IDocumentPartitioner createPartitioner() { final RuleBasedPartitionScanner scan= new RuleBasedPartitionScanner(); final List<SingleLineRule> rules= new ArrayList<>(); rules.add(new SingleLineRule("|a", "a|", new Token("a"))); rules.add(new SingleLineRule("|b", "b|", new Token("b"))); rules.add(new SingleLineRule("|c", "c|", new Token("c"))); scan.setPredicateRules(rules.toArray(new IPredicateRule[rules.size()])); scan.setDefaultReturnToken(new Token(DEFAULT_PARTITION)); return new FastPartitioner(scan, new String[] { DEFAULT_PARTITION, "a", "b", "c" }); } }