View Javadoc
1   /*
2    * Copyright 2018-2020 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://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,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.bremersee.common.model;
18  
19  import com.fasterxml.jackson.annotation.JsonAnyGetter;
20  import com.fasterxml.jackson.annotation.JsonAnySetter;
21  import com.fasterxml.jackson.annotation.JsonIgnore;
22  import io.swagger.v3.oas.annotations.media.Schema;
23  import java.util.Collections;
24  import java.util.LinkedHashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Optional;
28  import java.util.StringTokenizer;
29  import java.util.function.Function;
30  import lombok.AllArgsConstructor;
31  import lombok.EqualsAndHashCode;
32  import lombok.NoArgsConstructor;
33  import lombok.ToString;
34  
35  /**
36   * This base class allows to keep unknown json properties.
37   *
38   * @author Christian Bremer
39   */
40  @ToString
41  @EqualsAndHashCode
42  @NoArgsConstructor
43  @AllArgsConstructor
44  public abstract class UnknownAware {
45  
46    @Schema(description = "Unknown properties.", hidden = true)
47    @JsonIgnore
48    private Map<String, Object> unknown;
49  
50    /**
51     * Gets the unknown json properties (can be {@code null}).
52     *
53     * @return the unknown
54     */
55    @JsonAnyGetter
56    public Map<String, Object> unknown() {
57      return unknown;
58    }
59  
60    /**
61     * Sets the unknown json properties.
62     *
63     * @param unknown the unknown json properties
64     */
65    public void unknown(Map<String, Object> unknown) {
66      if (unknown != null && !unknown.isEmpty()) {
67        this.unknown = unknown;
68      }
69    }
70  
71    /**
72     * Any json setter.
73     *
74     * @param name the name
75     * @param value the value
76     */
77    @JsonAnySetter
78    public void unknown(String name, Object value) {
79      if (name == null || name.trim().length() == 0) {
80        return;
81      }
82      if (unknown == null) {
83        unknown = new LinkedHashMap<>();
84      }
85      unknown.put(name, value);
86    }
87  
88    /**
89     * Returns {@code true} if there are unknown properties, otherwise {@code false}.
90     *
91     * @return {@code true} if there are unknown properties, otherwise {@code false}
92     */
93    public boolean hasUnknown() {
94      return unknown != null && !unknown.isEmpty();
95    }
96  
97    /**
98     * Find a value from the unknown map.
99     *
100    * @param <T> the class type
101    * @param jsonPath the json path, e. g. {@code $.firstKey.secondKey.thirdKey}
102    * @param clazz the expected result class
103    * @return an empty optional if the value was not found or can not be casted, otherwise the value
104    */
105   public <T> Optional<T> findUnknown(final String jsonPath, final Class<T> clazz) {
106     if (!hasUnknown() || !isJsonPath(jsonPath) || clazz == null) {
107       return Optional.empty();
108     }
109     Object value = null;
110     Map<String, Object> tmpUnknown = unknown;
111     final StringTokenizer tokenizer = new StringTokenizer(jsonPath.substring(2), ".");
112     while (tokenizer.hasMoreTokens()) {
113       final String token = tokenizer.nextToken();
114       value = tmpUnknown.get(token);
115       if (value == null) {
116         break;
117       }
118       if ((value instanceof Map) && tokenizer.hasMoreTokens()) {
119         try {
120           //noinspection unchecked,rawtypes
121           tmpUnknown = (Map) value;
122         } catch (Exception e) {
123           return Optional.empty();
124         }
125       }
126     }
127     if (value == null) {
128       return Optional.empty();
129     }
130     try {
131       return Optional.of(clazz.cast(value));
132     } catch (Exception e) {
133       return Optional.empty();
134     }
135   }
136 
137   /**
138    * Find a list from the unknown map.
139    *
140    * @param <E> the list element type
141    * @param jsonPath the json path, e. g. {@code $.firstKey.secondKey.thirdKey}
142    * @param clazz he list element type
143    * @return an empty optional if the list was not found or can not be casted, otherwise the list
144    */
145   public <E> Optional<List<E>> findUnknownList(final String jsonPath, final Class<E> clazz) {
146     if (clazz == null) {
147       return Optional.empty();
148     }
149     try {
150       //noinspection Convert2MethodRef,unchecked,rawtypes
151       return findUnknown(jsonPath, List.class).map(
152           (Function<List, List<E>>) list -> Collections.unmodifiableList(list));
153 
154     } catch (RuntimeException ignored) {
155       return Optional.empty();
156     }
157   }
158 
159   /**
160    * Find a map / json object from the unknown map.
161    *
162    * @param jsonPath the json path, e. g. {@code $.firstKey.secondKey.thirdKey}
163    * @return an empty optional if the map / json object was not found or can not be casted,
164    *     otherwise the map / json object
165    */
166   public Optional<Map<String, Object>> findUnknownMap(final String jsonPath) {
167     try {
168       //noinspection Convert2MethodRef,unchecked,rawtypes
169       return findUnknown(jsonPath, Map.class)
170           .map((Function<Map, Map<String, Object>>) map -> Collections.unmodifiableMap(map));
171 
172     } catch (RuntimeException ignored) {
173       return Optional.empty();
174     }
175   }
176 
177   private boolean isJsonPath(final String jsonPath) {
178     return jsonPath != null && jsonPath.startsWith("$.") && jsonPath.length() > 2;
179   }
180 
181 }