1   /*
2    * Copyright 2017 LINE Corporation
3    *
4    * LINE Corporation licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  /*
17   * Copyright (c) 2014, Francis Galiegue (fgaliegue@gmail.com)
18   *
19   * This software is dual-licensed under:
20   *
21   * - the Lesser General Public License (LGPL) version 3.0 or, at your option, any
22   *   later version;
23   * - the Apache Software License (ASL) version 2.0.
24   *
25   * The text of this file and of both licenses is available at the root of this
26   * project or, if you have the jar distribution, in directory META-INF/, under
27   * the names LGPL-3.0.txt and ASL-2.0.txt respectively.
28   *
29   * Direct link to the sources:
30   *
31   * - LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt
32   * - ASL 2.0: https://www.apache.org/licenses/LICENSE-2.0.txt
33   */
34  
35  package com.linecorp.centraldogma.internal.jsonpatch;
36  
37  import java.util.ArrayList;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.function.Supplier;
41  
42  import javax.annotation.Nullable;
43  
44  import com.fasterxml.jackson.core.JsonPointer;
45  import com.fasterxml.jackson.databind.JsonNode;
46  import com.google.common.base.Equivalence;
47  import com.google.common.base.Predicate;
48  
49  // TODO: cleanup
50  final class DiffProcessor {
51  
52      private static final Equivalence<JsonNode> EQUIVALENCE = JsonNumEquals.getInstance();
53  
54      private final List<JsonPatchOperation> diffs = new ArrayList<>();
55      private final ReplaceMode replaceMode;
56      private final Supplier<Map<JsonPointer, JsonNode>> unchangedValuesSupplier;
57  
58      DiffProcessor(ReplaceMode replaceMode, final Supplier<Map<JsonPointer, JsonNode>> unchangedValuesSupplier) {
59          this.replaceMode = replaceMode;
60          this.unchangedValuesSupplier = new Supplier<Map<JsonPointer, JsonNode>>() {
61  
62              @Nullable
63              private Map<JsonPointer, JsonNode> unchangedValues;
64  
65              @Override
66              public Map<JsonPointer, JsonNode> get() {
67                  if (unchangedValues == null) {
68                      unchangedValues = unchangedValuesSupplier.get();
69                  }
70                  return unchangedValues;
71              }
72          };
73      }
74  
75      void valueReplaced(final JsonPointer pointer, final JsonNode oldValue, final JsonNode newValue) {
76          switch (replaceMode) {
77              case RFC6902:
78                  diffs.add(new ReplaceOperation(pointer, newValue));
79                  break;
80              case SAFE:
81                  diffs.add(new SafeReplaceOperation(pointer, oldValue, newValue));
82                  break;
83          }
84      }
85  
86      void valueRemoved(final JsonPointer pointer) {
87          diffs.add(new RemoveOperation(pointer));
88      }
89  
90      void valueAdded(final JsonPointer pointer, final JsonNode value) {
91          final JsonPatchOperation op;
92          if (value.isContainerNode()) {
93              // Use copy operation only for container nodes.
94              final JsonPointer ptr = findUnchangedValue(value);
95              op = ptr != null ? new CopyOperation(ptr, pointer)
96                               : new AddOperation(pointer, value);
97          } else {
98              op = new AddOperation(pointer, value);
99          }
100 
101         diffs.add(op);
102     }
103 
104     JsonPatch getPatch() {
105         return new JsonPatch(diffs);
106     }
107 
108     @Nullable
109     private JsonPointer findUnchangedValue(final JsonNode value) {
110         final Map<JsonPointer, JsonNode> unchangedValues = unchangedValuesSupplier.get();
111         if (unchangedValues.isEmpty()) {
112             return null;
113         }
114 
115         final Predicate<JsonNode> predicate = EQUIVALENCE.equivalentTo(value);
116         for (final Map.Entry<JsonPointer, JsonNode> entry : unchangedValues.entrySet()) {
117             if (predicate.apply(entry.getValue())) {
118                 return entry.getKey();
119             }
120         }
121         return null;
122     }
123 }