| /* ==================================================================== |
| 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.hdgf.chunks; |
| |
| import java.util.ArrayList; |
| |
| import org.apache.poi.hdgf.chunks.ChunkFactory.CommandDefinition; |
| import org.apache.poi.util.LittleEndian; |
| import org.apache.poi.util.POILogFactory; |
| import org.apache.poi.util.POILogger; |
| |
| /** |
| * Base of all chunks, which hold data, flags etc |
| */ |
| public final class Chunk { |
| /** |
| * The contents of the chunk, excluding the header, |
| * trailer and separator |
| */ |
| private byte[] contents; |
| private ChunkHeader header; |
| /** May be null */ |
| private ChunkTrailer trailer; |
| /** May be null */ |
| private ChunkSeparator separator; |
| /** The possible different commands we can hold */ |
| protected CommandDefinition[] commandDefinitions; |
| /** The command+value pairs we hold */ |
| private Command[] commands; |
| /** The blocks (if any) we hold */ |
| //private Block[] blocks |
| /** The name of the chunk, as found from the commandDefinitions */ |
| private String name; |
| |
| /** For logging warnings about the structure of the file */ |
| private POILogger logger = POILogFactory.getLogger(Chunk.class); |
| |
| public Chunk(ChunkHeader header, ChunkTrailer trailer, ChunkSeparator separator, byte[] contents) { |
| this.header = header; |
| this.trailer = trailer; |
| this.separator = separator; |
| this.contents = contents.clone(); |
| } |
| |
| public byte[] _getContents() { |
| return contents; |
| } |
| public ChunkHeader getHeader() { |
| return header; |
| } |
| /** Gets the separator between this chunk and the next, if it exists */ |
| public ChunkSeparator getSeparator() { |
| return separator; |
| } |
| /** Gets the trailer for this chunk, if it exists */ |
| public ChunkTrailer getTrailer() { |
| return trailer; |
| } |
| /** |
| * Gets the command definitions, which define and describe much |
| * of the data held by the chunk. |
| */ |
| public CommandDefinition[] getCommandDefinitions() { |
| return commandDefinitions; |
| } |
| public Command[] getCommands() { |
| return commands; |
| } |
| /** |
| * Get the name of the chunk, as found from the CommandDefinitions |
| */ |
| public String getName() { |
| return name; |
| } |
| |
| /** |
| * Returns the size of the chunk, including any |
| * headers, trailers and separators. |
| */ |
| public int getOnDiskSize() { |
| int size = header.getSizeInBytes() + contents.length; |
| if(trailer != null) { |
| size += trailer.getTrailerData().length; |
| } |
| if(separator != null) { |
| size += separator.separatorData.length; |
| } |
| return size; |
| } |
| |
| /** |
| * Uses our CommandDefinitions to process the commands |
| * our chunk type has, and figure out the |
| * values for them. |
| */ |
| protected void processCommands() { |
| if(commandDefinitions == null) { |
| throw new IllegalStateException("You must supply the command definitions before calling processCommands!"); |
| } |
| |
| // Loop over the definitions, building the commands |
| // and getting their values |
| ArrayList<Command> commandList = new ArrayList<Command>(); |
| for(CommandDefinition cdef : commandDefinitions) { |
| int type = cdef.getType(); |
| int offset = cdef.getOffset(); |
| |
| // Handle virtual commands |
| if(type == 10) { |
| name = cdef.getName(); |
| continue; |
| } else if(type == 18) { |
| continue; |
| } |
| |
| |
| // Build the appropriate command for the type |
| Command command; |
| if(type == 11 || type == 21) { |
| command = new BlockOffsetCommand(cdef); |
| } else { |
| command = new Command(cdef); |
| } |
| |
| // Bizarely, many of the offsets are from the start of the |
| // header, not from the start of the chunk body |
| switch(type) { |
| case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: |
| case 11: case 21: |
| case 12: case 16: case 17: case 18: case 28: case 29: |
| // Offset is from start of chunk |
| break; |
| default: |
| // Offset is from start of header! |
| if(offset >= 19) { |
| offset -= 19; |
| } |
| } |
| |
| // Check we seem to have enough data |
| if(offset >= contents.length) { |
| logger.log(POILogger.WARN, |
| "Command offset " + offset + " past end of data at " + contents.length |
| ); |
| continue; |
| } |
| |
| try { |
| // Process |
| switch(type) { |
| // Types 0->7 = a flat at bit 0->7 |
| case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: |
| int val = contents[offset] & (1<<type); |
| command.value = Boolean.valueOf(val > 0); |
| break; |
| case 8: |
| command.value = Byte.valueOf(contents[offset]); |
| break; |
| case 9: |
| command.value = new Double( |
| LittleEndian.getDouble(contents, offset) |
| ); |
| break; |
| case 12: |
| // A Little Endian String |
| // Starts 8 bytes into the data segment |
| // Ends at end of data, or 00 00 |
| |
| // Ensure we have enough data |
| if(contents.length < 8) { |
| command.value = ""; |
| break; |
| } |
| |
| // Find the end point |
| int startsAt = 8; |
| int endsAt = startsAt; |
| for(int j=startsAt; j<contents.length-1 && endsAt == startsAt; j++) { |
| if(contents[j] == 0 && contents[j+1] == 0) { |
| endsAt = j; |
| } |
| } |
| if(endsAt == startsAt) { |
| endsAt = contents.length; |
| } |
| |
| int strLen = endsAt - startsAt; |
| command.value = new String(contents, startsAt, strLen, header.getChunkCharset().name()); |
| break; |
| case 25: |
| command.value = Short.valueOf( |
| LittleEndian.getShort(contents, offset) |
| ); |
| break; |
| case 26: |
| command.value = Integer.valueOf( |
| LittleEndian.getInt(contents, offset) |
| ); |
| break; |
| |
| // Types 11 and 21 hold the offset to the blocks |
| case 11: case 21: |
| if(offset < contents.length - 3) { |
| int bOffset = (int)LittleEndian.getUInt(contents, offset); |
| BlockOffsetCommand bcmd = (BlockOffsetCommand)command; |
| bcmd.setOffset(bOffset); |
| } |
| break; |
| |
| default: |
| logger.log(POILogger.INFO, |
| "Command of type " + type + " not processed!"); |
| } |
| } |
| catch (Exception e) { |
| logger.log(POILogger.ERROR, "Unexpected error processing command, ignoring and continuing. Command: " + |
| command, e); |
| } |
| |
| // Add to the array |
| commandList.add(command); |
| } |
| |
| // Save the commands we liked the look of |
| this.commands = commandList.toArray( |
| new Command[commandList.size()] ); |
| |
| // Now build up the blocks, if we had a command that tells |
| // us where a block is |
| } |
| |
| /** |
| * A command in the visio file. In order to make things fun, |
| * all the chunk actually stores is the value of the command. |
| * You have to have your own lookup table to figure out what |
| * the commands are based on the chunk type. |
| */ |
| public static class Command { |
| protected Object value; |
| private CommandDefinition definition; |
| |
| private Command(CommandDefinition definition, Object value) { |
| this.definition = definition; |
| this.value = value; |
| } |
| private Command(CommandDefinition definition) { |
| this(definition, null); |
| } |
| |
| public CommandDefinition getDefinition() { return definition; } |
| public Object getValue() { return value; } |
| } |
| /** |
| * A special kind of command that is an artificat of how we |
| * process CommandDefinitions, and so doesn't actually exist |
| * in the chunk |
| */ |
| // public static class VirtualCommand extends Command { |
| // private VirtualCommand(CommandDefinition definition) { |
| // super(definition); |
| // } |
| // } |
| /** |
| * A special kind of command that holds the offset to |
| * a block |
| */ |
| private static class BlockOffsetCommand extends Command { |
| private BlockOffsetCommand(CommandDefinition definition) { |
| super(definition, null); |
| } |
| private void setOffset(int offset) { |
| value = Integer.valueOf(offset); |
| } |
| } |
| } |