1 //
2 //  XPathTable.java
3 //  fourjay
4 //
5 //  Created by Steven R. Loomis on 20/10/2005.
6 //  Copyright 2005-2014 IBM. All rights reserved.
7 //
8 //
9 
10 package org.unicode.cldr.web;
11 
12 import java.sql.Connection;
13 import java.sql.PreparedStatement;
14 import java.sql.ResultSet;
15 import java.sql.SQLException;
16 import java.sql.Statement;
17 import java.util.Collections;
18 import java.util.HashSet;
19 import java.util.Hashtable;
20 import java.util.Map;
21 import java.util.Set;
22 
23 import org.unicode.cldr.icu.LDMLConstants;
24 import org.unicode.cldr.util.CLDRConfig;
25 import org.unicode.cldr.util.CLDRConfig.Environment;
26 import org.unicode.cldr.util.LDMLUtilities;
27 import org.unicode.cldr.util.PrettyPath;
28 import org.unicode.cldr.util.StringId;
29 import org.unicode.cldr.util.XMLSource;
30 import org.unicode.cldr.util.XPathParts;
31 
32 import com.ibm.icu.dev.util.ElapsedTimer;
33 import com.ibm.icu.impl.Utility;
34 
35 /**
36  * This class maps between full and partial xpaths, and the small integers (xpids) which
37  * are actually stored in the database. It keeps an in-memory cache which is
38  * populated as ids are requested.
39  *
40  *
41  * Definitions:
42  *    xpath:        an XPath, such as "//ldml/shoeSize"
43  *    xpid / int:   an integer 'token' value, such as 123.
44  *                   This is old and deprecated.
45  *                   Specific to this instance of SurveyTool.
46  *                   This is usually what is meant by "int xpath" in code or in the database.
47  *    strid / hex:  a hexadecimal "hash" of the full xpath such as "b1dfb436c841a73".
48  *                   This is the preferred method of condensing of xpaths.
49  *                   Note that you can't calculate the xpath from this without a look-up table.
50  *    "long" StringID:    this is the "long" form of the hex id.  Not used within the SurveyTool, some CLDR tools use it.
51  */
52 public class XPathTable {
53     public static final String CLDR_XPATHS = "cldr_xpaths";
54 
55     private PrettyPath ppath = new PrettyPath();
56 
57     private static final boolean DEBUG = false;
58 
59     /**
60      * Called by SM to create the reg
61      *
62      * @param ourConn
63      *            the conn to use
64      */
createTable(Connection ourConn)65     public static XPathTable createTable(Connection ourConn) throws SQLException {
66         try {
67             boolean isNew = !DBUtils.hasTable(ourConn, CLDR_XPATHS);
68             XPathTable reg = new XPathTable();
69             if (isNew) {
70                 reg.setupDB();
71             }
72             reg.loadXPaths(ourConn);
73 
74             return reg;
75         } finally {
76             DBUtils.closeDBConnection(ourConn);
77         }
78     }
79 
loadXPaths(Connection conn)80     private void loadXPaths(Connection conn) throws SQLException {
81         if (stringToId.size() != 0) { // Only load the entire stringToId map
82             // once.
83             return;
84         }
85         ElapsedTimer et = new ElapsedTimer("XPathTable: load all xpaths");
86         int ixpaths = 0;
87         PreparedStatement queryStmt = DBUtils.prepareForwardReadOnly(conn, "SELECT id,xpath FROM " + CLDR_XPATHS);
88         // First, try to query it back from the DB.
89         ResultSet rs = queryStmt.executeQuery();
90         while (rs.next()) {
91             int id = rs.getInt(1);
92             String xpath = Utility.unescape(rs.getString(2));
93             setById(id, xpath);
94             stat_dbFetch++;
95             ixpaths++;
96         }
97         queryStmt.close();
98         final boolean hushMessages = CLDRConfig.getInstance().getEnvironment() == Environment.UNITTEST;
99         if (!hushMessages) System.err.println(et + ": " + ixpaths + " loaded");
100     }
101 
102     /**
103      * Called by SM to shutdown
104      *
105      * @deprecated unneeded
106      */
107     @Deprecated
shutdownDB()108     public void shutdownDB() throws SQLException {
109 
110     }
111 
112     /**
113      * internal - called to setup db
114      */
setupDB()115     private void setupDB() throws SQLException {
116         String sql = null;
117         Connection conn = null;
118         try {
119             conn = DBUtils.getInstance().getDBConnection();
120             SurveyLog.debug("XPathTable DB: initializing... conn: " + conn + ", db:" + CLDR_XPATHS + ", id:"
121                 + DBUtils.DB_SQL_IDENTITY);
122             Statement s = conn.createStatement();
123             if (s == null) {
124                 throw new InternalError("S is null");
125             }
126             String xpathindex = "xpath";
127             String uniqueness = ", " + "unique(xpath)";
128             if (DBUtils.db_Mysql) {
129                 uniqueness = "";
130                 xpathindex = "xpath(768)";
131             }
132             sql = ("create table " + CLDR_XPATHS + "(id INT NOT NULL " + DBUtils.DB_SQL_IDENTITY + ", " + "xpath "
133                 + DBUtils.DB_SQL_VARCHARXPATH + DBUtils.DB_SQL_MB4 + " NOT NULL" + uniqueness + ") " + DBUtils.DB_SQL_ENGINE_INNO + DBUtils.DB_SQL_MB4);
134             s.execute(sql);
135             sql = ("CREATE INDEX " + CLDR_XPATHS + "_id on " + CLDR_XPATHS + "(id)");
136             s.execute(sql);
137             sql = ("CREATE INDEX " + CLDR_XPATHS + "_xpath on " + CLDR_XPATHS + " (" + xpathindex + ")");
138             s.execute(sql);
139             sql = null;
140             s.close();
141             conn.commit();
142         } finally {
143             DBUtils.close(conn);
144             if (sql != null) {
145                 SurveyLog.logger.warning("Last SQL: " + sql);
146             }
147         }
148     }
149 
150     public Hashtable<String, Integer> stringToId = new Hashtable<>(4096); // public for statistics only
151     public Hashtable<Long, String> sidToString = new Hashtable<>(4096); // public for statistics only
152 
statistics()153     public String statistics() {
154         return "DB: " + stat_dbAdd + "add/" + stat_dbFetch + "fetch/"
155             + (stat_dbAdd + stat_dbFetch) + "total." + "-" + idStats();
156     }
157 
158     private static int stat_dbAdd = 0;
159     private static int stat_dbFetch = 0;
160 
XPathTable()161     public XPathTable() {
162     }
163 
164     /**
165      * SpecialTable implementation
166      */
167     IntHash<String> xptHash = new IntHash<>();
168 
idToString_put(int id, String str)169     String idToString_put(int id, String str) {
170         return xptHash.put(id, str);
171     }
172 
idToString_get(int id)173     String idToString_get(int id) {
174         return xptHash.get(id);
175     }
176 
idStats()177     String idStats() {
178         return xptHash.stats();
179     }
180 
181     /** END specialtable implementation */
182 
183     /**
184      * Loads all xpath-id mappings from the database. If there are any xpaths in
185      * the specified XMLSource which are not already in the database, they will
186      * be created here.
187      */
loadXPaths(XMLSource source)188     public synchronized void loadXPaths(XMLSource source) {
189         // Get list of xpaths that aren't already loaded.
190         Set<String> unloadedXpaths = new HashSet<>();
191         for (String xpath : source) {
192             unloadedXpaths.add(xpath);
193         }
194         unloadedXpaths.removeAll(stringToId.keySet());
195 
196         Connection conn = null;
197         PreparedStatement queryStmt = null;
198         try {
199             conn = DBUtils.getInstance().getDBConnection();
200             if (!DEBUG) {
201                 addXpaths(unloadedXpaths, conn);
202             } else {
203                 // Debug: add paths one by one.
204                 for (final String path : unloadedXpaths) {
205                     try {
206                         addXpaths(Collections.singleton(path), conn);
207                     } catch (SQLException se) {
208                         SurveyLog.logException(se, "While loading xpath: " + Utility.escape(path));
209                         throw se;
210                     }
211                 }
212             }
213         } catch (SQLException sqe) {
214             SurveyLog.logException(sqe, "loadXPaths(" + source.getLocaleID() + ")");
215             SurveyMain.busted("loadXPaths(" + source.getLocaleID() + ")", sqe);
216         } finally {
217             DBUtils.close(queryStmt, conn);
218         }
219     }
220 
221     /**
222      * Add a set of xpaths to the database.
223      *
224      * @param xpaths
225      * @param conn
226      * @throws SQLException
227      */
addXpaths(Set<String> xpaths, Connection conn)228     private synchronized void addXpaths(Set<String> xpaths, Connection conn) throws SQLException {
229         if (xpaths.size() == 0)
230             return;
231 
232         PreparedStatement queryStmt = null;
233         PreparedStatement insertStmt = null;
234         // Insert new xpaths.
235         insertStmt = conn.prepareStatement("INSERT INTO " + CLDR_XPATHS + " (xpath) " + " values ("
236             + " ?)");
237         for (String xpath : xpaths) {
238             insertStmt.setString(1, Utility.escape(xpath));
239             insertStmt.addBatch();
240             stat_dbAdd++;
241         }
242         insertStmt.executeBatch();
243         conn.commit();
244         insertStmt.close();
245 
246         // PreparedStatement.getGeneratedKeys() only returns the ID of the
247         // last INSERT statement, so we have to improvise here by performing
248         // another SELECT to get the newly-inserted IDs.
249         queryStmt = conn.prepareStatement("SELECT id,xpath FROM " + CLDR_XPATHS + " ORDER BY id DESC");
250         queryStmt.setMaxRows(xpaths.size());
251         queryStmt.setFetchSize(xpaths.size());
252         ResultSet rs = queryStmt.executeQuery();
253         while (rs.next()) {
254             int id = rs.getInt(1);
255             String xpath = Utility.unescape(rs.getString(2));
256             setById(id, xpath);
257         }
258         queryStmt.close();
259     }
260 
261     /**
262      * @return the xpath's id (as an Integer)
263      */
addXpath(String xpath, boolean addIfNotFound, Connection inConn)264     private synchronized Integer addXpath(String xpath, boolean addIfNotFound, Connection inConn) {
265         Integer nid = stringToId.get(xpath); // double check
266         if (nid != null) {
267             return nid;
268         }
269 
270         Connection conn = null;
271         PreparedStatement queryStmt = null;
272         PreparedStatement insertStmt = null;
273         try {
274             if (inConn != null) {
275                 conn = inConn;
276             } else {
277                 conn = DBUtils.getInstance().getDBConnection();
278             }
279             queryStmt = conn.prepareStatement("SELECT id FROM " + CLDR_XPATHS + "   " + " where XPATH="
280                 + " ? ");
281             queryStmt.setString(1, Utility.escape(xpath));
282             // First, try to query it back from the DB.
283             ResultSet rs = queryStmt.executeQuery();
284             if (!rs.next()) {
285                 if (!addIfNotFound) {
286                     return -1;
287                 } else {
288                     // add it
289                     insertStmt = conn.prepareStatement("INSERT INTO " + CLDR_XPATHS + " (xpath ) " + " values ("
290                         + " ?)", Statement.RETURN_GENERATED_KEYS);
291 
292                     insertStmt.setString(1, Utility.escape(xpath));
293                     insertStmt.execute();
294                     conn.commit();
295                     rs = insertStmt.getGeneratedKeys();
296                     if (!rs.next()) {
297                         SurveyLog.errln("Couldn't retrieve newly added xpath " + xpath);
298                     } else {
299                         stat_dbAdd++;
300                     }
301                 }
302             } else {
303                 stat_dbFetch++;
304             }
305 
306             int id = rs.getInt(1);
307             nid = Integer.valueOf(id);
308             setById(id, xpath);
309             // logger.info("Mapped " + id + " back to " + xpath);
310             rs.close();
311             return nid;
312         } catch (SQLException sqe) {
313             SurveyLog.logger.warning("xpath [" + xpath + "] len " + xpath.length());
314             SurveyLog.logger.severe("XPathTable: Failed in addXPath(" + xpath + "): " + DBUtils.unchainSqlException(sqe));
315             SurveyMain.busted("XPathTable: Failed in addXPath(" + xpath + "): " + DBUtils.unchainSqlException(sqe));
316         } finally {
317             if (inConn != null) {
318                 conn = null; // don't close
319             }
320             DBUtils.close(insertStmt, queryStmt, conn);
321         }
322         return null; // an exception occured.
323     }
324 
325     //    /**
326     //     * needs a new name.. This uses the string pool and also adds it to the
327     //     * table
328     //     */
329     //    final String poolx(String x) {
330     //        if (x == null) {
331     //            return null;
332     //        }
333     //
334     //        String y = (String) xstringHash.get(x);
335     //        if (y == null) {
336     //            xstringHash.put(x, x);
337     //
338     //            // addXpath(x);
339     //
340     //            return x;
341     //        } else {
342     //            return y;
343     //        }
344     //    }
345 
346     /**
347      * API for get by ID
348      */
getById(int id)349     public final String getById(int id) {
350         if (id == -1) {
351             return null;
352         }
353         String s = idToString_get(id);
354         if (s != null) {
355             return s;
356         }
357         throw new RuntimeException(id + " not found. Make sure loadXpaths() was called first!");
358     }
359 
360     /**
361      * Adds an xpathid-xpath value pair to the XPathTable. This method is used
362      * by classes to cache the values obtained by using their own queries.
363      *
364      * @param id
365      * @param xpath
366      */
setById(int id, String xpath)367     public final void setById(int id, String xpath) {
368         stringToId.put(idToString_put(id, xpath), id);
369         sidToString.put(getStringID(xpath), xpath);
370     }
371 
372     /**
373      * get an xpath id by value, add it if not found
374      *
375      * @param xpath
376      *            string string to add
377      * @return the id for the specified path
378      */
getByXpath(String xpath)379     public final int getByXpath(String xpath) {
380         Integer nid = stringToId.get(xpath);
381         if (nid != null) {
382             return nid.intValue();
383         } else {
384             return addXpath(xpath, true, null).intValue();
385         }
386     }
387 
388     /**
389      * Look up xpath id by value. Return -1 if not found
390      *
391      * @param xpath
392      * @return id, or -1 if not found
393      */
peekByXpath(String xpath)394     public final int peekByXpath(String xpath) {
395         Integer nid = stringToId.get(xpath);
396         if (nid != null) {
397             return nid.intValue();
398         } else {
399             return addXpath(xpath, false, null).intValue();
400         }
401     }
402 
403     /**
404      * get an xpath id by value, add it if not found
405      *
406      * @param xpath
407      *            string string to add
408      * @return the id for the specified path
409      */
getByXpath(String xpath, Connection conn)410     public final int getByXpath(String xpath, Connection conn) {
411         Integer nid = stringToId.get(xpath);
412         if (nid != null) {
413             return nid.intValue();
414         } else {
415             return addXpath(xpath, true, conn).intValue();
416         }
417     }
418 
419     /**
420      * Look up xpath id by value. Return -1 if not found
421      *
422      * @param xpath
423      * @return id, or -1 if not found
424      */
peekByXpath(String xpath, Connection conn)425     public final int peekByXpath(String xpath, Connection conn) {
426         Integer nid = stringToId.get(xpath);
427         if (nid != null) {
428             return nid.intValue();
429         } else {
430             return addXpath(xpath, false, conn).intValue();
431         }
432     }
433 
434     /**
435      *
436      * @param path
437      * @param xpp
438      * @return
439      */
altFromPathToTinyXpath(String path)440     public String altFromPathToTinyXpath(String path) {
441         return whatFromPathToTinyXpath(path, LDMLConstants.ALT);
442     }
443 
444     /**
445      *
446      * @param path
447      * @return
448      *
449      * Called by handlePathValue and makeProposedFile
450      */
removeAlt(String path)451     public static String removeAlt(String path) {
452         XPathParts xpp = XPathParts.getFrozenInstance(path).cloneAsThawed(); // not frozen, for removeAttribute
453         xpp.removeAttribute(-1, LDMLConstants.ALT);
454         return xpp.toString();
455     }
456 
457     /**
458      * remove the 'draft=' and 'alt=*proposed' from the XPath. Makes the path
459      * almost distinguishing, except that certain attributes, such as numbers=,
460      * will be left.
461      *
462      * @param path
463      * @return
464      */
removeDraftAltProposed(String path)465     public static String removeDraftAltProposed(String path) {
466         XPathParts xpp = XPathParts.getFrozenInstance(path).cloneAsThawed(); // not frozen, for removeAttribute
467         Map<String, String> lastAtts = xpp.getAttributes(-1);
468 
469         // Remove alt proposed, but leave the type
470         String oldAlt = lastAtts.get(LDMLConstants.ALT);
471         if (oldAlt != null) {
472             String newAlt = LDMLUtilities.parseAlt(oldAlt)[0]; // #0 : altType
473             if (newAlt == null) {
474                 xpp.removeAttribute(-1, LDMLConstants.ALT); // alt dropped out existence
475             } else {
476                 xpp.putAttributeValue(-1, LDMLConstants.ALT, newAlt);
477             }
478         }
479 
480         // always remove draft
481         xpp.removeAttribute(-1, LDMLConstants.DRAFT);
482 
483         return xpp.toString();
484     }
485 
getUndistinguishingElementsFor(String path)486     public Map<String, String> getUndistinguishingElementsFor(String path) {
487         return XPathParts.getFrozenInstance(path).getSpecialNondistinguishingAttributes();
488     }
489 
490     /**
491      *
492      * @param path
493      * @return
494      *
495      * Called by handlePathValue and makeProposedFile
496      */
getAlt(String path)497     public static String getAlt(String path) {
498         XPathParts xpp = XPathParts.getFrozenInstance(path);
499         return xpp.getAttributeValue(-1, LDMLConstants.ALT);
500     }
501 
xpathToBaseXpathId(String xpath)502     public final int xpathToBaseXpathId(String xpath) {
503         return getByXpath(xpathToBaseXpath(xpath));
504     }
505 
506     /**
507      * note does not remove draft. expects a dpath.
508      *
509      * @param xpath
510      *
511      * This is NOT the same as the two-parameter xpathToBaseXpath elsewhere in this file
512      */
xpathToBaseXpath(String xpath)513     public static String xpathToBaseXpath(String xpath) {
514         XPathParts xpp = XPathParts.getFrozenInstance(xpath);
515         Map<String, String> lastAtts = xpp.getAttributes(-1);
516         String oldAlt = lastAtts.get(LDMLConstants.ALT);
517         if (oldAlt == null) {
518             return xpath; // no change
519         }
520 
521         String newAlt = LDMLUtilities.parseAlt(oldAlt)[0]; // #0 : altType
522         if (newAlt == null) {
523             xpp = xpp.cloneAsThawed();
524             xpp.removeAttribute(-1, LDMLConstants.ALT); // alt dropped out existence
525         } else if (newAlt.equals(oldAlt)) {
526             return xpath; // No change
527         } else {
528             xpp = xpp.cloneAsThawed();
529             xpp.putAttributeValue(-1, LDMLConstants.ALT, newAlt);
530         }
531         String newXpath = xpp.toString();
532         // SurveyLog.logger.warning("xp2Bxp: " + xpath + " --> " + newXpath);
533         return newXpath;
534     }
535 
536     /**
537      * Modify the given XPathParts by possibly changing or removing its ALT attribute.
538      *
539      * @param xpp the XPathParts, whose contents get changed here and used/modified by the caller
540      *
541      * Called only from submit.jsp
542      */
xPathPartsToBase(XPathParts xpp)543     public static void xPathPartsToBase(XPathParts xpp) {
544         Map<String, String> lastAtts = xpp.getAttributes(-1);
545         String oldAlt = lastAtts.get(LDMLConstants.ALT);
546         if (oldAlt == null) {
547             return; // no change
548         }
549         String newAlt = LDMLUtilities.parseAlt(oldAlt)[0]; // #0 : altType
550         if (newAlt == null) {
551             xpp.removeAttribute(-1, LDMLConstants.ALT); // alt dropped out existence
552         } else if (newAlt.equals(oldAlt)) {
553             return; // No change
554         } else {
555             xpp.putAttributeValue(-1, LDMLConstants.ALT, newAlt);
556         }
557     }
558 
559     /**
560      * Get the type attribute for the given path and LDMLConstants
561      *
562      * @param path
563      * @param what LDMLConstants.ALT
564      * @return the type as a string
565      */
whatFromPathToTinyXpath(String path, String what)566     private String whatFromPathToTinyXpath(String path, String what) {
567         XPathParts xpp = XPathParts.getFrozenInstance(path).cloneAsThawed(); // not frozen, for removeAttribute
568         Map<String, String> lastAtts = xpp.getAttributes(-1);
569         String type = lastAtts.get(what);
570         if (type != null) {
571             xpp.removeAttribute(-1, what);
572         }
573         xpp.removeAttribute(-1, LDMLConstants.ALT);
574         xpp.removeAttribute(-1, LDMLConstants.TYPE);
575         xpp.removeAttribute(-1, LDMLConstants.DRAFT);
576         xpp.removeAttribute(-1, LDMLConstants.REFERENCES);
577         // SurveyLog.logger.warning("Type on " + path + " with -1 is " + type );
578         if ((type == null) && (path.indexOf(what) >= 0)) {
579             try {
580                 // less common case - type isn't the last
581                 for (int n = -2; (type == null) && ((0 - xpp.size()) < n); n--) {
582                     // SurveyLog.logger.warning("Type on n="+n
583                     // +", "+path+" with "+n+" is " + type );
584                     lastAtts = xpp.getAttributes(n);
585                     if (lastAtts != null) {
586                         type = lastAtts.get(what);
587                         if (type != null) {
588                             xpp.removeAttribute(n, what);
589                         }
590                     }
591                 }
592             } catch (ArrayIndexOutOfBoundsException aioobe) {
593                 // means we ran out of elements.
594             }
595         }
596         return type;
597     }
598 
599     // proposed-u4-1
600     public static final String PROPOSED_U = LDMLConstants.PROPOSED + "-u";
601     public static final String PROPOSED_SEP = "-";
602     public static final String PROPOSED_V = "v";
603     public static final int NO_XPATH = -1;
604 
appendAltProposedPrefix(StringBuilder sb, int userid, Integer voteValue)605     public static final StringBuilder appendAltProposedPrefix(StringBuilder sb, int userid, Integer voteValue) {
606         sb.append(PROPOSED_U);
607         sb.append(userid);
608         if (voteValue != null) {
609             sb.append(PROPOSED_V);
610             sb.append(voteValue);
611         }
612         sb.append(PROPOSED_SEP);
613         return sb;
614     }
615 
616     /**
617      * parse an alt-proposed, such as "proposed-u4-1" into a userid (4, in this
618      * case). returns -1 if altProposed is null or in any way malformed.
619      */
altProposedToUserid(String altProposed, Integer voteValue[])620     public static final int altProposedToUserid(String altProposed, Integer voteValue[]) {
621         if ((altProposed == null) || !altProposed.contains(PROPOSED_U)) {
622             return -1;
623         }
624         // skip over 'proposed-u'
625         String idStr = altProposed.substring(altProposed.indexOf(PROPOSED_U) + PROPOSED_U.length());
626         int dash;
627         if (-1 != (dash = idStr.indexOf(PROPOSED_SEP))) {
628             idStr = idStr.substring(0, dash);
629         }
630         try {
631             return Integer.parseInt(idStr);
632         } catch (Throwable t) {
633             return -1;
634         }
635     }
636 
637     // re export PrettyPath API but synchronized
638     /**
639      * Gets sortable form of the pretty path, and caches the mapping for faster
640      * later mapping.
641      *
642      * @param path
643      * @deprecated PrettyPath is deprecated.
644      */
645     @Deprecated
getPrettyPath(String path)646     public String getPrettyPath(String path) {
647         if (path == null) {
648             return null;
649         }
650         synchronized (ppath) {
651             return ppath.getPrettyPath(path);
652         }
653     }
654 
655     /**
656      * @deprecated PrettyPath
657      * @param path
658      * @return
659      */
660     @Deprecated
getPrettyPath(int path)661     public String getPrettyPath(int path) {
662         if (path == -1) {
663             return null;
664         }
665         return getPrettyPath(getById(path));
666     }
667 
668     /**
669      * Get original path. ONLY works if getPrettyPath was called with the
670      * original!
671      *
672      * @param prettyPath
673      * @return original path
674      * @deprecated PrettyPath
675      */
676     @Deprecated
getOriginal(String prettyPath)677     public String getOriginal(String prettyPath) {
678         synchronized (ppath) {
679             return ppath.getOriginal(prettyPath);
680         }
681     }
682 
683     /**
684      * How much is inside?
685      *
686      * @return Number of xpaths in the table
687      */
count()688     public int count() {
689         return stringToId.size();
690     }
691 
692     /**
693      * xpath to long
694      * @param xpath a string identifying a path, for example "//ldml/numbers/symbols[@numberSystem="sund"]/infinity"
695      * @return a long integer, which is a hash of xpath; for example 2795888612892500012 (decimal) = 6d37a14eec91cee6 (hex)
696      */
getStringID(String xpath)697     public static final long getStringID(String xpath) {
698         return StringId.getId(xpath);
699     }
700 
701     /**
702      * xpid to hex
703      * @param baseXpath
704      * @return
705      */
getStringIDString(int baseXpath)706     public String getStringIDString(int baseXpath) {
707         return getStringIDString(getById(baseXpath));
708     }
709 
710     /**
711      * xpath to hex
712      * @param xpath a string identifying a path, for example "//ldml/numbers/symbols[@numberSystem="sund"]/infinity"
713      * @return a sixteen-digit hex string, which is a hash of xpath; for example "6d37a14eec91cee6"
714      */
getStringIDString(String xpath)715     public static final String getStringIDString(String xpath) {
716         return Long.toHexString(getStringID(xpath));
717     }
718 
719     /**
720      * Turn a strid into a xpid (int token)
721      * @param sid
722      * @return
723      */
getXpathIdFromStringId(String sid)724     public final int getXpathIdFromStringId(String sid) {
725         return getByXpath(getByStringID(sid));
726     }
727 
728     /**
729      * Given an XPath stringid, return an integer xpid or NO_XPATH
730      * This function is there to ease transition away from xpids.
731      * @param xpath a StringID (hex) or a decimal id of the form "#1234"
732      * @return the integer xpid or  {@link XPathTable#NO_XPATH}
733      */
getXpathIdOrNoneFromStringID(String xpath)734     public int getXpathIdOrNoneFromStringID(String xpath) {
735         int base_xpath;
736         if (xpath == null || xpath.isEmpty()) {
737             base_xpath = XPathTable.NO_XPATH;
738         } else if (xpath.startsWith("#")) {
739             base_xpath = Integer.parseInt(xpath.substring(1));
740         } else {
741             base_xpath = getXpathIdFromStringId(xpath);
742         }
743         return base_xpath;
744     }
745 
getByStringID(String id)746     public String getByStringID(String id) {
747         if (id == null) return null;
748         Long l = Long.parseLong(id, 16);
749         String s = sidToString.get(l);
750         if (s != null)
751             return s;
752         // slow way
753         for (String x : stringToId.keySet()) {
754             if (getStringID(x) == l) {
755                 sidToString.put(l, x);
756                 return x;
757             }
758         }
759         if (SurveyMain.isUnofficial())
760             System.err.println("xpt: Couldn't find stringid " + id + " - sid has " + sidToString.size());
761         // it may be
762         return null;
763     }
764 
765 }