Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,8 @@
</rules>
<excludes>
<exclude>**/AppCommandLineRunner*</exclude>
<exclude>**/ExamplesCommandLineRunner*</exclude>
<exclude>**/ProducerConsumerExampleRunner*</exclude>
<exclude>**/Consumer*</exclude>
<exclude>**/JavaPatternsAndConstructsApplication*</exclude>
<exclude>**/MainWindow*</exclude>
<exclude>**/HyperlinkMouseListener*</exclude>
<exclude>**/ObservableAbstract*</exclude>
</excludes>
</configuration>
</execution>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,34 @@

public class HyperlinkMouseListener implements MouseListener {

private Logger log = LoggerFactory.getLogger(HyperlinkMouseListener.class);
/** Opens a link in the user's browser. Replaceable in tests, where there is no desktop. */
@FunctionalInterface
interface LinkOpener {
void open(URI uri) throws IOException;
}

ApplicationProperties props;
private static final Logger log = LoggerFactory.getLogger(HyperlinkMouseListener.class);

private final ApplicationProperties props;
private final LinkOpener linkOpener;
private String lastText;

public HyperlinkMouseListener(ApplicationProperties props) {
this(props, uri -> Desktop.getDesktop().browse(uri));
}

HyperlinkMouseListener(ApplicationProperties props, LinkOpener linkOpener) {
this.props = props;
this.linkOpener = linkOpener;
}

@Override
public void mouseClicked(MouseEvent e) {
log.debug("Hyperlink text: " + lastText);
try {
Desktop.getDesktop().browse(new URI(lastText));
} catch (IOException | URISyntaxException e1) {
log.error("Error opening link", e);
linkOpener.open(new URI(lastText));
} catch (IOException | URISyntaxException | RuntimeException ex) {
log.error("Error opening link", ex);
}
}

Expand All @@ -53,11 +64,12 @@ public void mouseExited(MouseEvent e) {
}

@Override
public void mousePressed(MouseEvent arg0) {
public void mousePressed(MouseEvent e) {
// Nothing to do: the link opens on click.
}

@Override
public void mouseReleased(MouseEvent arg0) {
public void mouseReleased(MouseEvent e) {
// Nothing to do: the link opens on click.
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,27 @@ public class Consumer implements Runnable {

private static final Logger log = LoggerFactory.getLogger(Consumer.class);

private BlockingQueue<Integer> queue;

private int myId;
private final BlockingQueue<Integer> queue;
private final int myId;

public Consumer(BlockingQueue<Integer> blockingQueue, int myId) {
this.queue = blockingQueue;
this.myId = myId;
}

/**
* Consumes integers until the thread is interrupted, which is how the
* example runner asks the consumers to stop once the producer is done.
*/
@Override
public void run() {
while (true) {
while (!Thread.currentThread().isInterrupted()) {
try {
var consumed = queue.take();
log.trace(String.format(" %d: Consumed [%2d]", myId, consumed));
} catch (InterruptedException finish) {
Thread.currentThread().interrupt();
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,26 @@ public class ProducerConsumerExampleRunner implements ExampleRunnerInterface {
private static final Logger log = LoggerFactory.getLogger(ProducerConsumerExampleRunner.class);

@Override
public void runExample() throws InterruptedException {
public void runExample() throws Exception {
log.trace("Executing Producer/Consumer implementation:");

BlockingQueue<Integer> blockingQueue = new LinkedBlockingDeque<Integer>(3);
BlockingQueue<Integer> blockingQueue = new LinkedBlockingDeque<>(3);
ExecutorService executor = Executors.newFixedThreadPool(3);

Consumer consumer1 = new Consumer(blockingQueue, 1);
Consumer consumer2 = new Consumer(blockingQueue, 2);
Producer producer = new Producer(blockingQueue);

executor.execute(consumer1);
executor.execute(consumer2);
executor.execute(producer);

executor.awaitTermination(1, TimeUnit.SECONDS);
try {
executor.execute(new Consumer(blockingQueue, 1));
executor.execute(new Consumer(blockingQueue, 2));
// Wait until the producer has put every item in the queue...
executor.submit(new Producer(blockingQueue)).get();
// ...and until the consumers have taken all of them.
while (!blockingQueue.isEmpty()) {
Thread.sleep(10);
}
} finally {
// Consumers block forever waiting for more work; interrupt them so
// the pool threads do not leak after the example finishes.
executor.shutdownNow();
if (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
log.warn("Consumers did not stop in time");
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.penapereira.example.constructs.app;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.concurrent.atomic.AtomicInteger;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.GenericApplicationContext;

import com.penapereira.example.constructs.app.properties.ApplicationProperties;
import com.penapereira.example.constructs.app.properties.Messages;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;

class ExamplesCommandLineRunnerTests {

private final Logger runnerLog = (Logger) LoggerFactory.getLogger(ExamplesCommandLineRunner.class);
private final Level originalLevel = runnerLog.getLevel();
private final ListAppender<ILoggingEvent> logged = new ListAppender<>();

private final AtomicInteger executions = new AtomicInteger();
private GenericApplicationContext ctx;
private ApplicationProperties props;
private Messages msg;

@BeforeEach
void setUp() {
logged.start();
runnerLog.addAppender(logged);

ctx = new GenericApplicationContext();
ctx.registerBean("fakeExampleRunner", ExampleRunnerInterface.class, () -> executions::incrementAndGet);
ctx.refresh();

props = new ApplicationProperties();
msg = new Messages();
msg.setExamplesFound("Examples");
msg.setSeparator("---");
msg.setEnableTraceToSeeExamplesDetails("enable trace");
msg.setEnableDebugToSeeExamplesList("enable debug");
}

@AfterEach
void tearDown() {
runnerLog.detachAppender(logged);
runnerLog.setLevel(originalLevel);
ctx.close();
}

private ExamplesCommandLineRunner runner() {
return new ExamplesCommandLineRunner(ctx, msg, props);
}

private boolean logged(String text) {
return logged.list.stream().anyMatch(e -> e.getFormattedMessage().contains(text));
}

@Test
void doesNothingWhenDisabled() throws Exception {
props.setEnableCommandLineRunner(false);

runner().run();

assertEquals(0, executions.get());
assertTrue(logged.list.isEmpty());
}

@Test
void listsAndExecutesExamplesWhenTraceIsEnabled() throws Exception {
props.setEnableCommandLineRunner(true);
runnerLog.setLevel(Level.TRACE);

runner().run();

assertEquals(1, executions.get());
assertTrue(logged("Examples (ExampleRunnerInterface):"));
assertTrue(logged(" 1 : fake"));
assertTrue(logged("---"));
assertFalse(logged("enable trace"));
assertFalse(logged("enable debug"));
}

@Test
void tellsHowToSeeDetailsWhenLogLevelIsTooHigh() throws Exception {
props.setEnableCommandLineRunner(true);
runnerLog.setLevel(Level.INFO);

runner().run();

assertEquals(1, executions.get());
assertTrue(logged("enable debug"));
assertTrue(logged("enable trace"));
assertFalse(logged(" 1 : fake"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

import java.awt.Color;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.net.URI;
import java.util.concurrent.atomic.AtomicReference;

import javax.swing.JLabel;

Expand All @@ -30,4 +33,52 @@ void hyperlinkChangesColorAndText() {
assertEquals("http://example.com", label.getText());
assertEquals(Color.decode(props.getLinkColor()), label.getForeground());
}

private static ApplicationProperties props() {
ApplicationProperties props = new ApplicationProperties();
props.setLinkColor("#000000");
props.setLinkColorHover("#ffffff");
return props;
}

private static MouseEvent eventOn(JLabel label, int id) {
return new MouseEvent(label, id, 0, 0, 0, 0, 1, false);
}

@Test
void clickOpensTheLinkShownWhenTheMouseEntered() {
AtomicReference<URI> opened = new AtomicReference<>();
HyperlinkMouseListener listener = new HyperlinkMouseListener(props(), opened::set);
JLabel label = new JLabel("http://example.com");

listener.mouseEntered(eventOn(label, MouseEvent.MOUSE_ENTERED));
listener.mousePressed(eventOn(label, MouseEvent.MOUSE_PRESSED));
listener.mouseReleased(eventOn(label, MouseEvent.MOUSE_RELEASED));
listener.mouseClicked(eventOn(label, MouseEvent.MOUSE_CLICKED));

assertEquals(URI.create("http://example.com"), opened.get());
}

@Test
void clickOnMalformedLinkIsLoggedNotThrown() {
AtomicReference<URI> opened = new AtomicReference<>();
HyperlinkMouseListener listener = new HyperlinkMouseListener(props(), opened::set);
JLabel label = new JLabel("http://exa mple.com");

listener.mouseEntered(eventOn(label, MouseEvent.MOUSE_ENTERED));
assertDoesNotThrow(() -> listener.mouseClicked(eventOn(label, MouseEvent.MOUSE_CLICKED)));

assertNull(opened.get());
}

@Test
void failureToOpenTheBrowserIsLoggedNotThrown() {
HyperlinkMouseListener listener = new HyperlinkMouseListener(props(), uri -> {
throw new IOException("no browser");
});
JLabel label = new JLabel("http://example.com");

listener.mouseEntered(eventOn(label, MouseEvent.MOUSE_ENTERED));
assertDoesNotThrow(() -> listener.mouseClicked(eventOn(label, MouseEvent.MOUSE_CLICKED)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,16 @@ void listenerCanBeRemoved() {
subject.doSomethingWith(1); // should not throw
assertEquals(0, subject.getSupport().getPropertyChangeListeners().length);
}

@Test
void allListenersCanBeRemovedAtOnce() {
Observable subject = new Observable();
subject.addPropertyChangeListener(new Observer());
subject.addPropertyChangeListener(event -> {});
assertEquals(2, subject.getSupport().getPropertyChangeListeners().length);

subject.removeAllListeners();

assertEquals(0, subject.getSupport().getPropertyChangeListeners().length);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.penapereira.example.constructs.producerconsumer;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;

import org.junit.jupiter.api.Test;

class ConsumerTests {

private static void waitUntilBlockedOnQueue(Thread t) {
while (t.getState() != Thread.State.WAITING) {
Thread.onSpinWait();
}
}

@Test
void consumesEverythingAndStopsWhenInterrupted() throws InterruptedException {
BlockingQueue<Integer> queue = new LinkedBlockingDeque<>();
queue.put(1);
queue.put(2);
Thread t = new Thread(new Consumer(queue, 1));

t.start();
waitUntilBlockedOnQueue(t);
assertTrue(queue.isEmpty());

t.interrupt();
t.join(5_000);
assertFalse(t.isAlive());
}

@Test
void stopsImmediatelyWhenInterruptedBeforeStarting() throws InterruptedException {
BlockingQueue<Integer> queue = new LinkedBlockingDeque<>();
queue.put(1);
Thread t = new Thread(() -> {
Thread.currentThread().interrupt();
new Consumer(queue, 2).run();
});

t.start();
t.join(5_000);

assertFalse(t.isAlive());
assertFalse(queue.isEmpty());
}
}
Loading