View Javadoc

1   package com.tapina.robe.swi;
2   
3   import com.tapina.robe.runtime.Environment;
4   import com.tapina.robe.runtime.MemoryMap;
5   import com.tapina.robe.runtime.RawDataBlock;
6   
7   import java.lang.reflect.Method;
8   import java.nio.ByteBuffer;
9   import java.nio.CharBuffer;
10  import java.nio.charset.Charset;
11  import java.text.SimpleDateFormat;
12  import java.util.*;
13  import java.util.logging.Logger;
14  
15  /***
16   * Created by IntelliJ IDEA.
17   * User: gareth
18   * Date: Sep 9, 2003
19   * Time: 7:51:48 AM
20   */
21  public final class Territory extends SWIHandler {
22      private static Territory ourInstance;
23  
24      public synchronized static Territory getInstance() {
25          if (ourInstance == null) {
26              ourInstance = new Territory();
27          }
28          return ourInstance;
29      }
30  
31      private Territory() {
32      }
33  
34      public static int getBase() {
35          return 0x43040;
36      }
37  
38      private static final String[] methodNames = new String[] {
39          "", "", "", "", "", "", "", "", // 0x43040
40          "", "", "", "ConvertDateAndTime", "", "", "", "", // 0x43048
41          "", "", "", "", "", "", "CharacterPropertyTable", "LowerCaseTable", // 0x43050
42          "UpperCaseTable", "", "", "", "", "", "", "", // 0x43058
43          "", "", "", "", "", "", "", "", // 0x43060
44          "", "", "", "", "", "", "", "", // 0x43068
45          "", "", "", "", "", "", "", "", // 0x43070
46          "", "", "", "", "", "", "", "" // 0x43078
47      };
48  
49      public static Method getMethod(Integer offset) throws NoSuchMethodException {
50          return getMethod(methodNames[offset.intValue()]);
51      }
52  
53      private static final Method getMethod(String name) throws NoSuchMethodException {
54          return Territory.class.getMethod(name, METHOD_PARAMETERS);
55      }
56  
57      private final static String[] languages = new String[] {
58          null, "en", null, null, "it", "es", "fr", "de", "pt"
59      };
60  
61      private final static String[] countries = new String[] {
62          null, "GB", null, null, "IT", "ES", "FR", "DE", "PT", "Esperanto", "GR",
63          "SE", "FI", null, "DK", "NO", "IS", "CA", "CA", "CA", "TK",
64          "Arabic", "IE", "HK", "RU", "RU", "IL", "MX", "LatinAm", null, null,
65          null, null, null, null, null, null, null, null, null, null,
66          null, null, null, null, null, null, null, "US"
67      };
68  
69      private final static Locale getLocaleForTerritory(int territoryNumber) {
70          return new Locale(languages[territoryNumber], countries[territoryNumber]);
71      }
72  
73      private final static String[] charsets = new String[] {
74          null, "ISO-8859-1"
75      };
76  
77      private final static Charset getCharsetForTerritory(int territoryNumber) {
78          return Charset.forName(charsets[territoryNumber]);
79      }
80  
81      private final static Map characterPropertyTableDataBlockMap = new HashMap(30);
82      private final static Map lowerCaseTableDataBlockMap = new HashMap(30);
83      private final static Map upperCaseTableDataBlockMap = new HashMap(30);
84  
85      /***
86       * Format the date and time in the given territory
87       * @param env
88       */
89      public final void ConvertDateAndTime(Environment env) {
90          final int[] R = env.getCpu().R;
91          final MemoryMap memoryMap = env.getMemoryMap();
92          String out = convertDateAndTime(R[0], OS.convertToDate(memoryMap.get5ByteValue(R[1])),
93                  memoryMap.getString0(R[4]));
94          R[0] = R[2];
95          if (out.length() > R[3] - 1) {
96              out = out.substring(0, R[3] - 1);
97          }
98          memoryMap.storeString0(R[2], out);
99          R[1] = R[2] + out.length();
100         R[2] = R[3] - out.length();
101         R[3] = R[4];
102     }
103 
104     public final String convertDateAndTime(int territoryNumber, Date date, String format) {
105         if (territoryNumber == -1) {
106             territoryNumber = getDefaultTerritoryNumber();
107         }
108         format = convertDateFormatString(format);
109         final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format, getLocaleForTerritory(territoryNumber));
110         return simpleDateFormat.format(date);
111     }
112 
113     private final String convertDateFormatString(String acornFormat) {
114         StringBuffer outputFormat = new StringBuffer(acornFormat.length());
115         boolean inQuotes = false;
116         for (int i = 0; i < acornFormat.length(); i++) {
117             final char c = acornFormat.charAt(i);
118             switch (c) {
119                 case '\'':
120                     if (inQuotes) {
121                         outputFormat.append('\'');
122                         inQuotes = false;
123                     }
124                     outputFormat.append("\'\'"); // Use double-single-quote for a single quote
125                     break;
126                 case '%':
127                     if (inQuotes) {
128                         outputFormat.append('\'');
129                         inQuotes = false;
130                     }
131                     if (acornFormat.charAt(i + 1) == '%') {
132                         i++; // Treat the next % as a literal
133                     } else {
134                         // This is a token
135                         if (acornFormat.charAt(i + 1) == '0') {
136                             outputFormat.append('\0');
137                             i++;
138                         } else {
139                             boolean suppressZeros = acornFormat.charAt(i + 1) == 'Z';
140                             if (suppressZeros) {
141                                 i++;
142                             }
143                             final String token = acornFormat.substring(i + 1, i + 3); // Two-character token
144                             if (token.equals("DY")) { // Date (08)
145                                 outputFormat.append(suppressZeros? "d" : "dd");
146                             } else if (token.equals("ST")) { // st, th or nd by date
147                                 log.warning("No Java equivalent of '%ST' in format string");
148                                 outputFormat.append("'  '");
149                             } else if (token.equals("MN")) { // Month as number (04)
150                                 outputFormat.append(suppressZeros? "M" : "MM");
151                             } else if (token.equals("MO")) { // Month as long entry (April)
152                                 outputFormat.append("MMMM");
153                             } else if (token.equals("M3")) { // Month as 3 letters (Apr)
154                                 outputFormat.append("MMM");
155                             } else if (token.equals("WK")) { // Week of year
156                                 outputFormat.append(suppressZeros? "w" : "ww");
157                             } else if (token.equals("DN")) { // Day within year (098)
158                                 outputFormat.append(suppressZeros? "D" : "DDD");
159                             } else if (token.equals("WN")) { // Week day as number (7 = Saturday)
160                                 outputFormat.append("F");
161                             } else if (token.equals("W3")) { // Week day as 3 letters (Sat)
162                                 outputFormat.append("EEE");
163                             } else if (token.equals("WE")) { // Week day as long (Saturday)
164                                 outputFormat.append("EEEE");
165                             } else if (token.equals("CE")) { // Century as 2 digits (19)
166                                 if (acornFormat.substring(i + 3, i + 6).equals("%YR")) {
167                                     outputFormat.append("yyyy");
168                                     i += 3;
169                                 } else {
170                                     log.warning("Cannot output century without year (%YR)");
171                                 }
172                             } else if (token.equals("YR")) { // Year as 2 digits (95)
173                                 outputFormat.append("yy");
174                             } else if (token.equals("24")) { // Hour as 24 hour (15)
175                                 outputFormat.append(suppressZeros? "H": "HH");
176                             } else if (token.equals("12")) { // Hour as 12 hour (03)
177                                 outputFormat.append(suppressZeros? "h" : "hh");
178                             } else if (token.equals("AM") || token.equals("PM")) { // am or pm
179                                 outputFormat.append("a");
180                             } else if (token.equals("MI")) { // Minute as 2 digits (25)
181                                 outputFormat.append(suppressZeros? "m" : "mm");
182                             } else if (token.equals("SE")) { // Second as 2 digits (50)
183                                 outputFormat.append(suppressZeros ? "s" : "ss");
184                             } else if (token.equals("CS")) { // Centisecond as 2 digits (87)
185                                 log.warning("Centiseconds substituted with milliseconds");
186                                 outputFormat.append(suppressZeros ? "S" : "SS");
187                             } else if (token.equals("TZ")) { // Time zone (BST/DST/+01:00, etc)
188                                 outputFormat.append("zzz");
189                             }
190                             i += 2;
191                         }
192                         break;
193                     } // Fall through, we are evaluating a '%'
194                 default:
195                     if (!inQuotes) {
196                         outputFormat.append('\'');
197                         inQuotes = true;
198                     }
199                     outputFormat.append(c);
200             }
201         }
202         if (inQuotes) {
203             outputFormat.append('\'');
204         }
205         return outputFormat.toString();
206     }
207 
208     /***
209      * Return an data block of bytes which are the lower-case representations of
210      * the first 256 characters in the default character set for the given territory.
211      * @param env
212      */
213     public final void LowerCaseTable(Environment env) {
214         final int[] R = env.getCpu().R;
215         if (R[0] == -1) {
216             R[0] = getDefaultTerritoryNumber();
217         }
218         final Integer territoryNumber = new Integer(R[0]);
219         final RawDataBlock dataBlock;
220         if (!lowerCaseTableDataBlockMap.containsKey(territoryNumber)) {
221             final char[] table = getLowerCaseTable(territoryNumber.intValue());
222             dataBlock = createCharDataBlock(env, territoryNumber, table);
223             lowerCaseTableDataBlockMap.put(territoryNumber, dataBlock);
224         } else {
225             dataBlock = (RawDataBlock) lowerCaseTableDataBlockMap.get(territoryNumber);
226         }
227         R[0] = dataBlock.getAddress();
228     }
229 
230     /***
231      * Return an data block of bytes which are the upper-case representations of
232      * the first 256 characters in the default character set for the given territory.
233      * @param env
234      */
235     public final void UpperCaseTable(Environment env) {
236         final int[] R = env.getCpu().R;
237         if (R[0] == -1) {
238             R[0] = getDefaultTerritoryNumber();
239         }
240         final Integer territoryNumber = new Integer(R[0]);
241         final RawDataBlock dataBlock;
242         if (!upperCaseTableDataBlockMap.containsKey(territoryNumber)) {
243             final char[] table = getUpperCaseTable(territoryNumber.intValue());
244             dataBlock = createCharDataBlock(env, territoryNumber, table);
245             upperCaseTableDataBlockMap.put(territoryNumber, dataBlock);
246         } else {
247             dataBlock = (RawDataBlock) upperCaseTableDataBlockMap.get(territoryNumber);
248         }
249         R[0] = dataBlock.getAddress();
250     }
251 
252     private RawDataBlock createCharDataBlock(Environment env, final Integer territoryNumber, final char[] table) {
253         final RawDataBlock charBlock = env.getMemoryMap().createSystemDataBlock(table.length);
254         final Charset charset = getCharsetForTerritory(territoryNumber.intValue());
255         charset.encode(CharBuffer.wrap(table)).get(charBlock.getBytes());
256         return charBlock;
257     }
258 
259     public final void CharacterPropertyTable(Environment env) {
260         final int[] R = env.getCpu().R;
261         if (R[0] == -1) {
262             R[0] = getDefaultTerritoryNumber();
263         }
264         final Integer territoryNumber = new Integer(R[0]);
265         final RawDataBlock dataBlock;
266         if (!characterPropertyTableDataBlockMap.containsKey(territoryNumber)) {
267             final BitSet bits = getCharacterPropertyTable(territoryNumber.intValue(), R[1]);
268             dataBlock = env.getMemoryMap().createSystemDataBlock(32); // 256 bits
269             final byte[] bytes = dataBlock.getBytes();
270             // bytes will be initialised to zero, so we're just interested in the set bits
271             // this should be a lot faster than stepping through every bit since the vast
272             // majority will always be unset
273             for (int bit = bits.nextSetBit(0); bit >= 0; bit = bits.nextSetBit(bit + 1)) {
274                 bytes[bit >> 3] |= (1 << (bit & 3));
275             }
276             characterPropertyTableDataBlockMap.put(territoryNumber, dataBlock);
277         } else {
278             dataBlock = (RawDataBlock) characterPropertyTableDataBlockMap.get(territoryNumber);
279         }
280         R[0] = dataBlock.getAddress();
281     }
282 
283     public static final int CONTROL_CODE = 0;
284     public static final int UPPERCASE = 1;
285     public static final int LOWERCASE = 2;
286     public static final int ALPHABETIC = 3;
287     public static final int PUNCTUATION = 4;
288     public static final int WHITE_SPACE = 5;
289     public static final int DIGIT = 6;
290     public static final int HEX_DIGIT = 7;
291     public static final int ACCENT = 8;
292     public static final int FORWARDS = 9;
293     public static final int BACKWARDS = 10;
294 
295     private abstract static class CharacterTest {
296         Logger log = Logger.getLogger(CharacterTest.class.getName());
297         abstract boolean test(char ch);
298     }
299     private static final CharacterTest[] CHARACTER_TESTS = new CharacterTest[] {
300         new CharacterTest() {
301             boolean test(char ch) {
302                 return Character.isISOControl(ch);
303             }
304         },
305         new CharacterTest() {
306             boolean test(char ch) {
307                 return Character.isUpperCase(ch);
308             }
309         },
310         new CharacterTest() {
311             boolean test(char ch) {
312                 return Character.isLowerCase(ch);
313             }
314         },
315         new CharacterTest() {
316             boolean test(char ch) {
317                 return Character.isLetter(ch);
318             }
319         },
320         new CharacterTest() {
321             boolean test(char ch) {
322                 return !Character.isLetterOrDigit(ch) && !Character.isWhitespace(ch)
323                         && !Character.isISOControl(ch);
324             }
325         },
326         new CharacterTest() {
327             boolean test(char ch) {
328                 return Character.isWhitespace(ch);
329             }
330         },
331         new CharacterTest() {
332             boolean test(char ch) {
333                 return Character.isDigit(ch);
334             }
335         },
336         new CharacterTest() {
337             boolean test(char ch) {
338                 return "0123456789ABCDEF".indexOf(ch) > 0;
339             }
340         },
341         new CharacterTest() {
342             boolean test(char ch) {
343                 // If a character is a letter and not in A-Za-z then it's accented
344                 return Character.isLetter(ch) && !String.valueOf(ch).matches("[A-Za-z]");
345             }
346         },
347         new CharacterTest() {
348             boolean test(char ch) {
349                 log.warning("Character directionality not supported.");
350                 return true;
351             }
352         },
353         new CharacterTest() {
354             boolean test(char ch) {
355                 log.warning("Character directionality not supported.");
356                 return false;
357             }
358         }
359     };
360 
361     public final BitSet getCharacterPropertyTable(int territoryNumber, int property) {
362         if (territoryNumber == -1) {
363             territoryNumber = getDefaultTerritoryNumber();
364         }
365         final CharBuffer allChars = getWholeCharset(territoryNumber);
366         final BitSet result = new BitSet(allChars.length());
367         for (int position = 0; allChars.hasRemaining(); position++) {
368             final char c = allChars.get();
369             result.set(position, CHARACTER_TESTS[property].test(c));
370         }
371         return result;
372     }
373 
374     /***
375      * Return an array of characters which are the lower-case representations of
376      * the first 256 characters in the default character set for the given territory.
377      * @param territoryNumber or -1 for the current territory
378      * @return array of lower-case characters
379      */
380     public final char[] getLowerCaseTable(int territoryNumber) {
381         if (territoryNumber == -1) {
382             territoryNumber = getDefaultTerritoryNumber();
383         }
384         String allChars = getWholeCharset(territoryNumber).toString();
385         // Lower case them and convert to an array
386         return allChars.toLowerCase().toCharArray();
387     }
388 
389     /***
390      * Return an array of characters which are the upper-case representations of
391      * the first 256 characters in the default character set for the given territory.
392      * @param territoryNumber or -1 for the current territory
393      * @return array of upper-case characters
394      */
395     public final char[] getUpperCaseTable(int territoryNumber) {
396         if (territoryNumber == -1) {
397             territoryNumber = getDefaultTerritoryNumber();
398         }
399         String allChars = getWholeCharset(territoryNumber).toString();
400         // Upper case them and convert to an array
401         return allChars.toUpperCase().toCharArray();
402     }
403 
404     int getDefaultTerritoryNumber() {
405         log.warning("Default territory is always set to UK");
406         return 1;
407     }
408 
409     private CharBuffer getWholeCharset(int territoryNumber) {
410         // Get the first 256 characters in the right charset
411         final Charset charset = getCharsetForTerritory(territoryNumber);
412         final ByteBuffer encodedChars = ByteBuffer.allocate(256);
413         for (int i = 0; i < 256; i++) {
414             encodedChars.put(i, (byte) i);
415         }
416         return charset.decode(encodedChars);
417     }
418 
419     public static void main(String[] args) {
420         Environment environment = new Environment(0x8000, new byte[0]);
421         Territory territory = getInstance();
422 
423         environment.getCpu().R[0] = 1;
424         territory.UpperCaseTable(environment);
425         byte[] upperBytes =
426                 ((RawDataBlock) environment.getMemoryMap().getDataBlock(environment.getCpu().R[0])).getBytes();
427         char[] upperChars = territory.getUpperCaseTable(1);
428         BitSet upperBits = territory.getCharacterPropertyTable(1, Territory.UPPERCASE);
429         for (int i = 0; i < upperChars.length; i++) {
430             final int value = ((int) upperBytes[i]) & 0xff;
431             System.out.println(i + " " + upperChars[i] + " " + value + (upperBits.get(i)? "*" : ""));
432         }
433 
434     }
435 
436 }