| /* |
| * 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.geode.cache.query.internal; |
| |
| import java.io.DataInput; |
| import java.io.DataOutput; |
| import java.io.IOException; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Iterator; |
| import java.util.Set; |
| |
| import it.unimi.dsi.fastutil.Hash; |
| |
| import org.apache.geode.cache.query.Struct; |
| import org.apache.geode.cache.query.internal.types.CollectionTypeImpl; |
| import org.apache.geode.cache.query.internal.types.StructTypeImpl; |
| import org.apache.geode.cache.query.types.CollectionType; |
| import org.apache.geode.cache.query.types.ObjectType; |
| import org.apache.geode.cache.query.types.StructType; |
| import org.apache.geode.internal.cache.CachePerfStats; |
| import org.apache.geode.internal.serialization.DeserializationContext; |
| import org.apache.geode.internal.serialization.SerializationContext; |
| |
| /** |
| * A Bag constrained to contain Structs of all the same type. To conserve on objects, we store the |
| * StructType once and reuse it to generate Struct instances on demand. |
| * |
| * The values in this set are stored as Object[] and get wrapped in Structs as necessary. |
| * |
| * @since GemFire 5.1 |
| */ |
| public class StructBag extends ResultsBag implements StructFields { |
| /** |
| * Holds value of property modifiable. |
| */ |
| private boolean modifiable = true; |
| |
| /** |
| * This implementation uses Arrays.equals(Object[]) as it hashing strategy. |
| */ |
| protected static class ObjectArrayHashingStrategy implements HashingStrategy { |
| |
| @Override |
| public int hashCode(Object o) { |
| Object[] oa = (Object[]) o; |
| return Arrays.deepHashCode(oa); |
| } |
| |
| @Override |
| public boolean equals(Object o1, Object o2) { |
| if (o1 == null) |
| return o2 == null; |
| if (!(o1 instanceof Object[]) || !(o2 instanceof Object[])) { |
| return o1.equals(o2); |
| } |
| return Arrays.deepEquals((Object[]) o1, (Object[]) o2); |
| } |
| } |
| |
| /** |
| * This implementation uses Arrays.equals(Object[]) as it hashing strategy. |
| */ |
| protected static class ObjectArrayFUHashingStrategy implements Hash.Strategy<Object> { |
| private static final long serialVersionUID = 8975047264555337042L; |
| |
| @Override |
| public int hashCode(Object o) { |
| // throws ClassCastException if not Object[] |
| // compute hash code based on all elements |
| if (!(o instanceof Object[])) { |
| throw new ClassCastException(String.format("Expected an Object[], but actual is %s", |
| o.getClass().getName())); |
| } |
| Object[] oa = (Object[]) o; |
| int h = 0; |
| for (int i = 0; i < oa.length; i++) { |
| Object obj = oa[i]; |
| if (obj != null) |
| h += obj.hashCode(); |
| } |
| return h; |
| } |
| |
| @Override |
| public boolean equals(Object o1, Object o2) { |
| // throws ClassCastException if not Object[] |
| if (o1 == null) |
| return o2 == null; |
| if (!(o1 instanceof Object[]) || !(o2 instanceof Object[])) { |
| return o1.equals(o2); |
| } |
| return Arrays.equals((Object[]) o1, (Object[]) o2); |
| } |
| } |
| |
| /** |
| * This constructor should only be used by DataSerializer |
| */ |
| public StructBag() { |
| |
| } |
| |
| |
| /** |
| * Creates a new instance of StructBag |
| * |
| * @param stats the CachePerfStats to track hash collisions. Should be null unless this is used as |
| * a query execution-time result set. |
| */ |
| public StructBag(StructType structType, CachePerfStats stats) { |
| super(new ObjectArrayHashingStrategy(), stats); |
| if (structType == null) { |
| throw new IllegalArgumentException( |
| "structType must not be null"); |
| } |
| this.elementType = structType; |
| } |
| |
| /** |
| * @param stats the CachePerfStats to track hash collisions. Should be null unless this is used as |
| * a query execution-time result set. |
| */ |
| public StructBag(Collection c, StructType structType, CachePerfStats stats) { |
| super(c, new ObjectArrayHashingStrategy(), stats); |
| if (structType == null) { |
| throw new IllegalArgumentException( |
| "structType must not be null"); |
| } |
| this.elementType = structType; |
| } |
| |
| /** |
| * @param stats the CachePerfStats to track hash collisions. Should be null unless this is used as |
| * a query execution-time result set. |
| */ |
| public StructBag(int initialCapacity, StructType structType, CachePerfStats stats) { |
| super(initialCapacity, new ObjectArrayHashingStrategy(), stats); |
| if (structType == null) { |
| throw new IllegalArgumentException( |
| "structType must not be null"); |
| } |
| this.elementType = structType; |
| } |
| |
| /** |
| * @param stats the CachePerfStats to track hash collisions. Should be null unless this is used as |
| * a query execution-time result set. |
| */ |
| public StructBag(int initialCapacity, float loadFactor, StructType structType, |
| CachePerfStats stats) { |
| super(initialCapacity, loadFactor, new ObjectArrayHashingStrategy(), stats); |
| if (structType == null) { |
| throw new IllegalArgumentException( |
| "structType must not be null"); |
| } |
| this.elementType = structType; |
| } |
| |
| @Override |
| public boolean equals(Object o) { // for findbugs |
| return super.equals(o); |
| } |
| |
| @Override |
| public int hashCode() { // for findbugs |
| return super.hashCode(); |
| } |
| |
| /** Add a Struct */ |
| @Override |
| public boolean add(Object obj) { |
| if (!(obj instanceof StructImpl)) { |
| throw new IllegalArgumentException( |
| "This set only accepts StructImpl"); |
| } |
| StructImpl s = (StructImpl) obj; |
| if (!this.elementType.equals(s.getStructType())) { |
| throw new IllegalArgumentException( |
| String.format( |
| "obj does not have the same StructType.; collection structype,%s; added obj type=%s", |
| this.elementType, s.getStructType())); |
| } |
| return addFieldValues(s.getFieldValues()); |
| } |
| |
| /** |
| * For internal use. Just add the Object[] values for a struct with same type |
| */ |
| @Override |
| public boolean addFieldValues(Object[] fieldValues) { |
| return super.add(fieldValues); |
| } |
| |
| /** Does this set contain specified struct? */ |
| @Override |
| public boolean contains(Object obj) { |
| if (!(obj instanceof Struct)) { |
| return false; |
| } |
| Struct s = (Struct) obj; |
| if (!this.elementType.equals(StructTypeImpl.typeFromStruct(s))) { |
| return false; |
| } |
| return containsFieldValues(s.getFieldValues()); |
| } |
| |
| /** |
| * Does this set contain a Struct of the correct type with the specified values? |
| */ |
| @Override |
| public boolean containsFieldValues(Object[] fieldValues) { |
| // Asif: The fieldValues can never be null . If the Struc contained |
| // null , then the the getFieldValues would have returned |
| // a zero size Object array. So we need not bother about null here |
| if (this.hasLimitIterator) { |
| Iterator fieldItr = this.fieldValuesIterator(); |
| while (fieldItr.hasNext()) { |
| if (Arrays.equals((Object[]) fieldItr.next(), fieldValues)) { |
| return true; |
| } |
| } |
| return false; |
| } else { |
| return super.contains(fieldValues); |
| } |
| } |
| |
| @Override |
| public int occurrences(Object element) { |
| if (!(element instanceof Struct)) { |
| return 0; |
| } |
| Struct s = (Struct) element; |
| if (!this.elementType.equals(StructTypeImpl.typeFromStruct(s))) { |
| return 0; |
| } |
| if (this.hasLimitIterator) { |
| int count = 0; |
| boolean encounteredObject = false; |
| Object[] fields = s.getFieldValues(); |
| for (Iterator itr = this.fieldValuesIterator(); itr.hasNext();) { |
| Object[] structFields = (Object[]) itr.next(); |
| if (Arrays.equals(fields, structFields)) { |
| count++; |
| encounteredObject = true; |
| } else if (encounteredObject) { |
| // Asif: No possibility of its occurrence again |
| break; |
| } |
| } |
| return count; |
| } else { |
| return this.map.get(s.getFieldValues()); // returns 0 if not found |
| } |
| } |
| |
| public int occurrences(Object[] element) { |
| return this.map.get(element); // returns 0 if not found |
| } |
| |
| |
| /** Remove the specified Struct */ |
| @Override |
| public boolean remove(Object o) { |
| if (!(o instanceof Struct)) { |
| return false; |
| } |
| Struct s = (Struct) o; |
| if (!this.elementType.equals(StructTypeImpl.typeFromStruct(s))) { |
| return false; |
| } |
| return removeFieldValues(s.getFieldValues()); |
| } |
| |
| /** Remove the field values from a struct of the correct type */ |
| @Override |
| public boolean removeFieldValues(Object[] fieldValues) { |
| if (this.hasLimitIterator) { |
| // Asif : Get the field value Iterator |
| Iterator fieldItr = this.fieldValuesIterator(); |
| while (fieldItr.hasNext()) { |
| if (Arrays.equals((Object[]) fieldItr.next(), fieldValues)) { |
| fieldItr.remove(); |
| return true; |
| } |
| } |
| return false; |
| } else { |
| return super.remove(fieldValues); |
| } |
| } |
| |
| @Override |
| public CollectionType getCollectionType() { |
| return new CollectionTypeImpl(StructBag.class, this.elementType); |
| } |
| |
| // downcast StructBags to call more efficient methods |
| @Override |
| public boolean addAll(Collection c) { |
| if (c instanceof StructFields) { |
| return addAll((StructFields) c); |
| } |
| return super.addAll(c); |
| } |
| |
| @Override |
| public boolean removeAll(Collection c) { |
| if (c instanceof StructFields) { |
| return removeAll((StructFields) c); |
| } |
| return super.removeAll(c); |
| } |
| |
| @Override |
| public boolean retainAll(Collection c) { |
| if (c instanceof StructFields) { |
| return retainAll((StructFields) c); |
| } |
| return super.retainAll(c); |
| } |
| |
| public boolean addAll(StructFields sb) { |
| boolean modified = false; |
| if (!this.elementType.equals(sb.getCollectionType().getElementType())) { |
| throw new IllegalArgumentException( |
| "types do not match"); |
| } |
| |
| for (Iterator itr = sb.fieldValuesIterator(); itr.hasNext();) { |
| // Check if query execution on this thread is canceled. |
| QueryMonitor.throwExceptionIfQueryOnCurrentThreadIsCanceled(); |
| |
| Object[] vals = (Object[]) itr.next(); |
| if (super.add(vals)) { |
| modified = true; |
| } |
| } |
| return modified; |
| } |
| |
| public boolean removeAll(StructFields ss) { |
| boolean modified = false; |
| if (!this.elementType.equals(ss.getCollectionType().getElementType())) { |
| return false; // nothing // modified |
| } |
| for (Iterator itr = ss.fieldValuesIterator(); itr.hasNext();) { |
| Object[] vals = (Object[]) itr.next(); |
| if (this.removeFieldValues(vals)) { |
| modified = true; |
| } |
| } |
| return modified; |
| } |
| |
| public boolean retainAll(StructFields ss) { |
| if (!this.elementType.equals(ss.getCollectionType().getElementType())) { |
| if (isEmpty()) { |
| return false; // nothing modified |
| } else { |
| clear(); |
| return true; // nothing retained in receiver collection |
| } |
| } |
| boolean changed = false; |
| int size = size(); |
| Iterator it; |
| it = fieldValuesIterator(); |
| while (size-- > 0) { |
| Object[] vals = (Object[]) it.next(); |
| if (!ss.containsFieldValues(vals)) { |
| it.remove(); |
| changed = true; |
| } |
| } |
| return changed; |
| } |
| |
| /** |
| * Return an iterator over the elements in this collection. Duplicates will show up the number of |
| * times it has occurrences. |
| */ |
| @Override |
| public Iterator iterator() { |
| return new StructBagIterator(fieldValuesIterator()); |
| } |
| |
| /** Returns an iterator over the fieldValues Object[] instances */ |
| @Override |
| public Iterator fieldValuesIterator() { |
| return super.iterator(); |
| } |
| |
| // note: this method is dangerous in that it could result in undefined |
| // behavior if the new struct type is not compatible with the data. |
| // For now just trust that the application knows what it is doing if it |
| // is overriding the element type in a set of structs |
| @Override |
| public void setElementType(ObjectType elementType) { |
| if (!(elementType instanceof StructTypeImpl)) { |
| throw new IllegalArgumentException( |
| "element type must be struct"); |
| } |
| this.elementType = elementType; |
| } |
| |
| @Override |
| public Set asSet() { |
| return new StructSet(this); |
| } |
| |
| /** |
| * Getter for property modifiable. |
| * |
| * @return Value of property modifiable. |
| */ |
| @Override |
| public boolean isModifiable() { |
| return this.modifiable; |
| } |
| |
| /** |
| * Setter for property modifiable. |
| * |
| * @param modifiable New value of property modifiable. |
| */ |
| public void setModifiable(boolean modifiable) { |
| this.modifiable = modifiable; |
| } |
| |
| @Override |
| public int getDSFID() { |
| return STRUCT_BAG; |
| } |
| |
| @Override |
| protected ObjectIntHashMap createMapForFromData() { |
| return new ObjectIntHashMap(this.size, new ObjectArrayHashingStrategy()); |
| } |
| |
| @Override |
| public void fromData(DataInput in, |
| DeserializationContext context) throws IOException, ClassNotFoundException { |
| super.fromData(in, context); |
| this.modifiable = in.readBoolean(); |
| } |
| |
| @Override |
| public void toData(DataOutput out, |
| SerializationContext context) throws IOException { |
| super.toData(out, context); |
| out.writeBoolean(this.modifiable); |
| } |
| |
| @Override |
| void writeNumNulls(DataOutput out) {} |
| |
| @Override |
| void readNumNulls(DataInput in) {} |
| |
| void createTObjectIntHashMap() { |
| this.map = new ObjectIntHashMap(this.size, new ObjectArrayHashingStrategy()); |
| } |
| |
| /** |
| * Iterator wrapper to construct Structs on demand. |
| */ |
| private class StructBagIterator extends BagIterator { |
| |
| private final Iterator itr; |
| |
| /** |
| * @param itr iterator over the Object[] instances of fieldValues |
| */ |
| StructBagIterator(Iterator itr) { |
| this.itr = itr; |
| } |
| |
| @Override |
| public boolean hasNext() { |
| return this.itr.hasNext(); |
| } |
| |
| @Override |
| public Object next() { |
| return new StructImpl((StructTypeImpl) StructBag.this.elementType, |
| (Object[]) this.itr.next()); |
| } |
| |
| @Override |
| public void remove() { |
| this.itr.remove(); |
| } |
| } |
| } |