00001
00002
00003
import java.util.*;
00004
import java.io.*;
00005
import java.net.*;
00006
import java.util.zip.*;
00007
import java.lang.reflect.*;
00008
00009 public class InputManager {
00010 public static final int SOURCE_KEYBOARD = 1;
00011 public static final int SOURCE_GUI = 2;
00012 public static final int SOURCE_CLIPBOARD = 3;
00013 public static final int SOURCE_FILE = 4;
00014
00015 public static final int KEY_BACKSPACE = -1;
00016
00017 public static final String
INPUT_BREAK =
"\uFFFF{{BREAK}}";
00018 public static final String
INPUT_BACKSPACE =
"\uFFFF{{BACKSPACE}}";
00019
00020 public static final int DOCUMENTATION_COLS = 3;
00021 public static final int CONTINUATIONS_LENGTH = 300;
00022
00023 public static class StringReplacement extends InputReplacement {
00024 String
string;
00025 String
tempReplacement;
00026 String
replacement;
00027 String
cont[];
00028 public StringReplacement(String _string, String _repl) {
00029
string = _string;
tempReplacement =
replacement = _repl; };
00030 public StringReplacement(String _string, String _tr, String _fr) {
00031
string = _string;
tempReplacement = _tr;
replacement = _fr; };
00032 public String
getReplacement(String input) {
return tempReplacement; };
00033 public String
getFinalReplacement(String input) {
00034
return replacement; };
00035 public String
getInputInfo() {
return string; };
00036 public String[]
isContinuationOf(String input) {
00037
if (
string.startsWith(input) && !
string.equals(input)) {
00038
if (
cont==null)
cont =
new String[]{
string};
00039
return cont;
00040 }
00041
return null; };
00042 public String
replacesPrefixOf(String input) {
00043
if (input.startsWith(
string))
00044
return string;
00045
return null;
00046 }
00047 }
00048 public class SwitchReplacement extends StringReplacement {
00049 public static final int ON = 1;
00050 public static final int OFF = 2;
00051 int state;
00052 int targetGroup;
00053 public SwitchReplacement(String s,
int target,
int st) {
00054 super(s,null);
00055
targetGroup = target;
00056
state = st; }
00057 public String
getReplacement(String input) {
return string; };
00058 public String
getFinalReplacement(String input) {
00059
Group g = getGroup(
targetGroup);
00060
try {
00061
switch (
state) {
00062
case ON: g.
activate();
break;
00063
case OFF: g.
deactivate();
break;
00064
default:
return "!!! internal error, group state "+
00065
state+
" !!!";
00066 }
00067 }
catch (Exception e) {
00068 e.printStackTrace();
00069 imContext.showMessage
00070 (
"Could not switch state of input method "+
00071 g.
getFullName()+
":\n"+e);
00072 }
00073
return "";
00074 }
00075 public String
getReplacementInfo() {
00076 String name = getGroup(
targetGroup).getScreenName();
00077
switch (
state) {
00078
case ON:
return "enable "+name;
00079
case OFF:
return "disable "+name;
00080
default:
throw new RuntimeException();
00081 }
00082 }
00083 }
00084
00085 static class Group implements Documentation {
00086 private String
name = null;
00087 private int index;
00088 private String
description =
"no description available";
00089 private URL
source = null;
00090 private boolean hidden =
false;
00091 private String
screenName = null;
00092
00093 private boolean active =
false;
00094 private URL
url = null;
00095 private InputManager inputManager = null;
00096
00097 boolean isHidden() {
return hidden; };
00098 void setHidden(
boolean h) {
hidden = h; };
00099
00100 String
getName() {
return name; };
00101 String
getScreenName() {
00102
if (
screenName!=null)
return screenName;
00103
return name; };
00104 String
getFullName() {
00105
if (
screenName!=null && !
screenName.equals(
name))
00106
return screenName+
" ("+
name+
")";
00107
return name; }
00108 public String
getFullDocuName() {
00109
return "input method "+
getFullName(); };
00110 void setScreenName(String sn) {
screenName = sn; };
00111 void load() throws IOException {
00112 URL _url =
url;
00113
try {
00114
if (url!=null) {
00115
inputManager.
imContext.
showMessage
00116 (
"Autoloading input method "+
getFullName());
00117 url = null;
00118
inputManager.
loadConfig(_url);
00119 _url = null;
00120 }
00121 } finally {
00122 url = _url;
00123 }
00124 }
00125 public void setSourceURL(URL s) {
source = s; };
00126 public URL
getSourceURL() {
return source; };
00127 public void activate() throws IOException {
00128
if (!
hidden &&
inputManager.
singleInputMethod)
00129
inputManager.
deselectNonHiddenGroups();
00130
load();
00131
active =
true;
00132
inputManager.
lastActivatedGroup =
this;
00133
if (!
hidden)
00134
inputManager.
imContext.
groupStateChanged(
name,
active);
00135 }
00136 public Group(
InputManager im, String n,
int i) {
00137 im.
newGroups =
true;
00138
inputManager = im;
name = n;
index = i; };
00139 public void deactivate() {
00140
active =
false;
00141
if (!
hidden)
00142
inputManager.
imContext.
groupStateChanged(
name,
active);
00143 };
00144 public boolean isActive() {
return active; };
00145 public void autoloadURL(URL _url) {
url = _url; };
00146 public void setDescription(String descr) {
description = descr; };
00147 public String
getDescription() throws IOException {
00148
load();
return description; };
00149 public String
getDocumentation() throws IOException {
00150 StringBuffer docu =
new StringBuffer();
00151 docu.append(
"<html><head>\n");
00152 docu.append(
"<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">\n");
00153 docu.append(
"<link rel=stylesheet type=text/css href=international_input.css>\n");
00154 String title =
"Input method “"+
getScreenName()+
00155
"” - Documentation";
00156 docu.append(
"<title>");
00157 docu.append(HTMLEscapeSB(title,
true));
00158 docu.append(
"</title>\n</head><body><h1>");
00159 docu.append(title);
00160 docu.append(
"</h1>\n");
00161 docu.append(
getDescription());
00162 URL
source =
getSourceURL();
00163
if (source!=null) {
00164 docu.append(
"<p><b><a href=\""); docu.append(source);
00165 docu.append(
"\" type=\"text/plain\" charset=\"utf-8\">[Source]</a></b>");
00166 }
00167 docu.append(
"<p><h3>All mappings:</h3><p><center><table cellpadding=5 border=1 rules=rows frame=box><tr>\n");
00168
int rplsize =
inputManager.
replacements.size();
00169
int count = 0;
00170
for (
int i=0; i<DOCUMENTATION_COLS; i++) {
00171
if (i>0) docu.append(
"<th> . . . . . . . . </th>");
00172 docu.append(
"<th>Input</th><th>Output</th>");
00173 }
00174
for (
int i=0; i<rplsize; i++) {
00175
InputReplacement rpl =
00176 (
InputReplacement)
inputManager.
replacements.elementAt(i);
00177
if (rpl.
group!=
index)
continue;
00178
if (count%DOCUMENTATION_COLS==0) docu.append(
"</tr>\n<tr>");
00179
else docu.append(
"</td><td>");
00180 docu.append(
"<td><tt>");
00181 docu.append(HTMLEscapeSB(rpl.
getInputInfo(),
true));
00182 docu.append(
"</tt></td><td class=replacement>");
00183 docu.append(HTMLEscapeSB(prefixNSM(rpl.
getReplacementInfo()),
true));
00184 docu.append(
"</td>");
00185 count++;
00186 }
00187 docu.append(
"</tr></table></center>\n");
00188 docu.append(
"</body></html>\n");
00189
return docu.toString();
00190 }
00191 }
00192 public static class WebPage implements Documentation {
00193 public static class Token {
00194 public static final int SOURCE_WORD = 1;
00195 public static final int SOURCE_CONST = 2;
00196
00197 public static final int MODE_NORMAL = 1;
00198 public static final int MODE_EXAMPLE = 2;
00199 public static final int MODE_HIGHLIGHT = 3;
00200
00201 public static final int ENCODE_URL = 1;
00202 public static final int ENCODE_DEC = 2;
00203 public static final int ENCODE_HEX = 3;
00204 String
name;
00205 int source;
00206 int encoding =
ENCODE_URL;
00207 String
string;
00208 String
charset =
"utf-8";
00209 public Token(String _name) {
00210
name = _name;
source =
SOURCE_WORD; };
00211 public void setConst(String _str) {
00212
source =
SOURCE_CONST;
string = _str; };
00213 public void setCharset(String cs) {
00214
charset = cs; };
00215 public void setEncoding(
int mode) {
00216
encoding = mode; };
00217 public String
getName() {
return name; };
00218 private String
getRawReplacement(String word,
int mode) {
00219
if (mode==
MODE_HIGHLIGHT)
00220
return "<span class=webpage-url-token>"+
00221 HTMLEscapeSB(
name,
false)+
"</span>";
00222
switch (
source) {
00223
case SOURCE_CONST:
return string;
00224
case SOURCE_WORD:
return word;
00225
default:
throw new RuntimeException
00226 (
"Internal error: Unknown source "+
source);
00227 }
00228 };
00229 public String
getReplacement(String word,
int mode) {
00230 String repl = getRawReplacement(word,mode);
00231
switch (mode) {
00232
case MODE_NORMAL:
00233
case MODE_EXAMPLE:
00234
switch (
encoding) {
00235
case ENCODE_URL:
00236
return urlEncode(repl,
charset).toString();
00237
case ENCODE_HEX:
00238
case ENCODE_DEC:
00239
if (repl.length()==0)
return "";
00240
return Integer.toString(repl.charAt(0),
encoding==
ENCODE_HEX?16:10);
00241
default:
00242
throw new RuntimeException
00243 (
"Internal error: Unknown encoding "+
source);
00244 }
00245
case MODE_HIGHLIGHT:
00246
return repl;
00247
default:
throw new RuntimeException
00248 (
"Internal error: Unknown mode "+
source);
00249 }
00250 };
00251 public String
getDocumentationPart() {
00252 StringBuffer docu =
new StringBuffer();
00253 docu.append(
"<span class=webpage-token>");
00254 docu.append(HTMLEscapeSB(
name,
false));
00255 docu.append(
"</span> ");
00256
00257
switch (
source) {
00258
case SOURCE_CONST:
00259 docu.append(
"has constant value \"<span class=webpage-token-value>");
00260 docu.append(HTMLEscapeSB(
string,
true));
00261 docu.append(
"</span>\"");
00262
break;
00263
case SOURCE_WORD:
00264 docu.append(
"is replaced by the selection");
00265
break;
00266
default:
00267 docu.append(
"<b>has unknown source "+
source+
"!!!!!");
00268
break;
00269 }
00270
00271
switch (
encoding) {
00272
case ENCODE_URL:
00273 docu.append(
", and is ").append(HTMLEscapeSB(
charset,
false));
00274 docu.append(
" encoded");
00275
break;
00276
case ENCODE_DEC:
00277 docu.append(
", the first character's code position in decimal is inserted in the URL");
00278
break;
00279
case ENCODE_HEX:
00280 docu.append(
", the first character's code position in hex is inserted in the URL");
00281
break;
00282 }
00283 docu.append(
".");
00284
return docu.toString();
00285 }
00286 };
00287 private String
name;
00288 private String
shortName;
00289 private String
url;
00290 private boolean needsTopFrame =
false;
00291 private LinkedList
tokens =
new LinkedList();
00292 private String
description =
"no description available";
00293 private URL
source = null;
00294 public WebPage(String _name, String _url) {
00295
name = _name;
url = _url; };
00296 public String
getName() {
return name; };
00297 public String
getFullDocuName() {
00298
return "web page \""+
getName()+
"\""; };
00299 public void addToken(
Token token) {
00300
tokens.add(token); };
00301 public String
getShortName() {
00302
if (
shortName!=null)
return shortName;
00303
return name; };
00304 public void setShortName(String sn) {
shortName = sn; };
00305 public boolean getNeedsTopFrame() {
return needsTopFrame; };
00306 public void setNeedsTopFrame(
boolean tf) {
00307
needsTopFrame = tf; };
00308 public String
getToolTipText() {
return url; };
00309 public URL
run(String word)
throws MalformedURLException {
00310
return new URL(
replaceTokens(word,
Token.MODE_NORMAL)); };
00311 public String
replaceTokens(String word,
int mode) {
00312 StringBuffer u =
new StringBuffer(
url);
00313
int fromIndex = 0;
00314
while (
true) {
00315 String u2 =
new String(u);
00316
int pos = u2.length();
00317
Token tok = null;
00318
for (ListIterator e =
tokens.listIterator();
00319 e.hasNext();) {
00320
Token thistok = (
Token)e.next();
00321
int thispos = u2.indexOf(thistok.
getName(),fromIndex);
00322
if (thispos==-1)
continue;
00323
if (pos>thispos) { pos = thispos; tok = thistok; };
00324 }
00325
if (tok==null)
break;
00326 String repl = tok.
getReplacement(word,mode);
00327 u.replace(pos,pos+tok.
getName().length(),repl);
00328 fromIndex = pos+repl.length();
00329 }
00330
return u.toString();
00331 }
00332 public String
getDocumentation() {
00333 StringBuffer docu =
new StringBuffer();
00334 docu.append(
"<html><head>\n");
00335 docu.append(
"<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">\n");
00336 docu.append(
"<link rel=stylesheet type=text/css href=international_input.css>\n");
00337 String title =
"Web page “"+
getName()+
"” - Documentation";
00338 docu.append(
"<title>");
00339 docu.append(HTMLEscapeSB(title,
true));
00340 docu.append(
"</title>\n</head><body><h1>");
00341 docu.append(title);
00342 docu.append(
"</h1>\n");
00343 docu.append(
getDescription());
00344 URL
source =
getSourceURL();
00345
if (source!=null) {
00346 docu.append(
"<p><b><a href=\""); docu.append(source);
00347 docu.append(
"\" type=\"text/plain\" charset=\"utf-8\">[Source]</a></b>\n");
00348 }
00349 String htmlURL = replaceTokens(
"test",
Token.MODE_HIGHLIGHT);
00350 String testURL = replaceTokens(
"test",
Token.MODE_EXAMPLE);
00351 docu.append(
"<p>URL: <a class=webpage-url href=\"");
00352 docu.append(testURL).append(
"\">").append(htmlURL);
00353 docu.append(
"</a>\n");
00354 docu.append(
"<p><ul>\n");
00355
for (ListIterator e =
tokens.listIterator();
00356 e.hasNext();) {
00357
Token tok = (
Token)e.next();
00358 docu.append(
"<li>");
00359 docu.append(tok.
getDocumentationPart());
00360 }
00361 docu.append(
"</ul>");
00362 docu.append(
"</body></html>\n");
00363
return docu.toString();
00364 }
00365 public String
getDescription() {
00366
return description; }
00367 public void setDescription(String d) {
00368
description = d; };
00369 public void setSourceURL(URL s) {
source = s; };
00370 public URL
getSourceURL() {
return source; };
00381 public boolean equals(Object a) {
00382
if (a==null)
return false;
00383
if (a==
this)
return true;
00384
if (!(a instanceof
WebPage))
return false;
00385
WebPage aa = (
WebPage)a;
00386
if (!
name.equals(aa.
name))
return false;
00387
if (!
url.equals(aa.
url))
return false;
00388
return true;
00389 };
00390 }
00391
00392 Vector
replacements =
new Vector();
00393 Vector
groups =
new Vector();
00394 Vector
webPages =
new Vector();
00395 InputViewer viewer = null;
00396 String
temporaryOutput = null;
00397 String
temporaryInput = null;
00398 Group lastActivatedGroup = null;
00399 InternationalInputContext iiContext = null;
00400 InputManagerContext imContext = null;
00401 boolean newGroups =
false;
00402 boolean singleInputMethod =
true;
00403
00404 Group getGroup(
int index) {
00405
return (
Group)
groups.get(index); };
00406 Group getGroup(String name) {
00407
if (name==null)
00408
throw new IllegalArgumentException
00409 (
"Group name is null");
00410
for (Enumeration e =
groups.elements();
00411 e.hasMoreElements();) {
00412
Group g = (
Group)e.nextElement();
00413
if (g.
name.equals(name))
return g;
00414 }
00415
Group g =
new Group(
this,name,
groups.size());
00416
groups.add(g);
00417
return g;
00418 }
00419
00420 public void activateGroup(String name)
throws IOException {
00421 getGroup(name).
activate();
00422 }
00423
00424 public void deactivateGroup(String name)
throws IOException {
00425 getGroup(name).
deactivate();
00426 }
00427
00428 public String
getLastActivatedGroupName() {
00429
if (
lastActivatedGroup==null)
return null;
00430
return lastActivatedGroup.
name;
00431 }
00432
00433 public void setViewer(
InputViewer _viewer) {
00434
viewer = _viewer; };
00435
00436 static String
prefixNSM(String str) {
00437
if (str.length()==0)
return str;
00438
char c = str.charAt(0);
00439
try {
00440
if (
Normalizer.isCombining(c))
00441
return "◌"+str;
00442 }
catch (IOException e) {
00443 e.printStackTrace(); }
00444
return str;
00445 }
00446
00447 public static StringBuffer
HTMLEscapeSB(String str,
boolean br) {
00448 StringBuffer res =
new StringBuffer();
00449
int len = str.length();
00450
for (
int i=0; i<len; i++) {
00451
char c = str.charAt(i);
00452
switch (c) {
00453
case '&': res.append(
"&");
break;
00454
case '<': res.append(
"<");
break;
00455
case '>': res.append(
">");
break;
00456
case '\n': res.append(br?
"<br>":
"\n");
break;
00457
default: res.append(c);
00458 }
00459 }
00460
return res;
00461 }
00462
00463 public void inputChar(
int source,
char chr) {
00464
input(source,
charToString(chr));
00465 }
00466
00467 public void input(
int source, String str) {
00468
00469
if (str.equals(
INPUT_BREAK)) {
00470
viewer.
removeTemporary();
00471
while (
makeOutput(
true));
00472
00473
if (
temporaryOutput!=null)
00474
throw new RuntimeException
00475 (
"Internal error: makeOutput(true) did not clear temporaryOutput");
00476
if (
temporaryInput!=null)
00477
throw new RuntimeException
00478 (
"Internal error: makeOutput(true) did not clear temporaryInput");
00479 }
else if (str.equals(
INPUT_BACKSPACE)) {
00480
00481
viewer.
removeTemporary();
00482
if (
temporaryInput!=null) {
00483
if (
temporaryInput.length()>1)
00484
temporaryInput =
00485
temporaryInput.substring(0,
temporaryInput.length()-1);
00486
else temporaryInput = null;
00487
while (
makeOutput(
false));
00488
if (
temporaryOutput!=null)
00489
viewer.
addTemporary(
temporaryOutput);
00490 }
else
00491
viewer.
specialKey(
KEY_BACKSPACE);
00492 }
else {
00493
viewer.
removeTemporary();
00494
if (
temporaryInput==null)
temporaryInput =
"";
00495
temporaryInput += str;
00496
while (
makeOutput(
false));
00497
if (
temporaryOutput!=null)
00498
viewer.
addTemporary(
temporaryOutput);
00499 }
00500 };
00501
00502 public InputManager(
InputManagerContext _context)
00503
throws IOException {
00504
imContext = _context;
00505
iiContext =
imContext.
getInternationalInputContext();
00506 getGroup(
"default").
setHidden(
true);
00507 getGroup(
"default").
activate();
00508 }
00509
00514 boolean makeOutput(
boolean finished) {
00515
boolean changed =
false;
00516
if (
temporaryInput==null) {
00517
temporaryOutput = null;
return changed; }
00518
00519
InputReplacement longestRpl = null;
00520 String longestPfx =
"";
00521
boolean strIsPfx =
false;
00522
int rplsize =
replacements.size();
00523 StringBuffer continuations = null;
00524
boolean needContinuations = !finished;
00525
for (
int i=0; i<rplsize; i++) {
00526
InputReplacement rpl = (
InputReplacement)
replacements.elementAt(i);
00527
if (!getGroup(rpl.
group).
isActive())
continue;
00528
00529 String pfx = rpl.
replacesPrefixOf(
temporaryInput);
00530
00531
if (pfx != null &&
00532 pfx.length() > longestPfx.length()) {
00533 longestRpl = rpl; longestPfx = pfx; }
00534
00535
if (needContinuations) {
00536 String cont[] = rpl.
isContinuationOf(
temporaryInput);
00537
00538
if (cont!=null) {
00539
for (
int j=0; j<cont.length; j++)
00540
if (continuations==null ||
00541 continuations.length()<=
CONTINUATIONS_LENGTH) {
00542
if (continuations!=null)
00543 continuations.append(
", ");
00544
else continuations =
new StringBuffer();
00545 continuations.append(cont[j]);
00546 }
else needContinuations =
false;
00547 strIsPfx =
true;
00548 }
00549 }
00550 }
00551
00552
if (continuations!=null && continuations.length()>
CONTINUATIONS_LENGTH)
00553 continuations =
00554
new StringBuffer(continuations.substring
00555 (0,
CONTINUATIONS_LENGTH)
00556 +
"...");
00557
00558
try {
00559
if (continuations!=null)
00560
iiContext.
showInfo(continuations.toString(),
false);
00561
else
00562
iiContext.
showInfo(
"",
false);
00563 }
catch (UnsupportedOperationException e) {};
00564
00565 String finalOut = null;
00566
if (longestRpl!=null && strIsPfx) {
00567
temporaryOutput = longestRpl.
getReplacement(
temporaryInput)+
00568
temporaryInput.substring(longestPfx.length());
00569 }
else if (longestRpl!=null && !strIsPfx) {
00570
int len = longestPfx.length();
00571
if (len==0)
00572
throw new RuntimeException
00573 (
"internal error: longestPfx.length()==0");
00574 finalOut = longestRpl.
getFinalReplacement(
temporaryInput);
00575
temporaryInput =
temporaryOutput =
temporaryInput.substring(len);
00576 changed =
true;
00577 }
else if (longestRpl==null && strIsPfx) {
00578
temporaryOutput =
temporaryInput;
00579 }
else if (longestRpl==null && !strIsPfx) {
00580 finalOut =
temporaryInput.substring(0,1);
00581
temporaryInput =
temporaryOutput =
temporaryInput.substring(1);
00582 changed =
true;
00583 }
00584
00585
if (finalOut!=null) {
00586
viewer.
addFinal(finalOut);
00587 }
00588
if (
temporaryOutput!=null &&
temporaryOutput.length()==0)
00589
temporaryOutput = null;
00590
if (
temporaryInput!=null &&
temporaryInput.length()==0)
00591
temporaryInput = null;
00592
00593 System.out.println(
"final="+finalOut+
" temp-out="+
temporaryOutput+
00594
" temp-in="+
temporaryInput);
00595
return changed;
00596 }
00597
00598 String
charToString(
int c) {
00599
if (c<0)
00600
throw new IllegalArgumentException
00601 (
"chars need positive values, not "+c);
00602
char cc[] = { (
char)c };
00603
return new String(cc);
00604 }
00605
00609 public static URL
makeProxyURL(URL base, URL url) {
00610
try {
00611
return new URL(base,
"proxy/xxx?"+url);
00612 }
catch (MalformedURLException e) {
00613
throw new RuntimeException(
"Internal error");
00614 }
00615 }
00616
00617 InputStream
openURL(URL url)
throws IOException {
00618
00619
try {
00620
return url.openStream();
00621 }
catch (SecurityException e) {
00622 URL proxy = makeProxyURL(
iiContext.
getDocumentBase(),url);
00623 System.out.println(
"Security restriction. Using proxy "+proxy);
00624
return proxy.openStream();
00625 }
00626 }
00627
00628 public void loadConfigString(String str)
throws IOException {
00629
loadConfig_native
00630 (
new StringReader
00631 (
"#!/bin/sh international-input-config\n"+str),
00632
new URL(
iiContext.
getDocumentBase(),
"no-source.html"));
00633 }
00634
00635 public void loadConfig(URL url)
throws IOException {
00636
00637
imContext.
showMessage(
"Loading config file "+url);
00638 InputStream inputStream = null;
00639 String lcPath = url.toString().toLowerCase();
00640
if (lcPath.endsWith(
".gz")) {
00641 lcPath = lcPath.substring(0,lcPath.length()-3);
00642
try {
00643 inputStream =
new GZIPInputStream(openURL(url));
00644 }
catch (FileNotFoundException e) {
00645 String ustr = url.toString();
00646 url =
new URL(ustr.substring(0,ustr.length()-3));
00647 inputStream = openURL(url);
00648 }
00649 }
else
00650 inputStream = openURL(url);
00651
00652
if (lcPath.endsWith(
".inp")) {
00653 System.out.println(
"Loading "+url+
" as native config file.");
00654
loadConfig_native(
new InputStreamReader(inputStream,
"utf-8"),url);
00655 }
else if (lcPath.endsWith(
".my")) {
00656 System.out.println(
"Loading "+url+
" as Yudit .my file");
00657
loadConfig_yudit(inputStream,url,null);
00658
00659
00660 }
else {
00661
throw new FileFormatException(
"File "+url+
" does not have a recognisable file name extension (should be .inp/.inp.gz/.my/.my.gz)");
00662 }
00663 }
00664
00665 public void loadConfig_yudit(InputStream inputStream, URL url,
00666 Writer _converted)
00667
throws IOException {
00668 DataInputStream in =
new DataInputStream(
new BufferedInputStream(inputStream));
00669 PrintWriter converted = null;
00670
if (_converted!=null) converted =
new PrintWriter(_converted,
true);
00671 byte buff[] =
new byte[256];
00672
00673
00674 in.readFully(buff,0,16);
00675 String str =
new String(buff,0,16,
"us-ascii");
00676
00677
if (!str.equals(
"YUDIT-NtoN 1.0\000"))
00678
throw new FileFormatException
00679 (
"YUDIT file must start with \"YUDIT-NtoN 1.0\\000\"");
00680
00681
00682 in.readFully(buff,0,32);
00683 String name =
yudit_cutCString(
new String(buff,0,32,
"us-ascii"));
00684
00685 name = name.toLowerCase();
00686
if (name.endsWith(
".kmap"))
00687 name = name.substring(0,name.length()-5);
00688
else if (name.endsWith(
".my"))
00689 name = name.substring(0,name.length()-3);
00690
else if (name.endsWith(
".mys"))
00691 name = name.substring(0,name.length()-4);
00692 name =
"y."+name;
00693
00694
00695
int len = in.readInt();
00696
if (buff.length<len) buff =
new byte[len+100];
00697 in.readFully(buff,0,len);
00698 String comment =
00699 yudit_cutCString(
new String(buff,0,len,
"us-ascii"));
00700
00701
00702 String docHead =
"Imported Yudit <tt>.my</tt> file.";
00703
if (comment.length()>0)
00704 comment = docHead+
"<p>Original comment:<br><pre>\n"+
00705 HTMLEscapeSB(comment,
false)+
"</pre>";
00706
else
00707 comment = docHead;
00708
if (converted!=null) {
00709 converted.println(
"#!/bin/sh international-input-config");
00710 converted.println(
"=GROUP");
00711 converted.println(name);
00712 converted.println(
"on");
00713 converted.println(
"=DESCRIPTION");
00714 converted.println(comment);
00715 converted.println(
"###\n");
00716 }
00717
Group group = getGroup(name);
00718 group.
setDescription(comment);
00719 group.
setSourceURL(url);
00720 group.
activate();
00721
00722
00723 in.skipBytes(4);
00724
00725
00726
int mapSize = in.readInt();
00727
00728
00729
int maps[] =
new int[mapSize+1];
00730
for (
int i=0; i<=mapSize; i++) {
00731 maps[i] = in.readInt();
00732
00733
00734 }
00735
00736
int mapOffset = 0;
00737
int numDecodeMaps = 0;
00738
for (
int map=0; map<=mapSize; map++) {
00739
if (mapOffset<maps[map]) {
00740 in.skipBytes(maps[map]-mapOffset);
00741 mapOffset = maps[map]; }
00742
if (map==mapSize)
break;
00743
00744 in.readFully(buff,0,32); mapOffset+=32;
00745 str = yudit_cutCString(
new String(buff,0,32,
"us-ascii"));
00746
00747
00748 len = in.readInt(); mapOffset+=4;
00749
if (buff.length<len) buff =
new byte[len+100];
00750 in.readFully(buff,0,len); mapOffset+=len;
00751 str = yudit_cutCString(
new String(buff,0,len,
"us-ascii"));
00752
00753
00754
int t = in.readUnsignedByte(); mapOffset++;
00755
boolean decode = ((t&1)==0);
00756
00757
if (!decode)
00758
continue;
00759
if (++numDecodeMaps>1) {
00760
throw new UnsupportedOperationException
00761 (
"Multipart maps not supported"); }
00762
00763
00764
int inputByteSize = 1<<in.readUnsignedByte();
00765
int outputByteSize = 1<<in.readUnsignedByte();
00766
int inputByteLen = 1<<in.readUnsignedByte();
00767
int outputByteLen = 1<<in.readUnsignedByte();
00768 mapOffset+=4;
00769
00770
00771
00772
00773
00774 in.skipBytes(8); mapOffset+=8;
00775
00776
00777
int codeSize = in.readInt(); mapOffset+=4;
00778
int codes[] =
new int[codeSize+1];
00779
for (
int i=0; i<=codeSize; i++) {
00780 codes[i] = in.readInt(); mapOffset+=4;
00781
00782
00783 }
00784
00785
int codeOffset = 0;
00786
for (
int code=0; code<=codeSize; code++) {
00787
if (codeOffset<codes[code]) {
00788 in.skipBytes(codes[code]-codeOffset);
00789 mapOffset += codes[code]-codeOffset;
00790 codeOffset = codes[code]; }
00791
if (code==codeSize)
break;
00792
00793
00794
int keySize =
yudit_readNByte(in,inputByteLen);
00795
int subSize = yudit_readNByte(in,inputByteLen);
00796 mapOffset += 2*inputByteLen;
00797 codeOffset += 2*inputByteLen;
00798
00799
00800
if (keySize==0) {
00801 System.out.println(
"keySize == 0, ignoring.");
00802
continue; }
00803
00804
00805
if (buff.length<keySize) buff =
new byte[keySize+100];
00806 in.readFully(buff,0,keySize);
00807 mapOffset += keySize; codeOffset += keySize;
00808 String key =
yudit_parseString(buff,keySize,inputByteSize);
00809
if (key==null)
continue;
00810
00811
00812
int resultSize = yudit_readNByte(in,outputByteLen);
00813 mapOffset += outputByteLen; codeOffset += outputByteLen;
00814
00815
00816
00817
if (buff.length<resultSize)
00818 buff =
new byte[resultSize+100];
00819 in.readFully(buff,0,resultSize);
00820 mapOffset += resultSize; codeOffset += resultSize;
00821 String result = yudit_parseString(buff,resultSize,
00822 outputByteSize);
00823
if (result==null)
continue;
00824
00825
00826
if (subSize!=keySize) {
00827 System.out.println(key+
" => "+result+
00828
" has subSize!=keySize ("+
00829 subSize+
"!="+keySize+
")");
00830
continue; }
00831
00832
if (converted!=null) {
00833
if (key.indexOf(
'\n')!=-1 ||
00834 result.indexOf(
'\n')!=-1)
00835
throw new UnsupportedOperationException
00836 (
"Cannot produce =REPLACE-NL");
00837 converted.println(
"=REPLACE");
00838 converted.println(key);
00839 converted.println(result);
00840 }
00841
StringReplacement newrpl =
00842
new StringReplacement(key,result);
00843 newrpl.
group = group.
index;
00844
replacements.addElement(newrpl);
00845 }
00846 }
00847
00848
if (numDecodeMaps<1)
00849
throw new FileFormatException(
"No decode maps");
00850
00851
if (converted!=null) converted.flush();
00852 }
00853
00854 private static String
yudit_parseString(byte bytes[],
int len,
00855
int width) {
00856 StringBuffer str =
new StringBuffer();
00857
switch (width) {
00858
case 1:
00859
for (
int i=0; i<len; i++) {
00860 str.append((
char)(bytes[i]&0xff)); }
00861
break;
00862
case 2:
00863
for (
int i=0; i<len; i+=2) {
00864 str.append((
char)((bytes[i]&0xff)<<8 | (bytes[i+1]&0xff)));
00865 }
00866
break;
00867
case 4:
00868 System.out.println(
"start");
00869
for (
int i=0; i<len; i+=4) {
00870
long cp = ((
long)(bytes[i]&0xff))<<24 | ((
long)(bytes[i+1]&0xff))<<16 |
00871 ((
long)(bytes[i+2]&0xff))<<8 | ((
long)(bytes[i+3]&0xff));
00872
if (cp<0 || cp>0xFFFF) {
00873
00874
00875
00876
00877
00878 str.append((
char)(((cp-0x10000)>>10)+0xD800));
00879 str.append((
char)((cp&0x3FF)+0xDC00));
00880 }
else
00881 str.append((
char)cp);
00882 }
00883
break;
00884
default:
00885
throw new IllegalArgumentException
00886 (
"yudit_parseString only supports lens 1/2/4, not "+width);
00887 }
00888
return str.toString();
00889 }
00890 private static int yudit_readNByte(DataInputStream in,
int num)
00891
throws IOException {
00892
switch (num) {
00893
case 1:
return in.readUnsignedByte();
00894
case 2:
return in.readUnsignedShort();
00895
case 4:
return in.readInt();
00896
default:
00897
throw new IllegalArgumentException
00898 (
"yudit_readNByte only supports len 1/2/4, not "+num);
00899 }
00900 }
00901 private static String
yudit_cutCString(String str) {
00902
return yudit_cutCString(str,
'\000');
00903 }
00904 private static String yudit_cutCString(String str,
char cut) {
00905
int pos = str.indexOf(cut);
00906
if (pos==-1)
return str;
00907
return str.substring(0,pos);
00908 }
00909
00910 public void loadConfig_native(Reader inputReader, URL url)
00911
throws IOException {
00912 BufferedReader input =
00913
new BufferedReader(inputReader);
00914
boolean newWebPages =
false;
00915 String line;
00916 line = input.readLine();
00917
if (line==null ||
00918 !line.startsWith(
"#!/bin/sh international-input-config"))
00919
throw new FileFormatException
00920 (
"Config file does not start with \n"+
00921
" \"#!/bin/sh international-input-config\", but with \n \""+
00922 line+
"\".");
00923
int groupIndex = getGroup(
"default").
index;
00924
while ((line=input.readLine())!=null) {
00925
if (line.equals(
"=REPLACE") ||
00926 line.equals(
"=REPLACE-NL") ||
00927 line.equals(
"=REPLACE-EXT") ||
00928 line.equals(
"=REPLACE-EXT-NL")) {
00929 String string = input.readLine();
00930 String by = input.readLine();
00931 String finalBy = null;
00932
if (line.equals(
"=REPLACE-EXT") ||
00933 line.equals(
"=REPLACE-EXT-NL")) {
00934 finalBy = input.readLine();
00935
if (finalBy==null)
00936
throw new FileFormatException
00937 (
"=REPLACE-EXT not followed by three lines");
00938 }
else finalBy = by;
00939
if (line.equals(
"=REPLACE-NL") ||
00940 line.equals(
"=REPLACE-EXT-NL")) {
00941 String nl = input.readLine();
00942
if (nl==null || nl.length()!=1)
00943
throw new FileFormatException
00944 (
"=REPLACE-NL needs one char string as arg 3");
00945
char c = nl.charAt(0);
00946 string = string.replace(c,
'\n');
00947 by = by.replace(c,
'\n');
00948 finalBy = finalBy.replace(c,
'\n');
00949 }
00950
if (string==null || by==null)
00951
throw new FileFormatException
00952 (
"=REPLACE not followed by two lines");
00953
StringReplacement newrpl =
00954
new StringReplacement(string,by,finalBy);
00955 newrpl.
group = groupIndex;
00956
replacements.addElement(newrpl);
00957 }
else if (line.equals(
"=GROUP")) {
00958 String groupName = input.readLine();
00959 String state = input.readLine();
00960
if (state==null)
00961
throw new FileFormatException
00962 (
"=GROUP must be followed by two lines.");
00963
Group group = getGroup(groupName);
00964 group.
setSourceURL(url);
00965 groupIndex = group.
index;
00966
if (state.equals(
"on")) {
00967 group.
activate();
00968 }
else if (state.equals(
"hidden")) {
00969 group.
setHidden(
true);
00970 group.
activate();
00971 }
else if (state.equals(
"off")) {
00972 group.
deactivate();
00973 }
else {
00974
throw new FileFormatException
00975 (
"Invalid group state "+state+
" for group "+group.
getName());
00976 }
00977 }
else if (line.equals(
"=SWITCH")) {
00978 String string = input.readLine();
00979 String group = input.readLine();
00980 String stateName = input.readLine();
00981
int state;
00982
if (stateName.equals(
"on")) {
00983 state =
SwitchReplacement.ON;
00984 }
else if (stateName.equals(
"on")) {
00985 state =
SwitchReplacement.ON;
00986 }
else if (stateName.equals(
"off")) {
00987 state =
SwitchReplacement.OFF;
00988 }
else {
00989
throw new FileFormatException
00990 (
"Bad state argument to =SWITCH: "+stateName);
00991 }
00992
SwitchReplacement newrpl =
00993
new SwitchReplacement
00994 (string,getGroup(group).index,state);
00995 newrpl.
group = groupIndex;
00996
replacements.addElement(newrpl);
00997 }
else if (line.equals(
"=AUTOLOAD")) {
00998 String group = input.readLine();
00999 String alurl = input.readLine();
01000
if (alurl==null)
01001
throw new FileFormatException
01002 (
"=AUTOLOAD not followed by two lines");
01003 getGroup(group).autoloadURL(
new URL(url,alurl));
01004 }
else if (line.equals(
"=DESCRIPTION")) {
01005 String descr =
"";
01006
while (
true) {
01007 String l = input.readLine();
01008
if (l==null)
01009
throw new FileFormatException(
"=DESCRIPTION ended by EOF");
01010
if (l.equals(
"###"))
break;
01011 descr += l+
"\n";
01012 getGroup(groupIndex).setDescription(descr);
01013 }
01014
01015
01016 }
else if (line.equals(
"=NAME")) {
01017 String name = input.readLine();
01018 String screenName = input.readLine();
01019
if (screenName==null)
01020
throw new FileFormatException
01021 (
"=NAME not followed by two lines");
01022 getGroup(name).setScreenName(screenName);
01023 }
else if (line.equals(
"=JAVAREPLACE")) {
01024
InputReplacement newrpl =
01025 (
InputReplacement)
PluginLoader.load
01026 (
InputReplacement.class,url,input);
01027 newrpl.
group = groupIndex;
01028
replacements.add(newrpl);
01029 }
else if (line.equals(
"=WEBPAGE")) {
01030
loadConfig_native_webpage(input,url);
01031 newWebPages =
true;
01032 }
else if (line.equals(
"") || line.charAt(0)==
'#') {
01033 }
else {
01034
throw new FileFormatException
01035 (
"Unknown config command "+line);
01036 }
01037 }
01038 System.out.println(
"Done.");
01039
if (
newGroups) {
01040
createIMMenu();
newGroups =
false;
01041 }
01042
if (newWebPages) {
01043
createWebPageMenu(); newWebPages =
false;
01044 }
01045 }
01046
01047 void loadConfig_native_webpage(BufferedReader input,
01048 URL source)
throws IOException {
01049 String name = input.readLine();
01050 String url = input.readLine();
01051
if (url==null)
01052
throw new FileFormatException
01053 (
"=WEBPAGE not followed by name/url (two lines)");
01054
WebPage webpage =
new WebPage(name,url);
01055 webpage.
setSourceURL(source);
01056 WebPage.Token token = null;
01057
while (
true) {
01058 String command = input.readLine();
01059
if (command==null)
01060
throw new FileFormatException(
"=WEBPAGE ended by EOF");
01061
else if (command.equals(
":TOKEN")) {
01062 String tokname = input.readLine();
01063
if (tokname==null)
01064
throw new FileFormatException
01065 (
":TOKEN (in =WEBPAGE) not "+
01066
"followed by a line (token's name)");
01067
if (token!=null) webpage.
addToken(token);
01068 token =
new WebPage.Token(tokname);
01069 }
else if (command.equals(
"###")) {
01070
if (token!=null) webpage.
addToken(token);
01071
break;
01072 }
else if (command.equals(
":CONST")) {
01073
if (token==null)
01074
throw new FileFormatException
01075 (
":CONST (in =WEBPAGE) must be preceded by a :TOKEN");
01076 String str = input.readLine();
01077
if (str==null)
01078
throw new FileFormatException
01079 (
":CONST (in =WEBPAGE) not followed by a line");
01080 token.setConst(str);
01081 }
else if (command.equals(
":ENCODING")) {
01082
if (token==null)
01083
throw new FileFormatException
01084 (
":ENCODING (in =WEBPAGE) must be preceded by a :TOKEN");
01085 String enc = input.readLine();
01086
if (enc==null)
01087
throw new FileFormatException
01088 (
":ENCODING (in =WEBPAGE) not followed by a line");
01089
if (enc.equals(
"hex"))
01090 token.setEncoding(WebPage.Token.ENCODE_HEX);
01091
else if (enc.equals(
"dec"))
01092 token.setEncoding(WebPage.Token.ENCODE_DEC);
01093
else if (enc.equals(
"url"))
01094 token.setEncoding(WebPage.Token.ENCODE_URL);
01095
else
01096
throw new FileFormatException
01097 (
":ENCODING (in =WEBPAGE) followed by invalid encoding "+enc);
01098 }
else if (command.equals(
":CHARSET")) {
01099
if (token==null)
01100
throw new FileFormatException
01101 (
":CHARSET (in =WEBPAGE) must be preceded by a :TOKEN");
01102 String cs = input.readLine();
01103
if (cs==null)
01104
throw new FileFormatException
01105 (
":CHARSET (in =WEBPAGE) not followed by a line");
01106 token.setCharset(cs);
01107 }
else if (command.equals(
":SHORTNAME")) {
01108 String sn = input.readLine();
01109
if (sn==null)
01110
throw new FileFormatException
01111 (
":SHORTNAME (in =WEBPAGE) not followed by a line");
01112 webpage.
setShortName(sn);
01113 }
else if (command.equals(
":TOPFRAME")) {
01114 webpage.
setNeedsTopFrame(
true);
01115 }
else if (command.equals(
":DESCRIPTION")) {
01116 String descr =
"";
01117
while (
true) {
01118 String l = input.readLine();
01119
if (l==null)
01120
throw new FileFormatException(
":DESCRIPTION (in =WEBPAGE) ended by EOF");
01121
if (l.equals(
":###"))
break;
01122 descr += l+
"\n";
01123
if (token!=null)
01124
throw new FileFormatException
01125 (
":DESCRIPTION (in =WEBPAGE) must come prior to any :TOKEN");
01126 webpage.
setDescription(descr);
01127 }
01128 }
else {
01129
throw new FileFormatException
01130 (
"Unknown webpage parameter "+command);
01131 }
01132 }
01133
if (!
webPages.contains(webpage))
01134
webPages.add(webpage);
01135 }
01136
01137 void createWebPageMenu() {
01138
01139
int numWP =
webPages.size();
01140
WebPage wp[] =
new WebPage[numWP];
01141
for (
int i=0; i<numWP; i++)
01142 wp[i] = (WebPage)
webPages.elementAt(i);
01143
imContext.
createWebPageMenu(wp);
01144 }
01145
01146 void createIMMenu() {
01147 String im[] =
new String[
groups.size()];
01148
int size =
groups.size();
01149
for (
int i=0; i<size; i++) {
01150
Group g = (
Group)
groups.elementAt(i);
01151
if (!g.
isHidden()) {
01152 im[i] = g.
getName();
01153 }
01154 }
01155
imContext.
createIMMenu(im);
01156 }
01157
01158 public boolean isInputMethodActive(String name) {
01159
return getGroup(name).
isActive();
01160 }
01161 public String
getInputMethodScreenName(String name) {
01162
return getGroup(name).
getScreenName();
01163 }
01164
01165 public void deselectNonHiddenGroups() {
01166
for (Enumeration e =
groups.elements();
01167 e.hasMoreElements();) {
01168
Group g = (
Group)e.nextElement();
01169
if (!g.
isHidden() && g.
isActive())
01170 g.
deactivate();
01171 }
01172 }
01173
01174 public boolean getSingleInputMethod() {
return singleInputMethod; };
01175 public void setSingleInputMethod(
boolean on)
throws IOException {
01176
singleInputMethod = on;
01177
if (on) {
01178
Group last =
lastActivatedGroup;
01179
if (!last.
isActive()) last = null;
01180
deselectNonHiddenGroups();
01181
if (last!=null) last.
activate();
01182 }
01183 }
01184
01185 public static StringBuffer
urlEncode(String string) {
01186
return urlEncode(string,
"utf-8"); };
01187 public static final char hexDigits[] = {
01188
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
01189
'8',
'9',
'A',
'B',
'C',
'D',
'E',
'F' };
01190 public static StringBuffer urlEncode(String string, String charset) {
01191 byte str[];
01192
try {
01193 str = string.getBytes(charset);
01194 }
catch (UnsupportedEncodingException e) {
01195
throw new RuntimeException(
"Internal error: "+e);
01196 }
01197 StringBuffer res =
new StringBuffer();
01198
for (
int i=0; i<str.length; i++) {
01199
int code = str[i]&0xFF;
01200
if (code >=
'A' && code <=
'Z') {
01201 res.append((
char)code);
01202 }
else if (code >=
'a' && code <=
'z') {
01203 res.append((
char)code);
01204 }
else if (code >=
'0' && code <=
'9') {
01205 res.append((
char)code);
01206 }
else switch (code) {
01207
case '!':
case '#':
case '$':
case '\'':
case '(':
case ')':
case '*':
01208
case ',':
case '-':
case '.':
case '/':
case ':':
case ';':
01209
case '<':
case '=':
case '>':
case '@':
case '[':
case ']':
case '^':
01210
case '_':
case '`':
case '\\':
case '{':
case '|':
case '}':
case '~':
01211 res.append((
char)code);
break;
01212
default:
01213 res.append(
'%');
01214 res.append(
hexDigits[code>>4]);
01215 res.append(
hexDigits[code&0xF]);
01216 }
01217 }
01218
return res;
01219 }
01220 }
01221