12 changed files with 788 additions and 20 deletions
@ -0,0 +1,309 @@
|
||||
package net.sopod.soim.client.cmd.console; |
||||
|
||||
import com.google.common.base.*; |
||||
import com.google.common.collect.Iterables; |
||||
import net.sopod.soim.client.cmd.console.handlers.ConsoleBoxPasswordHandler; |
||||
|
||||
import java.awt.*; |
||||
import java.awt.image.BufferedImage; |
||||
import java.io.PrintStream; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.regex.Pattern; |
||||
|
||||
/** |
||||
* represents a console display box in the system console (or any PrintStream). |
||||
* used to display friendly technical information |
||||
* @author Willie Scholtz |
||||
*/ |
||||
public final class ConsoleBox { |
||||
|
||||
/** |
||||
* the character to use in box corners |
||||
*/ |
||||
private String boxChar = " + "; |
||||
|
||||
/** |
||||
* the character to use to pad strings with |
||||
*/ |
||||
private char padChar = ' '; |
||||
|
||||
/** |
||||
* the character used for the box's right and left sides |
||||
*/ |
||||
private String endChar = " | "; |
||||
|
||||
/** |
||||
* the character used for the box's top, title and bottom sides |
||||
*/ |
||||
private String borderChar = "-"; |
||||
|
||||
/** |
||||
* the character used for representing black in a color |
||||
*/ |
||||
private String blackChar = " "; |
||||
|
||||
/** |
||||
* the character used for representing white in a color |
||||
*/ |
||||
private String whiteChar = "#"; |
||||
|
||||
/** |
||||
* the character used for representing aliasing of fonts. |
||||
* actually other colors, but since our image is only black and white |
||||
* it will represent a shade, which in this case is aliasing |
||||
*/ |
||||
private String aliasChar = " "; |
||||
|
||||
/** |
||||
* the key value separator for names and values |
||||
*/ |
||||
private String keyValueSeparator = " : "; |
||||
|
||||
private final List<ConsoleBoxKeyHandler> handlers; |
||||
private final StringBuilder builder; |
||||
private boolean content; |
||||
private final int width; |
||||
|
||||
/** |
||||
* creates a new instance of a ConsoleBox |
||||
* @param boxWidth the width of the box (in character count) |
||||
* @param title the initial title of the box, leave blank for none |
||||
*/ |
||||
public ConsoleBox(int boxWidth, String title) { |
||||
this.width = boxWidth; |
||||
this.builder = new StringBuilder(); |
||||
this.handlers = new ArrayList<ConsoleBoxKeyHandler>(); |
||||
|
||||
// add default password handler
|
||||
this.handlers.add(new ConsoleBoxPasswordHandler()); |
||||
|
||||
if (!Strings.isNullOrEmpty(title)) { |
||||
this.title(title); |
||||
} |
||||
} |
||||
|
||||
/* |
||||
* creates a new instance of a ConsoleBox with no title |
||||
* @param boxWidth the width of the box (in character count) |
||||
*/ |
||||
public ConsoleBox(int boxWidth) { |
||||
this(boxWidth, null); |
||||
} |
||||
|
||||
/** |
||||
* pads a string from both sides |
||||
* @param string the string to pad |
||||
* @param pad the padding character |
||||
* @param length the length to pad |
||||
* @return the padded string |
||||
*/ |
||||
private String padBoth(String string, String pad, int length) { |
||||
int right = (length - string.length()) / 2 + string.length(); |
||||
String result = Strings.padEnd(string, right, pad.toCharArray()[0]); |
||||
return Strings.padStart(result, length, pad.toCharArray()[0]); |
||||
} |
||||
|
||||
/** |
||||
* configures the box characters, note that sensible defaults are already set. |
||||
* @param cornerChar the character to use in box corners |
||||
* @param padChar the character to use to pad strings with |
||||
* @param sideChar the character used for the box's right and left sides |
||||
* @param borderChar the character used for the box's top, title and bottom sides |
||||
* @return the current instance |
||||
*/ |
||||
public ConsoleBox setBoxCharacters(String cornerChar, char padChar, String sideChar, String borderChar) { |
||||
this.boxChar = Preconditions.checkNotNull(cornerChar); |
||||
this.padChar = Preconditions.checkNotNull(padChar); |
||||
this.endChar = Preconditions.checkNotNull(sideChar); |
||||
this.borderChar = Preconditions.checkNotNull(borderChar); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* configures the box's ASCII characters, note that sensible defaults are already set. |
||||
* @param blackChar the character used for representing black in a color |
||||
* @param whiteChar the character used for representing white in a color |
||||
* @param aliasChar the character used for representing aliasing of fonts. |
||||
* actually other colors, but since our image is only black and white |
||||
* it will represent a shade, which in this case is aliasing. |
||||
* @return the current instance |
||||
*/ |
||||
public ConsoleBox setAsciiCharacters(String blackChar, String whiteChar, String aliasChar) { |
||||
this.blackChar = Preconditions.checkNotNull(blackChar); |
||||
this.whiteChar = Preconditions.checkNotNull(whiteChar); |
||||
this.aliasChar = Preconditions.checkNotNull(aliasChar); |
||||
|
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* configures the box's key-value separator. |
||||
* @param keyValueSeparator the character used for representing black in a color |
||||
* actually other colors, but since our image is only black and white |
||||
* it will represent a shade, which in this case is aliasing. |
||||
* @return the current instance |
||||
*/ |
||||
public ConsoleBox setKeyValueSeparator(String keyValueSeparator) { |
||||
this.keyValueSeparator = Preconditions.checkNotNull(keyValueSeparator); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* adds a new key-value handler for this ConsoleBox |
||||
* @param handler the handler to use |
||||
* @return the current instance |
||||
*/ |
||||
public ConsoleBox handler(ConsoleBoxKeyHandler handler) { |
||||
this.handlers.add(handler); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* builds and writes this box to the specified output stream |
||||
* @param output |
||||
*/ |
||||
public void build(PrintStream output) { |
||||
this.title(""); |
||||
output.println(this.builder.toString()); |
||||
} |
||||
|
||||
/** |
||||
* adds a title section to the console box |
||||
* @param title the title to use |
||||
* @return the current box |
||||
*/ |
||||
public ConsoleBox title(String title) { |
||||
this.builder.append("\n").append(this.boxChar).append(padBoth(title, |
||||
this.borderChar, this.width)).append(this.boxChar); |
||||
|
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* adds an empty line section to the console box |
||||
* @return the current box |
||||
*/ |
||||
public ConsoleBox empty() { |
||||
this.builder.append("\n").append(this.boxChar).append( |
||||
padBoth("", " ", this.width)).append(this.boxChar); |
||||
|
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* generates and writes the specified text as an ASCII image into this box |
||||
* @param text the text to write as ASCII |
||||
* @param invert should the ASCII colors be inverted? |
||||
* @return the current box |
||||
*/ |
||||
public ConsoleBox ascii(String text, boolean invert) { |
||||
final BufferedImage image = new BufferedImage(this.width, |
||||
32, BufferedImage.TYPE_INT_RGB); |
||||
|
||||
final Graphics graphics = image.getGraphics(); |
||||
final Graphics2D g2d = (Graphics2D) graphics; |
||||
|
||||
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, |
||||
RenderingHints.VALUE_TEXT_ANTIALIAS_ON); |
||||
|
||||
Font textFont = new Font("Dialog", Font.BOLD, 22); |
||||
FontMetrics textMetrics = g2d.getFontMetrics(textFont); |
||||
g2d.setFont(textFont); |
||||
|
||||
int tX = (image.getWidth() / 2) - (textMetrics.stringWidth(text) / 2); |
||||
int tY = (image.getHeight() / 2) + (textMetrics.getHeight() / 2) - 5; |
||||
|
||||
g2d.drawString(text, tX, tY); |
||||
g2d.drawRenderedImage(image, null); |
||||
g2d.dispose(); |
||||
|
||||
final int iHeight = image.getHeight(); |
||||
final int iWidth = image.getWidth(); |
||||
|
||||
final String bChar = invert ? this.whiteChar : this.blackChar; |
||||
final String wChar = invert ? this.blackChar : this.whiteChar; |
||||
|
||||
for (int y = 0; y < iHeight; y++) { |
||||
final StringBuilder sb = new StringBuilder(); |
||||
for (int x = 0; x < iWidth; x++) { |
||||
final int rgbColor = image.getRGB(x, y); |
||||
sb.append(rgbColor == -16777216 ? bChar : rgbColor == -1 ? wChar : aliasChar); |
||||
} |
||||
|
||||
if (sb.toString().trim().isEmpty()) { |
||||
continue; |
||||
} |
||||
|
||||
this.builder.append("\n").append(this.endChar) |
||||
.append(sb).append(this.endChar); |
||||
} |
||||
|
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* adds a informational line to the console box, |
||||
* automatically splitting large values |
||||
* @param key the name of the value to display |
||||
* @param value the value of this line |
||||
* @return the current box |
||||
*/ |
||||
public ConsoleBox line(String key, String value) { |
||||
key = Strings.isNullOrEmpty(key) ? "null" : key; |
||||
value = Strings.isNullOrEmpty(value) ? "" : value; |
||||
|
||||
// get the key length
|
||||
final int kL = key.length(); |
||||
final int kSl = this.keyValueSeparator.length(); |
||||
|
||||
// calculate remaining box space for the value
|
||||
final int ths = (this.width - kL - kSl); |
||||
Preconditions.checkState(ths > -1, "key[" + key + "] is to long " |
||||
+ "for box with a " + width + " width!"); |
||||
|
||||
// \n | the_key_length_in_spaces
|
||||
final String joinOn = ("\n" + this.endChar + Strings.padEnd("", |
||||
kL + kSl, this.padChar)); |
||||
|
||||
// get key handlers and modify if neccessary
|
||||
for (ConsoleBoxKeyHandler handler : this.handlers) { |
||||
if (handler.shouldHandle(key)) { |
||||
value = handler.handleValue(key, value); |
||||
// don't break, possibilitty of multiple handlers
|
||||
} |
||||
} |
||||
|
||||
// if a key handler returns null, a key should be skipped
|
||||
if (value != null) { |
||||
// split the string on either length or new lines
|
||||
Iterable<String> splitted = Splitter.on(Pattern |
||||
.compile("(?<=\\G.{" + ths + "})|\\n")).split(value); |
||||
|
||||
// add the value + end characters (multiple lines)
|
||||
String formatted = Joiner.on(joinOn).join( |
||||
Iterables.transform(splitted, new Function<String, String>() { |
||||
@Override |
||||
public String apply(String input) { |
||||
return Strings.padEnd(input, ths, ' ') + endChar; |
||||
} |
||||
})); |
||||
|
||||
// write completed line to builder
|
||||
this.builder.append("\n").append(this.endChar).append(key) |
||||
.append(this.keyValueSeparator).append(formatted); |
||||
|
||||
this.content = true; |
||||
} |
||||
|
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* @return true if {@link #line(java.lang.String, java.lang.String)} |
||||
* has been called at least once |
||||
*/ |
||||
public boolean hasContent() { |
||||
return content; |
||||
} |
||||
} |
||||
@ -0,0 +1,24 @@
|
||||
package net.sopod.soim.client.cmd.console; |
||||
|
||||
/** |
||||
* interface for defining handlers for certain console box keys |
||||
* @author Willie Scholtz |
||||
*/ |
||||
public interface ConsoleBoxKeyHandler { |
||||
|
||||
/** |
||||
* determines if the specified key should be handled by this key handler |
||||
* @param key the key to check |
||||
* @return true if this key should be handled by this key handler |
||||
*/ |
||||
public boolean shouldHandle(final String key); |
||||
|
||||
/** |
||||
* if this handler is set to handle the specified key it, will execute this method to handle it's value |
||||
* @param key the key being handled |
||||
* @param value the original value to handle, note that console box automatically handles new lines and long lines |
||||
* @return the formatted value for ConsoleBox, or null if the whole line should be skipped |
||||
*/ |
||||
public String handleValue(final String key, final String value); |
||||
|
||||
} |
||||
@ -0,0 +1,41 @@
|
||||
package net.sopod.soim.client.cmd.console.handlers; |
||||
|
||||
import net.sopod.soim.client.cmd.console.ConsoleBoxKeyHandler; |
||||
|
||||
import java.util.regex.Pattern; |
||||
|
||||
/** |
||||
* ensures no passwords are present in the ConsoleBox |
||||
* @author Willie Scholtz |
||||
*/ |
||||
public class ConsoleBoxPasswordHandler implements ConsoleBoxKeyHandler { |
||||
/** |
||||
* pattern defining which attributes should be blurred |
||||
*/ |
||||
private static final Pattern IGNORE_ATT_PATT = Pattern |
||||
.compile("pass(word)?", Pattern.CASE_INSENSITIVE); |
||||
|
||||
/** |
||||
* replacement value for sensitive data |
||||
*/ |
||||
private static final String SAFE_REPLACEMENT = "*****"; |
||||
|
||||
/** |
||||
* determines if a certain parameter is safe to transmit over the wire |
||||
* @param key the name of the parameter that will be sent |
||||
* @return true if the parameter may be sent |
||||
*/ |
||||
private static boolean safeToTransmit(final String key) { |
||||
return !IGNORE_ATT_PATT.matcher(key).find(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean shouldHandle(String key) { |
||||
return !safeToTransmit(key); |
||||
} |
||||
|
||||
@Override |
||||
public String handleValue(String key, String value) { |
||||
return SAFE_REPLACEMENT; |
||||
} |
||||
} |
||||
@ -0,0 +1,72 @@
|
||||
|
||||
package net.sopod.soim.client.cmd.console.handlers; |
||||
|
||||
import com.google.common.base.Function; |
||||
import com.google.common.base.Joiner; |
||||
import com.google.common.base.Splitter; |
||||
import com.google.common.collect.Iterables; |
||||
import net.sopod.soim.client.cmd.console.ConsoleBoxKeyHandler; |
||||
|
||||
/** |
||||
* ensures that JVM system properties are handled correctly by consolebox |
||||
* @author Willie Scholtz |
||||
*/ |
||||
public class JvmKeyHandler implements ConsoleBoxKeyHandler { |
||||
|
||||
private static final boolean WINDOWS = System.getProperty("os.name").indexOf("Windows") > -1; |
||||
|
||||
/** |
||||
* keys that should be converted to new lines |
||||
*/ |
||||
private static final String[] CLASSPATHS = new String[] { |
||||
"java.library.path", "java.class.path", "sun.boot.class.path" |
||||
}; |
||||
|
||||
/** |
||||
* keys that should be skipped |
||||
*/ |
||||
private static final String[] SKIPS = new String[] { |
||||
"line.seperator" |
||||
}; |
||||
|
||||
@Override |
||||
public boolean shouldHandle(String key) { |
||||
for (String string : CLASSPATHS) { |
||||
if (string.equals(key)) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
for (String string : SKIPS) { |
||||
if (string.equals(key)) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public String handleValue(String key, String value) { |
||||
for (String string : CLASSPATHS) { |
||||
if (string.equals(key)) { |
||||
value = Joiner.on("\n").join(Iterables.transform(Splitter.on(WINDOWS ? ";" : ":").split(value), |
||||
new Function<String, String>() { |
||||
int item = 0; |
||||
@Override |
||||
public String apply(String input) { |
||||
return "[" + ++item + "] " + input; |
||||
} |
||||
})); |
||||
} |
||||
} |
||||
|
||||
for (String string : SKIPS) { |
||||
if (string.equals(key)) { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
return value; |
||||
} |
||||
} |
||||
@ -0,0 +1,241 @@
|
||||
package net.sopod.soim.client.prettyconsole; |
||||
|
||||
import com.google.common.base.Charsets; |
||||
import junit.framework.Test; |
||||
import junit.framework.TestCase; |
||||
import junit.framework.TestSuite; |
||||
import net.sopod.soim.client.cmd.console.ConsoleBox; |
||||
import net.sopod.soim.client.cmd.console.ConsoleBoxKeyHandler; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.PrintStream; |
||||
import java.io.UnsupportedEncodingException; |
||||
import java.util.logging.Level; |
||||
import java.util.logging.Logger; |
||||
|
||||
/** |
||||
* Unit test for testing console box |
||||
*/ |
||||
public class ConsoleBoxTest extends TestCase { |
||||
|
||||
/** |
||||
* Create the test case |
||||
* @param testName name of the test case |
||||
*/ |
||||
public ConsoleBoxTest(String testName) { |
||||
super(testName); |
||||
} |
||||
|
||||
/** |
||||
* @return the suite of tests being tested |
||||
*/ |
||||
public static Test suite() { |
||||
return new TestSuite(ConsoleBoxTest.class); |
||||
} |
||||
|
||||
private ConsoleBox basicBox(int width) { |
||||
ConsoleBox box = new ConsoleBox(width); |
||||
box.title("TEST"); |
||||
return box; |
||||
} |
||||
|
||||
private String getBox(ConsoleBox box) { |
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
||||
box.build(new PrintStream(baos)); |
||||
|
||||
String actual = null; |
||||
try { |
||||
actual = baos.toString(Charsets.UTF_8.name()); |
||||
} catch (UnsupportedEncodingException ex) { |
||||
Logger.getLogger(ConsoleBoxTest.class.getName()).log( |
||||
Level.SEVERE, "failed to get UTF-8", ex); |
||||
} |
||||
|
||||
System.out.println(actual); |
||||
return actual; |
||||
} |
||||
|
||||
/** |
||||
* @throws java.io.UnsupportedEncodingException |
||||
*/ |
||||
public void testCreation() throws UnsupportedEncodingException { |
||||
ConsoleBox box = this.basicBox(10); |
||||
box.line("a", "1"); |
||||
|
||||
final String actual = this.getBox(box); |
||||
final String expected = "\n" + |
||||
" + ---TEST--- + \n" + |
||||
" | a : 1 | \n" + |
||||
" + ---------- + \r\n"; |
||||
|
||||
assertEquals("basic box build", expected, actual); |
||||
} |
||||
|
||||
public void testOverflow() throws UnsupportedEncodingException { |
||||
ConsoleBox box = this.basicBox(10); |
||||
box.line("a", "123456789987654321"); |
||||
|
||||
final String actual = this.getBox(box); |
||||
final String expected = "\n" + |
||||
" + ---TEST--- + \n" + |
||||
" | a : 123456 | \n" + |
||||
" | 789987 | \n" + |
||||
" | 654321 | \n" + |
||||
" | | \n" + |
||||
" + ---------- + \r\n"; |
||||
|
||||
assertEquals("overflow in content", expected, actual); |
||||
} |
||||
|
||||
public void testNewLineOverflow() throws UnsupportedEncodingException { |
||||
ConsoleBox box = this.basicBox(10); |
||||
box.line("a", "1\n2\n3\n4\n5\n6\n7\n8\n9\n12345679abcdefg"); |
||||
|
||||
final String actual = this.getBox(box); |
||||
final String expected = "\n" + |
||||
" + ---TEST--- + \n" + |
||||
" | a : 1 | \n" + |
||||
" | 2 | \n" + |
||||
" | 3 | \n" + |
||||
" | 4 | \n" + |
||||
" | 5 | \n" + |
||||
" | 6 | \n" + |
||||
" | 7 | \n" + |
||||
" | 8 | \n" + |
||||
" | 9 | \n" + |
||||
" | 123456 | \n" + |
||||
" | 79abcd | \n" + |
||||
" | efg | \n" + |
||||
" + ---------- + \r\n"; |
||||
|
||||
assertEquals("overflow in content with new lines", expected, actual); |
||||
} |
||||
|
||||
public void testDefaultPasswordHandling() throws UnsupportedEncodingException { |
||||
ConsoleBox box = this.basicBox(20); |
||||
box.line("password", "supercoolpassword"); |
||||
box.line("pass", "mysecret"); |
||||
|
||||
final String actual = this.getBox(box); |
||||
final String expected = "\n" + |
||||
" + --------TEST-------- + \n" + |
||||
" | password : ***** | \n" + |
||||
" | pass : ***** | \n" + |
||||
" + -------------------- + \r\n"; |
||||
|
||||
|
||||
assertEquals("default password handling", expected, actual); |
||||
} |
||||
|
||||
public void testLinesAndTitles() throws UnsupportedEncodingException { |
||||
ConsoleBox box = this.basicBox(25); |
||||
box.line("keyOne", "valueOne"); |
||||
box.line("keyTwo", "valueTwo"); |
||||
box.title("TITLE"); |
||||
box.line("keyThree", "valueThree"); |
||||
box.line("keyFour", "valueFour"); |
||||
|
||||
final String actual = this.getBox(box); |
||||
final String expected = "\n" + |
||||
" + -----------TEST---------- + \n" + |
||||
" | keyOne : valueOne | \n" + |
||||
" | keyTwo : valueTwo | \n" + |
||||
" + ----------TITLE---------- + \n" + |
||||
" | keyThree : valueThree | \n" + |
||||
" | keyFour : valueFour | \n" + |
||||
" + ------------------------- + \r\n"; |
||||
|
||||
assertEquals("with lines and titles", expected, actual); |
||||
} |
||||
|
||||
public void testAscii() throws UnsupportedEncodingException { |
||||
ConsoleBox box = this.basicBox(60); |
||||
box.line("keyOne", "valueOne"); |
||||
box.line("keyTwo", "valueTwo"); |
||||
box.title("TITLE"); |
||||
box.line("keyThree", "valueThree"); |
||||
box.line("keyFour", "valueFour"); |
||||
box.title(""); |
||||
box.ascii("ASCII", false); |
||||
|
||||
final String actual = this.getBox(box); |
||||
final String expected = "\n" + |
||||
" + ----------------------------TEST---------------------------- + \n" + |
||||
" | keyOne : valueOne | \n" + |
||||
" | keyTwo : valueTwo | \n" + |
||||
" + ----------------------------TITLE--------------------------- + \n" + |
||||
" | keyThree : valueThree | \n" + |
||||
" | keyFour : valueFour | \n" + |
||||
" + ------------------------------------------------------------ + \n" + |
||||
" | ### ### ### | \n" + |
||||
" | #### ######## ####### ### ### | \n" + |
||||
" | ##### ########## ########## ### ### | \n" + |
||||
" | ## ## ## ## ## ## ### ### | \n" + |
||||
" | ## # ## ## ## ### ### | \n" + |
||||
" | ## ## ## ## ### ### | \n" + |
||||
" | ## ## ### ## ### ### | \n" + |
||||
" | ## # ###### ## ### ### | \n" + |
||||
" | ## ## ### ## ### ### | \n" + |
||||
" | ######### ## ## ### ### | \n" + |
||||
" | ########### ## ## ## ### ### | \n" + |
||||
" | ########### ## ## ## ### ### | \n" + |
||||
" | ## ## ### ## ## ## ### ### | \n" + |
||||
" | ## ## ########## ######### ### ### | \n" + |
||||
" | ## ## ######## ####### ### ### | \n" + |
||||
" | ## ## # ### ### | \n" + |
||||
" + ------------------------------------------------------------ + \r\n"; |
||||
|
||||
assertEquals("with ascii text", expected, actual); |
||||
} |
||||
|
||||
public void testCustomHandler() throws UnsupportedEncodingException { |
||||
ConsoleBox box = this.basicBox(25); |
||||
box.handler(new ConsoleBoxKeyHandler() { |
||||
public boolean shouldHandle(String key) { |
||||
return "a".equals(key) || "c".equals(key); |
||||
} |
||||
|
||||
public String handleValue(String key, String value) { |
||||
if ("a".equals(key)) { |
||||
return "a_replace"; |
||||
} |
||||
|
||||
if ("c".equals(key)) { |
||||
return "c_replace"; |
||||
} |
||||
|
||||
return value; |
||||
} |
||||
}); |
||||
box.line("a", "valueOne"); |
||||
box.line("b", "valueTwo"); |
||||
box.line("c", "valueThree"); |
||||
box.line("d", "valueFour"); |
||||
|
||||
final String actual = this.getBox(box); |
||||
final String expected = "\n" + |
||||
" + -----------TEST---------- + \n" + |
||||
" | a : a_replace | \n" + |
||||
" | b : valueTwo | \n" + |
||||
" | c : c_replace | \n" + |
||||
" | d : valueFour | \n" + |
||||
" + ------------------------- + \r\n"; |
||||
|
||||
assertEquals("with a custom handler", expected, actual); |
||||
} |
||||
|
||||
public void testEmpty() throws UnsupportedEncodingException { |
||||
ConsoleBox box = this.basicBox(5); |
||||
box.empty(); |
||||
box.empty(); |
||||
|
||||
final String actual = this.getBox(box); |
||||
final String expected = "\n" + |
||||
" + -TEST + \n" + |
||||
" + + \n" + |
||||
" + + \n" + |
||||
" + ----- + \r\n"; |
||||
|
||||
assertEquals("with empty content", expected, actual); |
||||
} |
||||
} |
||||
@ -0,0 +1,26 @@
|
||||
package net.sopod.soim.router.api.route; |
||||
|
||||
import org.apache.dubbo.common.URL; |
||||
import org.apache.dubbo.rpc.Invocation; |
||||
import org.apache.dubbo.rpc.Invoker; |
||||
import org.apache.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance; |
||||
|
||||
import java.util.List; |
||||
|
||||
/** |
||||
* ConsistentHashRoute |
||||
* 一致性 hash 服务路由 |
||||
* |
||||
* |
||||
* @author tmy |
||||
* @date 2022-04-29 14:41 |
||||
*/ |
||||
public class ConsistentHashRoute extends AbstractLoadBalance { |
||||
|
||||
@Override |
||||
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) { |
||||
Invoker<T> invoker = invokers.get(0); |
||||
return null; |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue