package org.freeplane.plugin.script;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import org.freeplane.core.resources.ResourceController;
import org.freeplane.features.map.MapModel;
import org.freeplane.features.map.NodeModel;
import org.freeplane.features.mode.Controller;
import org.freeplane.main.application.FreeplaneStarter;
import org.freeplane.plugin.script.proxy.ConversionException;
import org.freeplane.plugin.script.proxy.Convertible;
import org.freeplane.plugin.script.proxy.ConvertibleNoteText;
import org.freeplane.plugin.script.proxy.ConvertibleText;
import org.junit.BeforeClass;
import org.junit.Test;
public class ConvertibleTest {
private static final String FREEPLANE_URI = "http://www.freeplane.org";
/** provides an easy mean to create a Convertible with a null text. */
public static final class TestConvertible extends Convertible {
public TestConvertible(NodeModel nodeModel, String text) {
super(FormulaUtils.evalIfScript(nodeModel, null, text));
}
}
@BeforeClass
public static void initStatics() {
// we have to start Freeplane to create a Controller for script execution could we avoid that?
System.setProperty("org.freeplane.nosplash", "true");
final Controller controller = new FreeplaneStarter().createController();
new FreeplaneStarter().createModeControllers(controller);
ResourceController.getResourceController().setProperty(ScriptingPermissions.RESOURCES_EXECUTE_SCRIPTS_WITHOUT_ASKING, true);
}
@Test
public void testGetNum() throws ConversionException, ExecuteScriptException {
assertEquals(null, convertible(null).getNum());
assertEquals(new Long(20), convertible("20").getNum());
assertEquals(new Double(0.00001), ((Double) convertible("0.00001").getNum()), 1e-9);
assertEquals(new Long(31), convertible("0x1f").getNum());
assertEquals(new Long(-31), convertible("-0x1F").getNum());
assertEquals(new Long(31), convertible("#1F").getNum());
assertEquals(new Long(-31), convertible("-#1f").getNum());
assertEquals(new Long(23), convertible("027").getNum());
assertEquals(new Long(-23), convertible("-027").getNum());
assertThrowsNumberConversionException("");
assertThrowsNumberConversionException("xyz");
assertThrowsNumberConversionException(" 12");
}
private void assertThrowsNumberConversionException(String string) {
boolean caughtException = false;
final String notANumber = string;
try {
convertible(notANumber).getNum();
}
catch (ConversionException e) {
caughtException = true;
}
assertTrue("should have been detected as not-a-number: \"" + notANumber + '"', caughtException);
}
@Test
public void testGetNum0() throws ConversionException, ExecuteScriptException {
assertEquals(new Long(0), convertible(null).getNum0());
assertEquals(new Long(20), convertible("20").getNum0());
assertEquals(new Double(0.00001), ((Double) convertible("0.00001").getNum0()), 1e-9);
assertEquals(new Long(31), convertible("0x1f").getNum0());
assertEquals(new Long(-31), convertible("-0x1F").getNum0());
assertEquals(new Long(31), convertible("#1F").getNum0());
assertEquals(new Long(-31), convertible("-#1f").getNum0());
assertEquals(new Long(23), convertible("027").getNum0());
assertEquals(new Long(-23), convertible("-027").getNum0());
// now the test the fallback on conversion errors
assertEquals(new Long(0), convertible("xyz").getNum0());
assertEquals(new Long(0), convertible("2010-08-16 22:31:55.123+0530").getNum0());
}
private Convertible convertible(String text) {
final MapModel mapModel = Controller.getCurrentModeController().getMapController().newModel();
NodeModel nodeModel = new NodeModel(mapModel);
return new TestConvertible(nodeModel, text);
}
@Test
public void testGetDate() throws ConversionException, ExecuteScriptException {
testOneDatePattern(null, null);
testOneDatePattern("2010-08-16 22:31:55.123+0530", "2010-08-16 22:31:55.123+0530");
// note that SimpleDateFormat uses local time zone for conversion - use DateFormatUtils instead!
final String Z = getLocalTimeZoneOffsetString();
testOneDatePattern("2010-08-16 22:31:55.123" + Z, "2010-08-16 22:31:55.123");
testOneDatePattern("2010-08-16 22:31:55", "2010-08-16 22:31:55.000");
testOneDatePattern("2010-08-16 22:31:55", "2010-08-16 22:31:55");
testOneDatePattern("2010-08-16 22:31:00", "2010-08-16 22:31");
testOneDatePattern("2010-08-16 00:00:00", "2010-08-16");
//
testOneDatePattern("2010-08-16 22:31:55", "2010-08-16T22:31:55.000");
testOneDatePattern("2010-08-16 22:31:55", "2010-08-16T22:31:55");
testOneDatePattern("2010-08-16 22:31:00", "2010-08-16T22:31");
testOneDatePattern("2010-08-16 00:00:00", "2010-08-16");
//
testOneDatePattern("2010-08-16 22:31:55", "20100816223155.000");
testOneDatePattern("2010-08-16 22:31:55", "20100816223155");
testOneDatePattern("2010-08-16 22:31:00", "201008162231");
testOneDatePattern("2010-08-16 00:00:00", "20100816");
//
testOneDatePattern("2010-08-16 22:31:55", "20100816T223155.000");
testOneDatePattern("2010-08-16 22:31:55", "20100816T223155");
testOneDatePattern("2010-08-16 22:31:00", "20100816T2231");
testOneDatePattern("2010-08-16 00:00:00", "20100816");
// parse errors
// parse error
boolean caughtException = false;
final String notADate = "2010-08-";
try {
convertible(notADate).getDate();
}
catch (ConversionException e) {
caughtException = true;
}
assertTrue("not a date: " + notADate, caughtException);
}
private void testOneDatePattern(String expected, String testInput) throws ConversionException,
ExecuteScriptException {
assertEquals("expected: " + Convertible.toString(date(expected)) + "!=" + Convertible.toString(convertible(testInput).getDate()),
date(expected), convertible(testInput).getDate());
assertEquals(calendar(expected), convertible(testInput).getCalendar());
}
private Calendar calendar(String expected) throws ConversionException {
if (expected == null)
return null;
GregorianCalendar result = new GregorianCalendar(0, 0, 0);
result.setTime(date(expected));
return result;
}
private Date date(String string) throws ConversionException {
try {
if (string == null)
return null;
else if (string.matches(".*\\.\\d{3}[-+]\\d{4}"))
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ").parse(string);
else if (string.matches(".*:\\d{2}\\.\\d{3}[-+]\\d{4}"))
return new SimpleDateFormat("yyyy-MM-dd HH:mmZ").parse(string);
else if (string.matches(".* \\d{2}:\\d{2}:\\d{2}\\.\\d{3}"))
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").parse(string);
else if (string.matches(".* \\d{2}:\\d{2}:\\d{2}"))
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(string);
else
return new SimpleDateFormat("yyyy-MM-dd HH:mm").parse(string);
}
catch (ParseException e) {
throw new ConversionException(e);
}
}
private String getLocalTimeZoneOffsetString() {
final TimeZone timeZone = TimeZone.getDefault();
int offsetMinutes = (timeZone.getRawOffset() + timeZone.getDSTSavings()) / 60000;
return String.format("%+03d%02d", offsetMinutes / 60, offsetMinutes % 60);
}
@Test
public void testGetUrl() throws ConversionException {
assertEquals(null, convertible(null).getUri());
assertEquals(uri(FREEPLANE_URI), convertible(FREEPLANE_URI).getUri());
// scheme: is mandatory
assertThrowsUriConversionException("");
assertThrowsUriConversionException("scheme:");
assertEquals(uri("scheme:bla"), convertible("scheme:bla").getUri());
}
private void assertThrowsUriConversionException(String string) {
boolean caughtException = false;
final String notAnUrl = string;
try {
convertible(notAnUrl).getUri();
}
catch (ConversionException e) {
caughtException = true;
}
assertTrue("should have been detected as not-an-url: \"" + notAnUrl + '"', caughtException);
}
private URI uri(String string) {
try {
return new URI(string);
}
catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
@Test
public void testGetObject() throws ConversionException, ExecuteScriptException {
assertEquals(null, convertible(null).getObject());
// Convertibles contain the evaluated text
assertEquals(new Long(3), convertible("= 1 + 2").getObject());
assertEquals(new Long(1234567890), convertible("1234567890").getObject());
assertEquals(new Double(0.00001), convertible("0.00001").getObject());
assertEquals(date("2010-12-24 23:59:59"), convertible("2010-12-24 23:59:59").getObject());
assertEquals("a text", convertible("a text").getObject());
}
@Test
public void testGetPlain() throws ConversionException, ExecuteScriptException {
assertEquals(null, convertible(null).getPlain());
assertEquals("12", convertible("12").getPlain());
assertEquals("text", convertible("text").getPlain());
// text must start with <html> to get transformed
assertEquals("text", convertible("<html>text</html>").getPlain());
assertEquals("text", convertible("<html>text").getPlain());
assertEquals("text", convertible("<html><p>text<p></html>").getPlain());
assertEquals("<p>text</p>", convertible("<p>text</p>").getPlain());
assertEquals("<xyz>text</xyz>", convertible("<xyz>text</xyz>").getPlain());
assertEquals("<?xml version=\"1.0\"?><p>text</p>", convertible("<?xml version=\"1.0\"?><p>text</p>").getPlain());
}
@Test
public void testIsNum() throws ConversionException, ExecuteScriptException {
assertFalse(convertible(null).isNum());
assertTrue("Convertibles contain evaluated text", convertible("= 1 + 2").isNum());
// broken:
// assertTrue(convertible("0x1f").isNum());
// assertTrue(convertible("-0x1F").isNum());
assertFalse("this is a known NumberUtils bug - use 0x encoding instead", convertible("#1F").isNum());
assertFalse("this is a known NumberUtils bug - use 0x encoding instead", convertible("-#1f").isNum());
assertTrue(convertible("027").isNum());
assertTrue(convertible("-027").isNum());
assertTrue(convertible("1234567890").isNum());
assertTrue(convertible("0.00001").isNum());
assertFalse(convertible("2010-12-24 23:59:59").isNum());
assertFalse(convertible("a text").isNum());
assertFalse(convertible("").isNum());
}
@Test
public void testIsDate() throws ConversionException, ExecuteScriptException {
assertFalse(convertible(null).isDate());
assertFalse(convertible("").isDate());
assertFalse(convertible("12").isDate());
assertFalse(convertible("1.2").isDate());
assertFalse(convertible("text").isDate());
assertTrue(convertible("2010-08-16 22:31:55.123+0530").isDate());
assertTrue(convertible("2010-08-16 22:31:55.123").isDate());
assertTrue(convertible("2010-08-16 22:31:55").isDate());
assertTrue(convertible("2010-08-16 22:31").isDate());
assertTrue(convertible("2010-08-16").isDate());
assertTrue(convertible("2010-08-16T22:31:55.123+0530").isDate());
assertTrue(convertible("2010-08-16T22:31:55.123").isDate());
assertTrue(convertible("2010-08-16T22:31:55").isDate());
assertTrue(convertible("2010-08-16T22:31").isDate());
assertTrue(convertible("2010-08-16").isDate());
assertTrue(convertible("20100816223155.123+0530").isDate());
assertTrue(convertible("20100816223155.123").isDate());
assertTrue(convertible("20100816223155").isDate());
assertTrue(convertible("201008162231").isDate());
assertTrue(convertible("20100816").isDate());
assertTrue(convertible("20100816T223155.123+0530").isDate());
assertTrue(convertible("20100816T223155.123").isDate());
assertTrue(convertible("20100816T223155").isDate());
assertTrue(convertible("20100816T2231").isDate());
assertTrue(convertible("20100816").isDate());
}
@Test
public void testGetStringAndToString() throws ConversionException, ExecuteScriptException {
assertGetStringAndToStringEqualsInputText(null);
assertGetStringAndToStringEqualsInputText("2010-08-16 22:31:55");
assertGetStringAndToStringEqualsInputText("12");
assertGetStringAndToStringEqualsInputText("1.2");
assertGetStringAndToStringEqualsInputText("text");
assertGetStringAndToStringEqualsInputText("");
}
private void assertGetStringAndToStringEqualsInputText(String text) {
assertEquals(text, convertible(text).toString());
assertEquals(text, convertible(text).getString());
assertEquals(text, convertible(text).getText());
}
@Test
public void testGetProperty() throws ConversionException, ExecuteScriptException {
assertEquals(null, convertible(null).getProperty("string"));
assertEquals(null, convertible(null).getProperty("num"));
assertEquals("12", convertible("12").getProperty("string"));
assertEquals("12", convertible("12").getProperty("text"));
assertEquals("12", convertible("12").getProperty("plain"));
assertEquals(new Long(12), convertible("12").getProperty("num"));
// "bytes" is a virtual property of class String (byte[] getBytes())
assertEquals("12", new String((byte[]) convertible("12").getProperty("bytes")));
assertEquals(date("2010-08-16 22:31"), convertible("2010-08-16T22:31").getProperty("date"));
assertEquals(calendar("2010-08-16 22:31"), convertible("2010-08-16T22:31").getProperty("calendar"));
assertEquals(uri(FREEPLANE_URI), convertible(FREEPLANE_URI).getProperty("uri"));
}
@Test
public void testNullObject() {
assertEquals(convertible(null), null);
assertEquals(convertible(null), convertible(null));
}
@Test
public void testInvokeMethod() throws ConversionException, ExecuteScriptException {
assertEquals(null, convertible(null).invokeMethod("getString", null));
assertEquals("12", convertible("12").invokeMethod("getString", null));
assertEquals("12", convertible("12").invokeMethod("getText", null));
assertEquals("12", convertible("12").invokeMethod("getPlain", null));
assertEquals(new Long(12), convertible("12").invokeMethod("getNum", null));
assertEquals(Boolean.TRUE, convertible("12").invokeMethod("startsWith", new Object[] { "1" }));
}
@Test
public void testConvertibleNodeTextMayNotBeNull() throws ConversionException {
boolean exceptionThrown = false;
try {
final NodeModel nodeModel = new NodeModel(null);
nodeModel.setText(null);
// not reached: new ConvertibleNodeText(nodeModel);
}
catch (NullPointerException e) {
exceptionThrown = true;
}
assertTrue("you may not set a ConvertibleNodeText's text to null!", exceptionThrown);
}
@Test
public void testConvertibleAttributeValuesMayBeNull() throws ConversionException, ExecuteScriptException {
new ConvertibleText(new NodeModel(null), null, null);
}
@Test
public void testOtherConvertibleNoteTextMayBeNull() throws ConversionException {
try {
// by default there are no notes, i.e. they are null
assertNull(new ConvertibleNoteText(new NodeModel(null), null).getText());
}
catch (Exception e) {
fail("null texts are allowed for other Convertibles");
}
}
@Test
public void testSomethingToString() throws ConversionException, ExecuteScriptException {
// this works but you may not set a Convertibles's text to null!
assertEquals(null, Convertible.toString(null));
testSomethingToStringImpl("12", 12L, "num");
testSomethingToStringImpl("1.2", 1.2d, "num");
final String Z = getLocalTimeZoneOffsetString();
// default conversion doesn't contain milliseconds
testSomethingToStringImpl("2010-08-16T22:31" + Z, date("2010-08-16 22:31" + Z), "date");
}
private void testSomethingToStringImpl(String expected, Object toConvert, String propertyName)
throws ConversionException, ExecuteScriptException {
assertEquals(expected, Convertible.toString(toConvert));
// the result of Convertible.toString(Xyz) must be a valid input to Convertible.getXyz()
assertEquals(toConvert, convertible(expected).getProperty(propertyName));
}
}