static JsonNode addToArray(final JsonPointer path, final JsonNode node, final JsonNode value) { final ArrayNode target = (ArrayNode) node.at(path.head()); final String rawToken = path.last().getMatchingProperty(); if (rawToken.equals(LAST_ARRAY_ELEMENT)) { target.add(value); return node; } final int size = target.size(); final int index; try { index = Integer.parseInt(rawToken); } catch (NumberFormatException ignored) { throw new JsonPatchException("not an index: " + rawToken + " (expected: a non-negative integer)"); } if (index < 0 || index > size) { throw new JsonPatchException("index out of bounds: " + index + " (expected: >= 0 && <= " + size + ')'); } target.insert(index, value); return node; }
private static JsonNode ensureParent(JsonNode node, JsonPointer path, String typeName) { /* * Check the parent node: it must exist and be a container (ie an array * or an object) for the add operation to work. */ final JsonPointer parentPath = path.head(); final JsonNode parentNode = node.at(parentPath); if (parentNode.isMissingNode()) { throw new JsonPatchException("non-existent " + typeName + " parent: " + parentPath); } if (!parentNode.isContainerNode()) { throw new JsonPatchException(typeName + " parent is not a container: " + parentPath + " (" + parentNode.getNodeType() + ')'); } return parentNode; }
private void setInPath(ObjectNode obj, JsonPointer path, JsonNode value) { String key = path.getMatchingProperty(); path = path.tail(); if (path.matches()) { if (!obj.has(key)) { obj.set(key, value); } else if (canMerge(obj, value, key)) { merge(obj, value, key); } else if (canAdd(obj, value)) { add(obj, value); } else { throw badStructure(); } } else { obj = obj.has(key) ? (ObjectNode) obj.get(key) : obj.putObject(key); setInPath(obj, path, value); } }
public Pair<T, JsonPointer> find(JsonPointer path) { if (path.matches()) { return Pair.of(patternMap.get(null), path); } else { String key = path.getMatchingProperty(); if (children.containsKey(key)) { return children.get(key).find(path.tail()); } // see if any non-null pattern matches key, return our T value with the same // JsonPointer. This case addresses objects that implenent maps with // pattern-constrained keys. We resolve to that map-like object, and the // remaining path starts with a key into that map. for (Pattern pat : patternMap.keySet()) { if (pat != null && pat.matcher(key).matches()) { return Pair.of(patternMap.get(pat), path); } } // no non-null patterns matched - return our null-pattern value, with the // remaining path to be applied to it. return Pair.of(patternMap.get(null), path); } }
@Test public void test() throws JsonProcessingException, IOException { Object parsedYaml = new Yaml().load(modelUrl.openStream()); JsonNode tree = new YAMLMapper().convertValue(parsedYaml, JsonNode.class); final OpenApi3 model = (OpenApi3) new OpenApiParser().parse(modelUrl, false); Predicate<JsonNode> valueNodePredicate = n -> n.isValueNode(); JsonTreeWalker.WalkMethod valueChecker = new JsonTreeWalker.WalkMethod() { @Override public void run(JsonNode node, JsonPointer path) { IJsonOverlay<?> overlay = ((OpenApi3Impl) model).find(path); assertNotNull("No overlay object found for path: " + path, overlay); Object fromJson = getValue(node); String msg = String.format("Wrong overlay value for path '%s': expected '%s', got '%s'", path, fromJson, overlay.get()); assertEquals(msg, fromJson, overlay.get()); } }; JsonTreeWalker.walkTree(tree, valueNodePredicate, valueChecker); }
/** * * @param targetJsonNode * @param operations * @return */ protected List<PatchOperation> optimize(JsonNode targetJsonNode, List<PatchOperation> operations) { List<PatchOperation> optimizedOperations = new ArrayList<>(); Map<JsonPointer, JsonPointerData> parentToJsonPointerDataMap = new HashMap<>(); for (PatchOperation operation: operations) { JsonNode pathJsonNode = JacksonUtils.locateHeadContainer(targetJsonNode, operation.getPath()); if (pathJsonNode.isObject()) { if (operation.getClass() == RemoveOperation.class) { optimizedOperations.add(operation); } else { JsonPointer parentPointer = operation.getPath().head(); if (!parentToJsonPointerDataMap.containsKey(parentPointer)) { parentToJsonPointerDataMap.put(parentPointer, new JsonPointerData()); } parentToJsonPointerDataMap.get(parentPointer).getOperations().add(operation); parentToJsonPointerDataMap.get(parentPointer).getFieldNames().add(JacksonUtils.lastFieldName(operation.getPath())); } } else if (pathJsonNode.isArray()) { optimizedOperations.add(operation); } } // merge process must start handling arrays optimizedOperations.addAll(optimize(targetJsonNode, parentToJsonPointerDataMap)); return optimizedOperations; }
/** * * @param targetJsonNode * @param parentToJsonPointerDataMap * @return */ protected List<PatchOperation> optimize(JsonNode targetJsonNode, Map<JsonPointer, JsonPointerData> parentToJsonPointerDataMap) { List<PatchOperation> optimizedOperations = new ArrayList<>(); Map<JsonPointer, MergeOperation> parentToMergeOperation = new HashMap<>(); for (JsonPointer parentPath : parentToJsonPointerDataMap.keySet()) { JsonPointerData jsonPointerData = parentToJsonPointerDataMap.get(parentPath); JsonNode parentJsonNode = JacksonUtils.locateContainer(targetJsonNode, parentPath); ObjectNode parentObjectNode = (ObjectNode) parentJsonNode.deepCopy(); parentObjectNode.retain(jsonPointerData.getFieldNames()); MergeOperation mergeOperation = new MergeOperation(parentPath, parentObjectNode); mergeOperation = parentObjectMergeOperation(targetJsonNode, mergeOperation); MergeOperation parentMergeOperation = parentToMergeOperation.get(mergeOperation.getPath()); if (parentMergeOperation == null) { parentToMergeOperation.put(mergeOperation.getPath(), mergeOperation); } else { JsonNode mergedJsonNode = new MergeOperation(parentMergeOperation.getValue()).apply(mergeOperation.getValue()); parentMergeOperation.setValue(mergedJsonNode); } } if (!parentToMergeOperation.isEmpty()) { optimizedOperations.addAll(parentToMergeOperation.values()); } return optimizedOperations; }
/** * Acquires or builds the json pointer for the passed expression * @param expression The json pointer expression * @return the JsonPointer wrapped in a weak ref */ protected static WeakReference<JsonPointer> getJsonPointerRef(final String expression) { WeakReference<JsonPointer> ref = jsonPointers.get(expression); if(ref==null || ref.get()==null) { synchronized(jsonPointers) { ref = jsonPointers.get(expression); if(ref==null || ref.get()==null) { final JsonPointer jp = JsonPointer.compile(expression); ref = ReferenceService.getInstance().newWeakReference(jp, new Runnable(){ @Override public void run() { jsonPointers.remove(expression); } }); jsonPointers.put(expression, ref); } } } return ref; }
protected void validateOperationIdReferences(Model model, AbstractNode node, Set<SwaggerError> errors) { JsonPointer schemaPointer = JsonPointer.compile("/definitions/link/properties/operationId"); if (node != null && node.getType() != null && schemaPointer.equals(node.getType().getPointer())) { List<AbstractNode> nodes = model.findByType(operationPointer); Iterator<AbstractNode> it = nodes.iterator(); boolean found = false; while (it.hasNext() && !found) { AbstractNode current = it.next(); AbstractNode value = current.get("operationId"); found = value != null && Objects.equals(node.asValue().getValue(), value.asValue().getValue()); } if (!found) { errors.add(error(node, IMarker.SEVERITY_ERROR, Messages.error_invalid_operation_id)); } } }
protected void validateParameters(Model model, AbstractNode node, Set<SwaggerError> errors) { final JsonPointer pointer = JsonPointer.compile("/definitions/parameterOrReference"); if (node != null && node.getType() != null && pointer.equals(node.getType().getPointer())) { // validation parameter location value if (node.isObject() && node.asObject().get("in") != null) { AbstractNode valueNode = node.asObject().get("in"); try { Object value = valueNode.asValue().getValue(); if (!Lists.newArrayList("query", "header", "path", "cookie").contains(value)) { errors.add(error(valueNode, IMarker.SEVERITY_ERROR, Messages.error_invalid_parameter_location)); } } catch (Exception e) { errors.add(error(valueNode, IMarker.SEVERITY_ERROR, Messages.error_invalid_parameter_location)); } } } }
@Override protected IHyperlink[] doDetect(JsonDocument doc, ITextViewer viewer, HyperlinkInfo info, JsonPointer pointer) { Model model = doc.getModel(); AbstractNode node = model.find(pointer); List<AbstractNode> nodes = model.findByType(JsonPointer.compile("/definitions/operation")); Iterator<AbstractNode> it = nodes.iterator(); AbstractNode found = null; while (it.hasNext() && found == null) { AbstractNode current = it.next(); AbstractNode value = current.get("operationId"); if (value != null && Objects.equals(node.asValue().getValue(), value.asValue().getValue())) { found = value; } } if (found != null) { IRegion target = doc.getRegion(found.getPointer()); if (target != null) { return new IHyperlink[] { new SwaggerHyperlink(info.text, viewer, info.region, target) }; } } return null; }
@Override protected IHyperlink[] doDetect(JsonDocument doc, ITextViewer viewer, HyperlinkInfo info, JsonPointer pointer) { Matcher matcher = PATTERN.matcher(pointer.toString()); String link = matcher.find() ? matcher.group(1) : null; if (link != null) { Model model = doc.getModel(); AbstractNode securityScheme = model.find("/components/securitySchemes/" + link); if (securityScheme != null) { IRegion target = doc.getRegion(securityScheme.getPointer()); if (target != null) { return new IHyperlink[] { new SwaggerHyperlink(info.text, viewer, info.region, target) }; } } } return null; }
protected ObjectNode deserializeObjectNode(JsonParser p, DeserializationContext context, JsonLocation startLocation) throws IllegalArgumentException, IOException { final Model model = (Model) context.getAttribute(ATTRIBUTE_MODEL); final AbstractNode parent = (AbstractNode) context.getAttribute(ATTRIBUTE_PARENT); final JsonPointer ptr = (JsonPointer) context.getAttribute(ATTRIBUTE_POINTER); final ObjectNode node = model.objectNode(parent, ptr); node.setStartLocation(createLocation(startLocation)); while (p.nextToken() != JsonToken.END_OBJECT) { String name = p.getCurrentName(); JsonPointer pp = JsonPointer.compile(ptr.toString() + "/" + name.replaceAll("/", "~1")); context.setAttribute(ATTRIBUTE_PARENT, node); context.setAttribute(ATTRIBUTE_POINTER, pp); AbstractNode v = deserialize(p, context); v.setProperty(name); node.put(name, v); } node.setEndLocation(createLocation(p.getCurrentLocation())); return node; }
protected ArrayNode deserializeArrayNode(JsonParser p, DeserializationContext context, JsonLocation startLocation) throws IOException { final Model model = (Model) context.getAttribute(ATTRIBUTE_MODEL); final AbstractNode parent = (AbstractNode) context.getAttribute(ATTRIBUTE_PARENT); final JsonPointer ptr = (JsonPointer) context.getAttribute(ATTRIBUTE_POINTER); ArrayNode node = model.arrayNode(parent, ptr); int i = 0; while (p.nextToken() != JsonToken.END_ARRAY) { JsonPointer pp = JsonPointer.compile(ptr.toString() + "/" + i); context.setAttribute(ATTRIBUTE_PARENT, node); context.setAttribute(ATTRIBUTE_POINTER, pp); AbstractNode v = deserialize(p, context); node.add(v); i++; } node.setStartLocation(createLocation(startLocation)); node.setEndLocation(createLocation(p.getCurrentLocation())); return node; }
@Override protected IHyperlink[] doDetect(JsonDocument doc, ITextViewer viewer, HyperlinkInfo info, JsonPointer pointer) { JsonPointer targetPath; if (pointer.toString().matches(REQUIRED_PATTERN)) { targetPath = getRequiredPropertyPath(doc, info, pointer); } else { targetPath = getTagDefinitionPath(doc, info, pointer); } if (targetPath == null) { return null; } IRegion target = doc.getRegion(targetPath); if (target == null) { return null; } return new IHyperlink[] { new SwaggerHyperlink(info.text, viewer, info.region, target) }; }
protected JsonPointer getRequiredPropertyPath(JsonDocument doc, HyperlinkInfo info, JsonPointer pointer) { Matcher matcher = Pattern.compile(REQUIRED_PATTERN).matcher(pointer.toString()); String containerPath = null; if (matcher.find()) { containerPath = matcher.group(1); } if (emptyToNull(containerPath) == null) { return null; } AbstractNode container = doc.getModel().find(JsonPointer.compile(containerPath)); if (container.get("properties") != null && container.get("properties").get(info.text) != null) { return container.get("properties").get(info.text).getPointer(); } else { return null; } }
@Override public IHyperlink[] detectHyperlinks(ITextViewer textViewer, IRegion region, boolean canShowMultipleHyperlinks) { JsonDocument document = (JsonDocument) textViewer.getDocument(); JsonPointer basePath = document.getPath(region); if (!canDetect(basePath)) { return null; } HyperlinkInfo info = getHyperlinkInfo(textViewer, region); if (info == null) { return null; } return doDetect(document, textViewer, info, basePath); }
/** * Returns the type of a node. * * <br/> * * Note: this method should be used only during initialization of a model. * * @param node * @return node's type */ public TypeDefinition getType(AbstractNode node) { JsonPointer pointer = node.getPointer(); if (JsonPointer.compile("").equals(pointer)) { return swaggerType.getType(); } String[] paths = pointer.toString().substring(1).split("/"); TypeDefinition current = swaggerType.getType(); if (current != null) { for (String property : paths) { TypeDefinition next = current.getPropertyType(property); // not found, we stop here if (next == null) { break; } current = next; } } return current; }
/** * Returns collection of JSON reference proposals. * * If the scope is local, it will only return JSON references from within the current document. * * If the scope is project, it will return all JSON references from within the current document and from all * documents inside the same project. * * If the scope is workspace, it will return all JSON references from within the current document and from all * documents inside the same workspace. * * @param pointer * @param document * @param scope * @return proposals */ public Collection<Proposal> getProposals(JsonPointer pointer, JsonDocument document, Scope scope) { final ContextType type = contextTypes.get(document.getModel(), pointer); final IFile currentFile = getActiveFile(); final IPath basePath = currentFile.getParent().getFullPath(); final List<Proposal> proposals = Lists.newArrayList(); if (scope == Scope.LOCAL) { proposals.addAll(type.collectProposals(document, null)); } else if (!type.isLocalOnly()) { final SwaggerFileFinder fileFinder = new SwaggerFileFinder(fileContentType); for (IFile file : fileFinder.collectFiles(scope, currentFile)) { IPath relative = file.equals(currentFile) ? null : file.getFullPath().makeRelativeTo(basePath); if (file.equals(currentFile)) { proposals.addAll(type.collectProposals(document, relative)); } else { proposals.addAll(type.collectProposals(manager.getDocument(file.getLocationURI()), relative)); } } } return proposals; }
protected boolean isReference(Model model, JsonPointer pointer) { AbstractNode contextNode = model.find(pointer); if (contextNode == null) { return false; } TypeDefinition type = contextNode.getType(); if (type instanceof MultipleTypeDefinition) { // MultipleTypeDefinition is a special case, it happens when several properties match a property for (TypeDefinition nestedType : ((MultipleTypeDefinition) type).getMultipleTypes()) { if (getReferencePointerString().equals(nestedType.getPointer().toString())) { return true; } } } JsonPointer pointerToType = type.getPointer(); if (pointerToType == null) { return false; } return getReferencePointerString().equals(pointerToType.toString()); }
protected boolean isReferenceToComponent(Model model, JsonPointer pointer) { AbstractNode parentNode = model.find(pointer.head()); if (parentNode == null) { return false; } TypeDefinition parentType = parentNode.getType(); if (parentType instanceof ComplexTypeDefinition) { Collection<TypeDefinition> types = ((ComplexTypeDefinition) parentType).getComplexTypes(); for (TypeDefinition type : types) { if (hasRefToComponent(type.getContent())) { return true; } } } return hasRefToComponent(parentType.getContent()); }
@Test public void testContextualInvalidPath() { Pet bird = new Pet(3, 4, Arrays.asList("Nick")); List<Object> pets = new ArrayList<Object>(Arrays.asList(bird)); JsonPatchOperation addPetInstruction = new JsonPatchOperation(JsonPatchOperationType.ADD, JsonPointer.compile("/incorrect/path"), mapper.convertValue(pets, JsonNode.class)); try { ClientResponse response = resources.client().target("/users/contextual/0") .request(MediaType.APPLICATION_JSON) .method("PATCH", Entity.json(Arrays.asList(addPetInstruction)), ClientResponse.class); } catch (WebApplicationException e) { assertThat(e.getResponse().getStatus()).isEqualTo(422); } }
@Test public void testInvalidPath() { Pet bird = new Pet(3, 4, Arrays.asList("Nick")); List<Object> pets = new ArrayList<Object>(Arrays.asList(bird)); JsonPatchOperation addPetInstruction = new JsonPatchOperation(JsonPatchOperationType.ADD, JsonPointer.compile("/incorrect/path"), mapper.convertValue(pets, JsonNode.class)); try { ClientResponse response = resources.client().target("/users/0").request(MediaType.APPLICATION_JSON) .method("PATCH", Entity.json(Arrays.asList(addPetInstruction)), ClientResponse.class); } catch (WebApplicationException e) { assertThat(e.getResponse().getStatus()).isEqualTo(422); } }
@Test public void testContextualOperationNotSupported() { Pet bird = new Pet(3, 4, Arrays.asList("Nick")); List<Object> pets = new ArrayList<Object>(Arrays.asList(bird)); JsonPatchOperation addPetInstruction = new JsonPatchOperation(JsonPatchOperationType.ADD, JsonPointer .compile("/pets"), mapper.convertValue(pets, JsonNode.class)); try { ClientResponse response = resources.client().target("/users/contextual/no-operations/0") .request(MediaType.APPLICATION_JSON) .method("PATCH", Entity.json(Arrays.asList(addPetInstruction)), ClientResponse.class); } catch (WebApplicationException e) { assertThat(e.getResponse().getStatus()).isEqualTo(415); } }
@Test public void testOperationNotSupported() { Pet bird = new Pet(3, 4, Arrays.asList("Nick")); List<Object> pets = new ArrayList<Object>(Arrays.asList(bird)); JsonPatchOperation addPetInstruction = new JsonPatchOperation(JsonPatchOperationType.ADD, JsonPointer.compile("/pets"), mapper.convertValue(pets, JsonNode.class)); try { ClientResponse response = resources.client().target("/users/no-operations/0") .request(MediaType.APPLICATION_JSON) .method("PATCH", Entity.json(Arrays.asList(addPetInstruction)), ClientResponse.class); } catch (WebApplicationException e) { assertThat(e.getResponse().getStatus()).isEqualTo(415); } }
@GET @Path("/{objectId}{pointer:(/.*)?}") public JsonNode getObjectAttribute( @PathParam("sourceId") String sourceId, @PathParam("objectId") String domainId, @PathParam("pointer") String pointer) { JsonNode node = assertIsValidSource(sourceId).findByDomainId(domainId); if (pointer.isEmpty() || pointer.equals("/")) return node; try { JsonPointer jsonPointer = JsonPointer.compile(pointer); JsonNode result = node.at(jsonPointer); if (result.isMissingNode()) throw RestUtils.createNotFoundException(); return result; } catch (IllegalArgumentException iae) { // thrown by JsonPointer.compile throw RestUtils.createNotFoundException(); } }
private String createDomainIdJavascriptProperty(JsonPointer domainIdKey) { StringBuilder keyBuilder = new StringBuilder(); keyBuilder.append("doc"); JsonPointer pointer = domainIdKey; while (pointer != null && !pointer.toString().isEmpty()) { if (pointer.mayMatchProperty()) { keyBuilder.append("."); keyBuilder.append(pointer.getMatchingProperty()); } else { keyBuilder.append("["); keyBuilder.append(pointer.getMatchingIndex()); keyBuilder.append("]"); } pointer = pointer.tail(); } return keyBuilder.toString(); }
@Test public void testStart() { new Expectations() {{ sourceRepository.getAll(); result = Arrays.asList(dataSource); repositoryFactory.createSourceDataRepository(SOURCE_ID, (JsonPointer) any); result = dataRepository; }}; sourceManager.start(); new Verifications() {{ Map<DataSource, DataRepository> sources; processorChainManager.startAllProcessorChains(sources = withCapture()); Assert.assertTrue(sources.containsKey(dataSource)); }}; }
@Override public TokenFilter includeElement(int index) { JsonPointer next = _pathToMatch.matchElement(index); if (next == null) { return null; } if (next.matches()) { return TokenFilter.INCLUDE_ALL; } return new JsonPointerBasedFilter(next); }
@Override public TokenFilter includeProperty(String name) { JsonPointer next = _pathToMatch.matchProperty(name); if (next == null) { return null; } if (next.matches()) { return TokenFilter.INCLUDE_ALL; } return new JsonPointerBasedFilter(next); }
private static void generateDiffs(final DiffProcessor processor, final JsonPointer pointer, final JsonNode source, final JsonNode target) { if (EQUIVALENCE.equivalent(source, target)) { return; } final JsonNodeType sourceType = source.getNodeType(); final JsonNodeType targetType = target.getNodeType(); /* * Node types differ: generate a replacement operation. */ if (sourceType != targetType) { processor.valueReplaced(pointer, source, target); return; } /* * If we reach this point, it means that both nodes are the same type, * but are not equivalent. * * If this is not a container, generate a replace operation. */ if (!source.isContainerNode()) { processor.valueReplaced(pointer, source, target); return; } /* * If we reach this point, both nodes are either objects or arrays; * delegate. */ if (sourceType == JsonNodeType.OBJECT) { generateObjectDiffs(processor, pointer, (ObjectNode) source, (ObjectNode) target); } else { // array generateArrayDiffs(processor, pointer, (ArrayNode) source, (ArrayNode) target); } }
private static void computeUnchanged(final Map<JsonPointer, JsonNode> ret, final JsonPointer pointer, final JsonNode source, final JsonNode target) { if (EQUIVALENCE.equivalent(source, target)) { ret.put(pointer, target); return; } final JsonNodeType sourceType = source.getNodeType(); final JsonNodeType targetType = target.getNodeType(); if (sourceType != targetType) { return; // nothing in common } // We know they are both the same type, so... switch (sourceType) { case OBJECT: computeUnchangedObject(ret, pointer, source, target); break; case ARRAY: computeUnchangedArray(ret, pointer, source, target); break; default: /* nothing */ } }
private static void computeUnchangedObject(final Map<JsonPointer, JsonNode> ret, final JsonPointer pointer, final JsonNode source, final JsonNode target) { final Iterator<String> sourceFields = source.fieldNames(); while (sourceFields.hasNext()) { final String name = sourceFields.next(); if (!target.has(name)) { continue; } computeUnchanged(ret, pointer.append(JsonPointer.valueOf('/' + name)), source.get(name), target.get(name)); } }
private static void computeUnchangedArray(final Map<JsonPointer, JsonNode> ret, final JsonPointer pointer, final JsonNode source, final JsonNode target) { final int size = Math.min(source.size(), target.size()); for (int i = 0; i < size; i++) { computeUnchanged(ret, pointer.append(JsonPointer.valueOf("/" + i)), source.get(i), target.get(i)); } }
@JsonCreator SafeReplaceOperation(@JsonProperty("path") final JsonPointer path, @JsonProperty("oldValue") JsonNode oldValue, @JsonProperty("value") JsonNode newValue) { super("safeReplace", path); this.oldValue = oldValue.deepCopy(); this.newValue = newValue.deepCopy(); }
DiffProcessor(ReplaceMode replaceMode, final Supplier<Map<JsonPointer, JsonNode>> unchangedValuesSupplier) { this.replaceMode = replaceMode; this.unchangedValuesSupplier = new Supplier<Map<JsonPointer, JsonNode>>() { private Map<JsonPointer, JsonNode> unchangedValues; @Override public Map<JsonPointer, JsonNode> get() { if (unchangedValues == null) { unchangedValues = unchangedValuesSupplier.get(); } return unchangedValues; } }; }
void valueReplaced(final JsonPointer pointer, final JsonNode oldValue, final JsonNode newValue) { switch (replaceMode) { case RFC6902: diffs.add(new ReplaceOperation(pointer, newValue)); break; case SAFE: diffs.add(new SafeReplaceOperation(pointer, oldValue, newValue)); break; } }
void valueAdded(final JsonPointer pointer, final JsonNode value) { final JsonPatchOperation op; if (value.isContainerNode()) { // Use copy operation only for container nodes. final JsonPointer ptr = findUnchangedValue(value); op = ptr != null ? new CopyOperation(ptr, pointer) : new AddOperation(pointer, value); } else { op = new AddOperation(pointer, value); } diffs.add(op); }