/*************************************************************************** * Copyright (C) 2012 by H-Store Project * * Brown University * * Massachusetts Institute of Technology * * Yale University * * * * Permission is hereby granted, free of charge, to any person obtaining * * a copy of this software and associated documentation files (the * * "Software"), to deal in the Software without restriction, including * * without limitation the rights to use, copy, modify, merge, publish, * * distribute, sublicense, and/or sell copies of the Software, and to * * permit persons to whom the Software is furnished to do so, subject to * * the following conditions: * * * * The above copyright notice and this permission notice shall be * * included in all copies or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.* * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * * OTHER DEALINGS IN THE SOFTWARE. * ***************************************************************************/ package edu.brown.utils; import java.io.File; import java.io.IOException; import java.util.BitSet; import java.util.Collection; import java.util.Iterator; import java.util.regex.Pattern; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONStringer; import org.voltdb.catalog.Database; import org.voltdb.messaging.FastDeserializer; import org.voltdb.messaging.FastSerializable; import org.voltdb.messaging.FastSerializer; import org.voltdb.utils.NotImplementedException; import edu.brown.hstore.HStoreConstants; /** * Container class that represents a list of partitionIds * This is the fastest way to represent a list of partitions in the system. * @author pavlo */ public class PartitionSet implements Collection<Integer>, JSONSerializable, FastSerializable { private final BitSet inner = new BitSet(); private boolean contains_null = false; private int[] values = null; // ---------------------------------------------------------------------------- // CONSTRUCTORS // ---------------------------------------------------------------------------- public PartitionSet() { // Nothing... } /** * Initialize PartitionSet from Integer Collection * @param partitions */ public PartitionSet(Collection<Integer> partitions) { this.addAll(partitions); } /** * Initialize PartitionSet from an Integer array * @param partitions */ public PartitionSet(Integer...partitions) { for (Integer partition : partitions) this.add(partition); } /** * Initialize PartitionSet from an int array * @param partitions */ public PartitionSet(int partitions[]) { for (int partition : partitions) this.add(partition); } /** * Copy constructor * @param partitions */ public PartitionSet(PartitionSet partitions) { this.addAll(partitions); } // ---------------------------------------------------------------------------- // API METHODS // ---------------------------------------------------------------------------- /** * Return a cached int array of the partition ids in this PartitionSet. * This is the preferred way (i.e., faster) to iterate over the contents of the set. You will * want to use this instead of using its iterator. * @return */ public final int[] values() { if (this.values == null) { int remaining = this.inner.cardinality(); int size = remaining + (this.contains_null ? 1 : 0); this.values = new int[size]; int idx = 0; if (this.contains_null) { this.values[idx++] = HStoreConstants.NULL_PARTITION_ID; } for (int i = 0; idx < size; i++) { if (this.inner.get(i)) this.values[idx++] = i; } // FOR } return (this.values); } /** * Return first partition found in this PartitionSet. This is primarily * useful for single-partition txns when you just want the only partition in * this set. * @return * @throws IndexOutOfBoundsException */ public int get() throws IndexOutOfBoundsException { for (int partition = 0, cnt = this.inner.size(); partition < cnt; partition++) { if (this.inner.get(partition)) return (partition); } // FOR if (this.contains_null) return HStoreConstants.NULL_PARTITION_ID; throw new IndexOutOfBoundsException(); } @Override public boolean equals(Object obj) { if (obj instanceof PartitionSet) { return this.inner.equals(((PartitionSet)obj).inner); } else if (obj instanceof Collection<?>) { Collection<?> other = (Collection<?>)obj; if (this.inner.size() != other.size()) return (false); return (this.containsAll(other)); } return (false); } @Override public int hashCode() { return this.inner.hashCode(); } @Override public String toString() { String s = this.inner.toString(); // HACK if (this.contains_null) { s = s.substring(0, 1) + HStoreConstants.NULL_PARTITION_ID + ", " + s.substring(1); } return (s); } @Override public int size() { return (this.contains_null ? 1 : 0) + this.inner.cardinality(); // return (this.values().length); } @Override public void clear() { this.contains_null = false; this.inner.clear(); this.values = null; } @Override public boolean isEmpty() { return (this.contains_null == false && this.inner.isEmpty()); } @Override public boolean contains(Object o) { if (o instanceof Integer) { return this.contains(((Integer)o).intValue()); } return (false); } public boolean contains(Integer partition) { return this.contains(partition.intValue()); } public boolean contains(int partition) { if (partition == HStoreConstants.NULL_PARTITION_ID) { return (this.contains_null); } return (this.inner.get(partition)); } @Override public Object[] toArray() { int length = this.inner.cardinality(); Object arr[] = new Object[length]; int idx = 0; for (Integer partition : this) { arr[idx++] = partition; } return (arr); } @SuppressWarnings("unchecked") @Override public <T> T[] toArray(T[] a) { int length = this.inner.cardinality(); if (a.length != length) { a = (T[])new Object[length]; } int idx = 0; for (Integer partition : this) { a[idx++] = (T)partition; } // FOR return (a); } @Override public boolean add(Integer e) { return (this.add(e.intValue())); } public boolean add(int partition) { if (partition == HStoreConstants.NULL_PARTITION_ID) { this.contains_null = true; } else { this.inner.set(partition); } this.values = null; return (true); } @Override public boolean remove(Object o) { if (o instanceof Integer) { Integer partition = (Integer)o; return (this.remove(partition.intValue())); } return (false); } public boolean remove(int partition) { boolean ret = false; if (partition == HStoreConstants.NULL_PARTITION_ID) { ret = this.contains_null; this.contains_null = false; } else if (this.inner.get(partition)) { ret = true; this.inner.set(partition, false); } this.values = null; return (ret); } @Override public boolean containsAll(Collection<?> c) { for (Object o : c) { if (this.contains(o) == false) { return (false); } } // FOR return (true); } public boolean containsAll(PartitionSet partitions) { if (this.contains_null != partitions.contains_null) return (false); if (this.inner.intersects(partitions.inner)) { for (int partition = 0, cnt = partitions.inner.size(); partition < cnt; partition++) { if (partitions.inner.get(partition) && this.inner.get(partition) == false) return (false); } // FOR return (true); } return (false); } public boolean addAll(int partitions[]) { boolean ret = true; for (int partition : partitions) { ret = this.add(partition) && ret; } // FOR return (ret); } @Override public boolean addAll(Collection<? extends Integer> partitions) { boolean ret = true; for (Integer partition : partitions) { ret = this.add(partition.intValue()) && ret; } // FOR return (ret); } public boolean addAll(PartitionSet partitions) { if (partitions.contains_null) this.contains_null = true; this.inner.or(partitions.inner); return (true); } @Override public boolean removeAll(Collection<?> c) { boolean ret = false; for (Object o : c) { if (o instanceof Number) { ret = this.remove(((Number)o).intValue()) || ret; } } // FOR return (ret); } public boolean removeAll(PartitionSet partitions) { boolean ret = false; if (partitions.contains_null) { ret = this.contains_null; this.contains_null = false; } if (this.inner.intersects(partitions.inner)) { this.inner.andNot(partitions.inner); ret = true; } return (ret); } @Override public boolean retainAll(Collection<?> c) { for (Integer partition : this) { if (c.contains(partition) == false) { this.remove(partition); } } // FOR return (true); } public boolean retainAll(PartitionSet partitions) { if (this.contains_null != partitions.contains_null) this.contains_null = false; this.inner.and(partitions.inner); return (true); } @Override public Iterator<Integer> iterator() { return new Itr(); } // ---------------------------------------------------------------------------- // STATIC METHODS // ---------------------------------------------------------------------------- public static PartitionSet singleton(int partition) { PartitionSet ret = new PartitionSet(); ret.add(partition); return (ret); } /** * Convert a bitmap into a PartitionSet and print it out * @param bitmap * @return */ public static String toString(boolean bitmap[]) { PartitionSet ps = new PartitionSet(); for (int i = 0; i < bitmap.length; i++) { if (bitmap[i]) ps.add(i); } // FOR return (ps.toString()); } /** * Parse a description of partition ids and populate a PartitionSet. * This supports comma-separated lists (e.g., "1,2,3,4") and * ranges (e.g., "1-4"). You can also mix and match them together * (e.g., "1,2-3,4"). * @param partitionList * @return */ public static PartitionSet parse(String partitionList) { Pattern HYPHEN_SPLIT = Pattern.compile(Pattern.quote("-")); // Partition Ranges PartitionSet partitions = new PartitionSet(); for (String p : StringUtil.splitList(partitionList)) { int start = -1; int stop = -1; String range[] = HYPHEN_SPLIT.split(p); if (range.length == 2) { start = Integer.parseInt(range[0].trim()); stop = Integer.parseInt(range[1].trim()); } else { start = Integer.parseInt(p.trim()); stop = start; } for (int partition = start; partition < stop + 1; partition++) { partitions.add(partition); } // FOR } // FOR return (partitions); } // ---------------------------------------------------------------------------- // ITERATOR // ---------------------------------------------------------------------------- private class Itr implements Iterator<Integer> { int idx = 0; boolean shown_null = false; boolean need_seek = true; @Override public boolean hasNext() { this.need_seek = false; if (contains_null && this.shown_null == false) { return (true); } for (int cnt = inner.size(); this.idx < cnt; this.idx++) { if (inner.get(this.idx)) { return (true); } } // FOR return (false); } @Override public Integer next() { if (this.need_seek) { if (this.hasNext() == false) return (null); } if (contains_null && this.shown_null == false) { this.shown_null = true; return (HStoreConstants.NULL_PARTITION_ID); } return Integer.valueOf(this.idx++); } @Override public void remove() { throw new NotImplementedException("PartitionSet Iterator remove is not implemented"); } } // ---------------------------------------------------------------------------- // SERIALIZATION METHODS // ---------------------------------------------------------------------------- @Override public void readExternal(FastDeserializer in) throws IOException { this.clear(); this.contains_null = in.readBoolean(); int cnt = in.readShort(); for (int i = 0; i < cnt; i++) { int partition = in.readInt(); this.add(partition); } // FOR } @Override public void writeExternal(FastSerializer out) throws IOException { out.writeBoolean(this.contains_null); out.writeShort(this.inner.cardinality()); for (Integer partition : this) { out.writeInt(partition.intValue()); } // FOR } @Override public void load(File input_path, Database catalog_db) throws IOException { JSONUtil.load(this, catalog_db, input_path); } @Override public void save(File output_path) throws IOException { JSONUtil.save(this, output_path); } @Override public String toJSONString() { return (JSONUtil.toJSONString(this)); } @Override public void toJSON(JSONStringer stringer) throws JSONException { stringer.key("P").array(); for (Integer partition : this) { stringer.value(partition); } // FOR stringer.endArray(); } @Override public void fromJSON(JSONObject json_object, Database catalog_db) throws JSONException { JSONArray json_arr = json_object.getJSONArray("P"); for (int i = 0, cnt = json_arr.length(); i < cnt; i++) { this.inner.set(json_arr.getInt(i)); } } }