1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.basepom.mojo.propertyhelper;
16
17 import static com.google.common.base.Preconditions.checkNotNull;
18 import static com.google.common.base.Preconditions.checkState;
19 import static java.lang.String.format;
20
21 import org.basepom.mojo.propertyhelper.ValueProvider.MapValueProvider;
22 import org.basepom.mojo.propertyhelper.beans.AbstractDefinition;
23 import org.basepom.mojo.propertyhelper.beans.IgnoreWarnFailCreate;
24 import org.basepom.mojo.propertyhelper.util.Log;
25
26 import java.io.File;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.OutputStream;
30 import java.nio.file.Files;
31 import java.util.Map;
32 import java.util.Map.Entry;
33 import java.util.Objects;
34 import java.util.Optional;
35 import java.util.Properties;
36 import java.util.StringJoiner;
37 import javax.annotation.Nonnull;
38
39 import com.google.common.annotations.VisibleForTesting;
40 import com.google.common.collect.ForwardingMap;
41 import com.google.common.collect.Maps;
42
43 public final class ValueCache {
44
45 private static final Log LOG = Log.findLog();
46 private final Map<String, String> ephemeralValues = Maps.newHashMap();
47
48
49
50 private final Map<File, ValueCacheEntry> valueFiles = Maps.newHashMap();
51
52 @VisibleForTesting
53 static ValueProvider findCurrentValueProvider(final Map<String, String> values,
54 final AbstractDefinition<?> definition) {
55 checkNotNull(values, "values is null");
56 final String name = definition.getPropertyName();
57 final boolean hasValue = values.containsKey(name);
58
59 final boolean createProperty = IgnoreWarnFailCreate.checkState(
60 definition.getOnMissingProperty(), hasValue, name);
61
62 if (hasValue) {
63 return new MapValueProvider(values, name);
64 } else if (createProperty) {
65 if (definition.getInitialValue().isPresent()) {
66 values.put(name, definition.getInitialValue().get());
67 }
68 return new MapValueProvider(values, name);
69 } else {
70 return ValueProvider.NULL_PROVIDER;
71 }
72 }
73
74 public ValueProvider getValueProvider(final AbstractDefinition<?> definition)
75 throws IOException {
76 final Optional<Map<String, String>> values = getValues(definition);
77 if (values.isEmpty()) {
78 final String name = definition.getPropertyName();
79 final Optional<String> value = definition.getInitialValue();
80 value.ifPresent(s -> ephemeralValues.put(name, s));
81 return new MapValueProvider(ephemeralValues, name);
82 } else {
83 return ValueCache.findCurrentValueProvider(values.get(), definition);
84 }
85 }
86
87 @VisibleForTesting
88 Optional<Map<String, String>> getValues(final AbstractDefinition<?> definition)
89 throws IOException {
90 final Optional<File> definitionFile = definition.getPropertyFile();
91
92
93 if (definitionFile.isEmpty()) {
94 return Optional.empty();
95 }
96
97 ValueCacheEntry cacheEntry;
98 final File canonicalFile = definitionFile.get().getCanonicalFile();
99
100
101 final boolean createFile = IgnoreWarnFailCreate.checkState(definition.getOnMissingFile(),
102 canonicalFile.exists(),
103 definitionFile.get().getCanonicalPath());
104
105 cacheEntry = valueFiles.get(canonicalFile);
106
107 if (cacheEntry != null) {
108
109
110
111 if (createFile) {
112 cacheEntry.doCreate();
113 }
114 } else {
115
116 final Properties props = new Properties();
117
118 if (!canonicalFile.exists()) {
119 cacheEntry = new ValueCacheEntry(props, false, createFile);
120 valueFiles.put(canonicalFile, cacheEntry);
121 } else {
122 if (canonicalFile.isFile() && canonicalFile.canRead()) {
123 try (InputStream stream = Files.newInputStream(canonicalFile.toPath())) {
124 props.load(stream);
125 cacheEntry = new ValueCacheEntry(props, true, createFile);
126 valueFiles.put(canonicalFile, cacheEntry);
127 }
128 } else {
129 throw new IllegalStateException(
130 format("Can not load %s, not a file!", definitionFile.get().getCanonicalPath()));
131 }
132 }
133 }
134
135 return Optional.of(cacheEntry.getValues());
136 }
137
138 public void persist()
139 throws IOException {
140 for (final Entry<File, ValueCacheEntry> entries : valueFiles.entrySet()) {
141 final ValueCacheEntry entry = entries.getValue();
142 if (!entry.isDirty()) {
143 continue;
144 }
145 final File file = entries.getKey();
146 if (entry.isExists() || entry.isCreate()) {
147 checkNotNull(file, "no file defined, can not persist!");
148 final File oldFile = new File(file.getCanonicalPath() + ".bak");
149
150 if (entry.isExists()) {
151 checkState(file.exists(), "'%s' should exist!", file.getCanonicalPath());
152
153 if (oldFile.exists()) {
154 checkState(oldFile.delete(), "Could not delete '%s'", file.getCanonicalPath());
155 }
156 }
157
158 final File folder = file.getParentFile();
159 if (!folder.exists()) {
160 checkState(folder.mkdirs(), "Could not create folder '%s'", folder.getCanonicalPath());
161 }
162
163 final File newFile = new File(file.getCanonicalPath() + ".new");
164 try (OutputStream stream = Files.newOutputStream(newFile.toPath())) {
165 entry.store(stream, "created by property-helper-maven-plugin");
166 }
167
168 if (file.exists()) {
169 if (!file.renameTo(oldFile)) {
170 LOG.warn("Could not rename '%s' to '%s'!", file, oldFile);
171 }
172 }
173
174 if (!file.exists()) {
175 if (!newFile.renameTo(file)) {
176 LOG.warn("Could not rename '%s' to '%s'!", newFile, file);
177 }
178 }
179 }
180 }
181 }
182
183 public static class ValueCacheEntry {
184
185 private final Map<String, String> values = Maps.newHashMap();
186
187 private final boolean exists;
188
189 private boolean create;
190
191 private boolean dirty = false;
192
193 ValueCacheEntry(@Nonnull final Properties props,
194 final boolean exists,
195 final boolean create) {
196 checkNotNull(props, "props is null");
197
198 values.putAll(Maps.fromProperties(props));
199
200 this.exists = exists;
201 this.create = create;
202 }
203
204 public void store(final OutputStream out, final String comment)
205 throws IOException {
206 final Properties p = new Properties();
207 for (Entry<String, String> entry : values.entrySet()) {
208 p.setProperty(entry.getKey(), entry.getValue());
209 }
210 p.store(out, comment);
211 }
212
213 public boolean isDirty() {
214 return dirty;
215 }
216
217 public void dirty() {
218 this.dirty = true;
219 }
220
221 public Map<String, String> getValues() {
222 return new ForwardingMap<>() {
223 @Override
224 protected Map<String, String> delegate() {
225 return values;
226 }
227
228 @Override
229 public String remove(Object object) {
230 dirty();
231 return super.remove(object);
232 }
233
234 @Override
235 public void clear() {
236 dirty();
237 super.clear();
238 }
239
240 @Override
241 public String put(String key, String value) {
242 final String oldValue = super.put(key, value);
243 if (!Objects.equals(value, oldValue)) {
244 dirty();
245 }
246 return oldValue;
247 }
248
249 @Override
250 public void putAll(Map<? extends String, ? extends String> map) {
251 dirty();
252 super.putAll(map);
253 }
254 };
255 }
256
257 public boolean isExists() {
258 return exists;
259 }
260
261 public boolean isCreate() {
262 return create;
263 }
264
265 public void doCreate() {
266 this.create = true;
267 dirty();
268 }
269
270 @Override
271 public String toString() {
272 return new StringJoiner(", ", ValueCacheEntry.class.getSimpleName() + "[", "]")
273 .add("values=" + values)
274 .add("exists=" + exists)
275 .add("create=" + create)
276 .add("dirty=" + dirty)
277 .toString();
278 }
279
280 @Override
281 public boolean equals(Object o) {
282 if (this == o) {
283 return true;
284 }
285 if (o == null || getClass() != o.getClass()) {
286 return false;
287 }
288 ValueCacheEntry that = (ValueCacheEntry) o;
289 return exists == that.exists && create == that.create && dirty == that.dirty
290 && Objects.equals(values, that.values);
291 }
292
293 @Override
294 public int hashCode() {
295 return Objects.hash(values, exists, create, dirty);
296 }
297 }
298 }