001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.commons.beanquery;
018
019import static org.apache.juneau.commons.reflect.ReflectionUtils.*;
020import static org.apache.juneau.commons.utils.Shorts.*;
021
022import java.lang.reflect.*;
023import java.util.*;
024
025import org.apache.juneau.commons.bean.*;
026
027/**
028 * The query view of a bean class: its readable properties (query columns), its writable properties, and how to create
029 * an instance.
030 *
031 * <p>
032 * Built from Juneau's {@link BeanMeta}, so it follows the same bean rules as serializers and parsers:
033 * <ul>
034 *    <li>getters, setters and fields;
035 *    <li>records;
036 *    <li>{@link BeanIgnore @BeanIgnore} and {@link BeanProp @BeanProp};
037 *    <li>the configured {@link PropertyNamer}.
038 * </ul>
039 * Dynamic ({@code "*"}) properties are excluded.  A class that isn't a bean (for example, one with no properties)
040 * yields an empty model rather than an error.
041 *
042 * <h5 class='section'>Example:</h5>
043 * <p class='bjava'>
044 *    QueryModel&lt;Person&gt; <jv>model</jv> = QueryModel.<jsm>of</jsm>(Person.<jk>class</jk>);
045 *    <jk>for</jk> (QueryProperty <jv>p</jv> : <jv>model</jv>.getReadableProperties().values())
046 *       System.<jsf>out</jsf>.println(<jv>p</jv>.getName() + <js>" "</js> + <jv>p</jv>.getSearchType());
047 *
048 *    <jc>// Same rules as a serializer configured with a dashed-lower-case namer.</jc>
049 *    BeanConfigContext <jv>config</jv> = BeanConfigContext.<jsm>create</jsm>().propertyNamer(PropertyNamerDLC.<jsf>INSTANCE</jsf>).build();
050 *    QueryModel&lt;Person&gt; <jv>dashed</jv> = QueryModel.<jsm>of</jsm>(Person.<jk>class</jk>, <jv>config</jv>);
051 * </p>
052 *
053 * @param <T> The bean type.
054 * @since 10.0.0
055 */
056public final class QueryModel<T> {
057
058   /**
059    * Builds a model with {@link BeanConfigContext#DEFAULT}.
060    *
061    * @param <T> The bean type.
062    * @param beanClass The bean class.  Must not be <jk>null</jk>.
063    * @return A new model.
064    */
065   public static <T> QueryModel<T> of(Class<T> beanClass) {
066      return of(beanClass, BeanConfigContext.DEFAULT);
067   }
068
069   /**
070    * Builds a model with the given bean configuration.
071    *
072    * @param <T> The bean type.
073    * @param beanClass The bean class.  Must not be <jk>null</jk>.
074    * @param config The bean configuration.  Must not be <jk>null</jk>.
075    * @return A new model.
076    */
077   public static <T> QueryModel<T> of(Class<T> beanClass, BeanConfigContext config) {
078      return of(BeanMeta.of(reqnn("beanClass", beanClass), reqnn("config", config)));
079   }
080
081   /**
082    * Builds a model from existing bean metadata.
083    *
084    * @param <T> The bean type.
085    * @param beanMeta The bean metadata.  Must not be <jk>null</jk>.
086    * @return A new model.
087    */
088   public static <T> QueryModel<T> of(BeanMeta<T> beanMeta) {
089      return new QueryModel<>(reqnn("beanMeta", beanMeta));
090   }
091
092   private final Class<T> beanClass;
093   private final Map<String,QueryProperty> properties;
094   private final Map<String,QueryProperty> writableProperties;
095   private final Constructor<?> constructor;
096   private final List<String> constructorArgs;
097   private final List<Class<?>> constructorArgTypes;
098
099   private QueryModel(BeanMeta<T> bm) {
100      beanClass = bm.getClassInfo().inner();
101      var r = new LinkedHashMap<String,QueryProperty>();
102      var w = new LinkedHashMap<String,QueryProperty>();
103      bm.getProperties().forEach((name, p) -> {
104         if (p.isDyna())
105            return;
106         var qp = new QueryProperty(p);
107         if (qp.canRead())
108            r.put(name, qp);
109         if (qp.canWrite())
110            w.put(name, qp);
111      });
112      properties = Collections.unmodifiableMap(r);
113      writableProperties = Collections.unmodifiableMap(w);
114      // Non-static inner classes need an outer instance, which would misalign the constructor args with their types.
115      var ci = bm.getClassInfo().isNonStaticMemberClass() ? null : bm.getConstructor();
116      constructor = ci == null ? null : ci.inner();
117      constructorArgs = ci == null ? List.of() : List.copyOf(bm.getConstructorArgs());
118      constructorArgTypes = constructor == null ? List.of() : List.of(constructor.getParameterTypes());
119   }
120
121   /**
122    * Returns the bean class.
123    *
124    * @return The bean class.  Never <jk>null</jk>.
125    */
126   public Class<T> getBeanClass() { return beanClass; }
127
128   /**
129    * Returns the readable properties (the query columns), keyed by name, in bean property order.  Write-only
130    * properties are excluded.
131    *
132    * @return An unmodifiable map.  Never <jk>null</jk>.
133    */
134   public Map<String,QueryProperty> getReadableProperties() { return properties; }
135
136   /**
137    * Returns a readable property.  Write-only properties are excluded.
138    *
139    * @param name The property name.
140    * @return The property, or <jk>null</jk> if there is no readable property with that name.
141    */
142   public QueryProperty getReadableProperty(String name) { return properties.get(name); }
143
144   /**
145    * Returns the writable properties (setter or non-final field), keyed by name, in bean property order.  Read-only
146    * properties are excluded.
147    *
148    * <p>
149    * Record components are not listed here; they are set through the constructor (see {@link #getConstructorArgs()}).
150    *
151    * @return An unmodifiable map.  Never <jk>null</jk>.
152    */
153   public Map<String,QueryProperty> getWritableProperties() { return writableProperties; }
154
155   /**
156    * Returns the names of the properties passed to the constructor, in parameter order.
157    *
158    * <p>
159    * Non-empty for records (canonical constructor) and {@link BeanCtor @BeanCtor} classes; empty for no-arg beans.
160    *
161    * @return An unmodifiable list.  Never <jk>null</jk>.
162    */
163   public List<String> getConstructorArgs() { return constructorArgs; }
164
165   /**
166    * Returns the constructor parameter types, parallel to {@link #getConstructorArgs()}.
167    *
168    * @return An unmodifiable list.  Never <jk>null</jk>.
169    */
170   public List<Class<?>> getConstructorArgTypes() { return constructorArgTypes; }
171
172   /**
173    * Creates an instance.
174    *
175    * <p>
176    * Non-static inner classes are not supported (they need an outer instance); the model reports no usable
177    * constructor for them.
178    *
179    * @param args The constructor arguments, parallel to {@link #getConstructorArgs()} (none for a no-arg bean).  A
180    *    <jk>null</jk> for a primitive parameter becomes that primitive's default.  To pass a single <jk>null</jk>
181    *    argument, pass <c>(Object)<jk>null</jk></c>.
182    * @return A new instance.  Never <jk>null</jk>.
183    * @throws IllegalArgumentException If the args array is <jk>null</jk>, or the argument count doesn't match {@link #getConstructorArgs()}.
184    * @throws IllegalStateException If the class has no usable constructor.
185    * @throws BeanQueryExecutionException If the constructor throws, or the arguments can't be passed to it (for
186    *    example, a wrong type).  The cause is the constructor's own exception, or the reflection error.
187    */
188   public T newInstance(Object...args) {
189      reqnn("args", args);
190      if (constructor == null)
191         throw isex("%s has no usable constructor.", beanClass.getSimpleName());
192      if (args.length != constructorArgs.size())
193         throw iaex("Expected %s constructor arguments for %s but got %s.", constructorArgs.size(), beanClass.getSimpleName(), args.length);
194      var a = args.clone();
195      for (var i = 0; i < a.length; i++)
196         if (a[i] == null && constructorArgTypes.get(i).isPrimitive())
197            a[i] = info(constructorArgTypes.get(i)).getPrimitiveDefault();
198      try {
199         return beanClass.cast(constructor.newInstance(a));
200      } catch (InvocationTargetException e) {
201         throw new BeanQueryExecutionException(e.getCause(), "Failed to construct a %s.", beanClass.getSimpleName());
202      } catch (ReflectiveOperationException | RuntimeException e) {
203         throw new BeanQueryExecutionException(e, "Failed to construct a %s.", beanClass.getSimpleName());
204      }
205   }
206}