001package org.hl7.fhir.utilities.graphql; 002 003import java.io.FileNotFoundException; 004import java.io.IOException; 005import java.io.InputStream; 006import java.io.InputStreamReader; 007import java.io.Reader; 008import java.io.StringReader; 009import java.util.Map.Entry; 010 011import org.hl7.fhir.utilities.TextFile; 012import org.hl7.fhir.utilities.Utilities; 013import org.hl7.fhir.utilities.graphql.Argument.ArgumentListStatus; 014 015import com.google.gson.JsonElement; 016import com.google.gson.JsonObject; 017 018public class Parser { 019 public static Package parse(String source) throws IOException, EGraphQLException, EGraphEngine { 020 Parser self = new Parser(); 021 self.reader = new StringReader(source); 022 self.next(); 023 Document doc = self.parseDocument(); 024 return new Package(doc); 025 } 026 027 public static Package parse(InputStream source) throws IOException, EGraphQLException, EGraphEngine { 028 Parser self = new Parser(); 029 self.reader = new InputStreamReader(source); 030 self.next(); 031 Document doc = self.parseDocument(); 032 return new Package(doc); 033 } 034 035 public static Package parseFile(String filename) throws FileNotFoundException, IOException, EGraphQLException, EGraphEngine { 036 String src = TextFile.fileToString(filename); 037 return parse(src); 038 } 039 040 public static Package parseJson(InputStream source) throws EGraphQLException, IOException, EGraphEngine { 041 JsonObject json = (JsonObject) new com.google.gson.JsonParser().parse(TextFile.streamToString(source)); 042 Parser self = new Parser(); 043 self.reader = new StringReader(json.get("query").getAsString()); 044 self.next(); 045 Package result = new Package(self.parseDocument()); 046 result.setOperationName(json.get("operationName").getAsString()); 047 if (json.has("variables")) { 048 JsonObject vl = json.getAsJsonObject("variables"); 049 for (Entry<String, JsonElement> n : vl.entrySet()) 050 result.getVariables().add(new Argument(n.getKey(), n.getValue())); 051 } 052 return result; 053 } 054 055 enum LexType {gqlltNull, gqlltName, gqlltPunctuation, gqlltString, gqlltNumber} 056 057 static class SourceLocation { 058 int line; 059 int col; 060 } 061 062 private Reader reader; 063 private StringBuilder token; 064 private String peek; 065 private LexType lexType; 066 private SourceLocation location = new SourceLocation(); 067 boolean readerDone = false; 068 069 private char getNextChar() throws IOException { 070 char result = '\0'; 071 if (peek != null) { 072 result = peek.charAt(0); 073 peek = peek.length() == 1 ? null : peek.substring(1); 074 } else if (reader.ready()) { 075 int c = reader.read(); 076 if (c > -1) { 077 result = (char) c; 078 if (result == '\n') { 079 location.line++; 080 location.col = 1; 081 } else 082 location.col++; 083 } 084 } 085 readerDone = result == '\0'; 086 return result; 087 } 088 089 private void pushChar(char ch) { 090 if (ch != '\0') 091 if (peek == null) 092 peek = String.valueOf(ch); 093 else 094 peek = String.valueOf(ch)+peek; 095 } 096 097 private void skipIgnore() throws IOException{ 098 char ch = getNextChar(); 099 while (Character.isWhitespace(ch) || (ch == ',')) 100 ch = getNextChar(); 101 if (ch == '#') { 102 while (ch != '\r' && ch != '\n') 103 ch = getNextChar(); 104 pushChar(ch); 105 skipIgnore(); 106 } else 107 pushChar(ch); 108 } 109 110 private void next() throws IOException, EGraphQLException { 111 // var 112 // ch : Char; 113 // hex : String; 114 skipIgnore(); 115 token = new StringBuilder(); 116 if (readerDone && peek == null) 117 lexType = LexType.gqlltNull; 118 else { 119 char ch = getNextChar(); 120 if (Utilities.existsInList(ch, '!', '$', '(', ')', ':', '=', '@', '[', ']', '{', '|', '}')) { 121 lexType = LexType.gqlltPunctuation; 122 token.append(ch); 123 } else if (ch == '.') { 124 do { 125 token.append(ch); 126 ch = getNextChar(); 127 } while (ch == '.'); 128 pushChar(ch); 129 if ((token.length() != 3)) 130 throw new EGraphQLException("Found \""+token.toString()+"\" expecting \"...\""); 131 } else if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch == '_')) { 132 lexType = LexType.gqlltName; 133 do { 134 token.append(ch); 135 ch = getNextChar(); 136 } while ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || (ch == '_')); 137 pushChar(ch); 138 } else if ((ch >= '0' && ch <= '9') || (ch == '-')) { 139 lexType = LexType.gqlltNumber; 140 do { 141 token.append(ch); 142 ch = getNextChar(); 143 } while ((ch >= '0' && ch <= '9') || ((ch == '.') && token.toString().indexOf('.') == -1) || ((ch == 'e') && token.toString().indexOf('e') == -1)); 144 pushChar(ch); 145 } else if ((ch == '"')) { 146 lexType = LexType.gqlltString; 147 do { 148 ch = getNextChar(); 149 if (ch == '\\') { 150 if (!reader.ready()) 151 throw new EGraphQLException("premature termination of GraphQL during a string constant"); 152 ch = getNextChar(); 153 if (ch == '"') token.append('"'); 154 else if (ch == '\\') token.append('\''); 155 else if (ch == '/') token.append('/'); 156 else if (ch == 'n') token.append('\n'); 157 else if (ch == 'r') token.append('\r'); 158 else if (ch == 't') token.append('\t'); 159 else if (ch == 'u') { 160 String hex = String.valueOf(getNextChar()) + getNextChar() + getNextChar() + getNextChar(); 161 token.append((char) Integer.parseInt(hex, 16)); 162 } else 163 throw new EGraphQLException("Unexpected character: \""+ch+"\""); 164 ch = '\0'; 165 } else if (ch != '"') 166 token.append(ch); 167 } while (!(readerDone || ch == '"')); 168 if (ch != '"') 169 throw new EGraphQLException("premature termination of GraphQL during a string constant"); 170 } else 171 throw new EGraphQLException("Unexpected character \""+ch+"\""); 172 } 173 } 174 175 private boolean hasPunctuation(String punc) { 176 return lexType == LexType.gqlltPunctuation && token.toString().equals(punc); 177 } 178 179 private void consumePunctuation(String punc) throws EGraphQLException, IOException { 180 if (lexType != LexType.gqlltPunctuation) 181 throw new EGraphQLException("Found \""+token.toString()+"\" expecting \""+punc+"\""); 182 if (!token.toString().equals(punc)) 183 throw new EGraphQLException("Found \""+token.toString()+"\" expecting \""+punc+"\""); 184 next(); 185 } 186 187 private boolean hasName() { 188 return (lexType == LexType.gqlltName) && (token.toString().length() > 0); 189 } 190 191 private boolean hasName(String name) { 192 return (lexType == LexType.gqlltName) && (token.toString().equals(name)); 193 } 194 195 private String consumeName() throws EGraphQLException, IOException { 196 if (lexType != LexType.gqlltName) 197 throw new EGraphQLException("Found \""+token.toString()+"\" expecting a name"); 198 String result = token.toString(); 199 next(); 200 return result; 201 } 202 203 private void consumeName(String name) throws EGraphQLException, IOException{ 204 if (lexType != LexType.gqlltName) 205 throw new EGraphQLException("Found \""+token.toString()+"\" expecting a name"); 206 if (!token.toString().equals(name)) 207 throw new EGraphQLException("Found \""+token.toString()+"\" expecting \""+name+"\""); 208 next(); 209 } 210 211 private Value parseValue() throws EGraphQLException, IOException { 212 Value result = null; 213 switch (lexType) { 214 case gqlltNull: throw new EGraphQLException("Attempt to read a value after reading off the } of the GraphQL statement"); 215 case gqlltName: 216 result = new NameValue(token.toString()); 217 break; 218 case gqlltPunctuation: 219 if (hasPunctuation("$")) { 220 consumePunctuation("$"); 221 result = new VariableValue(token.toString()); 222 } else if (hasPunctuation("{")) { 223 consumePunctuation("{"); 224 ObjectValue obj = new ObjectValue(); 225 while (!hasPunctuation("}")) 226 obj.getFields().add(parseArgument()); 227 result = obj; 228 } else 229 throw new EGraphQLException("Attempt to read a value at \""+token.toString()+"\""); 230 break; 231 case gqlltString: 232 result = new StringValue(token.toString()); 233 break; 234 case gqlltNumber: 235 result = new NumberValue(token.toString()); 236 break; 237 } 238 next(); 239 return result; 240 } 241 242 private Argument parseArgument() throws EGraphQLException, IOException { 243 Argument result = new Argument(); 244 result.setName(consumeName()); 245 consumePunctuation(":"); 246 if (hasPunctuation("[")) { 247 result.setListStatus(ArgumentListStatus.REPEATING); 248 consumePunctuation("["); 249 while (!hasPunctuation("]")) 250 result.getValues().add(parseValue()); 251 consumePunctuation("]"); 252 } else 253 result.getValues().add(parseValue()); 254 return result; 255 } 256 257 private Directive parseDirective() throws EGraphQLException, IOException { 258 Directive result = new Directive(); 259 consumePunctuation("@"); 260 result.setName(consumeName()); 261 if (hasPunctuation("(")) { 262 consumePunctuation("("); 263 do { 264 result.getArguments().add(parseArgument()); 265 } while (!hasPunctuation(")")); 266 consumePunctuation(")"); 267 } 268 return result; 269 } 270 271 private Document parseDocument() throws EGraphQLException, IOException, EGraphEngine { 272 Document doc = new Document(); 273 if (!hasName()) { 274 Operation op = new Operation(); 275 parseOperationInner(op); 276 doc.getOperations().add(op); 277 278 } else { 279 while (!readerDone || (peek != null)) { 280 String s = consumeName(); 281 if (s.equals("mutation") || (s.equals("query"))) 282 doc.getOperations().add(parseOperation(s)); 283 else if (s.equals("fragment")) 284 doc.getFragments().add(parseFragment()); 285 else 286 throw new EGraphEngine("Not done yet"); // doc.Operations.Add(parseOperation(s))? 287 } 288 } 289 return doc; 290 } 291 292 private Field parseField() throws EGraphQLException, IOException { 293 Field result = new Field(); 294 result.setName(consumeName()); 295 result.setAlias(result.getName()); 296 if (hasPunctuation(":")) { 297 consumePunctuation(":"); 298 result.setName(consumeName()); 299 } 300 if (hasPunctuation("(")) { 301 consumePunctuation("("); 302 while (!hasPunctuation(")")) 303 result.getArguments().add(parseArgument()); 304 consumePunctuation(")"); 305 } 306 while (hasPunctuation("@")) 307 result.getDirectives().add(parseDirective()); 308 309 if (hasPunctuation("{")) { 310 consumePunctuation("{"); 311 do { 312 result.getSelectionSet().add(parseSelection()); 313 } while (!hasPunctuation("}")); 314 consumePunctuation("}"); 315 } 316 return result; 317 } 318 319 private void parseFragmentInner(Fragment fragment) throws EGraphQLException, IOException { 320 while (hasPunctuation("@")) 321 fragment.getDirectives().add(parseDirective()); 322 consumePunctuation("{"); 323 do 324 fragment.getSelectionSet().add(parseSelection()); 325 while (!hasPunctuation("}")); 326 consumePunctuation("}"); 327 } 328 329 private Fragment parseFragment() throws EGraphQLException, IOException { 330 Fragment result = new Fragment(); 331 result.setName(consumeName()); 332 consumeName("on"); 333 result.setTypeCondition(consumeName()); 334 parseFragmentInner(result); 335 return result; 336 } 337 338 private FragmentSpread parseFragmentSpread() throws EGraphQLException, IOException { 339 FragmentSpread result = new FragmentSpread(); 340 result.setName(consumeName()); 341 while (hasPunctuation("@")) 342 result.getDirectives().add(parseDirective()); 343 return result; 344 } 345 346 private Fragment parseInlineFragment() throws EGraphQLException, IOException { 347 Fragment result = new Fragment(); 348 if (hasName("on")) 349 { 350 consumeName("on"); 351 result.setTypeCondition(consumeName()); 352 } 353 parseFragmentInner(result); 354 return result; 355 } 356 357 private Operation parseOperation(String name) throws EGraphQLException, IOException { 358 Operation result = new Operation(); 359 if (name.equals("mutation")) { 360 result.setOperationType(Operation.OperationType.qglotMutation); 361 if (hasName()) 362 result.setName(consumeName()); 363 } else if (name.equals("query")) { 364 result.setOperationType(Operation.OperationType.qglotQuery); 365 if (hasName()) 366 result.setName(consumeName()); 367 } else 368 result.setName(name); 369 parseOperationInner(result); 370 return result; 371 } 372 373 private void parseOperationInner(Operation op) throws EGraphQLException, IOException { 374 if (hasPunctuation("(")) { 375 consumePunctuation("("); 376 do 377 op.getVariables().add(parseVariable()); 378 while (!hasPunctuation(")")); 379 consumePunctuation(")"); 380 } 381 while (hasPunctuation("@")) 382 op.getDirectives().add(parseDirective()); 383 if (hasPunctuation("{")) { 384 consumePunctuation("{"); 385 do 386 op.getSelectionSet().add(parseSelection()); 387 while (!hasPunctuation("}")); 388 consumePunctuation("}"); 389 } 390 } 391 392 private Selection parseSelection() throws EGraphQLException, IOException { 393 Selection result = new Selection(); 394 if (hasPunctuation("...")) { 395 consumePunctuation("..."); 396 if (hasName() && !token.toString().equals("on")) 397 result.setFragmentSpread(parseFragmentSpread()); 398 else 399 result.setInlineFragment(parseInlineFragment()); 400 } else 401 result.setField(parseField()); 402 return result; 403 } 404 405 private Variable parseVariable() throws EGraphQLException, IOException { 406 Variable result = new Variable(); 407 consumePunctuation("$"); 408 result.setName(consumeName()); 409 consumePunctuation(":"); 410 result.setTypeName(consumeName()); 411 if (hasPunctuation("=")) 412 { 413 consumePunctuation("="); 414 result.setDefaultValue(parseValue()); 415 } 416 return result; 417 } 418 419}