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 org.slf4j.Logger;
013    import org.slf4j.LoggerFactory;
014    
015    import ca.uhn.hl7v2.HL7Exception;
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 Logger log = LoggerFactory.getLogger(FileCodeMapper.class);
042    
043        private boolean throwIfNoMatch = false;
044        File baseDir;
045        private HashMap<String, HashMap<String, HashMap<String, String>>> interfaceToLocal;
046        private HashMap<String, HashMap<String, HashMap<String, String>>> 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<String, HashMap<String, HashMap<String, String>>>(10);
066            interfaceToLocal = new HashMap<String, HashMap<String, HashMap<String, String>>>(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 {} for interface code maps.", interfaceDirs[i].getName());
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<String, HashMap<String, String>> li = new HashMap<String, HashMap<String, String>>(50);
102                    HashMap<String, HashMap<String, String>> il = new HashMap<String, HashMap<String, String>>(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<String, String> codeMap = new HashMap<String, String>(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 {} codes to interface -> local map for {} in {} interface",
132                                    new Object[] {codeMap.size(), tableName, interfaceDirs[i].getName()});
133                        }
134                        else {
135                            li.put(tableName.toUpperCase(), codeMap);
136                            log.debug("Adding {} codes to local -> interface map for {} in {} interface",
137                                    new Object[] {codeMap.size(), tableName, interfaceDirs[i].getName()});
138                        }
139                    }
140    
141                    //add maps for this interface (this directory) to global list
142                    interfaceToLocal.put(interfaceDirs[i].getName(), il);
143                    localToInterface.put(interfaceDirs[i].getName(), li);
144                }
145    
146            }
147            catch (IOException e) {
148                throw new HL7Exception(
149                    "Can't read interface code maps from disk",
150                    HL7Exception.APPLICATION_INTERNAL_ERROR,
151                    e);
152            }
153        }
154    
155        /**
156         * Returns the local code for the given interface code as it appears in
157         * the given interface.
158         */
159        public String getLocalCode(String interfaceName, int hl7Table, String interfaceCode) throws HL7Exception {
160            String localCode = null;
161            try {
162                HashMap<String, HashMap<String, String>> interfaceMap = interfaceToLocal.get(interfaceName);
163                localCode = getCode(interfaceMap, hl7Table, interfaceCode);
164            }
165            catch (NullPointerException npe) {
166                if (this.throwIfNoMatch)
167                    throw new HL7Exception(
168                        "No local mapping for the interface code "
169                            + interfaceCode
170                            + " for HL7 table "
171                            + hl7Table
172                            + " for the interface '"
173                            + interfaceName
174                            + "'",
175                        HL7Exception.TABLE_VALUE_NOT_FOUND);
176            }
177            return localCode;
178        }
179    
180        /**
181         * Common code for getLocalcode and getInterfaceCode
182         */
183        private String getCode(HashMap<String, HashMap<String, String>> interfaceMap, int hl7Table, String code) {
184            String ret = null;
185    
186            //get map for the given table
187            StringBuffer tableName = new StringBuffer();
188            tableName.append("HL7");
189            if (hl7Table < 1000)
190                tableName.append("0");
191            if (hl7Table < 100)
192                tableName.append("0");
193            if (hl7Table < 10)
194                tableName.append("0");
195            tableName.append(hl7Table);
196            HashMap<String, String> tableMap = interfaceMap.get(tableName.toString());
197    
198            //get code
199            ret = tableMap.get(code).toString();
200            return ret;
201        }
202    
203        /**
204         * Returns the interface code for the given local code, for use in the context
205         * of the given interface.
206         */
207        public String getInterfaceCode(String interfaceName, int hl7Table, String localCode) throws HL7Exception {
208            String interfaceCode = null;
209            try {
210                HashMap<String, HashMap<String, String>> interfaceMap = localToInterface.get(interfaceName);
211                interfaceCode = getCode(interfaceMap, hl7Table, localCode);
212            }
213            catch (NullPointerException npe) {
214                if (this.throwIfNoMatch)
215                    throw new HL7Exception(
216                        "No interface mapping for the local code "
217                            + localCode
218                            + " for HL7 table "
219                            + hl7Table
220                            + " for the interface '"
221                            + interfaceName
222                            + "'",
223                        HL7Exception.TABLE_VALUE_NOT_FOUND);
224            }
225            return interfaceCode;
226        }
227    
228        /**
229         * Determines what happens if no matching code is found during a lookup.  If set to true,
230         * an HL7Exception is thrown if there is no match.  If false, null is returned.  The default
231         * is false.
232         */
233        public void throwExceptionIfNoMatch(boolean throwException) {
234            this.throwIfNoMatch = throwException;
235        }
236    
237        /**
238         * Test harness.
239         */
240        public static void main(String args[]) {
241            try {
242                //FileCodeMapper mapper = new FileCodeMapper();
243                CodeMapper.getInstance().throwExceptionIfNoMatch(true);
244                System.out.println("Local code for M is " + CodeMapper.getLocal("test", 1, "M"));
245                System.out.println("Interface code for female is " + CodeMapper.getInt("test", 1, "female"));
246    
247            }
248            catch (HL7Exception e) {
249                e.printStackTrace();
250            }
251        }
252    
253    }