/*
* 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.jackrabbit.core.security.authorization;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* <code>PrivilegeBits</code>
*/
public class PrivilegeBits {
public static final PrivilegeBits EMPTY = new PrivilegeBits(UnmodifiableData.EMPTY);
private static final long READ = 1; // PrivilegeRegistry.READ
private static final Map<Long, PrivilegeBits> BUILT_IN = new HashMap<Long, PrivilegeBits>();
static {
BUILT_IN.put(EMPTY.longValue(), EMPTY);
}
private final Data d;
/**
* Private constructor.
*
* @param d
*/
private PrivilegeBits(Data d) {
this.d = d;
}
/**
* Package private method used by <code>PrivilegeRegistry</code> to handle
* built-in privileges and calculate internal permissions.
*
* @return long representation of this instance.
* @see PrivilegeRegistry#calculatePermissions(PrivilegeBits, PrivilegeBits, boolean, boolean)
*/
long longValue() {
return d.longValue();
}
/**
* Package private method used by <code>PrivilegeRegistry</code> to calculate
* the privilege bits associated with a given built-in or custom privilege
* definition.
*
* @return an instance of <code>PrivilegeBits</code>
*/
PrivilegeBits nextBits() {
if (this == EMPTY) {
return EMPTY;
} else {
PrivilegeBits pb = new PrivilegeBits(d.next());
if (pb.d.isSimple()) {
BUILT_IN.put(pb.longValue(), pb);
}
return pb;
}
}
/**
* Package private method used by <code>PrivilegeRegistry</code> to get or
* create an instance of privilege bits for the specified long value.
*
* @param bits
* @return an instance of <code>PrivilegeBits</code>
*/
static PrivilegeBits getInstance(long bits) {
if (bits == PrivilegeRegistry.NO_PRIVILEGE) {
return EMPTY;
} else if (bits < PrivilegeRegistry.NO_PRIVILEGE) {
throw new IllegalArgumentException();
} else {
PrivilegeBits pb = BUILT_IN.get(bits);
if (pb == null) {
pb = new PrivilegeBits(new UnmodifiableData(bits));
BUILT_IN.put(bits, pb);
}
return pb;
}
}
/**
* Internal method to create a new instance of <code>PrivilegeBits</code>.
*
* @param bits
* @return an instance of <code>PrivilegeBits</code>
*/
private static PrivilegeBits getInstance(long[] bits) {
long[] bts = new long[bits.length];
System.arraycopy(bits, 0, bts, 0, bits.length);
return new PrivilegeBits(new UnmodifiableData(bts));
}
/**
* Creates a mutable instance of privilege bits.
*
* @return a new instance of privilege bits.
*/
public static PrivilegeBits getInstance() {
return new PrivilegeBits(new ModifiableData());
}
/**
* Creates a mutable instance of privilege bits.
*
* @param base
* @return a new instance of privilege bits.
*/
public static PrivilegeBits getInstance(PrivilegeBits base) {
return new PrivilegeBits(new ModifiableData(base.d));
}
/**
* Returns <code>true</code> if this privilege bits includes no privileges
* at all.
*
* @return <code>true</code> if this privilege bits includes no privileges
* at all; <code>false</code> otherwise.
* @see PrivilegeRegistry#NO_PRIVILEGE
*/
public boolean isEmpty() {
return d.isEmpty();
}
/**
* Returns an unmodifiable instance.
*
* @return an unmodifiable <code>PrivilegeBits</code> instance.
*/
public PrivilegeBits unmodifiable() {
if (d instanceof ModifiableData) {
return (d.isSimple()) ? getInstance(d.longValue()) : getInstance(d.longValues());
} else {
return this;
}
}
/**
* Returns <code>true</code> if this privilege bits instance can be altered.
*
* @return true if this privilege bits instance can be altered.
*/
public boolean isModifiable() {
return (d instanceof ModifiableData);
}
/**
* Returns <code>true</code> if this instance includes the jcr:read
* privilege. Shortcut for calling {@link PrivilegeBits#includes(PrivilegeBits)}
* where the other bits represented the jcr:read privilege.
*
* @return <code>true</code> if this instance includes the jcr:read
* privilege; <code>false</code> otherwise.
*/
public boolean includesRead() {
if (this == EMPTY) {
return false;
} else {
return d.includesRead();
}
}
/**
* Returns <code>true</code> if all privileges defined by the specified
* <code>otherBits</code> are present in this instance.
*
* @param otherBits
* @return <code>true</code> if all privileges defined by the specified
* <code>otherBits</code> are included in this instance; <code>false</code>
* otherwise.
*/
public boolean includes(PrivilegeBits otherBits) {
return d.includes(otherBits.d);
}
/**
* Adds the other privilege bits to this instance.
*
* @param other The other privilege bits to be added.
* @throws UnsupportedOperationException if this instance is immutable.
*/
public void add(PrivilegeBits other) {
if (d instanceof ModifiableData) {
((ModifiableData) d).add(other.d);
} else {
throw new UnsupportedOperationException("immutable privilege bits");
}
}
/**
* Subtracts the other PrivilegeBits from the this.<br>
* If the specified bits do not intersect with this, it isn't modified.<br>
* If <code>this</code> is included in <code>other</code> {@link #EMPTY empty}
* privilege bits is returned.
*
* @param other The other privilege bits to be substracted from this instance.
* @throws UnsupportedOperationException if this instance is immutable.
*/
public void diff(PrivilegeBits other) {
if (d instanceof ModifiableData) {
((ModifiableData) d).diff(other.d);
} else {
throw new UnsupportedOperationException("immutable privilege bits");
}
}
/**
* Subtracts the <code>b</code> from <code>a</code> and adds the result (diff)
* to this instance.
*
* @param a An instance of privilege bits.
* @param b An instance of privilege bits.
* @throws UnsupportedOperationException if this instance is immutable.
*/
public void addDifference(PrivilegeBits a, PrivilegeBits b) {
if (d instanceof ModifiableData) {
((ModifiableData) d).addDifference(a.d, b.d);
} else {
throw new UnsupportedOperationException("immutable privilege bits");
}
}
//-------------------------------------------------------------< Object >---
@Override
public int hashCode() {
return d.hashCode();
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (o instanceof PrivilegeBits) {
return d.equals(((PrivilegeBits) o).d);
} else {
return false;
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("PrivilegeBits: ");
if (d.isSimple()) {
sb.append(d.longValue());
} else {
sb.append(Arrays.toString(d.longValues()));
}
return sb.toString();
}
//------------------------------------------------------< inner classes >---
/**
* Base class for the internal privilege bits representation and handling.
*/
private static abstract class Data {
abstract boolean isEmpty();
abstract long longValue();
abstract long[] longValues();
abstract boolean isSimple();
abstract Data next();
abstract boolean includes(Data other);
abstract boolean includesRead();
boolean equalData(Data d) {
if (isSimple() != d.isSimple()) {
return false;
}
if (isSimple()) {
return longValue() == d.longValue();
} else {
return Arrays.equals(longValues(), d.longValues());
}
}
static boolean includes(long bits, long otherBits) {
return (bits | ~otherBits) == -1;
}
static boolean includes(long[] bits, long[] otherBits) {
if (otherBits.length <= bits.length) {
// test for each long if is included
for (int i = 0; i < otherBits.length; i++) {
if ((bits[i] | ~otherBits[i]) != -1) {
return false;
}
}
return true;
} else {
// otherbits array is longer > cannot be included in bits
return false;
}
}
}
/**
* Immutable Data object
*/
private static class UnmodifiableData extends Data {
private static final long MAX = Long.MAX_VALUE / 2;
private static final UnmodifiableData EMPTY = new UnmodifiableData(PrivilegeRegistry.NO_PRIVILEGE);
private final long bits;
private final long[] bitsArr;
private final boolean isSimple;
private final boolean includesRead;
private UnmodifiableData(long bits) {
this.bits = bits;
bitsArr = new long[] {bits};
isSimple = true;
includesRead = (bits & READ) == READ;
}
private UnmodifiableData(long[] bitsArr) {
bits = PrivilegeRegistry.NO_PRIVILEGE;
this.bitsArr = bitsArr;
isSimple = false;
includesRead = (bitsArr[0] & READ) == READ;
}
@Override
boolean isEmpty() {
return this == EMPTY;
}
@Override
long longValue() {
return bits;
}
@Override
long[] longValues() {
return bitsArr;
}
@Override
boolean isSimple() {
return isSimple;
}
@Override
Data next() {
if (this == EMPTY) {
return EMPTY;
} else if (isSimple) {
if (bits < MAX) {
long b = bits << 1;
return new UnmodifiableData(b);
} else {
return new UnmodifiableData(new long[] {bits}).next();
}
} else {
long[] bts;
long last = bitsArr[bitsArr.length-1];
if (last < MAX) {
bts = new long[bitsArr.length];
System.arraycopy(bitsArr, 0, bts, 0, bitsArr.length);
bts[bts.length-1] = last << 1;
} else {
bts = new long[bitsArr.length + 1];
bts[bts.length-1] = 1;
}
return new UnmodifiableData(bts);
}
}
@Override
boolean includes(Data other) {
if (isSimple) {
return (other.isSimple()) ? includes(bits, other.longValue()) : false;
} else {
return includes(bitsArr, other.longValues());
}
}
@Override
boolean includesRead() {
return includesRead;
}
//---------------------------------------------------------< Object >---
@Override
public int hashCode() {
return (isSimple) ? new Long(bits).hashCode() : Arrays.hashCode(bitsArr);
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (o instanceof UnmodifiableData) {
UnmodifiableData d = (UnmodifiableData) o;
if (isSimple != d.isSimple) {
return false;
}
if (isSimple) {
return bits == d.bits;
} else {
return Arrays.equals(bitsArr, d.bitsArr);
}
} else if (o instanceof ModifiableData) {
return equalData((Data) o);
} else {
return false;
}
}
}
/**
* Mutable implementation of the Data base class.
*/
private static class ModifiableData extends Data {
private long[] bits;
private ModifiableData() {
bits = new long[] {PrivilegeRegistry.NO_PRIVILEGE};
}
private ModifiableData(Data base) {
long[] b = base.longValues();
switch (b.length) {
case 0:
// empty
bits = new long[] {PrivilegeRegistry.NO_PRIVILEGE};
break;
case 1:
// single long
bits = new long[] {b[0]};
break;
default:
// copy
bits = new long[b.length];
System.arraycopy(b, 0, bits, 0, b.length);
}
}
@Override
boolean isEmpty() {
return bits.length == 1 && bits[0] == PrivilegeRegistry.NO_PRIVILEGE;
}
@Override
long longValue() {
return (bits.length == 1) ? bits[0] : PrivilegeRegistry.NO_PRIVILEGE;
}
@Override
long[] longValues() {
return bits;
}
@Override
boolean isSimple() {
return bits.length == 1;
}
@Override
Data next() {
throw new UnsupportedOperationException("Not implemented.");
}
@Override
boolean includes(Data other) {
if (bits.length == 1) {
return other.isSimple() && includes(bits[0], other.longValue());
} else {
return includes(bits, other.longValues());
}
}
@Override
boolean includesRead() {
return (bits[0] & READ) == READ;
}
/**
* Add the other Data to this instance.
*
* @param other
*/
private void add(Data other) {
if (other != this) {
if (bits.length == 1 && other.isSimple()) {
bits[0] |= other.longValue();
} else {
or(other.longValues());
}
}
}
/**
* Subtract the other Data from this instance.
*
* @param other
*/
private void diff(Data other) {
if (bits.length == 1 && other.isSimple()) {
bits[0] = bits[0] & ~other.longValue();
} else {
bits = diff(bits, other.longValues());
}
}
/**
* Add the diff between the specified Data a and b.
*
* @param a
* @param b
*/
private void addDifference(Data a, Data b) {
if (a.isSimple() && b.isSimple()) {
bits[0] |= a.longValue() & ~b.longValue();
} else {
long[] diff = diff(a.longValues(), b.longValues());
or(diff);
}
}
private void or(long[] b) {
if (b.length > bits.length) {
// enlarge the array
long[] res = new long[b.length];
System.arraycopy(bits, 0, res, 0, bits.length);
bits = res;
}
for (int i = 0; i < b.length; i++) {
bits[i] |= b[i];
}
}
private static long[] diff(long[] a, long[] b) {
int index = -1;
long[] res = new long[((a.length > b.length) ? a.length : b.length)];
for (int i = 0; i < res.length; i++) {
if (i < a.length && i < b.length) {
res[i] = a[i] & ~b[i];
} else {
res[i] = (i < a.length) ? a[i] : 0;
}
// remember start of trailing 0 array entries
if (res[i] != 0) {
index = -1;
} else if (index == -1) {
index = i;
}
}
switch (index) {
case -1:
// no need to remove trailing 0-long from the array
return res;
case 0 :
// array consisting of one or multiple 0
return new long[] {PrivilegeRegistry.NO_PRIVILEGE};
default:
// remove trailing 0-long entries from the array
long[] r2 = new long[index];
System.arraycopy(res, 0, r2, 0, index);
return r2;
}
}
//---------------------------------------------------------< Object >---
@Override
public int hashCode() {
// NOTE: mutable object. hashCode not implemented.
return 0;
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (o instanceof ModifiableData) {
ModifiableData d = (ModifiableData) o;
return Arrays.equals(bits, d.bits);
} else if (o instanceof UnmodifiableData) {
return equalData((Data) o);
} else {
return false;
}
}
}
}