| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.openjpa.kernel; |
| |
| import java.io.Serializable; |
| import java.lang.reflect.Array; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * Describes the shape of a query result. |
| * <br> |
| * A shape is described as a Java class by the generic type argument T. A shape may contain zero or more shapes. |
| * A shape is categorized as follows: |
| * <LI>A <em>primitive</em> shape can not have child shapes e.g. Foo or float. |
| * <LI>A <em>compound</em> shape has zero or more child shapes e.g. Foo{} or Foo{String, int} or |
| * Foo{String,Bar{Double},int}. |
| * <LI>A <em>nesting</em> shape has one or more compound child shape(s). |
| * For example, Foo{String,Bar{Double},int}. On the other hand, Foo{String, int} is a compound shape but is not |
| * nesting because all its child shapes are primitive. |
| * <br> |
| * A primitive category shape is declared during construction and immutable. |
| * The category of a non-primitive shape is mutable. |
| * <br> |
| * Notice that all nested shapes are compound shapes but not all compound shapes are nesting. |
| * <br> |
| * A compound shape can <em>add</em> other primitive shapes or <em>nest</em> other shapes to any arbitrary depth. |
| * However, a shape does not allow recursive nesting of shapes. |
| * <br> |
| * <B>Usage</B>: |
| * The purpose of a shape is to populate an instance of T from an array of input values where each |
| * array element is further specified with a type and an alias. FillStrategy determines how a shape |
| * populates an instance of T by consuming the input array element values. |
| * The input data is presented as an Object[] with a parallel array of types because the primitive |
| * types (short, float etc.) are not preserved in the input array. For certain FillStrategy such as |
| * MAP or BEAN, the alias of the input array element are used to identify the Map key or setter |
| * methods respectively. |
| * |
| * @author Pinaki Poddar |
| * |
| * @since 2.0.0 |
| * |
| */ |
| public class ResultShape<T> implements Serializable { |
| private static final long serialVersionUID = 1L; |
| private final Class<T> cls; // the type of value this shape represents or populates |
| private final boolean isPrimitive; // flags this shape as primitive |
| private boolean isNesting; // flags this shape as nesting |
| private String alias; |
| |
| private final FillStrategy<T> strategy; // the strategy to populate this shape |
| private final List<ResultShape<?>> children; // children of this shape. null for primitives |
| private Set<ResultShape<?>> parents; // the shapes that have nested this shape |
| |
| /** |
| * Construct a non-primitive shape with ASSIGN or ARRAY fill strategy. |
| */ |
| public ResultShape(Class<T> cls) { |
| this(cls, false); |
| } |
| |
| /** |
| * Construct a primitive or non-primitive shape with ASSIGN or ARRAY fill strategy. |
| * If the shape is declared as primitive then the given class can not be an array. |
| */ |
| public ResultShape(Class<T> cls, boolean primitive) { |
| this(cls, cls.isArray() ? new FillStrategy.Array<>(cls) : new FillStrategy.Assign<>(), primitive); |
| if (cls.isArray() && primitive) |
| throw new IllegalArgumentException(cls.getSimpleName() + " can not be primitive shape"); |
| } |
| |
| /** |
| * |
| * Construct a non-primitive shape with the given fill strategy. |
| */ |
| public ResultShape(Class<T> cls, FillStrategy<T> strategy) { |
| this(cls, strategy, false); |
| } |
| |
| /** |
| * Construct a shape with the given fill strategy. |
| * |
| */ |
| public ResultShape(Class<T> cls, FillStrategy<T> strategy, boolean primitive) { |
| if (cls == null) throw new NullPointerException(); |
| this.cls = cls; |
| this.strategy = strategy; |
| isPrimitive = primitive; |
| children = isPrimitive ? null : new ArrayList<>(); |
| } |
| |
| // /** |
| // * Construct a shape with the MAP fill strategy to invoke the given method. |
| // * |
| // */ |
| // public ResultShape(Class<T> cls, Method putMethod) { |
| // if (cls == null) throw new NullPointerException(); |
| // this.cls = cls; |
| // this.strategy = new FillStrategy.Map<T>(putMethod); |
| // isPrimitive = true; |
| // children = new ArrayList<ResultShape<?>>(); |
| // } |
| // |
| // /** |
| // * Construct a shape with the CONSTRUCTOR fill strategy to invoke the given constructor. |
| // * |
| // */ |
| // public ResultShape(Class<T> cls, Constructor<? extends T> cons) { |
| // if (cls == null) throw new NullPointerException(); |
| // this.cls = cls; |
| // this.strategy = new FillStrategy.NewInstance<T>(cons); |
| // isPrimitive = false; |
| // children = new ArrayList<ResultShape<?>>(); |
| // } |
| |
| /** |
| * Gets the type of instance populated by this shape. |
| */ |
| public Class<T> getType() { |
| return cls; |
| } |
| |
| public FillStrategy<T> getStrategy() { |
| return strategy; |
| } |
| |
| public ResultShape<T> setAlias(String alias) { |
| this.alias = alias; |
| return this; |
| } |
| |
| public String getAlias() { |
| return alias; |
| } |
| |
| /** |
| * Gets the list of classes to compose this shape and all its children. |
| * For example, a shape Foo{String,Bar{int, Date}, Double} will return |
| * {String, int, Date, Double} |
| */ |
| public List<Class<?>> getCompositeTypes() { |
| List<Class<?>> result = new ArrayList<>(); |
| if (isPrimitive() || children.isEmpty()) { |
| result.add(cls); |
| } else { |
| for (ResultShape<?> child : children) { |
| result.addAll(child.getCompositeTypes()); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Gets the list of classes to compose this shape only i.e. without expanding the children's shape. |
| * For example, a shape Foo{String,Bar{int, Date}, Double} will return {String, Bar, Double} |
| */ |
| public List<Class<?>> getTypes() { |
| List<Class<?>> result = new ArrayList<>(); |
| if (children.isEmpty()) { |
| result.add(cls); |
| } else { |
| for (ResultShape<?> child : children) { |
| result.add(child.getType()); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Creates a new shape of type X with the given class arguments and nests |
| * the new shape within this shape. |
| * |
| * @return newly created nested shape |
| */ |
| public <X> ResultShape<X> nest(Class<X> cls, FillStrategy strategy, Class<?>... classes) { |
| assertNotPrimitive(); |
| ResultShape<X> child = new ResultShape<X>(cls, strategy, true); |
| this.nest(child.add(classes)); |
| return child; |
| } |
| |
| /** |
| * Nest the given shape. |
| * |
| * @param shape The given shape can not be a parent of this shape |
| * to prohibit recursive nesting. |
| * |
| * @return this shape itself |
| */ |
| public ResultShape<T> nest(ResultShape<?> shape) { |
| assertNotPrimitive(); |
| if (shape.isParent(this)) |
| throw new IllegalArgumentException(this + " can not nest recursive " + shape); |
| children.add(shape); |
| shape.addParent(this); |
| isNesting |= !shape.isPrimitive(); |
| return this; |
| } |
| |
| /** |
| * Adds the given shape as one of the parents of this shape. |
| * |
| */ |
| private void addParent(ResultShape<?> p) { |
| if (parents == null) |
| parents = new HashSet<>(); |
| parents.add(p); |
| } |
| |
| /** |
| * Adds the given classes as child shapes of this shape. |
| * The child shapes are primitive shapes. |
| */ |
| public ResultShape<T> add(Class<?>... classes) { |
| assertNotPrimitive(); |
| for (Class<?> c : classes) { |
| children.add(new ResultShape(c, true)); |
| } |
| return this; |
| } |
| |
| /** |
| * Gets all the child shapes. |
| */ |
| public List<ResultShape<?>> getChildren() { |
| return Collections.unmodifiableList(children); |
| } |
| |
| /** |
| * Affirms if this shape can have child shapes. |
| */ |
| public boolean isCompound() { |
| return !isPrimitive; |
| } |
| |
| /** |
| * Affirms if this shape can not have any child shape. |
| * A primitive shape uses ASSIGN strategy. |
| */ |
| public boolean isPrimitive() { |
| return isPrimitive; |
| } |
| |
| /** |
| * Affirms if at least one child shape of this shape is a compound shape. |
| */ |
| public boolean isNesting() { |
| return isNesting; |
| } |
| |
| /** |
| * Affirms if this shape is nested within other shapes. |
| */ |
| public boolean isNested() { |
| return parents != null; |
| } |
| |
| /** |
| * Affirms if the given shape is a parent (or grandparent) of this shape. |
| */ |
| public boolean isParent(ResultShape<?> p) { |
| if (p.getParents().contains(this)) |
| return true; |
| if (children != null) { |
| for (ResultShape<?> child : children) { |
| if (child.isParent(p)) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void assertNotPrimitive() { |
| if (isPrimitive) |
| throw new UnsupportedOperationException("Can not add/nest shape to primitive shape " + this); |
| } |
| |
| /** |
| * Gets the immediate parents of this shape. |
| */ |
| public Set<ResultShape<?>> getParents() { |
| return parents == null ? Collections.EMPTY_SET : Collections.unmodifiableSet(parents); |
| } |
| |
| /** |
| * Total number of arguments required to populate the shape and all its child shapes. |
| */ |
| public int argLength() { |
| if (isPrimitive() || children.isEmpty()) |
| return 1; |
| int l = 0; |
| for (ResultShape<?> child : children) { |
| l += child.argLength(); |
| } |
| return l; |
| } |
| |
| /** |
| * Number of arguments to populate this shape only. |
| */ |
| public int length() { |
| if (isPrimitive() || children.isEmpty()) |
| return 1; |
| return children.size(); |
| } |
| |
| // ====================================================================================== |
| // Data Population Routines |
| // ====================================================================================== |
| |
| /** |
| * Fill this shape and its children with the given array element values. |
| * The parallel arrays contain the actual values, the types of these values and aliases. |
| * The type and alias information are used for packing Map or invoking constructor. |
| * The type can be different from what can be determined from array elements because |
| * of boxing of primitive types. |
| * The actual constructor argument types are sourced from types[] array. |
| */ |
| public T pack(Object[] values, Class<?>[] types, String[] aliases) { |
| if (values.length < argLength()) // input can be longer than required |
| throw new IndexOutOfBoundsException(values.length + " values are less than " + |
| argLength() + " argumenets required to pack " + this); |
| Object[] args = new Object[length()]; |
| Class<?>[] argTypes = new Class[length()]; |
| String[] argAliases = new String[length()]; |
| if (isPrimitive() || children.isEmpty()) { |
| args[0] = values[0]; |
| argTypes[0] = types[0]; |
| argAliases[0] = aliases[0]; |
| } else { // pack each children |
| int start = 0; |
| int i = 0; |
| for (ResultShape<?> rs : children) { |
| int finish = start + rs.argLength(); |
| args[i] = rs.pack(chop(values, start, finish), chop(types, start, finish), |
| chop(aliases, start, finish)); |
| argTypes[i] = rs.getType(); |
| argAliases[0] = rs.getAlias(); |
| start = finish; |
| i++; |
| } |
| } |
| return strategy.fill(args, argTypes, argAliases); |
| } |
| |
| /** |
| * Chop an array from start to finish. |
| */ |
| <X> X[] chop(X[] values, int start, int finish) { |
| X[] result = (X[])Array.newInstance(values.getClass().getComponentType(), finish-start); |
| System.arraycopy(values, start, result, 0, finish-start); |
| return result; |
| } |
| /** |
| * Gets a human-readable representation of this shape. |
| * |
| */ |
| @Override |
| public String toString() { |
| StringBuilder buf = new StringBuilder(cls.getSimpleName()); |
| if (isPrimitive() || children.isEmpty()) |
| return buf.toString(); |
| int i = 0; |
| for (ResultShape<?> child : children) { |
| buf.append(i++ == 0 ? "{" : ", "); |
| buf.append(child); |
| } |
| if (!children.isEmpty()) |
| buf.append("}"); |
| return buf.toString(); |
| } |
| } |