blob: 854988fc6981f15911b7e06d310e6fd6296e4292 [file] [log] [blame]
/*
* 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.
*/
package org.netbeans.modules.editor.bracesmatching;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.swing.JEditorPane;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.SimpleAttributeSet;
import org.netbeans.api.editor.mimelookup.MimePath;
import org.netbeans.junit.MockServices;
import org.netbeans.junit.NbTestCase;
import org.netbeans.spi.editor.bracesmatching.BracesMatcher;
import org.netbeans.spi.editor.bracesmatching.BracesMatcherFactory;
import org.netbeans.spi.editor.bracesmatching.MatcherContext;
import org.netbeans.spi.editor.highlighting.support.OffsetsBag;
import org.netbeans.api.editor.mimelookup.test.MockMimeLookup;
import org.netbeans.spi.editor.highlighting.HighlightsSequence;
/**
*
* @author vita
*/
public class MasterMatcherTest extends NbTestCase {
public MasterMatcherTest(String name) {
super(name);
}
@Override
protected void setUp() throws Exception {
super.setUp();
}
public void testContext() throws Exception {
MockServices.setServices(MockMimeLookup.class);
MockMimeLookup.setInstances(MimePath.EMPTY, new TestMatcher());
AttributeSet EAS = SimpleAttributeSet.EMPTY;
JEditorPane c = new JEditorPane();
Document d = c.getDocument();
OffsetsBag bag = new OffsetsBag(d);
d.insertString(0, "text text { text } text", null);
c.putClientProperty(MasterMatcher.PROP_MAX_BACKWARD_LOOKAHEAD, 3);
c.putClientProperty(MasterMatcher.PROP_MAX_FORWARD_LOOKAHEAD, 4);
c.putClientProperty(MasterMatcher.PROP_SEARCH_DIRECTION, MasterMatcher.D_FORWARD);
c.putClientProperty(MasterMatcher.PROP_CARET_BIAS, MasterMatcher.B_FORWARD);
TestMatcher.origin = new int [] { 7, 7 };
MasterMatcher.get(c).highlight(d, 7, bag, EAS, EAS, EAS, EAS);
TestMatcher.waitUntilCreated(1000);
{
TestMatcher tm = TestMatcher.lastMatcher;
assertNotNull("No matcher created", tm);
assertNotNull("No context passed to the matcher", tm.context);
assertSame("Wrong document", d, tm.context.getDocument());
assertEquals("Wrong caret offset", 7, tm.context.getSearchOffset());
assertFalse("Wrong search direction", tm.context.isSearchingBackward());
assertEquals("Wrong lookahead", 4, tm.context.getSearchLookahead());
}
TestMatcher.lastMatcher = null;
TestMatcher.origin = new int [] { 11, 11 };
c.putClientProperty(MasterMatcher.PROP_SEARCH_DIRECTION, MasterMatcher.D_BACKWARD);
c.putClientProperty(MasterMatcher.PROP_CARET_BIAS, MasterMatcher.B_BACKWARD);
MasterMatcher.get(c).highlight(d, 11, bag, EAS, EAS, EAS, EAS);
TestMatcher.waitUntilCreated(1000);
{
TestMatcher tm = TestMatcher.lastMatcher;
assertNotNull("No matcher created", tm);
assertNotNull("No context passed to the matcher", tm.context);
assertSame("Wrong document", d, tm.context.getDocument());
assertEquals("Wrong caret offset", 11, tm.context.getSearchOffset());
assertTrue("Wrong search direction", tm.context.isSearchingBackward());
assertEquals("Wrong lookahead", 3, tm.context.getSearchLookahead());
}
}
public void testAreas() throws Exception {
MockServices.setServices(MockMimeLookup.class);
MockMimeLookup.setInstances(MimePath.EMPTY, new TestMatcher());
AttributeSet EAS = SimpleAttributeSet.EMPTY;
JEditorPane c = new JEditorPane();
Document d = c.getDocument();
OffsetsBag bag = new OffsetsBag(d);
d.insertString(0, "text text { text } text", null);
c.putClientProperty(MasterMatcher.PROP_MAX_BACKWARD_LOOKAHEAD, 256);
c.putClientProperty(MasterMatcher.PROP_MAX_FORWARD_LOOKAHEAD, 256);
TestMatcher.origin = new int [] { 2, 3 };
TestMatcher.matches = new int [] { 10, 11 };
MasterMatcher.get(c).highlight(d, 7, bag, EAS, EAS, EAS, EAS);
TestMatcher.waitUntilCreated(1000);
{
TestMatcher tm = TestMatcher.lastMatcher;
assertNotNull("No matcher created", tm);
HighlightsSequence hs = bag.getHighlights(0, Integer.MAX_VALUE);
assertTrue("Wrong number of highlighted areas", hs.moveNext());
assertEquals("Wrong origin startOfset", 2, hs.getStartOffset());
assertEquals("Wrong origin endOfset", 3, hs.getEndOffset());
assertTrue("Wrong number of highlighted areas", hs.moveNext());
assertEquals("Wrong match startOfset", 10, hs.getStartOffset());
assertEquals("Wrong match endOfset", 11, hs.getEndOffset());
}
}
public void testBlockingByForLoop() throws Exception {
MockServices.setServices(MockMimeLookup.class);
MockMimeLookup.setInstances(MimePath.EMPTY, new BlockingMatcher());
AttributeSet EAS = SimpleAttributeSet.EMPTY;
JEditorPane c = new JEditorPane();
Document d = c.getDocument();
OffsetsBag bag = new OffsetsBag(d);
d.insertString(0, "text text { text } text", null);
c.putClientProperty(MasterMatcher.PROP_MAX_BACKWARD_LOOKAHEAD, 256);
c.putClientProperty(MasterMatcher.PROP_MAX_FORWARD_LOOKAHEAD, 256);
BlockingMatcher.origin = new int [] { 2, 3 };
BlockingMatcher.matches = new int [] { 10, 11 };
{
BlockingMatcher.blockInFindOrigin = true;
MasterMatcher.get(c).highlight(d, 7, bag, EAS, EAS, EAS, EAS);
Thread.sleep(300);
BlockingMatcher first = BlockingMatcher.lastMatcher;
assertNotNull("No first matcher", first);
assertTrue("Should be blocking", first.blocking);
MasterMatcher.get(c).highlight(d, 8, bag, EAS, EAS, EAS, EAS);
Thread.sleep(2000);
BlockingMatcher second = BlockingMatcher.lastMatcher;
assertNotNull("No second matcher", second);
assertFalse("First blocking matcher was not interrupted", first.blocking);
assertFalse("There should be no highlights", bag.getHighlights(0, Integer.MAX_VALUE).moveNext());
second.breakOutFromTheLoop = true; // stop the matchers loop
}
Thread.sleep(300);
bag.clear();
{
BlockingMatcher.blockInFindOrigin = false;
MasterMatcher.get(c).highlight(d, 7, bag, EAS, EAS, EAS, EAS);
Thread.sleep(300);
BlockingMatcher first = BlockingMatcher.lastMatcher;
assertNotNull("No first matcher", first);
assertTrue("First matcher should be blocking", first.blocking);
MasterMatcher.get(c).highlight(d, 8, bag, EAS, EAS, EAS, EAS);
Thread.sleep(2000);
BlockingMatcher second = BlockingMatcher.lastMatcher;
assertNotNull("No second matcher", second);
assertTrue("Second matcher should be blocking", second.blocking);
assertFalse("First blocking matcher was not interrupted", first.blocking);
assertFalse("There should be no highlights", bag.getHighlights(0, Integer.MAX_VALUE).moveNext());
second.breakOutFromTheLoop = true; // stop the matchers loop
Thread.sleep(300);
bag.clear();
}
}
public void testThreadResultsGCed() throws Exception {
testContext();
testAreas();
testBlockingByForLoop();
Thread.sleep(1000);
assertEquals("There should be no threads in the threadResults map", 0, MasterMatcher.THREAD_RESULTS.size());
}
private static final class TestMatcher implements BracesMatcher, BracesMatcherFactory {
public static TestMatcher lastMatcher = null;
public static int [] origin = null;
public static int [] matches = null;
static CountDownLatch latch;
public final MatcherContext context;
public TestMatcher() {
this(null);
}
private TestMatcher(MatcherContext context) {
this.context = context;
}
public int[] findOrigin() throws InterruptedException, BadLocationException {
return origin;
}
public int[] findMatches() throws InterruptedException, BadLocationException {
return matches;
}
public BracesMatcher createMatcher(MatcherContext context) {
lastMatcher = new TestMatcher(context);
return lastMatcher;
}
public static void waitUntilCreated(long millis) throws InterruptedException {
latch = new CountDownLatch(1);
latch.await(millis, TimeUnit.MILLISECONDS);
}
}
private static final class BlockingMatcher implements BracesMatcher, BracesMatcherFactory {
private static volatile int counter;
public volatile static BlockingMatcher lastMatcher = null;
public volatile static boolean blockInFindOrigin = false;
public volatile static int [] origin = null;
public volatile static int [] matches = null;
public final MatcherContext context;
public volatile boolean breakOutFromTheLoop = false;
public volatile boolean blocking;
private int matcherId = counter++;
public BlockingMatcher() {
this(null);
}
private BlockingMatcher(MatcherContext context) {
this.context = context;
}
public int[] findOrigin() throws InterruptedException, BadLocationException {
System.out.println(matcherId + ": findOrigin");
if (blockInFindOrigin) {
block();
}
System.out.println(matcherId + ": findOrigin return");
return origin;
}
public int[] findMatches() throws InterruptedException, BadLocationException {
System.out.println(matcherId + ": findMatches");
if (!blockInFindOrigin) {
block();
}
System.out.println(matcherId + ": findMatches return");
return matches;
}
private void block() throws InterruptedException {
blocking = true;
System.out.println(matcherId + ": blocking");
try {
//System.out.println("!!! Blocking: " + this + ", offset = " + context.getCaretOffset());
for( ; !breakOutFromTheLoop; ) {
if (MatcherContext.isTaskCanceled()) {
System.out.println(matcherId + ": cancelled");
return;
}
}
} finally {
//System.out.println("!!! Not Blocking: " + this + ", offset = " + context.getCaretOffset());
System.out.println(matcherId + ": block terminated");
blocking = false;
}
}
public BracesMatcher createMatcher(MatcherContext context) {
lastMatcher = new BlockingMatcher(context);
return lastMatcher;
}
}
}