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