org.hibernate.engine.spi.ExecutableList Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hibernate-core Show documentation
Show all versions of hibernate-core Show documentation
The core O/RM functionality as provided by Hibernate
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or .
*/
package org.hibernate.engine.spi;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.hibernate.action.spi.Executable;
import org.hibernate.internal.util.collections.CollectionHelper;
/**
* Specialized encapsulating of the state pertaining to each Executable list.
*
* Manages sorting the executables (lazily)
*
* Manages the querySpaces affected by the executables in the list, and caches this too.
*
* @author Steve Ebersole
* @author Anton Marsden
*
* @param Intersection type describing Executable implementations
*/
@SuppressWarnings("rawtypes")
public class ExecutableList implements Serializable, Iterable, Externalizable {
public static final int INIT_QUEUE_LIST_SIZE = 5;
/**
* Provides a sorting interface for ExecutableList.
*
* @param
*/
public static interface Sorter {
/**
* Sorts the list.
*/
void sort(List l);
}
private final ArrayList executables;
private final Sorter sorter;
private final boolean requiresSorting;
private boolean sorted;
/**
* Used to hold the query spaces (table names, roughly) that all the Executable instances contained
* in this list define. This information is ultimately used to invalidate cache regions as it is
* exposed from {@link #getQuerySpaces}. This value being {@code null} indicates that the
* query spaces should be calculated.
*/
private transient Set querySpaces;
/**
* Creates a new ExecutableList with the default settings.
*/
public ExecutableList() {
this( INIT_QUEUE_LIST_SIZE );
}
/**
* Creates a new ExecutableList with the specified initialCapacity.
*
* @param initialCapacity The initial capacity for instantiating the internal List
*/
public ExecutableList(int initialCapacity) {
// pass true for requiresSorting argument to maintain original behavior
this( initialCapacity, true );
}
public ExecutableList(boolean requiresSorting) {
this( INIT_QUEUE_LIST_SIZE, requiresSorting );
}
public ExecutableList(int initialCapacity, boolean requiresSorting) {
this.sorter = null;
this.executables = new ArrayList( initialCapacity );
this.querySpaces = null;
this.requiresSorting = requiresSorting;
this.sorted = requiresSorting;
}
/**
* Creates a new ExecutableList using the specified Sorter.
*
* @param sorter The Sorter to use; may be {@code null}
*/
public ExecutableList(ExecutableList.Sorter sorter) {
this( INIT_QUEUE_LIST_SIZE, sorter );
}
/**
* Creates a new ExecutableList with the specified initialCapacity and Sorter.
*
* @param initialCapacity The initial capacity for instantiating the internal List
* @param sorter The Sorter to use; may be {@code null}
*/
public ExecutableList(int initialCapacity, ExecutableList.Sorter sorter) {
this.sorter = sorter;
this.executables = new ArrayList( initialCapacity );
this.querySpaces = null;
// require sorting by default, even if sorter is null to maintain original behavior
this.requiresSorting = true;
this.sorted = true;
}
/**
* Lazily constructs the querySpaces affected by the actions in the list.
*
* @return the querySpaces affected by the actions in this list
*/
public Set getQuerySpaces() {
if ( querySpaces == null ) {
for ( E e : executables ) {
Serializable[] propertySpaces = e.getPropertySpaces();
if ( propertySpaces != null && propertySpaces.length > 0 ) {
if( querySpaces == null ) {
querySpaces = new HashSet();
}
Collections.addAll( querySpaces, propertySpaces );
}
}
if( querySpaces == null ) {
return Collections.emptySet();
}
}
return querySpaces;
}
/**
* @return true if the list is empty.
*/
public boolean isEmpty() {
return executables.isEmpty();
}
/**
* Removes the entry at position index in the list.
*
* @param index The index of the element to remove
*
* @return the entry that was removed
*/
public E remove(int index) {
// removals are generally safe in regards to sorting...
final E e = executables.remove( index );
// If the executable being removed defined query spaces we need to recalculate the overall query spaces for
// this list. The problem is that we don't know how many other executable instances in the list also
// contributed those query spaces as well.
//
// An alternative here is to use a "multiset" which is a specialized set that keeps a reference count
// associated to each entry. But that is likely overkill here.
if ( e.getPropertySpaces() != null && e.getPropertySpaces().length > 0 ) {
querySpaces = null;
}
return e;
}
/**
* Clears the list of executions.
*/
public void clear() {
executables.clear();
querySpaces = null;
sorted = requiresSorting;
}
/**
* Removes the last n entries from the list.
*
* @param n The number of elements to remove.
*/
public void removeLastN(int n) {
if ( n > 0 ) {
int size = executables.size();
for ( Executable e : executables.subList( size - n, size ) ) {
if ( e.getPropertySpaces() != null && e.getPropertySpaces().length > 0 ) {
// querySpaces could now be incorrect
querySpaces = null;
break;
}
}
executables.subList( size - n, size ).clear();
}
}
/**
* Add an Executable to this list.
*
* @param executable the executable to add to the list
*
* @return true if the object was added to the list
*/
public boolean add(E executable) {
final E previousLast = sorter != null || executables.isEmpty() ? null : executables.get( executables.size() - 1 );
boolean added = executables.add( executable );
if ( !added ) {
return false;
}
// if it was sorted before the addition, then check if the addition invalidated the sorting
if ( sorted ) {
if ( sorter != null ) {
// we don't have intrinsic insight into the sorter's algorithm, so invalidate sorting
sorted = false;
}
else {
// otherwise, we added to the end of the list. So check the comparison between the incoming
// executable and the one previously at the end of the list using the Comparable contract
if ( previousLast != null && previousLast.compareTo( executable ) > 0 ) {
sorted = false;
}
}
}
Serializable[] querySpaces = executable.getPropertySpaces();
if ( this.querySpaces != null && querySpaces != null ) {
Collections.addAll( this.querySpaces, querySpaces );
}
return added;
}
/**
* Sorts the list using the natural ordering or using the Sorter if it's not null.
*/
@SuppressWarnings("unchecked")
public void sort() {
if ( sorted || !requiresSorting ) {
// nothing to do
return;
}
if ( sorter != null ) {
sorter.sort( executables );
}
else {
Collections.sort( executables );
}
sorted = true;
}
/**
* @return the current size of the list
*/
public int size() {
return executables.size();
}
/**
* @param index The index of the element to retrieve
*
* @return The element at specified index
*/
public E get(int index) {
return executables.get( index );
}
/**
* Returns an iterator for the list. Wraps the list just in case something tries to modify it.
*
* @return an unmodifiable iterator
*/
@Override
public Iterator iterator() {
return Collections.unmodifiableList( executables ).iterator();
}
/**
* Write this list out to the given stream as part of serialization
*
* @param oos The stream to which to serialize our state
*/
@Override
public void writeExternal(ObjectOutput oos) throws IOException {
oos.writeBoolean( sorted );
oos.writeInt( executables.size() );
for ( E e : executables ) {
oos.writeObject( e );
}
// if the spaces are initialized, write them out for usage after deserialization
if ( querySpaces == null ) {
oos.writeInt( -1 );
}
else {
oos.writeInt( querySpaces.size() );
// these are always String, why we treat them as Serializable instead is beyond me...
for ( Serializable querySpace : querySpaces ) {
oos.writeUTF( querySpace.toString() );
}
}
}
/**
* Read this object state back in from the given stream as part of de-serialization
*
* @param in The stream from which to read our serial state
*/
@Override
@SuppressWarnings("unchecked")
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
sorted = in.readBoolean();
final int numberOfExecutables = in.readInt();
executables.ensureCapacity( numberOfExecutables );
if ( numberOfExecutables > 0 ) {
for ( int i = 0; i < numberOfExecutables; i++ ) {
E e = (E) in.readObject();
executables.add( e );
}
}
final int numberOfQuerySpaces = in.readInt();
if ( numberOfQuerySpaces < 0 ) {
this.querySpaces = null;
}
else {
querySpaces = new HashSet( CollectionHelper.determineProperSizing( numberOfQuerySpaces ) );
for ( int i = 0; i < numberOfQuerySpaces; i++ ) {
querySpaces.add( in.readUTF() );
}
}
}
/**
* Allow the Executables to re-associate themselves with the Session after deserialization.
*
* @param session The session to which to associate the Executables
*/
public void afterDeserialize(SessionImplementor session) {
for ( E e : executables ) {
e.afterDeserialize( session );
}
}
public String toString() {
return "ExecutableList{size=" + executables.size() + "}";
}
}