| /* ==================================================================== |
| 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.poi.hpsf; |
| |
| import java.nio.ByteBuffer; |
| import java.util.Arrays; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.UUID; |
| import java.util.function.Supplier; |
| |
| import org.apache.poi.common.Duplicatable; |
| import org.apache.poi.common.usermodel.GenericRecord; |
| import org.apache.poi.util.GenericRecordUtil; |
| import org.apache.poi.util.LittleEndianInput; |
| import org.apache.poi.util.LittleEndianOutput; |
| |
| /** |
| * Represents a class ID (16 bytes). Unlike other little-endian |
| * type the {@link ClassID} is not just 16 bytes stored in the wrong |
| * order. Instead, it is a double word (4 bytes) followed by two |
| * words (2 bytes each) followed by 8 bytes.<p> |
| * |
| * The ClassID (or CLSID) is a UUID - see RFC 4122 |
| */ |
| public class ClassID implements Duplicatable, GenericRecord { |
| |
| /** The number of bytes occupied by this object in the byte stream. */ |
| public static final int LENGTH = 16; |
| |
| /** |
| * The bytes making out the class ID in correct order, i.e. big-endian. |
| */ |
| private final byte[] bytes = new byte[LENGTH]; |
| |
| /** |
| * Creates a {@link ClassID} and reads its value from a byte array. |
| * |
| * @param src The byte array to read from. |
| * @param offset The offset of the first byte to read. |
| */ |
| public ClassID(final byte[] src, final int offset) { |
| read(src, offset); |
| } |
| |
| |
| /** |
| * Creates a {@link ClassID} and initializes its value with 0x00 bytes. |
| */ |
| public ClassID() { |
| Arrays.fill(bytes, (byte)0); |
| } |
| |
| /** |
| * Clones the given ClassID |
| */ |
| public ClassID(ClassID other) { |
| System.arraycopy(other.bytes, 0, bytes, 0, bytes.length); |
| } |
| |
| |
| /** |
| * Creates a {@link ClassID} from a human-readable representation of the Class ID in standard |
| * format {@code "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}"}. |
| * |
| * @param externalForm representation of the Class ID represented by this object. |
| */ |
| public ClassID(String externalForm) { |
| String clsStr = externalForm.replaceAll("[{}-]", ""); |
| for (int i=0; i<clsStr.length(); i+=2) { |
| bytes[i/2] = (byte)Integer.parseInt(clsStr.substring(i, i+2), 16); |
| } |
| } |
| |
| /** |
| * Reads the ClassID from the input |
| * @param lei the input (stream) |
| */ |
| public ClassID(LittleEndianInput lei) { |
| byte[] buf = bytes.clone(); |
| lei.readFully(buf); |
| read(buf, 0); |
| } |
| |
| /** |
| * @return The number of bytes occupied by this object in the byte stream. |
| */ |
| public int length() { |
| return LENGTH; |
| } |
| |
| |
| |
| /** |
| * Gets the bytes making out the class ID. They are returned in correct order, i.e. big-endian. |
| * |
| * @return the bytes making out the class ID. |
| */ |
| public byte[] getBytes() { |
| return bytes; |
| } |
| |
| |
| |
| /** |
| * Sets the bytes making out the class ID. |
| * |
| * @param bytes The bytes making out the class ID in big-endian format. They |
| * are copied without their order being changed. |
| */ |
| public void setBytes(final byte[] bytes) { |
| System.arraycopy(bytes, 0, this.bytes, 0, LENGTH); |
| } |
| |
| |
| |
| /** |
| * Reads the class ID's value from a byte array by turning little-endian into big-endian. |
| * |
| * @param src The byte array to read from |
| * @param offset The offset within the {@code src} byte array |
| * @return A byte array containing the class ID. |
| */ |
| @SuppressWarnings("PointlessArithmeticExpression") |
| public byte[] read(final byte[] src, final int offset) { |
| /* Read double word. */ |
| bytes[0] = src[3 + offset]; |
| bytes[1] = src[2 + offset]; |
| bytes[2] = src[1 + offset]; |
| bytes[3] = src[0 + offset]; |
| |
| /* Read first word. */ |
| bytes[4] = src[5 + offset]; |
| bytes[5] = src[4 + offset]; |
| |
| /* Read second word. */ |
| bytes[6] = src[7 + offset]; |
| bytes[7] = src[6 + offset]; |
| |
| /* Read 8 bytes. */ |
| System.arraycopy(src, 8 + offset, bytes, 8, 8); |
| |
| return bytes; |
| } |
| |
| /** |
| * Writes the class ID to a byte array in the little-endian format. |
| * |
| * @param dst The byte array to write to. |
| * |
| * @param offset The offset within the {@code dst} byte array. |
| * |
| * @exception ArrayStoreException if there is not enough room for the class |
| * ID 16 bytes in the byte array after the {@code offset} position. |
| */ |
| @SuppressWarnings("PointlessArithmeticExpression") |
| public void write(final byte[] dst, final int offset) |
| throws ArrayStoreException { |
| /* Check array size: */ |
| if (dst.length < LENGTH) { |
| throw new ArrayStoreException |
| ("Destination byte[] must have room for at least 16 bytes, " + |
| "but has a length of only " + dst.length + "."); |
| } |
| |
| /* Write double word. */ |
| dst[0 + offset] = bytes[3]; |
| dst[1 + offset] = bytes[2]; |
| dst[2 + offset] = bytes[1]; |
| dst[3 + offset] = bytes[0]; |
| |
| /* Write first word. */ |
| dst[4 + offset] = bytes[5]; |
| dst[5 + offset] = bytes[4]; |
| |
| /* Write second word. */ |
| dst[6 + offset] = bytes[7]; |
| dst[7 + offset] = bytes[6]; |
| |
| /* Write 8 bytes. */ |
| System.arraycopy(bytes, 8, dst, 8 + offset, 8); |
| } |
| |
| /** |
| * Write the class ID to a LittleEndianOutput (stream) |
| * |
| * @param leo the output |
| */ |
| public void write(LittleEndianOutput leo) { |
| byte[] buf = bytes.clone(); |
| write(buf, 0); |
| leo.write(buf); |
| } |
| |
| /** |
| * Checks whether this {@code ClassID} is equal to another object. |
| * |
| * @param o the object to compare this {@code ClassID} with |
| * @return {@code true} if the objects are equal, else {@code false}. |
| */ |
| @Override |
| public boolean equals(final Object o) { |
| return (o instanceof ClassID) && Arrays.equals(bytes, ((ClassID)o).bytes); |
| } |
| |
| /** |
| * Checks whether this {@code ClassID} is equal to another ClassID with inverted endianess, |
| * because there are apparently not only version 1 GUIDs (aka "network" with big-endian encoding), |
| * but also version 2 GUIDs (aka "native" with little-endian encoding) out there. |
| * |
| * @param o the object to compare this {@code ClassID} with |
| * @return {@code true} if the objects are equal, else {@code false}. |
| */ |
| public boolean equalsInverted(ClassID o) { |
| return |
| o.bytes[0] == bytes[3] && |
| o.bytes[1] == bytes[2] && |
| o.bytes[2] == bytes[1] && |
| o.bytes[3] == bytes[0] && |
| o.bytes[4] == bytes[5] && |
| o.bytes[5] == bytes[4] && |
| o.bytes[6] == bytes[7] && |
| o.bytes[7] == bytes[6] && |
| o.bytes[8] == bytes[8] && |
| o.bytes[9] == bytes[9] && |
| o.bytes[10] == bytes[10] && |
| o.bytes[11] == bytes[11] && |
| o.bytes[12] == bytes[12] && |
| o.bytes[13] == bytes[13] && |
| o.bytes[14] == bytes[14] && |
| o.bytes[15] == bytes[15] |
| ; |
| } |
| |
| |
| /** |
| * @see Object#hashCode() |
| */ |
| @Override |
| public int hashCode() { |
| return toString().hashCode(); |
| } |
| |
| /** |
| * Returns a human-readable representation of the Class ID in standard |
| * format {@code "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}"}. |
| * |
| * @return String representation of the Class ID represented by this object. |
| */ |
| @Override |
| public String toString() { |
| return "{" + toUUIDString() + "}"; |
| } |
| |
| /** |
| * Returns a human-readable representation of the Class ID in UUID |
| * format {@code "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}. |
| * |
| * @return UUID String representation of the Class ID represented by this object. |
| */ |
| public String toUUIDString() { |
| return toUUID().toString().toUpperCase(Locale.ROOT); |
| } |
| |
| /** |
| * Converts the ClassID to an UUID |
| * @return the ClassID as UUID |
| * |
| * @since POI 5.0.0 |
| */ |
| public UUID toUUID() { |
| final long mostSigBits = ByteBuffer.wrap(bytes, 0, 8).getLong(); |
| final long leastSigBits = ByteBuffer.wrap(bytes, 8, 8).getLong(); |
| return new UUID(mostSigBits, leastSigBits); |
| } |
| |
| |
| @Override |
| public ClassID copy() { |
| return new ClassID(this); |
| } |
| |
| @Override |
| public Map<String, Supplier<?>> getGenericProperties() { |
| return GenericRecordUtil.getGenericProperties("uuid", this::toString); |
| } |
| } |