/*
* Copyright (C) 2016 Haruki Hasegawa
*
* 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.h6ah4i.android.widget.advrecyclerview.adapter;
import android.support.annotation.IntRange;
import android.support.v7.widget.RecyclerView;
/**
* Utility class providing "Composed item ID" related definitions and methods.
* <p>
* Spec:
* <table summary="Bit usages of composed item ID">
* <tr><th>bit 63</th><td>Reserved</td></tr>
* <tr><th>bit 62-56</th><td>View type segment</td></tr>
* <tr><th>bit 55-28</th><td>Group ID</td></tr>
* <tr><th>bit 27-0</th><td>Child ID</td></tr>
* </table>
* </p>
*/
public class ItemIdComposer {
/**
* Bit offset of the reserved sign flag part.
*/
public static final int BIT_OFFSET_RESERVED_SIGN_FLAG = 63;
/**
* Bit offset of the segment part.
*/
public static final int BIT_OFFSET_SEGMENT = 56;
/**
* Bit offset of the group ID part.
*/
public static final int BIT_OFFSET_GROUP_ID = 28;
/**
* Bit offset of the child ID part.
*/
public static final int BIT_OFFSET_CHILD_ID = 0;
// ---
/**
* Bit width of the reserved sign flag part.
*/
public static final int BIT_WIDTH_RESERVED_SIGN_FLAG = 1;
/**
* Bit width of the segment part.
*/
public static final int BIT_WIDTH_SEGMENT = 7;
/**
* Bit width of the expandable group ID part.
*/
public static final int BIT_WIDTH_GROUP_ID = 28;
/**
* Bit width of the expandable child ID part.
*/
public static final int BIT_WIDTH_CHILD_ID = 28;
// ---
/**
* Bit mask of the reserved sign flag part.
*/
public static final long BIT_MASK_RESERVED_SIGN_FLAG = ((1L << BIT_WIDTH_RESERVED_SIGN_FLAG) - 1) << BIT_OFFSET_RESERVED_SIGN_FLAG;
/**
* Bit mask of the segment part.
*/
public static final long BIT_MASK_SEGMENT = ((1L << BIT_WIDTH_SEGMENT) - 1) << BIT_OFFSET_SEGMENT;
/**
* Bit mask of the expandable group ID part.
*/
public static final long BIT_MASK_GROUP_ID = ((1L << BIT_WIDTH_GROUP_ID) - 1) << BIT_OFFSET_GROUP_ID;
/**
* Bit mask of the expandable child ID part.
*/
public static final long BIT_MASK_CHILD_ID = ((1L << BIT_WIDTH_CHILD_ID) - 1) << BIT_OFFSET_CHILD_ID;
// ---
/**
* Minimum value of segment.
*/
public static final int MIN_SEGMENT = 0;
/**
* Maximum value of segment.
*/
public static final int MAX_SEGMENT = (1 << BIT_WIDTH_SEGMENT) - 1;
/**
* Minimum value of group ID.
*/
public static final long MIN_GROUP_ID = -(1L << (BIT_WIDTH_GROUP_ID - 1));
/**
* Maximum value of group ID.
*/
public static final long MAX_GROUP_ID = (1L << (BIT_WIDTH_GROUP_ID - 1)) - 1;
/**
* Minimum value of child ID.
*/
public static final long MIN_CHILD_ID = -(1L << (BIT_WIDTH_CHILD_ID - 1));
/**
* Maximum value of child ID.
*/
public static final long MAX_CHILD_ID = (1L << (BIT_WIDTH_CHILD_ID - 1)) - 1;
/**
* Minimum value of wrapped ID (= group + child) ID.
*/
public static final long MIN_WRAPPED_ID = -(1L << (BIT_WIDTH_GROUP_ID + BIT_WIDTH_CHILD_ID - 1));
/**
* Minimum value of wrapped ID (= group + child) ID.
*/
public static final long MAX_WRAPPED_ID = (1L << (BIT_WIDTH_GROUP_ID + BIT_WIDTH_CHILD_ID - 1)) - 1;
private ItemIdComposer() {
}
/**
* Makes a composed ID which represents a child item of an expandable group.
*
* @param groupId Group item ID
* @param childId Child item ID
* @return Composed expandable child ID
*/
public static long composeExpandableChildId(@IntRange(from = MIN_GROUP_ID, to = MAX_GROUP_ID) long groupId, @IntRange(from = MIN_CHILD_ID, to = MAX_CHILD_ID) long childId) {
if (groupId < MIN_GROUP_ID || groupId > MAX_GROUP_ID) {
throw new IllegalArgumentException("Group ID value is out of range. (groupId = " + groupId + ")");
}
if (childId < MIN_CHILD_ID || childId > MAX_CHILD_ID) {
throw new IllegalArgumentException("Child ID value is out of range. (childId = " + childId + ")");
}
//noinspection PointlessBitwiseExpression
return ((groupId << BIT_OFFSET_GROUP_ID) & BIT_MASK_GROUP_ID) | ((childId << BIT_OFFSET_CHILD_ID) & BIT_MASK_CHILD_ID);
}
/**
* Makes a composed ID which represents an expandable group item.
*
* @param groupId Group item ID
* @return Composed expandable group ID
*/
public static long composeExpandableGroupId(@IntRange(from = MIN_GROUP_ID, to = MAX_GROUP_ID) long groupId) {
if (groupId < MIN_GROUP_ID || groupId > MAX_GROUP_ID) {
throw new IllegalArgumentException("Group ID value is out of range. (groupId = " + groupId + ")");
}
//noinspection PointlessBitwiseExpression
return ((groupId << BIT_OFFSET_GROUP_ID) & BIT_MASK_GROUP_ID) | ((RecyclerView.NO_ID << BIT_OFFSET_CHILD_ID) & BIT_MASK_CHILD_ID);
}
/**
* Checks the composed ID is a expandable group or not.
*
* @param composedId Composed ID
* @return True if the specified composed ID is an expandable group ID. Otherwise, false.
*/
public static boolean isExpandableGroup(long composedId) {
return (composedId != RecyclerView.NO_ID) && ((composedId & BIT_MASK_CHILD_ID) == BIT_MASK_CHILD_ID);
}
/**
* Extracts "Segment" part from composed ID.
*
* @param composedId Composed ID
* @return Segment part
*/
@IntRange(from = MIN_SEGMENT, to = MAX_SEGMENT)
public static int extractSegmentPart(long composedId) {
return (int) ((composedId & BIT_MASK_SEGMENT) >>> BIT_OFFSET_SEGMENT);
}
/**
* Extracts "Group ID" part from composed ID.
*
* @param composedId Composed ID
* @return Group ID part. If the specified composed ID is not an expandable group, returns {@link RecyclerView#NO_ID}.
*/
@IntRange(from = MIN_GROUP_ID, to = MAX_GROUP_ID)
public static long extractExpandableGroupIdPart(long composedId) {
if ((composedId == RecyclerView.NO_ID) || !isExpandableGroup(composedId)) {
return RecyclerView.NO_ID;
}
return (composedId << (64 - BIT_WIDTH_GROUP_ID - BIT_OFFSET_GROUP_ID)) >> (64 - BIT_WIDTH_GROUP_ID);
}
/**
* Extracts "Child ID" part from composed ID.
*
* @param composedId Composed ID
* @return Child ID part. If the specified composed ID is not a child of an expandable group, returns {@link RecyclerView#NO_ID}.
*/
@IntRange(from = MIN_CHILD_ID, to = MAX_CHILD_ID)
public static long extractExpandableChildIdPart(long composedId) {
if ((composedId == RecyclerView.NO_ID) || isExpandableGroup(composedId)) {
return RecyclerView.NO_ID;
}
return (composedId << (64 - BIT_WIDTH_CHILD_ID - BIT_OFFSET_CHILD_ID)) >> (64 - BIT_WIDTH_CHILD_ID);
}
/**
* Extracts "Wrapped ID" (group ID + child ID) part from composed ID.
*
* @param composedId Composed ID
* @return Wrapped ID part.
*/
public static long extractWrappedIdPart(long composedId) {
if (composedId == RecyclerView.NO_ID) {
return RecyclerView.NO_ID;
}
return (composedId << (64 - BIT_WIDTH_GROUP_ID - BIT_WIDTH_CHILD_ID - BIT_OFFSET_CHILD_ID)) >> (64 - (BIT_WIDTH_GROUP_ID + BIT_WIDTH_CHILD_ID));
}
/**
* Makes a composed ID with specified segment and wrapped ID.
*
* @param segment Segment
* @param wrappedId Wrapped ID
* @return Composed ID.
*/
public static long composeSegment(@IntRange(from = MIN_SEGMENT, to = MAX_SEGMENT) int segment, long wrappedId) {
if (segment < MIN_SEGMENT || segment > MAX_SEGMENT) {
throw new IllegalArgumentException("Segment value is out of range. (segment = " + segment + ")");
}
return (((long) segment) << BIT_OFFSET_SEGMENT) | (wrappedId & (BIT_MASK_RESERVED_SIGN_FLAG | BIT_MASK_GROUP_ID | BIT_MASK_CHILD_ID));
}
}