Skip to content

Commit 722491f

Browse files
committed
Code showing impementation for issue #181
1 parent e21091a commit 722491f

File tree

1 file changed

+191
-0
lines changed

1 file changed

+191
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package com.mihnita.mf2.messageformat;
2+
3+
import static com.mihnita.mf2.messageformat.helpers.MFU.ph;
4+
import static com.mihnita.mf2.messageformat.helpers.MFU.sm;
5+
import static org.junit.Assert.assertEquals;
6+
7+
import java.text.ParseException;
8+
import java.util.Date;
9+
import java.util.Locale;
10+
import java.util.Map;
11+
import java.util.Objects;
12+
13+
import org.junit.Test;
14+
import org.junit.runner.RunWith;
15+
import org.junit.runners.JUnit4;
16+
17+
import com.ibm.icu.text.DateFormat;
18+
import com.ibm.icu.text.DateIntervalFormat;
19+
import com.ibm.icu.text.SimpleDateFormat;
20+
import com.ibm.icu.util.Calendar;
21+
import com.ibm.icu.util.DateInterval;
22+
import com.ibm.icu.util.GregorianCalendar;
23+
import com.ibm.icu.util.ULocale;
24+
import com.mihnita.mf2.messageformat.datamodel.functions.IPlaceholderFormatter;
25+
import com.mihnita.mf2.messageformat.impl.Message;
26+
import com.mihnita.mf2.messageformat.impl.Placeholder;
27+
import com.mihnita.mf2.messageformat.impl.SimpleMessage;
28+
29+
@RunWith(JUnit4.class)
30+
@SuppressWarnings("static-method")
31+
public class DynamicMessageReference2Test {
32+
// Dynamic References
33+
// https://github.com/unicode-org/message-format-wg/issues/181
34+
35+
static private final String LOCALE_ID = "en";
36+
static private final Locale LOCALE = Locale.forLanguageTag(LOCALE_ID);
37+
38+
static {
39+
Placeholder.CUSTOM_FORMATTER_FUNC.put("DATETIME_RANGE", new DateTimeRange());
40+
Placeholder.CUSTOM_FORMATTER_FUNC.put("NOW", new DateTimeNow());
41+
}
42+
43+
@Test
44+
public void test() {
45+
// A calendar object
46+
Message.VARIABLES.put("var_start", new GregorianCalendar(2019, Calendar.MAY, 17));
47+
// Milliseconds
48+
Message.VARIABLES.put("var_end", new GregorianCalendar(2021, Calendar.AUGUST, 23).getTimeInMillis());
49+
50+
Placeholder ph1 = ph("range", "DATETIME_RANGE", "start", "m:msg_start", "end", "$var_end", "skeleton", "y");
51+
Placeholder ph2 = ph("range", "DATETIME_RANGE", "start", "$var_start", "end", "$var_end", "skeleton", "y");
52+
Placeholder ph3 = ph("range", "DATETIME_RANGE", "start", "$var_name", "end", "f:NOW", "skeleton", "y");
53+
StringBuffer messageRef = new StringBuffer("m:msg_start");
54+
Message.VARIABLES.put("var_name", messageRef); // var_name contains a ref to a message
55+
56+
SimpleMessage msg_start = sm("msg_start", LOCALE, "1970");
57+
SimpleMessage msg_1 = sm("msg_1", LOCALE, "Years ", ph1);
58+
SimpleMessage msg_2 = sm("msg_2", LOCALE, "Years ", ph2);
59+
SimpleMessage msg_3 = sm("msg_3", LOCALE, "Years ", ph3);
60+
61+
Message.RES_MANAGER.put(msg_start.id(), msg_start);
62+
Message.RES_MANAGER.put(msg_1.id(), msg_1);
63+
Message.RES_MANAGER.put(msg_2.id(), msg_2);
64+
Message.RES_MANAGER.put(msg_3.id(), msg_3);
65+
66+
Message message;
67+
68+
message = Message.RES_MANAGER.get("msg_start");
69+
assertEquals("1970", message.format(null));
70+
71+
message = Message.RES_MANAGER.get("msg_1");
72+
assertEquals("Years 1970 – 2021", message.format(null));
73+
74+
message = Message.RES_MANAGER.get("msg_2");
75+
assertEquals("Years 2019 – 2021", message.format(null));
76+
77+
message = Message.RES_MANAGER.get("msg_3");
78+
assertEquals("Years 1970 – 2021", message.format(null));
79+
}
80+
81+
}
82+
83+
/**
84+
* Implementing the {@code DATETIME_RANGE} custom function
85+
*
86+
* <p>Normally in a separate file, in a shared folder,
87+
* but here to show that things are really isolated,
88+
* and what custom functions belong with what tests</p>
89+
*/
90+
class DateTimeRange implements IPlaceholderFormatter {
91+
92+
// This is extremely dodgy proposition.
93+
// Parsing "something that looks like a date / time" in a random locale,
94+
// and input by some random translator is just asking for trouble.
95+
// But this is to show that ES can do what EZ does, as good or as bad.
96+
@SuppressWarnings("deprecation")
97+
long hackParseDateTime(String s, String locale, Map<String, String> options) {
98+
String skeleton = options.get("skeleton");
99+
DateFormat df = SimpleDateFormat.getInstanceForSkeleton(skeleton, ULocale.forLanguageTag(locale));
100+
101+
try {
102+
Date tmpDate = df.parse(s);
103+
// We assume that "absurd" values are not real dates but milliseconds
104+
if (tmpDate.getYear() > -2000 && tmpDate.getYear() <= 5000)
105+
return tmpDate.getTime();
106+
} catch (ParseException e) {
107+
}
108+
109+
// Parsing as a date failed, we try parsing it as milliseconds
110+
try {
111+
return Long.parseLong(s);
112+
} catch (NumberFormatException e) {
113+
return 0;
114+
}
115+
}
116+
117+
Object hackyEval(String s, Object value, String locale, Map<String, String> options) {
118+
if (s.startsWith("f:")) { // function ref
119+
IPlaceholderFormatter funcName = Placeholder.CUSTOM_FORMATTER_FUNC.get(s.substring(2));
120+
String val = funcName.format(value, locale, options);
121+
return hackyEval(Objects.toString(val), value, locale, options);
122+
} else if (s.startsWith("m:")) { // message ref
123+
Message msg = Message.RES_MANAGER.get(s.substring(2));
124+
return hackyEval(msg.format(null), value, locale, options);
125+
} else if (s.startsWith("$")) { // var ref
126+
Object obj = Message.VARIABLES.get(s.substring(1));
127+
if (obj instanceof CharSequence) { // if it's a string we try to eval deeper
128+
return hackyEval(((CharSequence) obj).toString(), value, locale, options);
129+
} else {
130+
return obj;
131+
}
132+
}
133+
return s;
134+
}
135+
136+
/*
137+
* The value, locale, options are there to pass when formatting referred messages.
138+
* But I'm cheating a bit here to make the implementation simpler.
139+
* There is an example of that in DynamicMessageReferenceTest.
140+
*/
141+
long stringToLong(String s, Object value, String locale, Map<String, String> options) {
142+
if (s == null || s.isEmpty()) {
143+
return 0;
144+
}
145+
146+
Object obj = hackyEval(s, value, locale, options);
147+
if (obj instanceof Date) {
148+
return ((Date) obj).getTime();
149+
}
150+
if (obj instanceof Calendar) {
151+
return ((Calendar) obj).getTimeInMillis();
152+
}
153+
if (obj instanceof Long) {
154+
return ((Long) obj);
155+
}
156+
if (obj instanceof String) {
157+
try {
158+
long result = hackParseDateTime((String) obj, locale, options);
159+
return result;
160+
} catch (NumberFormatException e) {
161+
return 0;
162+
}
163+
}
164+
165+
return 0;
166+
}
167+
168+
@Override
169+
public String format(Object value, String locale, Map<String, String> options) {
170+
long start = stringToLong(options.get("start"), value, locale, options);
171+
long end = stringToLong(options.get("end"), value, locale, options);
172+
String skeleton = options.get("skeleton");
173+
174+
DateInterval di = new DateInterval(start, end);
175+
DateIntervalFormat dif = DateIntervalFormat.getInstance(skeleton, ULocale.forLanguageTag(locale));
176+
return dif.format(di);
177+
}
178+
}
179+
180+
/**
181+
* Implementing the {@code NOW} custom function
182+
*
183+
* <p>This is if we want to share and reuse it for other date/time formatters.
184+
* Otherwise we can consider it a special string value recognized by {@code DATETIME_RANGE}.</p>
185+
*/
186+
class DateTimeNow implements IPlaceholderFormatter {
187+
@Override
188+
public String format(Object value, String locale, Map<String, String> options) {
189+
return Long.toString(new Date().getTime());
190+
}
191+
}

0 commit comments

Comments
 (0)