/**
* Copyright 2016 Yahoo Inc.
*
* Licensed 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 com.yahoo.pulsar.common.naming;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.base.Objects;
import com.google.common.collect.BoundType;
import com.google.common.collect.Range;
import com.yahoo.pulsar.common.naming.DestinationName;
import com.yahoo.pulsar.common.naming.NamespaceName;
import com.yahoo.pulsar.common.naming.NamespaceBundle;
public class NamespaceBundle implements ServiceUnitId, Comparable<NamespaceBundle> {
private final NamespaceName nsname;
private final Range<Long> keyRange;
private final NamespaceBundleFactory factory;
public NamespaceBundle(NamespaceName nsname, Range<Long> keyRange, NamespaceBundleFactory factory) {
this.nsname = checkNotNull(nsname);
this.keyRange = checkNotNull(keyRange);
checkArgument(this.keyRange.lowerBoundType().equals(BoundType.CLOSED),
"Invalid hash range. Lower Endpoint has to be inclusive");
checkArgument(
(this.keyRange.upperEndpoint().equals(NamespaceBundles.FULL_UPPER_BOUND)
&& this.keyRange.upperBoundType().equals(BoundType.CLOSED))
|| (!this.keyRange.upperEndpoint().equals(NamespaceBundles.FULL_UPPER_BOUND)
&& this.keyRange.upperBoundType().equals(BoundType.OPEN)),
"Invalid hash range. Upper Endpoint should be exclusive unless it is 0xffffffff");
checkArgument(!this.keyRange.isEmpty(), "Cannot create bundle object for an empty key range");
this.factory = checkNotNull(factory);
}
@Override
public NamespaceName getNamespaceObject() {
return this.nsname;
}
@Override
public String toString() {
return getKey(this.nsname, this.keyRange);
}
@Override
public int compareTo(NamespaceBundle other) {
if (this.nsname.toString().compareTo(other.nsname.toString()) != 0) {
return this.nsname.toString().compareTo(other.nsname.toString());
}
if (equals(other)) {
// completely the same range, return true
return 0;
}
try {
/**
* <code>Range.insersection()</code> will throw <code>IllegalArgumentException</code> when two ranges are
* not connected at all, which is a OK case for our comparison. <code>checkState</code> here is to ensure
* that the two ranges we are comparing don't have overlaps.
*/
checkState(this.keyRange.intersection(other.keyRange).isEmpty(),
"Can't compare two key ranges with non-empty intersection set");
} catch (IllegalArgumentException iae) {
// OK if two ranges are not connected at all
} catch (IllegalStateException ise) {
// It is not OK if the intersection is not empty
throw new IllegalArgumentException(ise.getMessage(), ise);
}
// Now we are sure that two bundles don't have overlap
return this.keyRange.lowerEndpoint().compareTo(other.keyRange.lowerEndpoint());
}
@Override
public boolean equals(Object other) {
if (other instanceof NamespaceBundle) {
NamespaceBundle obj = (NamespaceBundle) other;
return Objects.equal(this.nsname, obj.nsname)
&& (Objects.equal(this.keyRange.lowerEndpoint(), obj.keyRange.lowerEndpoint())
&& Objects.equal(this.keyRange.lowerBoundType(), obj.keyRange.lowerBoundType())
&& Objects.equal(this.keyRange.upperEndpoint(), obj.keyRange.upperEndpoint())
&& Objects.equal(this.keyRange.upperBoundType(), obj.keyRange.upperBoundType()));
}
return false;
}
@Override
public boolean includes(DestinationName dn) {
if (!this.nsname.equals(dn.getNamespaceObject())) {
return false;
}
return this.keyRange.contains(factory.getLongHashCode(dn.toString()));
}
public String getBundleRange() {
return String.format("0x%08x_0x%08x", keyRange.lowerEndpoint(), keyRange.upperEndpoint());
}
private static String getKey(NamespaceName nsname, Range<Long> keyRange) {
return String.format("%s/0x%08x_0x%08x", nsname, keyRange.lowerEndpoint(), keyRange.upperEndpoint());
}
Range<Long> getKeyRange() {
return this.keyRange;
}
Long getLowerEndpoint() {
return this.keyRange.lowerEndpoint();
}
Long getUpperEndpoint() {
return this.keyRange.upperEndpoint();
}
public static String getBundleRange(String namespaceBundle) {
return namespaceBundle.substring(namespaceBundle.lastIndexOf('/') + 1);
}
}