// Copyright 2004-2014 Jim Voris // // 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.qumasoft.qvcslib; import java.io.IOException; import java.io.RandomAccessFile; import java.util.Date; import java.util.logging.Level; import java.util.logging.Logger; /** * Supplemental header info. This is meant to hold variable amounts of header information in a less structured format than the header elements that have specific header * data elements. This is kind of a kludge, a kind of poor man's property collection, but it seemed a good idea at the time. * @author Jim Voris */ public class SupplementalHeaderInfo implements java.io.Serializable { // Create our logger object private static final Logger LOGGER = Logger.getLogger("com.qumasoft.qvcslib"); private static final long serialVersionUID = 141L; private static final long MILLI_SECONDS_PER_SECOND = 1000L; // This is the only thing that gets serialized. private byte[] supplementalInfo; private transient String workfileCheckedOutToLocation = ""; private transient int lastModifierIndex = -1; private transient Date lastArchiveUpdateDate = null; private transient long lastWorkfileSize = -1; private transient int fileID = -1; private transient boolean valuesExtractedFromBufferFlag = false; private final transient byte[] markerString = {'Q', 'V', 'C', 'S', 'F', 'I', 'L', 'E', 'I', 'D', ':'}; /** * Creates a new instance of SupplementalHeaderInfo. */ public SupplementalHeaderInfo() { supplementalInfo = new byte[QVCSConstants.QVCS_SUPPLEMENTAL_SIZE]; lastArchiveUpdateDate = new Date(); } /** * Create a new instance with the given buffer size. * @param bufferSize define how big the buffer should be. */ public SupplementalHeaderInfo(int bufferSize) { supplementalInfo = new byte[bufferSize]; } /** * A sort of copy constructor where we create a new SupplementalHeaderInfo object using the buffer we pass in. * @param supplementInfo the existing supplementalInfo buffer. */ public SupplementalHeaderInfo(final byte[] supplementInfo) { supplementalInfo = new byte[supplementInfo.length]; System.arraycopy(supplementalInfo, 0, supplementInfo, 0, supplementInfo.length); extractValuesFromBuffer(); } /** * Get the workfile 'checked out to' location. * @return the workfile 'checked out to' location. */ public String getWorkfileCheckedOutToLocation() { extractValuesFromBuffer(); return workfileCheckedOutToLocation; } /** * Set the workfile 'checked out to' location. * @param checkedOutToLocation the workfile 'checked out to' location. */ public void setWorkfileCheckedOutToLocation(String checkedOutToLocation) { workfileCheckedOutToLocation = checkedOutToLocation; saveChangesToBuffer(); } /** * Get the last modifier index. * @return the last modifier index. */ public int getLastModifierIndex() { extractValuesFromBuffer(); return lastModifierIndex; } /** * Set the last modifier index. * @param modifierIndex the last modifier index. */ public void setLastModifierIndex(int modifierIndex) { lastModifierIndex = modifierIndex; saveChangesToBuffer(); } /** * Get the last archive update date. * @return the last archive update date. */ public Date getLastArchiveUpdateDate() { extractValuesFromBuffer(); return lastArchiveUpdateDate; } /** * Set the last archive update date. * @param date the last archive update date. */ public void setLastArchiveUpdateDate(Date date) { lastArchiveUpdateDate = date; saveChangesToBuffer(); } /** * Get the last workfile size. * @return the last workfile size. */ public long getLastWorkfileSize() { extractValuesFromBuffer(); return lastWorkfileSize; } /** * Set the last workfile size. * @param workfileSize the last workfile size. */ public void setLastWorkfileSize(long workfileSize) { lastWorkfileSize = workfileSize; saveChangesToBuffer(); } /** * Get the file id. * @return the file id. */ public int getFileID() { extractValuesFromBuffer(); return fileID; } /** * Set the file id. * @param fileId the file id. */ public void setFileID(int fileId) { fileID = fileId; saveChangesToBuffer(); } /** * Get the size of the supplemental info buffer. * @return the size of the supplemental info buffer. */ int getSize() { return supplementalInfo.length; } /** * Read the supplemental information from a file. * @param inStream the RandomAccessFile to read from. * @throws IOException if there are problems reading the stream. */ public void read(RandomAccessFile inStream) throws IOException { inStream.read(supplementalInfo); valuesExtractedFromBufferFlag = false; extractValuesFromBuffer(); } /** * Write the supplemental information to a file. * @param outStream the RandomAccessFile to write. * @throws IOException if there are problems writing the stream. */ public void write(RandomAccessFile outStream) throws IOException { outStream.write(supplementalInfo); } private void extractValuesFromBuffer() { if (!valuesExtractedFromBufferFlag) { // Make sure the buffer for supplemental info is the expected size. // (For really old QVCS/QVCS-Pro archives, it might be a non-standard // length); adjustBufferSize(); // Extract the workfile name from the supplemental info. int workfileLength = 0; int i; for (i = 0; i < supplementalInfo.length; i++, workfileLength++) { if (supplementalInfo[i] == '\0') { i++; break; } } workfileCheckedOutToLocation = new String(supplementalInfo, 0, workfileLength); if (i < supplementalInfo.length) { // Extract the last modifier index from the supplemental info. int lastModifierLengthStartIndex = i; int lastModifierLength; for (lastModifierLength = 0; i < supplementalInfo.length; i++, lastModifierLength++) { if (supplementalInfo[i] == '~') { i++; break; } } try { String lastModifierIndexString = new String(supplementalInfo, lastModifierLengthStartIndex, lastModifierLength); lastModifierIndex = Integer.parseInt(lastModifierIndexString); } catch (NumberFormatException e) { lastModifierIndex = -1; } // Extract the last update timestamp from the supplemental info. int lastUpdateTimestampStartIndex = i; int lastUpdateTimestampLength; for (lastUpdateTimestampLength = 0; i < supplementalInfo.length; i++, lastUpdateTimestampLength++) { if (supplementalInfo[i] == '~') { i++; break; } } try { String lastUpdateTimestampString = new String(supplementalInfo, lastUpdateTimestampStartIndex, lastUpdateTimestampLength); lastArchiveUpdateDate = new java.util.Date(Integer.parseInt(lastUpdateTimestampString) * MILLI_SECONDS_PER_SECOND); } catch (NumberFormatException e) { lastArchiveUpdateDate = new Date(); } // Extract the last workfile size int lastWorkfileSizeStartIndex = i; int lastWorkfileSizeLength; for (lastWorkfileSizeLength = 0; i < supplementalInfo.length; i++, lastWorkfileSizeLength++) { if (supplementalInfo[i] == '\0') { i++; break; } } try { String lastWorkfileSizeString = new String(supplementalInfo, lastWorkfileSizeStartIndex, lastWorkfileSizeLength); lastWorkfileSize = Integer.parseInt(lastWorkfileSizeString); } catch (NumberFormatException e) { lastWorkfileSize = 0; } // Make sure the FileID marker string is present. We put this // in a try/catch so we can scan for file ID's without assigning // a new fileID. If scanning is in progress, the FileIDManager will // throw an exception if asked to supply a new fileID. try { fileID = -1; int fileIDMarkerStringStartIndex = i; for (int j = 0; j < markerString.length; j++, i++) { if (supplementalInfo[i] != markerString[j]) { // Bad, or no marker string. We need to add the marker // and a fileID to the supplemental info. throw new QVCSException("No file ID found."); } } i = fileIDMarkerStringStartIndex + markerString.length; int fileIDValueStartIndex = i; int fileIDValueLength; for (fileIDValueLength = 0; i < supplementalInfo.length; i++, fileIDValueLength++) { if (supplementalInfo[i] == '\0') { i++; break; } } String fileIDString = new String(supplementalInfo, fileIDValueStartIndex, fileIDValueLength); fileID = Integer.parseInt(fileIDString); } catch (QVCSException e) { LOGGER.log(Level.INFO, "No fileID found."); } } valuesExtractedFromBufferFlag = true; } } private void saveChangesToBuffer() { // Start with an empty buffer. supplementalInfo = new byte[QVCSConstants.QVCS_SUPPLEMENTAL_SIZE]; // Save the workfile name to the supplemental info buffer int i; byte[] workfileBuffer = workfileCheckedOutToLocation.getBytes(); for (i = 0; i < workfileBuffer.length; i++) { supplementalInfo[i] = workfileBuffer[i]; } supplementalInfo[i++] = '\0'; // Save the last modifier index into the buffer. Integer lstModifierIndex = Integer.valueOf(lastModifierIndex); byte[] lastModifierIndexBuffer = lstModifierIndex.toString().getBytes(); for (int j = 0; j < lastModifierIndexBuffer.length; i++, j++) { supplementalInfo[i] = lastModifierIndexBuffer[j]; } supplementalInfo[i++] = '~'; // Prevent a null pointer exception. if (lastArchiveUpdateDate == null) { lastArchiveUpdateDate = new Date(); } // Save the last update timestamp into the buffer. long lastUpdateTime = lastArchiveUpdateDate.getTime() / MILLI_SECONDS_PER_SECOND; Long lastUpdateTimeValue = Long.valueOf(lastUpdateTime); byte[] lastUpdateTimeBuffer = lastUpdateTimeValue.toString().getBytes(); for (int j = 0; j < lastUpdateTimeBuffer.length; i++, j++) { supplementalInfo[i] = lastUpdateTimeBuffer[j]; } supplementalInfo[i++] = '~'; // Save the last workfile size into the buffer Long lstWorkfileSize = Long.valueOf(lastWorkfileSize); byte[] lastWorkfileSizeBuffer = lstWorkfileSize.toString().getBytes(); for (int j = 0; j < lastWorkfileSizeBuffer.length; i++, j++) { supplementalInfo[i] = lastWorkfileSizeBuffer[j]; } supplementalInfo[i++] = '\0'; if (fileID != -1) { // Write the FileID marker string for (int j = 0; j < markerString.length; j++, i++) { supplementalInfo[i] = markerString[j]; } Integer fileId = Integer.valueOf(fileID); byte[] fileIDBuffer = fileId.toString().getBytes(); for (int j = 0; j < fileIDBuffer.length; i++, j++) { supplementalInfo[i] = fileIDBuffer[j]; } supplementalInfo[i++] = '\0'; } } private void adjustBufferSize() { if (supplementalInfo.length != QVCSConstants.QVCS_SUPPLEMENTAL_SIZE) { byte[] newSupplementalInfo = new byte[QVCSConstants.QVCS_SUPPLEMENTAL_SIZE]; System.arraycopy(supplementalInfo, 0, newSupplementalInfo, 0, supplementalInfo.length); supplementalInfo = newSupplementalInfo; } } }