View Javadoc
1   /*
2    * Portions of this software was developed by employees of the National Institute
3    * of Standards and Technology (NIST), an agency of the Federal Government and is
4    * being made available as a public service. Pursuant to title 17 United States
5    * Code Section 105, works of NIST employees are not subject to copyright
6    * protection in the United States. This software may be subject to foreign
7    * copyright. Permission in the United States and in foreign countries, to the
8    * extent that NIST may hold copyright, to use, copy, modify, create derivative
9    * works, and distribute this software and its documentation without fee is hereby
10   * granted on a non-exclusive basis, provided that this notice and disclaimer
11   * of warranty appears in all copies.
12   *
13   * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
14   * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
15   * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
16   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
17   * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
18   * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
19   * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
20   * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
21   * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
22   * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
23   * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
24   * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
25   */
26  
27  package gov.nist.secauto.metaschema.databind.model;
28  
29  import gov.nist.secauto.metaschema.core.model.IModule;
30  import gov.nist.secauto.metaschema.core.model.ModuleScopeEnum;
31  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
32  import gov.nist.secauto.metaschema.databind.IBindingContext;
33  import gov.nist.secauto.metaschema.databind.io.BindingException;
34  
35  import java.lang.reflect.Constructor;
36  import java.lang.reflect.InvocationTargetException;
37  import java.lang.reflect.Method;
38  import java.util.Locale;
39  
40  import edu.umd.cs.findbugs.annotations.NonNull;
41  
42  abstract class AbstractClassBinding implements IClassBinding {
43    // private static final Logger logger =
44    // LogManager.getLogger(AbstractClassBinding.class);
45  
46    @NonNull
47    private final IBindingContext bindingContext;
48    @NonNull
49    private final Class<?> clazz;
50    private final Method beforeDeserializeMethod;
51    private final Method afterDeserializeMethod;
52    // REFACTOR: use lazy instead
53    private IModule module;
54  
55    /**
56     * Construct a new class binding for the provided class.
57     *
58     * @param clazz
59     *          the bound class
60     * @param bindingContext
61     *          the class binding context for which this class is participating
62     */
63    public AbstractClassBinding(@NonNull Class<?> clazz, @NonNull IBindingContext bindingContext) {
64      this.bindingContext = ObjectUtils.requireNonNull(bindingContext, "bindingContext");
65      this.clazz = ObjectUtils.requireNonNull(clazz, "clazz");
66      this.beforeDeserializeMethod = ClassIntrospector.getMatchingMethod(clazz, "beforeDeserialize", Object.class);
67      this.afterDeserializeMethod = ClassIntrospector.getMatchingMethod(clazz, "afterDeserialize", Object.class);
68    }
69  
70    @Override
71    public boolean isInline() {
72      return getBoundClass().getEnclosingClass() != null;
73    }
74  
75    @Override
76    public Class<?> getBoundClass() {
77      return clazz;
78    }
79  
80    @Override
81    public IBindingContext getBindingContext() {
82      return bindingContext;
83    }
84  
85    @Override
86    public String getUseName() { // NOPMD
87      // a use name is never provided
88      return null;
89    }
90  
91    @SuppressWarnings("null")
92    @Override
93    public String toCoordinates() {
94      return String.format("%s IClassBinding(%s): %s", getModelType().name().toLowerCase(Locale.ROOT), getName(),
95          getBoundClass().getName());
96    }
97  
98    @Override
99    public @NonNull ModuleScopeEnum getModuleScope() {
100     // TODO: is this the right value?
101     return ModuleScopeEnum.INHERITED;
102   }
103 
104   protected abstract Class<? extends IModule> getModuleClass();
105 
106   @SuppressWarnings("null")
107   @NonNull
108   protected IModule initModule() {
109     synchronized (this) {
110       if (module == null) {
111         Class<? extends IModule> metaschemaClass = getModuleClass();
112         module = getBindingContext().getModuleByClass(metaschemaClass);
113       }
114       return module;
115     }
116   }
117 
118   @Override
119   public IModule getContainingModule() {
120     return initModule();
121   }
122 
123   /**
124    * Gets a new instance of the bound class.
125    *
126    * @param <CLASS>
127    *          the type of the bound class
128    * @return a Java object for the class
129    * @throws BindingException
130    *           if the instance cannot be created due to a binding error
131    */
132   @Override
133   @NonNull
134   public <CLASS> CLASS newInstance() throws BindingException {
135     Class<?> clazz = getBoundClass();
136     try {
137       @SuppressWarnings("unchecked") Constructor<CLASS> constructor
138           = (Constructor<CLASS>) clazz.getDeclaredConstructor();
139       return ObjectUtils.notNull(constructor.newInstance());
140     } catch (NoSuchMethodException ex) {
141       String msg = String.format("Class '%s' does not have a required no-arg constructor.", clazz.getName());
142       throw new BindingException(msg, ex);
143     } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
144         | InvocationTargetException ex) {
145       throw new BindingException(ex);
146     }
147   }
148 
149   /**
150    * Calls the method named "beforeDeserialize" on each class in the object's
151    * hierarchy if the method exists on the class.
152    * <p>
153    * These methods can be used to set the initial state of the target bound object
154    * before data is read and applied during deserialization.
155    *
156    * @param targetObject
157    *          the data object target to call the method(s) on
158    * @param parentObject
159    *          the object target's parent object, which is used as the method
160    *          argument
161    * @throws BindingException
162    *           if an error occurs while calling the method
163    */
164   @Override
165   public void callBeforeDeserialize(Object targetObject, Object parentObject) throws BindingException {
166     if (beforeDeserializeMethod != null) {
167       try {
168         beforeDeserializeMethod.invoke(targetObject, parentObject);
169       } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
170         throw new BindingException(ex);
171       }
172     }
173   }
174 
175   /**
176    * Calls the method named "afterDeserialize" on each class in the object's
177    * hierarchy if the method exists.
178    * <p>
179    * These methods can be used to modify the state of the target bound object
180    * after data is read and applied during deserialization.
181    *
182    * @param targetObject
183    *          the data object target to call the method(s) on
184    * @param parentObject
185    *          the object target's parent object, which is used as the method
186    *          argument
187    * @throws BindingException
188    *           if an error occurs while calling the method
189    */
190   @Override
191   public void callAfterDeserialize(Object targetObject, Object parentObject) throws BindingException {
192     if (afterDeserializeMethod != null) {
193       try {
194         afterDeserializeMethod.invoke(targetObject, parentObject);
195       } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
196         throw new BindingException(ex);
197       }
198     }
199   }
200 
201   @Override
202   public Object copyBoundObject(Object item, Object parentInstance) throws BindingException {
203     Object instance = newInstance();
204 
205     callBeforeDeserialize(instance, parentInstance);
206 
207     copyBoundObjectInternal(item, instance);
208 
209     callAfterDeserialize(instance, parentInstance);
210 
211     return instance;
212   }
213 
214   protected void copyBoundObjectInternal(@NonNull Object fromInstance, @NonNull Object toInstance)
215       throws BindingException {
216     for (IBoundFlagInstance property : getFlagInstances()) {
217       property.copyBoundObject(fromInstance, toInstance);
218     }
219   }
220 }