UNPKG

37.3 kBJavaScriptView Raw
1import { BaseJavaVisitor, SCALAR_TO_WRITER_METHOD } from './base-java-visitor.js';
2import { indent, indentMultiline, getBaseTypeNode } from '@graphql-codegen/visitor-plugin-common';
3import { buildPackageNameFromPath, JavaDeclarationBlock } from '@graphql-codegen/java-common';
4import { getBaseType } from '@graphql-codegen/plugin-helpers';
5import { print, Kind, isNonNullType, isScalarType, isEnumType, isUnionType, isInterfaceType, isObjectType, isInputObjectType, GraphQLString, isListType, } from 'graphql';
6import { Imports } from './imports.js';
7import { createHash } from 'crypto';
8import pluralize from 'pluralize';
9import { visitFieldArguments } from './field-arguments.js';
10import { camelCase, pascalCase } from 'change-case-all';
11export class OperationVisitor extends BaseJavaVisitor {
12 constructor(_schema, rawConfig, _availableFragments) {
13 super(_schema, rawConfig, {
14 package: rawConfig.package || buildPackageNameFromPath(process.cwd()),
15 fragmentPackage: rawConfig.fragmentPackage || 'fragment',
16 typePackage: rawConfig.typePackage || 'type',
17 });
18 this._availableFragments = _availableFragments;
19 this.visitingFragment = false;
20 }
21 printDocument(node) {
22 return print(node)
23 .replace(/\r?\n|\r/g, ' ')
24 .replace(/"/g, '\\"')
25 .trim();
26 }
27 getPackage() {
28 return this.visitingFragment ? this.config.fragmentPackage : this.config.package;
29 }
30 addCtor(className, node, cls) {
31 const variables = node.variableDefinitions || [];
32 const hasVariables = variables.length > 0;
33 const nonNullVariables = variables
34 .filter(v => v.type.kind === Kind.NON_NULL_TYPE)
35 .map(v => {
36 this._imports.add(Imports.Utils);
37 return `Utils.checkNotNull(${v.variable.name.value}, "${v.variable.name.value} == null");`;
38 });
39 const impl = [
40 ...nonNullVariables,
41 `this.variables = ${!hasVariables
42 ? 'Operation.EMPTY_VARIABLES'
43 : `new ${className}.Variables(${variables.map(v => v.variable.name.value).join(', ')})`};`,
44 ].join('\n');
45 cls.addClassMethod(className, null, impl, node.variableDefinitions.map(varDec => {
46 const outputType = getBaseTypeNode(varDec.type).name.value;
47 const schemaType = this._schema.getType(outputType);
48 const javaClass = this.getJavaClass(schemaType);
49 const typeToUse = this.getListTypeNodeWrapped(javaClass, varDec.type);
50 const isNonNull = varDec.type.kind === Kind.NON_NULL_TYPE;
51 return {
52 name: varDec.variable.name.value,
53 type: typeToUse,
54 annotations: [isNonNull ? 'Nonnull' : 'Nullable'],
55 };
56 }), null, 'public');
57 }
58 getRootType(operation) {
59 if (operation === 'query') {
60 return this._schema.getQueryType();
61 }
62 if (operation === 'mutation') {
63 return this._schema.getMutationType();
64 }
65 if (operation === 'subscription') {
66 return this._schema.getSubscriptionType();
67 }
68 return null;
69 }
70 createUniqueClassName(inUse, name, count = 0) {
71 const possibleNewName = count === 0 ? name : `${name}${count}`;
72 if (inUse.includes(possibleNewName)) {
73 return this.createUniqueClassName(inUse, name, count + 1);
74 }
75 return possibleNewName;
76 }
77 transformSelectionSet(options, isRoot = true) {
78 if (!options.result) {
79 options.result = {};
80 }
81 if (!isObjectType(options.schemaType) && !isInterfaceType(options.schemaType)) {
82 return options.result;
83 }
84 const className = this.createUniqueClassName(Object.keys(options.result), options.className);
85 const cls = new JavaDeclarationBlock()
86 .access('public')
87 .asKind('class')
88 .withName(className)
89 .implements(options.implements || []);
90 if (!options.nonStaticClass) {
91 cls.static();
92 }
93 options.result[className] = cls;
94 const fields = options.schemaType.getFields();
95 const childFields = [...(options.additionalFields || [])];
96 const childInlineFragments = [];
97 const childFragmentSpread = [...(options.additionalFragments || [])];
98 const selections = [...(options.selectionSet || [])];
99 const responseFieldArr = [];
100 for (const selection of selections) {
101 if (selection.kind === Kind.FIELD) {
102 this._imports.add(Imports.ResponseField);
103 const field = fields[selection.name.value];
104 const isObject = selection.selectionSet && selection.selectionSet.selections && selection.selectionSet.selections.length > 0;
105 const isNonNull = isNonNullType(field.type);
106 const fieldAnnotation = isNonNull ? 'Nonnull' : 'Nullable';
107 this._imports.add(Imports[fieldAnnotation]);
108 const baseType = getBaseType(field.type);
109 const isList = isListType(field.type) || (isNonNullType(field.type) && isListType(field.type.ofType));
110 if (isObject) {
111 let childClsName = this.convertName(field.name);
112 if (isList && pluralize.isPlural(childClsName)) {
113 childClsName = pluralize.singular(childClsName);
114 }
115 this.transformSelectionSet({
116 className: childClsName,
117 result: options.result,
118 selectionSet: selection.selectionSet.selections,
119 schemaType: baseType,
120 }, false);
121 childFields.push({
122 rawType: field.type,
123 isObject: true,
124 isList,
125 isFragment: false,
126 type: baseType,
127 isNonNull,
128 annotation: fieldAnnotation,
129 className: childClsName,
130 fieldName: field.name,
131 });
132 }
133 else {
134 const javaClass = this.getJavaClass(baseType);
135 childFields.push({
136 rawType: field.type,
137 isObject: false,
138 isFragment: false,
139 isList,
140 type: baseType,
141 isNonNull,
142 annotation: fieldAnnotation,
143 className: javaClass,
144 fieldName: field.name,
145 });
146 }
147 this._imports.add(Imports.ResponseField);
148 this._imports.add(Imports.Collections);
149 const operationArgs = visitFieldArguments(selection, this._imports);
150 const responseFieldMethod = this._resolveResponseFieldMethodForBaseType(field.type);
151 responseFieldArr.push(`ResponseField.${responseFieldMethod.fn}("${selection.alias ? selection.alias.value : selection.name.value}", "${selection.name.value}", ${operationArgs}, ${!isNonNullType(field.type)},${responseFieldMethod.custom ? ` CustomType.${baseType.name},` : ''} Collections.<ResponseField.Condition>emptyList())`);
152 }
153 else if (selection.kind === Kind.INLINE_FRAGMENT) {
154 if (isUnionType(options.schemaType) || isInterfaceType(options.schemaType)) {
155 childInlineFragments.push({
156 onType: selection.typeCondition.name.value,
157 node: selection,
158 });
159 }
160 else {
161 selections.push(...selection.selectionSet.selections);
162 }
163 }
164 else if (selection.kind === Kind.FRAGMENT_SPREAD) {
165 const fragment = this._availableFragments.find(f => f.name === selection.name.value);
166 if (fragment) {
167 childFragmentSpread.push(fragment);
168 this._imports.add(`${this.config.fragmentPackage}.${fragment.name}`);
169 }
170 else {
171 throw new Error(`Fragment with name ${selection.name.value} was not loaded as document!`);
172 }
173 }
174 }
175 if (childInlineFragments.length > 0) {
176 const childFieldsBase = [...childFields];
177 childFields.push(...childInlineFragments.map(inlineFragment => {
178 const cls = `As${inlineFragment.onType}`;
179 const schemaType = this._schema.getType(inlineFragment.onType);
180 this.transformSelectionSet({
181 additionalFields: childFieldsBase,
182 additionalFragments: childFragmentSpread,
183 className: cls,
184 result: options.result,
185 selectionSet: inlineFragment.node.selectionSet.selections,
186 schemaType,
187 }, false);
188 this._imports.add(Imports.Nullable);
189 return {
190 isFragment: false,
191 rawType: schemaType,
192 isObject: true,
193 isList: false,
194 type: schemaType,
195 isNonNull: false,
196 annotation: 'Nullable',
197 className: cls,
198 fieldName: `as${inlineFragment.onType}`,
199 };
200 }));
201 responseFieldArr.push(...childInlineFragments.map(f => {
202 this._imports.add(Imports.Arrays);
203 return `ResponseField.forInlineFragment("__typename", "__typename", Arrays.asList("${f.onType}"))`;
204 }));
205 }
206 if (childFragmentSpread.length > 0) {
207 responseFieldArr.push(`ResponseField.forFragment("__typename", "__typename", Arrays.asList(${childFragmentSpread
208 .map(f => `"${f.onType}"`)
209 .join(', ')}))`);
210 this._imports.add(Imports.ResponseField);
211 this._imports.add(Imports.Nonnull);
212 this._imports.add(Imports.Arrays);
213 const fragmentsClassName = 'Fragments';
214 childFields.push({
215 isObject: true,
216 isList: false,
217 isFragment: true,
218 rawType: options.schemaType,
219 type: options.schemaType,
220 isNonNull: true,
221 annotation: 'Nonnull',
222 className: fragmentsClassName,
223 fieldName: 'fragments',
224 });
225 const fragmentsClass = new JavaDeclarationBlock()
226 .withName(fragmentsClassName)
227 .access('public')
228 .static()
229 .final()
230 .asKind('class');
231 const fragmentMapperClass = new JavaDeclarationBlock()
232 .withName('Mapper')
233 .access('public')
234 .static()
235 .final()
236 .implements([`FragmentResponseFieldMapper<${fragmentsClassName}>`])
237 .asKind('class');
238 fragmentsClass.addClassMethod(fragmentsClassName, null, childFragmentSpread
239 .map(spread => {
240 const varName = camelCase(spread.name);
241 this._imports.add(Imports.Utils);
242 return `this.${varName} = Utils.checkNotNull(${varName}, "${varName} == null");`;
243 })
244 .join('\n'), childFragmentSpread.map(spread => ({
245 name: camelCase(spread.name),
246 type: spread.name,
247 annotations: ['Nonnull'],
248 })), [], 'public');
249 for (const spread of childFragmentSpread) {
250 const fragmentVarName = camelCase(spread.name);
251 fragmentsClass.addClassMember(fragmentVarName, spread.name, null, ['Nonnull'], 'private', { final: true });
252 fragmentsClass.addClassMethod(fragmentVarName, spread.name, `return this.${fragmentVarName};`, [], ['Nonnull'], 'public', {}, []);
253 fragmentMapperClass.addClassMember(`${fragmentVarName}FieldMapper`, `${spread.name}.Mapper`, `new ${spread.name}.Mapper()`, [], 'private', { final: true });
254 }
255 fragmentMapperClass.addClassMethod('map', fragmentsClassName, `
256${childFragmentSpread
257 .map(spread => {
258 const fragmentVarName = camelCase(spread.name);
259 return `${spread.name} ${fragmentVarName} = null;
260if (${spread.name}.POSSIBLE_TYPES.contains(conditionalType)) {
261 ${fragmentVarName} = ${fragmentVarName}FieldMapper.map(reader);
262}`;
263 })
264 .join('\n')}
265
266return new Fragments(${childFragmentSpread
267 .map(spread => {
268 const fragmentVarName = camelCase(spread.name);
269 return `Utils.checkNotNull(${fragmentVarName}, "${fragmentVarName} == null")`;
270 })
271 .join(', ')});
272 `, [
273 {
274 name: 'reader',
275 type: 'ResponseReader',
276 },
277 {
278 name: 'conditionalType',
279 type: 'String',
280 annotations: ['Nonnull'],
281 },
282 ], ['Nonnull'], 'public', {}, ['Override']);
283 this._imports.add(Imports.String);
284 this._imports.add(Imports.ResponseReader);
285 this._imports.add(Imports.ResponseFieldMarshaller);
286 this._imports.add(Imports.ResponseWriter);
287 fragmentsClass.addClassMethod('marshaller', 'ResponseFieldMarshaller', `return new ResponseFieldMarshaller() {
288 @Override
289 public void marshal(ResponseWriter writer) {
290${childFragmentSpread
291 .map(spread => {
292 const fragmentVarName = camelCase(spread.name);
293 return indentMultiline(`final ${spread.name} $${fragmentVarName} = ${fragmentVarName};\nif ($${fragmentVarName} != null) { $${fragmentVarName}.marshaller().marshal(writer); }`, 2);
294 })
295 .join('\n')}
296 }
297};
298 `, [], [], 'public');
299 fragmentsClass.addClassMember('$toString', 'String', null, [], 'private', { volatile: true });
300 fragmentsClass.addClassMember('$hashCode', 'int', null, [], 'private', { volatile: true });
301 fragmentsClass.addClassMember('$hashCodeMemoized', 'boolean', null, [], 'private', { volatile: true });
302 fragmentsClass.addClassMethod('toString', 'String', `if ($toString == null) {
303 $toString = "${fragmentsClassName}{"
304 ${childFragmentSpread
305 .map(spread => {
306 const varName = camelCase(spread.name);
307 return indent(`+ "${varName}=" + ${varName} + ", "`, 2);
308 })
309 .join('\n')}
310 + "}";
311 }
312
313 return $toString;`, [], [], 'public', {}, ['Override']);
314 // Add equals
315 fragmentsClass.addClassMethod('equals', 'boolean', `if (o == this) {
316 return true;
317 }
318 if (o instanceof ${fragmentsClassName}) {
319 ${fragmentsClassName} that = (${fragmentsClassName}) o;
320 return ${childFragmentSpread
321 .map(spread => {
322 const varName = camelCase(spread.name);
323 return `this.${varName}.equals(that.${varName})`;
324 })
325 .join(' && ')};
326 }
327
328 return false;`, [{ name: 'o', type: 'Object' }], [], 'public', {}, ['Override']);
329 // hashCode
330 fragmentsClass.addClassMethod('hashCode', 'int', `if (!$hashCodeMemoized) {
331 int h = 1;
332 ${childFragmentSpread
333 .map(spread => {
334 const varName = camelCase(spread.name);
335 return indentMultiline(`h *= 1000003;\nh ^= ${varName}.hashCode();`, 1);
336 })
337 .join('\n')}
338 $hashCode = h;
339 $hashCodeMemoized = true;
340 }
341
342 return $hashCode;`, [], [], 'public', {}, ['Override']);
343 this._imports.add(Imports.FragmentResponseFieldMapper);
344 fragmentsClass.nestedClass(fragmentMapperClass);
345 cls.nestedClass(fragmentsClass);
346 }
347 if (responseFieldArr.length > 0 && !isRoot) {
348 responseFieldArr.unshift(`ResponseField.forString("__typename", "__typename", null, false, Collections.<ResponseField.Condition>emptyList())`);
349 }
350 if (!isRoot) {
351 this._imports.add(Imports.Nonnull);
352 childFields.unshift({
353 isObject: false,
354 isFragment: false,
355 isList: false,
356 type: GraphQLString,
357 rawType: GraphQLString,
358 isNonNull: true,
359 annotation: 'Nonnull',
360 className: 'String',
361 fieldName: '__typename',
362 });
363 }
364 // Add members
365 childFields.forEach(c => {
366 cls.addClassMember(c.fieldName, this.getListTypeWrapped(c.className, c.rawType), null, [c.annotation], 'private', { final: true });
367 });
368 // Add $toString, $hashCode, $hashCodeMemoized
369 cls.addClassMember('$toString', 'String', null, [], 'private', { volatile: true });
370 cls.addClassMember('$hashCode', 'int', null, [], 'private', { volatile: true });
371 cls.addClassMember('$hashCodeMemoized', 'boolean', null, [], 'private', { volatile: true });
372 // Add responseFields for all fields
373 cls.addClassMember('$responseFields', 'ResponseField[]', `{\n${indentMultiline(responseFieldArr.join(',\n'), 2) + '\n }'}`, [], null, { static: true, final: true });
374 // Add Ctor
375 this._imports.add(Imports.Utils);
376 cls.addClassMethod(className, null, childFields
377 .map(c => `this.${c.fieldName} = ${c.isNonNull ? `Utils.checkNotNull(${c.fieldName}, "${c.fieldName} == null")` : c.fieldName};`)
378 .join('\n'), childFields.map(c => ({
379 name: c.fieldName,
380 type: this.getListTypeWrapped(c.className, c.rawType),
381 annotations: [c.annotation],
382 })), null, 'public');
383 // Add getters for all members
384 childFields.forEach(c => {
385 cls.addClassMethod(c.fieldName, this.getListTypeWrapped(c.className, c.rawType), `return this.${c.fieldName};`, [], [c.annotation], 'public', {});
386 });
387 // Add .toString()
388 cls.addClassMethod('toString', 'String', `if ($toString == null) {
389 $toString = "${className}{"
390${childFields.map(c => indent(`+ "${c.fieldName}=" + ${c.fieldName} + ", "`, 2)).join('\n')}
391 + "}";
392}
393
394return $toString;`, [], [], 'public', {}, ['Override']);
395 // Add equals
396 cls.addClassMethod('equals', 'boolean', `if (o == this) {
397 return true;
398}
399if (o instanceof ${className}) {
400 ${className} that = (${className}) o;
401 return ${childFields
402 .map(c => c.isNonNull
403 ? `this.${c.fieldName}.equals(that.${c.fieldName})`
404 : `((this.${c.fieldName} == null) ? (that.${c.fieldName} == null) : this.${c.fieldName}.equals(that.${c.fieldName}))`)
405 .join(' && ')};
406}
407
408return false;`, [{ name: 'o', type: 'Object' }], [], 'public', {}, ['Override']);
409 // hashCode
410 cls.addClassMethod('hashCode', 'int', `if (!$hashCodeMemoized) {
411 int h = 1;
412${childFields
413 .map(f => indentMultiline(`h *= 1000003;\nh ^= ${!f.isNonNull ? `(${f.fieldName} == null) ? 0 : ` : ''}${f.fieldName}.hashCode();`, 1))
414 .join('\n')}
415 $hashCode = h;
416 $hashCodeMemoized = true;
417}
418
419return $hashCode;`, [], [], 'public', {}, ['Override']);
420 this._imports.add(Imports.ResponseReader);
421 this._imports.add(Imports.ResponseFieldMarshaller);
422 this._imports.add(Imports.ResponseWriter);
423 // marshaller
424 cls.addClassMethod('marshaller', 'ResponseFieldMarshaller', `return new ResponseFieldMarshaller() {
425 @Override
426 public void marshal(ResponseWriter writer) {
427${childFields
428 .map((f, index) => {
429 const writerMethod = this._getWriterMethodByType(f.type);
430 if (f.isList) {
431 return indentMultiline(`writer.writeList($responseFields[${index}], ${f.fieldName}, new ResponseWriter.ListWriter() {
432 @Override
433 public void write(Object value, ResponseWriter.ListItemWriter listItemWriter) {
434 listItemWriter.${writerMethod.name}(((${f.className}) value)${writerMethod.useMarshaller ? '.marshaller()' : ''});
435 }
436});`, 2);
437 }
438 let fValue = `${f.fieldName}${writerMethod.useMarshaller ? '.marshaller()' : ''}`;
439 if (writerMethod.checkNull || !f.isNonNull) {
440 fValue = `${f.fieldName} != null ? ${fValue} : null`;
441 }
442 return indent(`writer.${writerMethod.name}(${writerMethod.castTo ? `(${writerMethod.castTo}) ` : ''}$responseFields[${index}], ${fValue});`, 2);
443 })
444 .join('\n')}
445 }
446};`, [], [], 'public');
447 cls.nestedClass(this.buildMapperClass(className, childFields));
448 return options.result;
449 }
450 getReaderFn(baseType) {
451 if (isScalarType(baseType)) {
452 if (baseType.name === 'String') {
453 return { fn: `readString` };
454 }
455 if (baseType.name === 'Int') {
456 return { fn: `readInt` };
457 }
458 if (baseType.name === 'Float') {
459 return { fn: `readDouble` };
460 }
461 if (baseType.name === 'Boolean') {
462 return { fn: `readBoolean` };
463 }
464 return { fn: `readCustomType`, custom: true };
465 }
466 if (isEnumType(baseType)) {
467 return { fn: `readString` };
468 }
469 return { fn: `readObject`, object: baseType.name };
470 }
471 buildMapperClass(parentClassName, childFields) {
472 const wrapList = (childField, rawType, edgeStr) => {
473 if (isNonNullType(rawType)) {
474 return wrapList(childField, rawType.ofType, edgeStr);
475 }
476 if (isListType(rawType)) {
477 const typeStr = this.getListTypeWrapped(childField.className, rawType.ofType);
478 const innerContent = wrapList(childField, rawType.ofType, edgeStr);
479 const inner = isListType(rawType.ofType) ? `return listItemReader.readList(${innerContent});` : innerContent;
480 return `new ResponseReader.ListReader<${typeStr}>() {
481 @Override
482 public ${typeStr} read(ResponseReader.ListItemReader listItemReader) {
483${indentMultiline(inner, 2)}
484 }
485}`;
486 }
487 return edgeStr;
488 };
489 this._imports.add(Imports.ResponseReader);
490 const mapperBody = childFields.map((f, index) => {
491 const varDec = `final ${this.getListTypeWrapped(f.className, f.rawType)} ${f.fieldName} =`;
492 const readerFn = this.getReaderFn(f.type);
493 if (f.isFragment) {
494 return `${varDec} reader.readConditional($responseFields[${index}], new ResponseReader.ConditionalTypeReader<${f.className}>() {
495 @Override
496 public ${f.className} read(String conditionalType, ResponseReader reader) {
497 return fragmentsFieldMapper.map(reader, conditionalType);
498 }
499 });`;
500 }
501 if (f.isList) {
502 const listReader = readerFn.object
503 ? `return listItemReader.${readerFn.fn}(new ResponseReader.ObjectReader<Item>() {
504 @Override
505 public Item read(ResponseReader reader) {
506 return ${f.fieldName}FieldMapper.map(reader);
507 }
508 });`
509 : `return listItemReader.${readerFn.fn}();`;
510 const wrappedList = wrapList(f, f.rawType, listReader);
511 return `${varDec} reader.readList($responseFields[${index}], ${wrappedList});`;
512 }
513 if (readerFn.object) {
514 return `${varDec} reader.readObject($responseFields[${index}], new ResponseReader.ObjectReader<${f.className}>() {
515 @Override
516 public ${f.className} read(ResponseReader reader) {
517 return ${f.fieldName}FieldMapper.map(reader);
518 }
519 });`;
520 }
521 return `${varDec} reader.${readerFn.fn}(${readerFn.custom ? '(ResponseField.CustomTypeField) ' : ''}$responseFields[${index}]);`;
522 });
523 const mapperImpl = [
524 ...mapperBody,
525 `return new ${parentClassName}(${childFields.map(f => f.fieldName).join(', ')});`,
526 ].join('\n');
527 const cls = new JavaDeclarationBlock()
528 .access('public')
529 .static()
530 .final()
531 .asKind('class')
532 .withName('Mapper')
533 .implements([`ResponseFieldMapper<${parentClassName}>`])
534 .addClassMethod('map', parentClassName, mapperImpl, [
535 {
536 name: 'reader',
537 type: 'ResponseReader',
538 },
539 ], [], 'public', {}, ['Override']);
540 childFields
541 .filter(c => c.isObject)
542 .forEach(childField => {
543 cls.addClassMember(`${childField.fieldName}FieldMapper`, `${childField.className}.Mapper`, `new ${childField.className}.Mapper()`, [], 'private', { final: true });
544 });
545 return cls;
546 }
547 _resolveResponseFieldMethodForBaseType(baseType) {
548 if (isListType(baseType)) {
549 return { fn: `forList` };
550 }
551 if (isNonNullType(baseType)) {
552 return this._resolveResponseFieldMethodForBaseType(baseType.ofType);
553 }
554 if (isScalarType(baseType)) {
555 if (baseType.name === 'String') {
556 return { fn: `forString` };
557 }
558 if (baseType.name === 'Int') {
559 return { fn: `forInt` };
560 }
561 if (baseType.name === 'Float') {
562 return { fn: `forDouble` };
563 }
564 if (baseType.name === 'Boolean') {
565 return { fn: `forBoolean` };
566 }
567 this._imports.add(`${this.config.typePackage}.CustomType`);
568 return { fn: `forCustomType`, custom: true };
569 }
570 if (isEnumType(baseType)) {
571 return { fn: `forEnum` };
572 }
573 return { fn: `forObject` };
574 }
575 FragmentDefinition(node) {
576 this.visitingFragment = true;
577 const className = node.name.value;
578 const schemaType = this._schema.getType(node.typeCondition.name.value);
579 this._imports.add(Imports.Arrays);
580 this._imports.add(Imports.GraphqlFragment);
581 this._imports.add(Imports.List);
582 this._imports.add(Imports.String);
583 this._imports.add(Imports.Collections);
584 this._imports.add(Imports.Override);
585 this._imports.add(Imports.Generated);
586 this._imports.add(Imports.ResponseFieldMapper);
587 const dataClasses = this.transformSelectionSet({
588 className,
589 nonStaticClass: true,
590 implements: ['GraphqlFragment'],
591 selectionSet: node.selectionSet && node.selectionSet.selections ? node.selectionSet.selections : [],
592 result: {},
593 schemaType,
594 }, false);
595 const rootCls = dataClasses[className];
596 const printed = this.printDocument(node);
597 rootCls.addClassMember('FRAGMENT_DEFINITION', 'String', `"${printed}"`, [], 'public', {
598 static: true,
599 final: true,
600 });
601 const possibleTypes = isObjectType(schemaType) ? [schemaType.name] : this.getImplementingTypes(schemaType);
602 rootCls.addClassMember('POSSIBLE_TYPES', 'List<String>', `Collections.unmodifiableList(Arrays.asList(${possibleTypes.map(t => `"${t}"`).join(', ')}))`, [], 'public', { static: true, final: true });
603 Object.keys(dataClasses)
604 .filter(name => name !== className)
605 .forEach(clsName => {
606 rootCls.nestedClass(dataClasses[clsName]);
607 });
608 return rootCls.string;
609 }
610 OperationDefinition(node) {
611 this.visitingFragment = false;
612 const operationType = pascalCase(node.operation);
613 const operationSchemaType = this.getRootType(node.operation);
614 const className = node.name.value.endsWith(operationType) ? operationType : `${node.name.value}${operationType}`;
615 this._imports.add(Imports[operationType]);
616 this._imports.add(Imports.String);
617 this._imports.add(Imports.Override);
618 this._imports.add(Imports.Generated);
619 this._imports.add(Imports.OperationName);
620 this._imports.add(Imports.Operation);
621 this._imports.add(Imports.ResponseFieldMapper);
622 const cls = new JavaDeclarationBlock()
623 .annotate([`Generated("Apollo GraphQL")`])
624 .access('public')
625 .final()
626 .asKind('class')
627 .withName(className);
628 const printed = this.printDocument(node);
629 cls.implements([
630 `${operationType}<${className}.Data, ${className}.Data, ${node.variableDefinitions.length === 0 ? 'Operation' : className}.Variables>`,
631 ]);
632 cls.addClassMember('OPERATION_DEFINITION', 'String', `"${printed}"`, [], 'public', { static: true, final: true });
633 cls.addClassMember('QUERY_DOCUMENT', 'String', 'OPERATION_DEFINITION', [], 'public', { static: true, final: true });
634 cls.addClassMember('OPERATION_NAME', 'OperationName', `new OperationName() {
635 @Override
636 public String name() {
637 return "${node.name.value}";
638 }
639}`, [], 'public', { static: true, final: true });
640 cls.addClassMember('variables', `${node.variableDefinitions.length === 0 ? 'Operation' : className}.Variables`, null, [], 'private', { final: true });
641 cls.addClassMethod('queryDocument', `String`, `return QUERY_DOCUMENT;`, [], [], 'public', {}, ['Override']);
642 cls.addClassMethod('wrapData', `${className}.Data`, `return data;`, [
643 {
644 name: 'data',
645 type: `${className}.Data`,
646 },
647 ], [], 'public', {}, ['Override']);
648 cls.addClassMethod('variables', `${node.variableDefinitions.length === 0 ? 'Operation' : className}.Variables`, `return variables;`, [], [], 'public', {}, ['Override']);
649 cls.addClassMethod('responseFieldMapper', `ResponseFieldMapper<${className}.Data>`, `return new Data.Mapper();`, [], [], 'public', {}, ['Override']);
650 cls.addClassMethod('builder', `Builder`, `return new Builder();`, [], [], 'public', { static: true }, []);
651 cls.addClassMethod('name', `OperationName`, `return OPERATION_NAME;`, [], [], 'public', {}, ['Override']);
652 cls.addClassMethod('operationId', `String`, `return "${createHash('md5').update(printed).digest('hex')}";`, [], [], 'public', {}, []);
653 this.addCtor(className, node, cls);
654 this._imports.add(Imports.Operation);
655 const dataClasses = this.transformSelectionSet({
656 className: 'Data',
657 implements: ['Operation.Data'],
658 selectionSet: node.selectionSet && node.selectionSet.selections ? node.selectionSet.selections : [],
659 result: {},
660 schemaType: operationSchemaType,
661 });
662 Object.keys(dataClasses).forEach(className => {
663 cls.nestedClass(dataClasses[className]);
664 });
665 cls.nestedClass(this.createBuilderClass(className, node.variableDefinitions || []));
666 cls.nestedClass(this.createVariablesClass(className, node.variableDefinitions || []));
667 return cls.string;
668 }
669 createVariablesClass(parentClassName, variables) {
670 const className = 'Variables';
671 const cls = new JavaDeclarationBlock()
672 .static()
673 .access('public')
674 .final()
675 .asKind('class')
676 .extends(['Operation.Variables'])
677 .withName(className);
678 const ctorImpl = [];
679 const ctorArgs = [];
680 variables.forEach(variable => {
681 ctorImpl.push(`this.${variable.variable.name.value} = ${variable.variable.name.value};`);
682 ctorImpl.push(`this.valueMap.put("${variable.variable.name.value}", ${variable.variable.name.value});`);
683 const baseTypeNode = getBaseTypeNode(variable.type);
684 const schemaType = this._schema.getType(baseTypeNode.name.value);
685 const javaClass = this.getJavaClass(schemaType);
686 const annotation = isNonNullType(variable.type) ? 'Nullable' : 'Nonnull';
687 this._imports.add(Imports[annotation]);
688 ctorArgs.push({ name: variable.variable.name.value, type: javaClass, annotations: [annotation] });
689 cls.addClassMember(variable.variable.name.value, javaClass, null, [annotation], 'private');
690 cls.addClassMethod(variable.variable.name.value, javaClass, `return ${variable.variable.name.value};`, [], [], 'public');
691 });
692 this._imports.add(Imports.LinkedHashMap);
693 this._imports.add(Imports.Map);
694 cls.addClassMethod(className, null, ctorImpl.join('\n'), ctorArgs, [], 'public');
695 cls.addClassMember('valueMap', 'Map<String, Object>', 'new LinkedHashMap<>()', [], 'private', {
696 final: true,
697 transient: true,
698 });
699 cls.addClassMethod('valueMap', 'Map<String, Object>', 'return Collections.unmodifiableMap(valueMap);', [], [], 'public', {}, ['Override']);
700 const marshallerImpl = `return new InputFieldMarshaller() {
701 @Override
702 public void marshal(InputFieldWriter writer) throws IOException {
703${variables
704 .map(v => {
705 const baseTypeNode = getBaseTypeNode(v.type);
706 const schemaType = this._schema.getType(baseTypeNode.name.value);
707 const writerMethod = this._getWriterMethodByType(schemaType, true);
708 return indent(`writer.${writerMethod.name}("${v.variable.name.value}", ${writerMethod.checkNull
709 ? `${v.variable.name.value} != null ? ${v.variable.name.value}${writerMethod.useMarshaller ? '.marshaller()' : ''} : null`
710 : v.variable.name.value});`, 2);
711 })
712 .join('\n')}
713 }
714};`;
715 this._imports.add(Imports.InputFieldMarshaller);
716 this._imports.add(Imports.InputFieldWriter);
717 this._imports.add(Imports.IOException);
718 cls.addClassMethod('marshaller', 'InputFieldMarshaller', marshallerImpl, [], [], 'public', {}, ['Override']);
719 return cls;
720 }
721 _getWriterMethodByType(schemaType, idAsString = false) {
722 if (isScalarType(schemaType)) {
723 if (SCALAR_TO_WRITER_METHOD[schemaType.name] && (idAsString || schemaType.name !== 'ID')) {
724 return {
725 name: SCALAR_TO_WRITER_METHOD[schemaType.name],
726 checkNull: false,
727 useMarshaller: false,
728 };
729 }
730 return { name: 'writeCustom', checkNull: false, useMarshaller: false, castTo: 'ResponseField.CustomTypeField' };
731 }
732 if (isInputObjectType(schemaType)) {
733 return { name: 'writeObject', checkNull: true, useMarshaller: true };
734 }
735 if (isEnumType(schemaType)) {
736 return { name: 'writeString', checkNull: false, useMarshaller: false };
737 }
738 if (isObjectType(schemaType) || isInterfaceType(schemaType)) {
739 return { name: 'writeObject', checkNull: true, useMarshaller: true };
740 }
741 return { name: 'writeString', useMarshaller: false, checkNull: false };
742 }
743 createBuilderClass(parentClassName, variables) {
744 const builderClassName = 'Builder';
745 const cls = new JavaDeclarationBlock()
746 .static()
747 .final()
748 .access('public')
749 .asKind('class')
750 .withName(builderClassName)
751 .addClassMethod(builderClassName, null, '');
752 variables.forEach(variable => {
753 const baseTypeNode = getBaseTypeNode(variable.type);
754 const schemaType = this._schema.getType(baseTypeNode.name.value);
755 const javaClass = this.getJavaClass(schemaType);
756 const annotation = isNonNullType(variable.type) ? 'Nonnull' : 'Nullable';
757 this._imports.add(Imports[annotation]);
758 cls.addClassMember(variable.variable.name.value, javaClass, null, [annotation], 'private');
759 cls.addClassMethod(variable.variable.name.value, builderClassName, `this.${variable.variable.name.value} = ${variable.variable.name.value};\nreturn this;`, [
760 {
761 name: variable.variable.name.value,
762 type: javaClass,
763 annotations: [annotation],
764 },
765 ], [], 'public');
766 });
767 this._imports.add(Imports.Utils);
768 const nonNullChecks = variables
769 .filter(f => isNonNullType(f))
770 .map(f => `Utils.checkNotNull(${f.variable.name.value}, "${f.variable.name.value} == null");`);
771 const returnStatement = `return new ${parentClassName}(${variables.map(v => v.variable.name.value).join(', ')});`;
772 cls.addClassMethod('build', parentClassName, `${[...nonNullChecks, returnStatement].join('\n')}`, [], [], 'public');
773 return cls;
774 }
775}