001    package ca.uhn.hl7v2.util;
002    
003    import java.io.BufferedReader;
004    import java.io.File;
005    import java.io.FileFilter;
006    import java.io.FileReader;
007    import java.io.FilenameFilter;
008    import java.io.IOException;
009    import java.util.HashMap;
010    import java.util.StringTokenizer;
011    
012    import ca.uhn.hl7v2.HL7Exception;
013    import ca.uhn.hl7v2.util.Home;
014    import ca.uhn.log.HapiLog;
015    import ca.uhn.log.HapiLogFactory;
016    
017    /**
018     * <p>Implements CodeMapper using files to store code values.  Files are arranged
019     * in the following directory structure.  The base directory is called "codemap".
020     * This should be created under the hapi.home directory (see Home class; defaults to .).
021     * Under the base directory, there should be one directory for each interface, and
022     * each of these directories should be named after the interface.  For example if you
023     * had interfaces to pharmacy and lab systems you might have the following directories:</p>
024     * <p> <hapi.home>/codemap/pharmacy<br>
025     * <hapi.home>/codemap/lab</p>
026     * <p>Each directory should contain two files per HL7 table, named after the table numbers as
027     * follows: "hl7nnnn.li" and "hl7nnnn.il", where nnnn is the 4 digit table number.  The .il
028     * file contains maps from interface codes to local codes, and the .li file contains maps from
029     * local codes to interface codes (these unfortunately may not be symmetrical).</p>
030     * <p>Each line of a file contains a single code map, with the "from" value and the "to" value
031     * separated by a tab.  For example, the file <hapi.home>/lab/HL70001.li (to map local codes to interface
032     * codes for the HL7 admnistrative sex table in your lab system interface) might contain the
033     * following line: </p>
034     * <p>male&tab;M</p>
035     * <p>This means that the local code "male" maps to the interface code "M".</p>
036     * <p>Lines that start with "//" are treated as comments.</p>
037     * @author Bryan Tripp
038     */
039    public class FileCodeMapper extends CodeMapper {
040    
041        private static final HapiLog log = HapiLogFactory.getHapiLog(FileCodeMapper.class);
042    
043        private boolean throwIfNoMatch = false;
044        File baseDir;
045        private HashMap interfaceToLocal;
046        private HashMap localToInterface;
047    
048        /**
049         * Creates a new instance of FileCodeMapper.  You should probably not
050         * construct a FileCodeMapper directly.  Use CodeMapper.getInstance()
051         * instead ... this will ensure that only a single instance is created,
052         * which is important for performance because code maps are loaded from
053         * disk every time this constructor is called.
054         */
055        public FileCodeMapper() throws HL7Exception {
056            baseDir = new File(Home.getHomeDirectory().getAbsolutePath() + "/codemap");
057            refreshCache();
058        }
059    
060        /**
061         * If values are cached in such a way that they are not guaranteed to be current, a call
062         * to this method refreshes the values.
063         */
064        public void refreshCache() throws HL7Exception {
065            localToInterface = new HashMap(10);
066            interfaceToLocal = new HashMap(10);
067    
068            log.info("Refreshing cache");
069    
070            try {
071                //get list of child directories
072                File[] interfaceDirs = this.baseDir.listFiles(new FileFilter() {
073                    public boolean accept(File pathname) {
074                        boolean acc = false;
075                        if (pathname.isDirectory())
076                            acc = true;
077                        return acc;
078                    }
079                });
080    
081                //loop through directories and set up maps
082                for (int i = 0; i < interfaceDirs.length; i++) {
083    
084                    log.info(
085                        "Checking directory " + interfaceDirs[i].getName() + " for interface code maps.");
086    
087                    //get list of .li (local -> interface) and .il (interface -> local) files
088                    File[] mapFiles = interfaceDirs[i].listFiles(new FilenameFilter() {
089                        public boolean accept(File dir, String name) {
090                            boolean acc = false;
091                            if (name.toUpperCase().startsWith("HL7")) {
092                                if (name.substring(name.lastIndexOf('.')).equals(".li")
093                                    || name.substring(name.lastIndexOf('.')).equals(".il"))
094                                    acc = true;
095                            }
096                            return acc;
097                        }
098                    });
099    
100                    //read map entries from each file and add to hash maps for li and il codes
101                    HashMap li = new HashMap(50);
102                    HashMap il = new HashMap(50);
103                    for (int j = 0; j < mapFiles.length; j++) {
104                        log.info("Reading map entries from file " + mapFiles[j]);
105    
106                        String fName = mapFiles[j].getName();
107                        String tableName = fName.substring(0, fName.lastIndexOf('.'));
108                        String mapDirection = fName.substring(fName.lastIndexOf('.') + 1);
109    
110                        //read values and store in HashMap
111                        BufferedReader in = new BufferedReader(new FileReader(mapFiles[j]));
112                        HashMap codeMap = new HashMap(25);
113                        while (in.ready()) {
114                            String line = in.readLine();
115                            if (!line.startsWith("//")) {
116                                StringTokenizer tok = new StringTokenizer(line, "\t", false);
117                                String from = null;
118                                String to = null;
119                                if (tok.hasMoreTokens())
120                                    from = tok.nextToken();
121                                if (tok.hasMoreTokens())
122                                    to = tok.nextToken();
123                                if (from != null && to != null)
124                                    codeMap.put(from, to);
125                            }
126                        }
127    
128                        //add to appropriate map for this interface
129                        if (mapDirection.equals("il")) {
130                            il.put(tableName.toUpperCase(), codeMap);
131                            log.debug("Adding "
132                                    + codeMap.size()
133                                    + " codes to interface -> local map for "
134                                    + tableName
135                                    + " in "
136                                    + interfaceDirs[i].getName()
137                                    + " interface");
138                        }
139                        else {
140                            li.put(tableName.toUpperCase(), codeMap);
141                            log.debug("Adding "
142                                    + codeMap.size()
143                                    + " codes to local -> interface map for "
144                                    + tableName
145                                    + " in "
146                                    + interfaceDirs[i].getName()
147                                    + " interface");
148                        }
149                    }
150    
151                    //add maps for this interface (this directory) to global list
152                    interfaceToLocal.put(interfaceDirs[i].getName(), il);
153                    localToInterface.put(interfaceDirs[i].getName(), li);
154                }
155    
156            }
157            catch (IOException e) {
158                throw new HL7Exception(
159                    "Can't read interface code maps from disk",
160                    HL7Exception.APPLICATION_INTERNAL_ERROR,
161                    e);
162            }
163        }
164    
165        /**
166         * Returns the local code for the given interface code as it appears in
167         * the given interface.
168         */
169        public String getLocalCode(String interfaceName, int hl7Table, String interfaceCode) throws HL7Exception {
170            String localCode = null;
171            try {
172                HashMap interfaceMap = (HashMap) interfaceToLocal.get(interfaceName);
173                localCode = getCode(interfaceMap, hl7Table, interfaceCode);
174            }
175            catch (NullPointerException npe) {
176                if (this.throwIfNoMatch)
177                    throw new HL7Exception(
178                        "No local mapping for the interface code "
179                            + interfaceCode
180                            + " for HL7 table "
181                            + hl7Table
182                            + " for the interface '"
183                            + interfaceName
184                            + "'",
185                        HL7Exception.TABLE_VALUE_NOT_FOUND);
186            }
187            return localCode;
188        }
189    
190        /**
191         * Common code for getLocalcode and getInterfaceCode
192         */
193        private String getCode(HashMap interfaceMap, int hl7Table, String code) {
194            String ret = null;
195    
196            //get map for the given table
197            StringBuffer tableName = new StringBuffer();
198            tableName.append("HL7");
199            if (hl7Table < 1000)
200                tableName.append("0");
201            if (hl7Table < 100)
202                tableName.append("0");
203            if (hl7Table < 10)
204                tableName.append("0");
205            tableName.append(hl7Table);
206            HashMap tableMap = (HashMap) interfaceMap.get(tableName.toString());
207    
208            //get code
209            ret = tableMap.get(code).toString();
210            return ret;
211        }
212    
213        /**
214         * Returns the interface code for the given local code, for use in the context
215         * of the given interface.
216         */
217        public String getInterfaceCode(String interfaceName, int hl7Table, String localCode) throws HL7Exception {
218            String interfaceCode = null;
219            try {
220                HashMap interfaceMap = (HashMap) localToInterface.get(interfaceName);
221                interfaceCode = getCode(interfaceMap, hl7Table, localCode);
222            }
223            catch (NullPointerException npe) {
224                if (this.throwIfNoMatch)
225                    throw new HL7Exception(
226                        "No interface mapping for the local code "
227                            + localCode
228                            + " for HL7 table "
229                            + hl7Table
230                            + " for the interface '"
231                            + interfaceName
232                            + "'",
233                        HL7Exception.TABLE_VALUE_NOT_FOUND);
234            }
235            return interfaceCode;
236        }
237    
238        /**
239         * Determines what happens if no matching code is found during a lookup.  If set to true,
240         * an HL7Exception is thrown if there is no match.  If false, null is returned.  The default
241         * is false.
242         */
243        public void throwExceptionIfNoMatch(boolean throwException) {
244            this.throwIfNoMatch = throwException;
245        }
246    
247        /**
248         * Test harness.
249         */
250        public static void main(String args[]) {
251            try {
252                //FileCodeMapper mapper = new FileCodeMapper();
253                CodeMapper.getInstance().throwExceptionIfNoMatch(true);
254                System.out.println("Local code for M is " + CodeMapper.getLocal("test", 1, "M"));
255                System.out.println("Interface code for female is " + CodeMapper.getInt("test", 1, "female"));
256    
257            }
258            catch (HL7Exception e) {
259                e.printStackTrace();
260            }
261        }
262    
263    }