Browse Source

Start to merge the POIFS classes HeaderBlockReader and HeaderBlockWriter into a common HeaderBlock class

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1050764 13f79535-47bb-0310-9956-ffa450edef68
tags/REL_3_8_BETA1
Nick Burch 13 years ago
parent
commit
6703ad1c93

+ 21
- 27
src/java/org/apache/poi/poifs/dev/POIFSHeaderDumper.java View File

@@ -18,21 +18,16 @@
package org.apache.poi.poifs.dev;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Iterator;

import org.apache.poi.poifs.common.POIFSBigBlockSize;
import org.apache.poi.poifs.common.POIFSConstants;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.DocumentNode;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.poifs.property.PropertyTable;
import org.apache.poi.poifs.storage.BlockAllocationTableReader;
import org.apache.poi.poifs.storage.BlockList;
import org.apache.poi.poifs.storage.HeaderBlockReader;
import org.apache.poi.poifs.storage.HeaderBlock;
import org.apache.poi.poifs.storage.ListManagedBlock;
import org.apache.poi.poifs.storage.RawDataBlockList;
import org.apache.poi.poifs.storage.SmallBlockTableReader;
@@ -67,51 +62,50 @@ public class POIFSHeaderDumper {
InputStream inp = new FileInputStream(filename);
// Header
HeaderBlockReader header_block_reader =
new HeaderBlockReader(inp);
displayHeader(header_block_reader);
HeaderBlock header_block = new HeaderBlock(inp);
displayHeader(header_block);
// Raw blocks
POIFSBigBlockSize bigBlockSize = header_block_reader.getBigBlockSize();
POIFSBigBlockSize bigBlockSize = header_block.getBigBlockSize();
RawDataBlockList data_blocks = new RawDataBlockList(inp, bigBlockSize);
displayRawBlocksSummary(data_blocks);
// Main FAT Table
BlockAllocationTableReader batReader =
new BlockAllocationTableReader(
header_block_reader.getBigBlockSize(),
header_block_reader.getBATCount(),
header_block_reader.getBATArray(),
header_block_reader.getXBATCount(),
header_block_reader.getXBATIndex(),
header_block.getBigBlockSize(),
header_block.getBATCount(),
header_block.getBATArray(),
header_block.getXBATCount(),
header_block.getXBATIndex(),
data_blocks);
displayBATReader(batReader);

// Properties Table
PropertyTable properties =
new PropertyTable(
header_block_reader.getBigBlockSize(),
header_block_reader.getPropertyStart(),
header_block.getBigBlockSize(),
header_block.getPropertyStart(),
data_blocks);
// Mini Fat
BlockList sbat =
SmallBlockTableReader.getSmallDocumentBlocks(
bigBlockSize, data_blocks, properties.getRoot(),
header_block_reader.getSBATStart()
header_block.getSBATStart()
);
}

public static void displayHeader(HeaderBlockReader header_block_reader) throws Exception {
public static void displayHeader(HeaderBlock header_block) throws Exception {
System.out.println("Header Details:");
System.out.println(" Block size: " + header_block_reader.getBigBlockSize());
System.out.println(" BAT (FAT) header blocks: " + header_block_reader.getBATArray().length);
System.out.println(" BAT (FAT) block count: " + header_block_reader.getBATCount());
System.out.println(" XBAT (FAT) block count: " + header_block_reader.getXBATCount());
System.out.println(" XBAT (FAT) block 1 at: " + header_block_reader.getXBATIndex());
System.out.println(" SBAT (MiniFAT) block count: " + header_block_reader.getSBATCount());
System.out.println(" SBAT (MiniFAT) block 1 at: " + header_block_reader.getSBATStart());
System.out.println(" Property table at: " + header_block_reader.getPropertyStart());
System.out.println(" Block size: " + header_block.getBigBlockSize());
System.out.println(" BAT (FAT) header blocks: " + header_block.getBATArray().length);
System.out.println(" BAT (FAT) block count: " + header_block.getBATCount());
System.out.println(" XBAT (FAT) block count: " + header_block.getXBATCount());
System.out.println(" XBAT (FAT) block 1 at: " + header_block.getXBATIndex());
System.out.println(" SBAT (MiniFAT) block count: " + header_block.getSBATCount());
System.out.println(" SBAT (MiniFAT) block 1 at: " + header_block.getSBATStart());
System.out.println(" Property table at: " + header_block.getPropertyStart());
System.out.println("");
}


+ 12
- 12
src/java/org/apache/poi/poifs/eventfilesystem/POIFSReader.java View File

@@ -31,7 +31,7 @@ import org.apache.poi.poifs.property.Property;
import org.apache.poi.poifs.property.PropertyTable;
import org.apache.poi.poifs.storage.BlockAllocationTableReader;
import org.apache.poi.poifs.storage.BlockList;
import org.apache.poi.poifs.storage.HeaderBlockReader;
import org.apache.poi.poifs.storage.HeaderBlock;
import org.apache.poi.poifs.storage.RawDataBlockList;
import org.apache.poi.poifs.storage.SmallBlockTableReader;

@@ -75,32 +75,32 @@ public class POIFSReader
registryClosed = true;

// read the header block from the stream
HeaderBlockReader header_block_reader = new HeaderBlockReader(stream);
HeaderBlock header_block = new HeaderBlock(stream);

// read the rest of the stream into blocks
RawDataBlockList data_blocks = new RawDataBlockList(stream, header_block_reader.getBigBlockSize());
RawDataBlockList data_blocks = new RawDataBlockList(stream, header_block.getBigBlockSize());

// set up the block allocation table (necessary for the
// data_blocks to be manageable
new BlockAllocationTableReader(header_block_reader.getBigBlockSize(),
header_block_reader.getBATCount(),
header_block_reader.getBATArray(),
header_block_reader.getXBATCount(),
header_block_reader.getXBATIndex(),
new BlockAllocationTableReader(header_block.getBigBlockSize(),
header_block.getBATCount(),
header_block.getBATArray(),
header_block.getXBATCount(),
header_block.getXBATIndex(),
data_blocks);

// get property table from the document
PropertyTable properties =
new PropertyTable(header_block_reader.getBigBlockSize(),
header_block_reader.getPropertyStart(),
new PropertyTable(header_block.getBigBlockSize(),
header_block.getPropertyStart(),
data_blocks);

// process documents
processProperties(SmallBlockTableReader
.getSmallDocumentBlocks(
header_block_reader.getBigBlockSize(),
header_block.getBigBlockSize(),
data_blocks, properties.getRoot(),
header_block_reader.getSBATStart()),
header_block.getSBATStart()),
data_blocks, properties.getRoot()
.getChildren(), new POIFSDocumentPath());
}

+ 12
- 12
src/java/org/apache/poi/poifs/filesystem/POIFSFileSystem.java View File

@@ -43,7 +43,7 @@ import org.apache.poi.poifs.storage.BlockAllocationTableWriter;
import org.apache.poi.poifs.storage.BlockList;
import org.apache.poi.poifs.storage.BlockWritable;
import org.apache.poi.poifs.storage.HeaderBlockConstants;
import org.apache.poi.poifs.storage.HeaderBlockReader;
import org.apache.poi.poifs.storage.HeaderBlock;
import org.apache.poi.poifs.storage.HeaderBlockWriter;
import org.apache.poi.poifs.storage.RawDataBlockList;
import org.apache.poi.poifs.storage.SmallBlockTableReader;
@@ -146,12 +146,12 @@ public class POIFSFileSystem
this();
boolean success = false;

HeaderBlockReader header_block_reader;
HeaderBlock header_block;
RawDataBlockList data_blocks;
try {
// read the header block from the stream
header_block_reader = new HeaderBlockReader(stream);
bigBlockSize = header_block_reader.getBigBlockSize();
header_block = new HeaderBlock(stream);
bigBlockSize = header_block.getBigBlockSize();

// read the rest of the stream into blocks
data_blocks = new RawDataBlockList(stream, bigBlockSize);
@@ -163,29 +163,29 @@ public class POIFSFileSystem

// set up the block allocation table (necessary for the
// data_blocks to be manageable
new BlockAllocationTableReader(header_block_reader.getBigBlockSize(),
header_block_reader.getBATCount(),
header_block_reader.getBATArray(),
header_block_reader.getXBATCount(),
header_block_reader.getXBATIndex(),
new BlockAllocationTableReader(header_block.getBigBlockSize(),
header_block.getBATCount(),
header_block.getBATArray(),
header_block.getXBATCount(),
header_block.getXBATIndex(),
data_blocks);

// get property table from the document
PropertyTable properties =
new PropertyTable(bigBlockSize,
header_block_reader.getPropertyStart(),
header_block.getPropertyStart(),
data_blocks);

// init documents
processProperties(
SmallBlockTableReader.getSmallDocumentBlocks(
bigBlockSize, data_blocks, properties.getRoot(),
header_block_reader.getSBATStart()
header_block.getSBATStart()
),
data_blocks,
properties.getRoot().getChildren(),
null,
header_block_reader.getPropertyStart()
header_block.getPropertyStart()
);

// For whatever reason CLSID of root is always 0.

src/java/org/apache/poi/poifs/storage/HeaderBlockReader.java → src/java/org/apache/poi/poifs/storage/HeaderBlock.java View File

@@ -30,22 +30,31 @@ import static org.apache.poi.poifs.storage.HeaderBlockConstants._xbat_start_offs

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;

import org.apache.poi.poifs.common.POIFSBigBlockSize;
import org.apache.poi.poifs.common.POIFSConstants;
import org.apache.poi.poifs.filesystem.OfficeXmlFileException;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.util.HexDump;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.IntegerField;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LongField;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import org.apache.poi.util.ShortField;

/**
* The block containing the archive header
*
* @author Marc Johnson (mjohnson at apache dot org)
*/
public final class HeaderBlockReader {
public final class HeaderBlock {
private static final POILogger _logger =
POILogFactory.getLogger(HeaderBlock.class);
/**
* What big block size the file uses. Most files
* use 512 bytes, but a few use 4096
@@ -56,39 +65,42 @@ public final class HeaderBlockReader {
* number of big block allocation table blocks (int).
* (Number of FAT Sectors in Microsoft parlance)
*/
private final int _bat_count;
private int _bat_count;

/**
* Start of the property set block (int index of the property set
* chain's first big block).
*/
private final int _property_start;
private int _property_start;

/**
* start of the small block allocation table (int index of small
* block allocation table's first big block)
*/
private final int _sbat_start;
private int _sbat_start;
/**
* Number of small block allocation table blocks (int)
* (Number of MiniFAT Sectors in Microsoft parlance)
*/
private final int _sbat_count;
private int _sbat_count;

/**
* Big block index for extension to the big block allocation table
*/
private final int _xbat_start;
private int _xbat_start;
/**
* Number of big block allocation table blocks (int)
* (Number of DIFAT Sectors in Microsoft parlance)
*/
private final int _xbat_count;
private int _xbat_count;
/**
* The data
* The data. Only ever 512 bytes, because 4096 byte
* files use zeros for the extra header space.
*/
private final byte[] _data;
private static final byte _default_value = ( byte ) 0xFF;

/**
* create a new HeaderBlockReader from an InputStream
@@ -97,7 +109,7 @@ public final class HeaderBlockReader {
*
* @exception IOException on errors or bad data
*/
public HeaderBlockReader(InputStream stream) throws IOException {
public HeaderBlock(InputStream stream) throws IOException {
// Grab the first 512 bytes
// (For 4096 sized blocks, the remaining 3584 bytes are zero)
// Then, process the contents
@@ -111,11 +123,11 @@ public final class HeaderBlockReader {
}
}
public HeaderBlockReader(ByteBuffer buffer) throws IOException {
public HeaderBlock(ByteBuffer buffer) throws IOException {
this(buffer.array());
}
private HeaderBlockReader(byte[] data) throws IOException {
private HeaderBlock(byte[] data) throws IOException {
this._data = data;
// verify signature
@@ -144,24 +156,67 @@ public final class HeaderBlockReader {


// Figure out our block size
switch (_data[30]) {
case 12:
bigBlockSize = POIFSConstants.LARGER_BIG_BLOCK_SIZE_DETAILS; break;
case 9:
bigBlockSize = POIFSConstants.SMALLER_BIG_BLOCK_SIZE_DETAILS; break;
default:
throw new IOException("Unsupported blocksize (2^"
+ _data[30] + "). Expected 2^9 or 2^12.");
if (_data[30] == 12) {
this.bigBlockSize = POIFSConstants.LARGER_BIG_BLOCK_SIZE_DETAILS;
} else if(_data[30] == 9) {
this.bigBlockSize = POIFSConstants.SMALLER_BIG_BLOCK_SIZE_DETAILS;
} else {
throw new IOException("Unsupported blocksize (2^"+ _data[30] + "). Expected 2^9 or 2^12.");
}

_bat_count = getInt(_bat_count_offset, _data);
_property_start = getInt(_property_start_offset, _data);
_sbat_start = getInt(_sbat_start_offset, _data);
_sbat_count = getInt(_sbat_block_count_offset, _data);
_xbat_start = getInt(_xbat_start_offset, _data);
_xbat_count = getInt(_xbat_count_offset, _data);
// Setup the fields to read and write the counts and starts
_bat_count = new IntegerField(_bat_count_offset, data).get();
_property_start = new IntegerField(_property_start_offset,_data).get();
_sbat_start = new IntegerField(_sbat_start_offset, _data).get();
_sbat_count = new IntegerField(_sbat_block_count_offset, _data).get();
_xbat_start = new IntegerField(_xbat_start_offset, _data).get();
_xbat_count = new IntegerField(_xbat_count_offset, _data).get();
// Sanity check values
if(_bat_count > _max_bats_in_header) {
_logger.log(POILogger.WARN, "Too many BAT blocks listed in header, found "
+ _bat_count + " but the maximum is " + _max_bats_in_header);
_bat_count = _max_bats_in_header;
}
}
/**
* Create a single instance initialized with default values
*/
public HeaderBlock(POIFSBigBlockSize bigBlockSize) throws IOException
{
this.bigBlockSize = bigBlockSize;

// Our data is always 512 big no matter what
_data = new byte[ POIFSConstants.SMALLER_BIG_BLOCK_SIZE ];
Arrays.fill(_data, _default_value);
// Set all the default values
new LongField(_signature_offset, _signature, _data);
new IntegerField(0x08, 0, _data);
new IntegerField(0x0c, 0, _data);
new IntegerField(0x10, 0, _data);
new IntegerField(0x14, 0, _data);
new ShortField(0x18, ( short ) 0x3b, _data);
new ShortField(0x1a, ( short ) 0x3, _data);
new ShortField(0x1c, ( short ) -2, _data);
new ShortField(0x1e, bigBlockSize.getHeaderValue(), _data);
new IntegerField(0x20, 0x6, _data);
new IntegerField(0x24, 0, _data);
new IntegerField(0x28, 0, _data);
new IntegerField(0x34, 0, _data);
new IntegerField(0x38, 0x1000, _data);
// Initialise the variables
_bat_count = 0;
_sbat_count = 0;
_xbat_count = 0;
_property_start = POIFSConstants.END_OF_CHAIN;
_sbat_start = POIFSConstants.END_OF_CHAIN;
_xbat_start = POIFSConstants.END_OF_CHAIN;
}
private static byte[] readFirst512(InputStream stream) throws IOException {
// Grab the first 512 bytes
// (For 4096 sized blocks, the remaining 3584 bytes are zero)
@@ -173,10 +228,6 @@ public final class HeaderBlockReader {
return data;
}

private static int getInt(int offset, byte[] data) {
return LittleEndian.getInt(data, offset);
}

private static String longToHex(long value) {
return new String(HexDump.longToHex(value));
}
@@ -225,14 +276,15 @@ public final class HeaderBlockReader {
/**
* Returns the offsets to the first (up to) 109
* BAT sectors.
* Any additional BAT sectors
* Any additional BAT sectors are held in the XBAT (DIFAT)
* sectors in a chain.
* @return BAT offset array
*/
public int[] getBATArray() {
int[] result = new int[ _max_bats_in_header ];
int offset = _bat_array_offset;
for (int j = 0; j < _max_bats_in_header; j++) {
// Read them in
int[] result = new int[ _bat_count ];
int offset = _bat_array_offset;
for (int j = 0; j < _bat_count; j++) {
result[ j ] = LittleEndian.getInt(_data, offset);
offset += LittleEndianConsts.INT_SIZE;
}
@@ -259,4 +311,33 @@ public final class HeaderBlockReader {
public POIFSBigBlockSize getBigBlockSize() {
return bigBlockSize;
}
/**
* Write the block's data to an OutputStream
*
* @param stream the OutputStream to which the stored data should
* be written
*
* @exception IOException on problems writing to the specified
* stream
*/
void writeData(final OutputStream stream)
throws IOException
{
// Update the counts and start positions
new IntegerField(_bat_count_offset, _bat_count, _data);
new IntegerField(_property_start_offset, _property_start, _data);
new IntegerField(_sbat_start_offset, _sbat_start, _data);
new IntegerField(_sbat_block_count_offset, _sbat_count, _data);
new IntegerField(_xbat_start_offset, _xbat_start, _data);
new IntegerField(_xbat_count_offset, _xbat_count, _data);
// Write the main data out
stream.write(_data, 0, 512);
// Now do the padding if needed
for(int i=POIFSConstants.SMALLER_BIG_BLOCK_SIZE; i<POIFSConstants.LARGER_BIG_BLOCK_SIZE; i++) {
stream.write(0);
}
}
}

+ 6
- 6
src/testcases/org/apache/poi/poifs/filesystem/TestPOIFSFileSystem.java View File

@@ -29,7 +29,7 @@ import junit.framework.TestCase;
import org.apache.poi.POIDataSamples;
import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.poifs.common.POIFSBigBlockSize;
import org.apache.poi.poifs.storage.HeaderBlockReader;
import org.apache.poi.poifs.storage.HeaderBlock;
import org.apache.poi.poifs.storage.RawDataBlockList;

/**
@@ -184,14 +184,14 @@ public final class TestPOIFSFileSystem extends TestCase {
InputStream inp = _samples.openResourceAsStream("BlockSize4096.zvi");
// First up, check that we can process the header properly
HeaderBlockReader header_block_reader = new HeaderBlockReader(inp);
POIFSBigBlockSize bigBlockSize = header_block_reader.getBigBlockSize();
HeaderBlock header_block = new HeaderBlock(inp);
POIFSBigBlockSize bigBlockSize = header_block.getBigBlockSize();
assertEquals(4096, bigBlockSize.getBigBlockSize());
// Check the fat info looks sane
assertEquals(109, header_block_reader.getBATArray().length);
assertTrue(header_block_reader.getBATCount() > 0);
assertEquals(0, header_block_reader.getXBATCount());
assertEquals(1, header_block.getBATArray().length);
assertEquals(1, header_block.getBATCount());
assertEquals(0, header_block.getXBATCount());
// Now check we can get the basic fat
RawDataBlockList data_blocks = new RawDataBlockList(inp, bigBlockSize);

+ 2
- 2
src/testcases/org/apache/poi/poifs/storage/AllPOIFSStorageTests.java View File

@@ -33,8 +33,8 @@ public final class AllPOIFSStorageTests {
result.addTestSuite(TestBlockAllocationTableWriter.class);
result.addTestSuite(TestBlockListImpl.class);
result.addTestSuite(TestDocumentBlock.class);
result.addTestSuite(TestHeaderBlockReader.class);
result.addTestSuite(TestHeaderBlockWriter.class);
result.addTestSuite(TestHeaderBlockReading.class);
result.addTestSuite(TestHeaderBlockWriting.class);
result.addTestSuite(TestPropertyBlock.class);
result.addTestSuite(TestRawDataBlock.class);
result.addTestSuite(TestRawDataBlockList.class);

+ 7
- 3
src/testcases/org/apache/poi/poifs/storage/TestBlockAllocationTableReader.java View File

@@ -405,10 +405,10 @@ public final class TestBlockAllocationTableReader extends TestCase {

// similar code to POIFSFileSystem.<init>:
InputStream stream = new ByteArrayInputStream(data);
HeaderBlockReader hb;
HeaderBlock hb;
RawDataBlockList dataBlocks;
try {
hb = new HeaderBlockReader(stream);
hb = new HeaderBlock(stream);
dataBlocks = new RawDataBlockList(stream, bigBlockSize);
} catch (IOException e) {
throw new RuntimeException(e);
@@ -419,7 +419,11 @@ public final class TestBlockAllocationTableReader extends TestCase {
hb.getXBATIndex(), dataBlocks);
} catch (IOException e) {
// expected during successful test
assertEquals("Block count 538976257 is too high. POI maximum is 65535.", e.getMessage());
assertEquals(
"Your file contains 0 sectors, but the initial DIFAT array at index 0 referenced block # 538976288. This isn't allowed and your file is corrupt",
e.getMessage()
);
// assertEquals("Block count 538976257 is too high. POI maximum is 65535.", e.getMessage());
} catch (OutOfMemoryError e) {
if (e.getStackTrace()[1].getMethodName().equals("testBadSectorAllocationTableSize")) {
throw new AssertionFailedError("Identified bug 48085");

src/testcases/org/apache/poi/poifs/storage/TestHeaderBlockReader.java → src/testcases/org/apache/poi/poifs/storage/TestHeaderBlockReading.java View File

@@ -27,7 +27,7 @@ import junit.framework.TestCase;
*
* @author Marc Johnson
*/
public final class TestHeaderBlockReader extends TestCase {
public final class TestHeaderBlockReading extends TestCase {

public void testConstructors() throws IOException {
String[] hexData = {
@@ -49,7 +49,7 @@ public final class TestHeaderBlockReader extends TestCase {
"FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF",
};
byte[] content = RawDataUtil.decode(hexData);
HeaderBlockReader block = new HeaderBlockReader(new ByteArrayInputStream(content));
HeaderBlock block = new HeaderBlock(new ByteArrayInputStream(content));

assertEquals(-2, block.getPropertyStart());

@@ -58,7 +58,7 @@ public final class TestHeaderBlockReader extends TestCase {

System.arraycopy(content, 0, shortblock, 0, 511);
try {
block = new HeaderBlockReader(new ByteArrayInputStream(shortblock));
block = new HeaderBlock(new ByteArrayInputStream(shortblock));
fail("Should have caught IOException reading a short block");
} catch (IOException ignored) {

@@ -69,7 +69,7 @@ public final class TestHeaderBlockReader extends TestCase {
for (int index = 0; index < 8; index++) {
content[index] = (byte) (content[index] - 1);
try {
block = new HeaderBlockReader(new ByteArrayInputStream(content));
block = new HeaderBlock(new ByteArrayInputStream(content));
fail("Should have caught IOException corrupting byte " + index);
} catch (IOException ignored) {


src/testcases/org/apache/poi/poifs/storage/TestHeaderBlockWriter.java → src/testcases/org/apache/poi/poifs/storage/TestHeaderBlockWriting.java View File

@@ -32,7 +32,7 @@ import org.apache.poi.util.LittleEndianConsts;
*
* @author Marc Johnson
*/
public final class TestHeaderBlockWriter extends TestCase {
public final class TestHeaderBlockWriting extends TestCase {

private static void confirmEqual(String[] expectedDataHexDumpLines, byte[] actual) {
byte[] expected = RawDataUtil.decode(expectedDataHexDumpLines);
@@ -78,8 +78,8 @@ public final class TestHeaderBlockWriter extends TestCase {
block.setPropertyStart(0x87654321);
output = new ByteArrayOutputStream(512);
block.writeBlocks(output);
assertEquals(0x87654321, new HeaderBlockReader(new ByteArrayInputStream(output
.toByteArray())).getPropertyStart());
assertEquals(0x87654321, new HeaderBlock(
new ByteArrayInputStream(output.toByteArray())).getPropertyStart());
}

/**

Loading…
Cancel
Save