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