blob: b320a57aabfb8ed576e409353b95304b6b1cc150 [file] [log] [blame]
/*
* 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();
}
}