1 /**
2 * The contents of this file are subject to the Mozilla Public License Version 1.1
3 * (the "License"); you may not use this file except in compliance with the License.
4 * You may obtain a copy of the License at http://www.mozilla.org/MPL/
5 * Software distributed under the License is distributed on an "AS IS" basis,
6 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
7 * specific language governing rights and limitations under the License.
8 *
9 * The Original Code is "SourceGenerator.java". Description:
10 * "Manages automatic generation of HL7 API source code for all data types,
11 * segments, groups, and message structures"
12 *
13 * The Initial Developer of the Original Code is University Health Network. Copyright (C)
14 * 2001. All Rights Reserved.
15 *
16 * Contributor(s): ______________________________________.
17 *
18 * Alternatively, the contents of this file may be used under the terms of the
19 * GNU General Public License (the �GPL�), in which case the provisions of the GPL are
20 * applicable instead of those above. If you wish to allow use of your version of this
21 * file only under the terms of the GPL and not to allow others to use your version
22 * of this file under the MPL, indicate your decision by deleting the provisions above
23 * and replace them with the notice and other provisions required by the GPL License.
24 * If you do not delete the provisions above, a recipient may use your version of
25 * this file under either the MPL or the GPL.
26 *
27 */
28
29 package ca.uhn.hl7v2.sourcegen;
30
31 import java.util.StringTokenizer;
32 import java.io.File;
33 import java.io.IOException;
34
35 import ca.uhn.hl7v2.HL7Exception;
36 import org.apache.commons.lang.StringUtils;
37
38 /**
39 * <p>Manages automatic generation of HL7 API source code for all data types,
40 * segments, groups, and message structures. </p>
41 * <p>Note: should put a nice UI on this</p>
42 * @author Bryan Tripp (bryan_tripp@sourceforge.net)
43 */
44 public class SourceGenerator {
45
46 public static final String ENCODING = "UTF-8";
47
48 /** Creates new SourceGenerator */
49 public SourceGenerator() {
50 }
51
52 /**
53 * Generates source code for all data types, segments, groups, and messages.
54 * @param baseDirectory the directory where source should be written
55 * @throws HL7Exception -
56 */
57 public static void makeAll(String baseDirectory, String version, boolean failOnError, String theTemplatePackage, String theFileExt) throws HL7Exception {
58 //load driver and set DB URL
59 /*if (System.getProperty("ca.on.uhn.hl7.database.url") == null) {
60 System.setProperty("ca.on.uhn.hl7.database.url", "jdbc:odbc:hl7");
61 }*/
62
63 try {
64 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
65 MessageGenerator.makeAll(baseDirectory, version, failOnError, theTemplatePackage, theFileExt);
66 SegmentGenerator.makeAll(baseDirectory, version, theTemplatePackage, theFileExt);
67 DataTypeGenerator.makeAll(baseDirectory, version, theTemplatePackage, theFileExt);
68 // group and message not implemented
69 } catch (Exception e) {
70 throw new HL7Exception(e);
71 }
72
73 }
74
75 /**
76 * Make a Java-ish accessor method name out of a field or component description
77 * by removing non-letters and adding "get". One complication is that some description
78 * entries in the DB have their data types in brackets, and these should not be
79 * part of the method name. On the other hand, sometimes critical distinguishing
80 * information is in brackets, so we can't omit everything in brackets. The approach
81 * taken here is to eliminate bracketed text if a it looks like a data type.
82 */
83 public static String makeAccessorName(String fieldDesc, String parentName) {
84 StringBuilder aName = new StringBuilder();
85 char[] chars = fieldDesc.toCharArray();
86 boolean lastCharWasNotLetter = true;
87 int inBrackets = 0;
88 StringBuffer bracketContents = new StringBuffer();
89 for (char aChar : chars) {
90 if (aChar == '(') inBrackets++;
91 if (aChar == ')') inBrackets--;
92
93 if (Character.isLetterOrDigit(aChar)) {
94 if (inBrackets > 0) {
95 //buffer everthing in brackets
96 bracketContents.append(aChar);
97 } else {
98 //add capitalized bracketed text if appropriate
99 if (bracketContents.length() > 0) {
100 aName.append(capitalize(filterBracketedText(bracketContents.toString())));
101 bracketContents = new StringBuffer();
102 }
103 if (lastCharWasNotLetter) {
104 //first letter of each word is upper-case
105 aName.append(Character.toUpperCase(aChar));
106 } else {
107 aName.append(aChar);
108 }
109 lastCharWasNotLetter = false;
110 }
111 } else {
112 lastCharWasNotLetter = true;
113 }
114 }
115 aName.append(capitalize(filterBracketedText(bracketContents.toString())));
116 String retVal = aName.toString();
117
118 // Accessors with these two names conflict with existing superclass accessor names
119 if (retVal.equals("Parent")) {
120 retVal = parentName + "Parent";
121 }
122 if (retVal.equals("Name")) {
123 retVal = parentName + "Name";
124 }
125
126 return retVal;
127 }
128
129 /**
130 * Bracketed text in a field description should be included in the accessor
131 * name unless it corresponds to a data type name. Given the text that appears in
132 * brackets in a field description, this method returns an empty string if it
133 * corresponds to a data type name, or returns original text if not. It isn't
134 * convenient to actually check (e.g. with DataTypeGenerator) whether the given
135 * text actually corresponds to a data type name, so we are going to conclude that
136 * it is a data type if and only if it is all caps and has 2 or 3 characters.
137 */
138 private static String filterBracketedText(String text) {
139 String filtered = "";
140 boolean isDataType = true;
141 if (!text.equals(text.toUpperCase())) isDataType = false;
142 if (text.length() < 2 || text.length() > 3) isDataType = false;
143
144 if (!isDataType) filtered = text;
145 return filtered;
146 }
147
148 /** Capitalizes first character of the given text. */
149 private static String capitalize(String text) {
150 StringBuilder cap = new StringBuilder();
151 if (text.length() > 0) {
152 cap.append(Character.toUpperCase(text.charAt(0)));
153 cap.append(text.substring(1));
154 }
155 return cap.toString();
156 }
157
158 /**
159 * Creates the given directory if it does not exist.
160 */
161 public static File makeDirectory(String directory) throws IOException {
162 StringTokenizer tok = new StringTokenizer(directory, "\\/", false);
163
164 File currDir = directory.startsWith("/") ? new File("/") : null;
165 while (tok.hasMoreTokens()) {
166 String thisDirName = tok.nextToken();
167
168 currDir = new File(currDir, thisDirName); //if currDir null, defaults to 1 arg call
169
170 if (!currDir.exists()) {
171 //create
172 currDir.mkdir();
173 } else if (currDir.isFile()) {
174 throw new IOException("Can't create directory " + thisDirName +
175 " - file with same name exists.");
176 }
177 }
178
179 return currDir;
180 }
181
182 /**
183 * <p>Returns either the given data type name or an alternate data type that Composites
184 * and Segments should use in place of the given Type. </p>
185 * <p>As currently implemented, substitutions
186 * may be desired if there is a validating subclass of the given Type.
187 * By convention such classes would be named ValidX (where X is the Type name). This
188 * method checks the classpath for classes of this name in the datatype package and
189 * returns this name if one is found.</p>
190 * <p>Also converts "varies" to Varies which is an implementation of Type that contains
191 * a Type that can be set at run-time.</p>
192 */
193 public static String getAlternateType(String dataTypeName, String version) {
194 String ret = dataTypeName;
195
196 //convert to varies to Varies
197 if (ret.equals("varies")) ret = "Varies";
198
199 // Null/withdrawn
200 if (ret.equals("NUL")) ret = "NULLDT";
201 if (ret.equals("-")) ret = "NULLDT";
202
203 //Valid.. classes are removed as of HAPI 0.3 (validating code implemented directly in Primitive classes
204 /*try {
205 Class.forName(getVersionPackageName(version) + "datatype.Valid" + dataTypeName);
206 ret = "Valid" + dataTypeName;
207 } catch (Exception e) {
208 // fine ... there isn't a ValidX implementation
209 // I don't like using Class.forName here but I don't know a better way to
210 // search for the class
211 }*/
212
213 return ret;
214 }
215
216 public static void main(String[] args) throws ClassNotFoundException, HL7Exception {
217 Class.forName("com.mysql.jdbc.Driver");
218 System.setProperty("ca.on.uhn.hl7.database.url", "jdbc:mysql://localhost:3306/hl7v65");
219 System.setProperty("ca.on.uhn.hl7.database.user", "hl7");
220 System.setProperty("ca.on.uhn.hl7.database.password", "hl7");
221 makeAll("tmp", "2.5.1", true, "", "java");
222 }
223
224 public static String makeAlternateAccessorName(String fieldDesc, String parentName, int index) {
225 StringBuilder aName = new StringBuilder();
226
227 aName.append(StringUtils.capitalize(parentName.toLowerCase())).append(index).append("_");
228
229 char[] chars = fieldDesc.toCharArray();
230 boolean lastCharWasNotLetter = true;
231 int inBrackets = 0;
232 StringBuffer bracketContents = new StringBuffer();
233 for (char aChar : chars) {
234 if (aChar == '(') inBrackets++;
235 if (aChar == ')') inBrackets--;
236
237 if (Character.isLetterOrDigit(aChar)) {
238 if (inBrackets > 0) {
239 //buffer everthing in brackets
240 bracketContents.append(aChar);
241 } else {
242 //add capitalized bracketed text if appropriate
243 if (bracketContents.length() > 0) {
244 aName.append(capitalize(filterBracketedText(bracketContents.toString())));
245 bracketContents = new StringBuffer();
246 }
247 if (lastCharWasNotLetter) {
248 //first letter of each word is upper-case
249 aName.append(Character.toUpperCase(aChar));
250 } else {
251 aName.append(aChar);
252 }
253 lastCharWasNotLetter = false;
254 }
255 } else {
256 lastCharWasNotLetter = true;
257 }
258 }
259 aName.append(capitalize(filterBracketedText(bracketContents.toString())));
260
261
262 return aName.toString();
263 }
264
265 }