1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.linecorp.centraldogma.common;
18
19 import static com.linecorp.centraldogma.internal.Util.validateFilePath;
20 import static com.linecorp.centraldogma.internal.Util.validateJsonFilePath;
21 import static java.util.Objects.requireNonNull;
22
23 import java.util.Objects;
24
25 import javax.annotation.Nullable;
26
27 import com.fasterxml.jackson.annotation.JsonCreator;
28 import com.fasterxml.jackson.annotation.JsonProperty;
29 import com.fasterxml.jackson.core.JsonProcessingException;
30 import com.fasterxml.jackson.databind.JsonNode;
31 import com.google.common.base.MoreObjects;
32
33 import com.linecorp.centraldogma.internal.Jackson;
34
35 final class DefaultChange<T> implements Change<T> {
36
37 @JsonCreator
38 static DefaultChange<?> deserialize(@JsonProperty("type") ChangeType type,
39 @JsonProperty("path") String path,
40 @JsonProperty("content") @Nullable JsonNode content) {
41 requireNonNull(type, "type");
42 final Class<?> contentType = type.contentType();
43 if (contentType == Void.class) {
44 if (content != null && !content.isNull()) {
45 return rejectIncompatibleContent(content, Void.class);
46 }
47 } else if (type.contentType() == String.class) {
48 if (content == null || !content.isTextual()) {
49 return rejectIncompatibleContent(content, String.class);
50 }
51 }
52
53 if (type == ChangeType.REMOVE) {
54 return (DefaultChange<?>) Change.ofRemoval(path);
55 }
56
57 requireNonNull(content, "content");
58
59 final Change<?> result;
60 switch (type) {
61 case UPSERT_JSON:
62 result = Change.ofJsonUpsert(path, content);
63 break;
64 case UPSERT_TEXT:
65 result = Change.ofTextUpsert(path, content.asText());
66 break;
67 case RENAME:
68 result = Change.ofRename(path, content.asText());
69 break;
70 case APPLY_JSON_PATCH:
71 result = Change.ofJsonPatch(path, content);
72 break;
73 case APPLY_TEXT_PATCH:
74 result = Change.ofTextPatch(path, content.asText());
75 break;
76 default:
77
78 throw new Error();
79 }
80
81
82 return (DefaultChange<?>) result;
83 }
84
85 private static DefaultChange<?> rejectIncompatibleContent(@Nullable JsonNode content,
86 Class<?> contentType) {
87 throw new IllegalArgumentException("incompatible content: " + content +
88 " (expected: " + contentType.getName() + ')');
89 }
90
91 private final String path;
92 private final ChangeType type;
93 @Nullable
94 private final T content;
95 @Nullable
96 private String contentAsText;
97
98 DefaultChange(String path, ChangeType type, @Nullable T content) {
99 this.type = requireNonNull(type, "type");
100
101 if (type.contentType() == JsonNode.class) {
102 validateJsonFilePath(path, "path");
103 } else {
104 validateFilePath(path, "path");
105 }
106
107 this.path = path;
108 this.content = content;
109 }
110
111 @Override
112 public String path() {
113 return path;
114 }
115
116 @Override
117 public ChangeType type() {
118 return type;
119 }
120
121 @Override
122 public T content() {
123 return content;
124 }
125
126 @Override
127 @Nullable
128 public String contentAsText() {
129 if (contentAsText != null) {
130 return contentAsText;
131 }
132 if (content == null) {
133 return null;
134 }
135
136 if (content instanceof CharSequence) {
137 return contentAsText = content.toString();
138 }
139
140 if (content instanceof JsonNode) {
141 try {
142 return contentAsText = Jackson.writeValueAsString(content);
143 } catch (JsonProcessingException e) {
144
145 throw new Error(e);
146 }
147 }
148
149 throw new Error();
150 }
151
152 @Override
153 public int hashCode() {
154 return type.hashCode() * 31 + path.hashCode();
155 }
156
157 @Override
158 public boolean equals(Object o) {
159 if (this == o) {
160 return true;
161 }
162 if (!(o instanceof Change)) {
163 return false;
164 }
165
166 final Change<?> that = (Change<?>) o;
167 if (type != that.type()) {
168 return false;
169 }
170
171 if (!path.equals(that.path())) {
172 return false;
173 }
174
175 return Objects.equals(content, that.content());
176 }
177
178 @Override
179 public String toString() {
180 return MoreObjects.toStringHelper(Change.class)
181 .add("type", type)
182 .add("path", path)
183 .add("content", content)
184 .toString();
185 }
186 }