View Javadoc
1   /*
2    * (c) Copyright 2006-2020 by rapiddweller GmbH & Volker Bergmann. All rights reserved.
3    *
4    * Redistribution and use in source and binary forms, with or without
5    * modification, is permitted under the terms of the
6    * GNU General Public License.
7    *
8    * For redistributing this software or a derivative work under a license other
9    * than the GPL-compatible Free Software License as defined by the Free
10   * Software Foundation or approved by OSI, you must first obtain a commercial
11   * license to this software product from rapiddweller GmbH & Volker Bergmann.
12   *
13   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14   * WITHOUT A WARRANTY OF ANY KIND. ALL EXPRESS OR IMPLIED CONDITIONS,
15   * REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF
16   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE
17   * HEREBY EXCLUDED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
18   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24   * POSSIBILITY OF SUCH DAMAGE.
25   */
26  
27  package com.rapiddweller.model.data;
28  
29  import com.rapiddweller.common.Named;
30  import com.rapiddweller.common.NullSafeComparator;
31  import com.rapiddweller.common.Operation;
32  import com.rapiddweller.common.StringUtil;
33  import com.rapiddweller.common.collection.OrderedNameMap;
34  import com.rapiddweller.common.converter.AnyConverter;
35  import com.rapiddweller.common.converter.ToStringConverter;
36  
37  import java.util.List;
38  
39  /**
40   * Common parent class of all descriptors.<br/><br/>
41   * Created: 17.07.2006 21:30:45
42   *
43   * @author Volker Bergmann
44   * @since 0.1
45   */
46  public class FeatureDescriptor implements Named {
47  
48    /**
49     * The constant NAME.
50     */
51    public static final String NAME = "name";
52    /**
53     * The Details.
54     */
55    protected OrderedNameMap<FeatureDetail<?>> details;
56    /**
57     * The Provider.
58     */
59    protected DescriptorProvider provider;
60    /**
61     * The name of the feature. It is stored redundantly in the {@link #details} map and the copy in
62     * this attribute is used for high-performance retrieval of the name.
63     */
64    private String name;
65  
66    // constructor -----------------------------------------------------------------------------------------------------
67  
68    /**
69     * Instantiates a new Feature descriptor.
70     *
71     * @param name     the name
72     * @param provider the provider
73     */
74    public FeatureDescriptor(String name, DescriptorProvider provider) {
75      if (provider == null) {
76        throw new IllegalArgumentException("provider is null");
77      }
78      if (provider.getDataModel() == null) {
79        throw new IllegalArgumentException("provider's data model is null");
80      }
81      this.details = new OrderedNameMap<>();
82      this.provider = provider;
83      this.addConstraint(NAME, String.class, null);
84      this.setName(name);
85    }
86  
87    // typed interface -------------------------------------------------------------------------------------------------
88  
89    @Override
90    public String getName() {
91      return name;
92    }
93  
94    /**
95     * Sets name.
96     *
97     * @param name the name
98     */
99    public void setName(String name) {
100     this.name = name; // name is stored redundantly for better performance
101     setDetailValue(NAME, name);
102   }
103 
104   /**
105    * Gets provider.
106    *
107    * @return the provider
108    */
109   public DescriptorProvider getProvider() {
110     return provider;
111   }
112 
113   /**
114    * Gets data model.
115    *
116    * @return the data model
117    */
118   public DataModel getDataModel() {
119     return provider.getDataModel();
120   }
121 
122   // generic detail access -------------------------------------------------------------------------------------------
123 
124   /**
125    * Supports detail boolean.
126    *
127    * @param name the name
128    * @return the boolean
129    */
130   public boolean supportsDetail(String name) {
131     return details.containsKey(name);
132   }
133 
134   /**
135    * Gets declared detail value.
136    *
137    * @param name the name
138    * @return the declared detail value
139    */
140   public Object getDeclaredDetailValue(
141       String name) { // TODO v0.8 remove method? It does not differ from getDetailValue any more
142     return getConfiguredDetail(name).getValue();
143   }
144 
145   /**
146    * Gets detail value.
147    *
148    * @param name the name
149    * @return the detail value
150    */
151   public Object getDetailValue(
152       String name) { // TODO v0.8 remove generic feature access?
153     return this.getConfiguredDetail(name).getValue();
154   }
155 
156   /**
157    * Sets detail value.
158    *
159    * @param detailName  the detail name
160    * @param detailValue the detail value
161    */
162   public void setDetailValue(String detailName, Object detailValue) {
163     if ("name"
164         .equals(detailName)) {
165       // name is stored redundantly for better performance
166       this.name = (String) detailValue;
167     }
168     FeatureDetail<Object> detail = getConfiguredDetail(detailName);
169     Class<Object> detailType = detail.getType();
170     if (detailValue != null &&
171         !detailType.isAssignableFrom(detailValue.getClass())) {
172       detailValue = AnyConverter.convert(detailValue, detailType);
173     }
174     detail.setValue(detailValue);
175   }
176 
177   /**
178    * Gets details.
179    *
180    * @return the details
181    */
182   public List<FeatureDetail<?>> getDetails() {
183     return details.values();
184   }
185 
186   // java.lang overrides ---------------------------------------------------------------------------------------------
187 
188   @Override
189   public String toString() {
190     String name = getName();
191     if (StringUtil.isEmpty(name)) {
192       name = "anonymous";
193     }
194     return renderDetails(new StringBuilder(name)).toString();
195   }
196 
197   @Override
198   public boolean equals(Object o) {
199     if (this == o) {
200       return true;
201     }
202     if (o == null || getClass() != o.getClass()) {
203       return false;
204     }
205     final FeatureDescriptor../com/rapiddweller/model/data/FeatureDescriptor.html#FeatureDescriptor">FeatureDescriptor that = (FeatureDescriptor) o;
206     for (FeatureDetail<?> detail : details.values()) {
207       String detailName = detail.getName();
208       if (!NullSafeComparator
209           .equals(detail.getValue(),
210               that.getDetailValue(detailName))) {
211         return false;
212       }
213     }
214     return true;
215   }
216 
217   @Override
218   public int hashCode() {
219     return getClass().hashCode() * 29 + details.hashCode();
220   }
221 
222   // helpers ---------------------------------------------------------------------------------------------------------
223 
224   /**
225    * Render details string.
226    *
227    * @return the string
228    */
229   protected String renderDetails() {
230     return renderDetails(new StringBuilder()).toString();
231   }
232 
233   /**
234    * Render details string builder.
235    *
236    * @param builder the builder
237    * @return the string builder
238    */
239   protected StringBuilder renderDetails(StringBuilder builder) {
240     builder.append("[");
241     boolean empty = true;
242     for (FeatureDetail<?> descriptor : details.values()) {
243       if (descriptor.getValue() != null &&
244           !NAME.equals(descriptor.getName())) {
245         if (!empty) {
246           builder.append(", ");
247         }
248         empty = false;
249         builder.append(descriptor.getName()).append("=");
250         builder.append(ToStringConverter
251             .convert(descriptor.getValue(), "[null]"));
252       }
253     }
254     return builder.append("]");
255   }
256 
257   /**
258    * Gets detail type.
259    *
260    * @param detailName the detail name
261    * @return the detail type
262    */
263   protected Class<?> getDetailType(String detailName) {
264     FeatureDetail<?> detail = details.get(detailName);
265     if (detail == null) {
266       throw new UnsupportedOperationException(
267           "Feature detail not supported: " + detailName);
268     }
269     return detail.getType();
270   }
271 
272   /**
273    * Add config.
274    *
275    * @param <T>  the type parameter
276    * @param name the name
277    * @param type the type
278    */
279   protected <T> void addConfig(String name, Class<T> type) {
280     addConfig(name, type, false);
281   }
282 
283   /**
284    * Add config.
285    *
286    * @param <T>        the type parameter
287    * @param name       the name
288    * @param type       the type
289    * @param deprecated the deprecated
290    */
291   protected <T> void addConfig(String name, Class<T> type,
292                                boolean deprecated) {
293     addDetail(name, type, false, deprecated, null);
294   }
295 
296   /**
297    * Add constraint.
298    *
299    * @param <T>        the type parameter
300    * @param name       the name
301    * @param type       the type
302    * @param combinator the combinator
303    */
304   protected <T> void addConstraint(String name, Class<T> type,
305                                    Operation<T, T> combinator) {
306     addDetail(name, type, true, false, combinator);
307   }
308 
309   /**
310    * Add detail.
311    *
312    * @param <T>        the type parameter
313    * @param detailName the detail name
314    * @param detailType the detail type
315    * @param constraint the constraint
316    * @param deprecated the deprecated
317    * @param combinator the combinator
318    */
319   protected <T> void addDetail(String detailName, Class<T> detailType,
320                                boolean constraint,
321                                boolean deprecated,
322                                Operation<T, T> combinator) {
323     this.details.put(detailName,
324         new FeatureDetail<>(detailName, detailType, constraint,
325             combinator));
326   }
327 
328   // generic property access -----------------------------------------------------------------------------------------
329 
330   /**
331    * Gets configured detail.
332    *
333    * @param <T>  the type parameter
334    * @param name the name
335    * @return the configured detail
336    */
337   @SuppressWarnings("unchecked")
338   public <T> FeatureDetail<T> getConfiguredDetail(String name) {
339     if (!supportsDetail(name)) {
340       throw new UnsupportedOperationException("Feature detail '" + name +
341           "' not supported in feature type: " + getClass().getName());
342     }
343     return (FeatureDetail<T>) details.get(name);
344   }
345 
346 }