![JAR search and dependency download from the Maven repository](/logo.png)
org.redline_rpm.header.AbstractHeader Maven / Gradle / Ivy
package org.redline_rpm.header;
import org.redline_rpm.Util;
import java.io.IOException;
import java.lang.reflect.Array;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public abstract class AbstractHeader {
public interface Tag {
int NULL_ENTRY = 0;
int CHAR_ENTRY = 1;
int INT8_ENTRY = 2;
int INT16_ENTRY = 3;
int INT32_ENTRY = 4;
int INT64_ENTRY = 5;
int STRING_ENTRY = 6;
int BIN_ENTRY = 7;
int STRING_ARRAY_ENTRY = 8;
int I18NSTRING_ENTRY = 9;
int ASN1_ENTRY = 10;
int OPENPGP_ENTRY = 11;
int getCode();
int getType();
String getName();
}
protected static final int HEADER_HEADER_SIZE = 16;
protected static final int ENTRY_SIZE = 16;
protected static final int MAGIC_WORD = 0x8EADE801;
protected final Map< Integer, Tag> tags = new HashMap< Integer, Tag>();
protected final Map< Integer, Entry< ?>> entries = new TreeMap< Integer, Entry< ?>>();
protected final Map< Entry< ?>, Integer> pending = new LinkedHashMap< Entry< ?>, Integer>();
protected int startPos;
protected int endPos;
protected abstract boolean pad();
/**
* Reads the entire header contents for this channel and returns the number of entries
* found.
* @param in the ReadableByteChannel to read
* @return the number read
* @throws IOException there was an IO error
*/
public int read( ReadableByteChannel in) throws IOException {
ByteBuffer header = Util.fill( in, HEADER_HEADER_SIZE);
int magic = header.getInt();
// TODO: Determine if this hack to fix mangled headers for some RPMs is really needed.
if ( magic == 0) {
header.compact();
Util.fill( in, header);
magic = header.getInt();
}
Util.check( MAGIC_WORD, magic);
header.getInt();
final ByteBuffer index = Util.fill( in, header.getInt() * ENTRY_SIZE);
final int total = header.getInt();
final int pad = pad() ? Util.round( total, 7) - total : 0;
final ByteBuffer data = Util.fill( in, total + pad);
int count = 0;
while ( index.remaining() >= ENTRY_SIZE) {
readEntry( index.getInt(), index.getInt(), index.getInt(), index.getInt(), data);
count++;
}
return count;
}
/**
* Writes this header section to the provided file at the current position and returns the
* required padding. The caller is responsible for adding the padding immediately after
* this data.
* @param out the WritableByteChannel to output to
* @return the number written
* @throws IOException there was an IO error
*/
public int write( WritableByteChannel out) throws IOException {
final ByteBuffer header = getHeader();
final ByteBuffer index = getIndex();
final ByteBuffer data = getData( index);
data.flip();
int pad = pad() ? Util.round( data.remaining(), 7) - data.remaining() : 0;
header.putInt( data.remaining());
Util.empty( out, ( ByteBuffer) header.flip());
Util.empty( out, ( ByteBuffer) index.flip());
Util.empty( out, data);
return pad;
}
public int count() {
return entries.size();
}
/**
* Memory maps the portion of the destination file that will contain the header structure
* header and advances the file channels position. The resulting buffer will be prefilled with
* the necesssary magic data and the correct index count, but will require an integer value to
* be written with the total data section size once data writing is complete.
* This method must be invoked before mapping the index or data sections.
* @return a buffer containing the header
* @throws IOException there was an IO error
*/
protected ByteBuffer getHeader() throws IOException {
ByteBuffer buffer = ByteBuffer.allocate( HEADER_HEADER_SIZE);
buffer.putInt( MAGIC_WORD);
buffer.putInt( 0);
buffer.putInt( count());
return buffer;
}
/**
* Memory maps the portion of the destination file that will contain the index structure
* header and advances the file channels position. The resulting buffer will be ready for
* writing of the entry indexes.
* This method must be invoked before mapping the data section, but after mapping the header.
* @return a buffer containing the header
* @throws IOException there was an IO error
*/
protected ByteBuffer getIndex() throws IOException {
return ByteBuffer.allocate( count() * ENTRY_SIZE);
}
/**
* Writes the data section of the file, starting at the current position which must be immediately
* after the header section. Each entry writes its corresponding index into the provided index buffer
* and then writes its data to the file channel.
* @param index ByteBuffer of the index
* @return the total number of bytes written to the data section of the file.
* @throws IOException there was an IO error
*/
protected ByteBuffer getData( final ByteBuffer index) throws IOException {
int offset = 0;
final List< ByteBuffer> buffers = new LinkedList< ByteBuffer>();
final Iterator< Integer> i = entries.keySet().iterator();
index.position( 16);
final Entry< ?> first = entries.get( i.next());
Entry< ?> entry = null;
try {
while ( i.hasNext()) {
entry = entries.get( i.next());
offset = writeData( buffers, index, entry, offset);
}
index.position( 0);
offset = writeData( buffers, index, first, offset);
index.position( index.limit());
} catch ( IllegalArgumentException e) {
throw new RuntimeException( "Error while writing '" + entry + "'.", e);
}
ByteBuffer data = ByteBuffer.allocate( offset);
for ( ByteBuffer buffer : buffers) data.put( buffer);
return data;
}
protected int writeData( final Collection< ByteBuffer> buffers, final ByteBuffer index, final Entry< ?> entry, int offset) {
final int shift = entry.getOffset( offset) - offset;
if ( shift > 0) buffers.add( ByteBuffer.allocate( shift));
offset += shift;
final int size = entry.size();
final ByteBuffer buffer = ByteBuffer.allocate( size);
entry.index( index, offset);
if ( entry.ready()) {
entry.write( buffer);
buffer.flip();
}
else pending.put( entry, offset);
buffers.add( buffer);
return offset + size;
}
public void writePending( final FileChannel channel) {
for ( Entry< ?> entry : pending.keySet()) {
try {
ByteBuffer data = ByteBuffer.allocate( entry.size());
entry.write( data);
channel.position( Lead.LEAD_SIZE + HEADER_HEADER_SIZE + count() * ENTRY_SIZE + pending.get( entry));
Util.empty( channel, ( ByteBuffer) data.flip());
}
catch ( Exception e) {
throw new RuntimeException( "Error writing pending entry '" + entry.getTag() + "'.", e);
}
}
}
public Map< Entry< ?>, Integer> getPending() {
return pending;
}
public void removeEntry( final Entry< ?> entry) {
entries.remove( entry.getTag());
}
public Entry< ?> getEntry( final Tag tag) {
return getEntry( tag.getCode());
}
public Entry< ?> getEntry( final int tag) {
return entries.get( tag);
}
@SuppressWarnings( "unchecked")
public Entry< String[]> createEntry( Tag tag, CharSequence value) {
Entry< String[]> entry = ( Entry< String[]>) createEntry( tag.getCode(), tag.getType(), 1);
entry.setValues( new String[] { value.toString()});
return entry;
}
@SuppressWarnings( "unchecked")
public Entry< int[]> createEntry( Tag tag, int value) {
Entry< int[]> entry = ( Entry< int[]>) createEntry( tag.getCode(), tag.getType(), 1);
entry.setValues( new int[] { value});
return entry;
}
/**
* This is the main entry point through which entries are created from the builder code for
* types other than String.
* @param tag the Tag identifying the type of header this is bound for
* @param values the values to be stored in the entry.
* @throws ClassCastException - if the type of values is not compatible with the type
* required by tag
* @return a header entry of the appropriate type, with the supplied values set in it.
*/
@SuppressWarnings( "unchecked")
public < T> Entry< T> createEntry( Tag tag, T values) {
Entry< T> entry = ( Entry< T>) createEntry( tag.getCode(), tag.getType(), values.getClass().isArray() ? Array.getLength( values) : 1);
entry.setValues( values);
return entry;
}
/**
* This is the main entry point through which entries are created or appended to
* from the builder or from places like the ChangelogHandler. This is useful for
* header types which may have multiple components of each tag, as changelogs do.
* @param tag the Tag identifying the type of header this is bound for
* @param values the values to be stored in or appended to the entry.
* @throws ClassCastException - if the type of values is not compatible with the
* type required by tag
* @return a header entry of the appropriate type, with the supplied values
* set in or appended to it.
*/
@SuppressWarnings( { "unchecked", "rawtypes" })
public < T> Entry< T> addOrAppendEntry( Tag tag, T values) {
Entry< T> entry = ( Entry< T>) addOrAppendEntry( tag.getCode(), tag.getType(), values.getClass().isArray() ? Array.getLength( values) : 1);
T existingValues = entry.getValues();
if (existingValues == null) {
entry.setValues( values);
} else {
int oldSize = java.lang.reflect.Array.getLength( existingValues);
int newSize = values.getClass().isArray() ? Array.getLength( values) : 1;
Class elementType = existingValues.getClass().getComponentType();
T newValues = ( T) Array.newInstance( elementType, oldSize + newSize);
System.arraycopy( existingValues, 0, newValues, 0, oldSize);
System.arraycopy( values, 0, newValues, oldSize, newSize);
entry.setValues( newValues);
}
return entry;
}
@SuppressWarnings( "unchecked")
public < T> Entry< T> createEntry( Tag tag, int type, T values) {
Entry< T> entry = ( Entry< T>) createEntry( tag.getCode(), type, values.getClass().isArray() ? Array.getLength( values) : 1);
entry.setValues( values);
return entry;
}
@SuppressWarnings( "unchecked")
public < T> Entry< T> createEntry( int tag, int type, T values) {
Entry< T> entry = ( Entry< T>) createEntry( tag, type, values.getClass().isArray() ? Array.getLength( values) : 1);
entry.setValues( values);
return entry;
}
/**
* Adds a pending entry to this header. This entry will have the correctly sized buffer allocated, but
* will not be written until the caller writes a value and then invokes {@link #writePending} on this
* object.
* @param tag the tag
* @param count the count
* @return the entry added
*/
public Entry< ?> addEntry( Tag tag, int count) {
return createEntry( tag.getCode(), tag.getType(), count);
}
public Entry< ?> readEntry( final int tag, final int type, final int offset, final int count, final ByteBuffer data) {
final Entry< ?> entry = createEntry( tag, type, count);
final ByteBuffer buffer = data.duplicate();
buffer.position( offset);
entry.read( buffer);
entry.setOffset( offset);
return entry;
}
public Entry< ?> createEntry( final int tag, final int type, final int count) {
final Entry< ?> entry = createEntry( type);
entry.setTag( tag);
entry.setCount( count);
entries.put( tag, entry);
return entry;
}
public Entry< ?> addOrAppendEntry( final int tag, final int type, final int count) {
Entry< ?> entry = entries.get(tag);
if (entry == null) {
entry = createEntry( type);
entry.setTag( tag);
entry.setCount(count);
} else {
entry.incCount(count);
}
entries.put( tag, entry);
return entry;
}
protected Entry< ?> createEntry( int type) {
switch ( type) {
case Tag.NULL_ENTRY:
return new NullEntry();
case Tag.CHAR_ENTRY:
return new CharEntry();
case Tag.INT8_ENTRY:
return new Int8Entry();
case Tag.INT16_ENTRY:
return new Int16Entry();
case Tag.INT32_ENTRY:
return new Int32Entry();
case Tag.INT64_ENTRY:
return new Int64Entry();
case Tag.STRING_ENTRY:
return new StringEntry();
case Tag.BIN_ENTRY:
return new BinEntry();
case Tag.STRING_ARRAY_ENTRY:
return new StringArrayEntry();
case Tag.I18NSTRING_ENTRY:
return new I18NStringEntry();
default:
throw new IllegalStateException( "Unknown entry type '" + type + "'.");
}
}
public int getEndPos() {
return endPos;
}
public void setEndPos(int endPos) {
this.endPos = endPos;
}
public int getStartPos() {
return startPos;
}
public void setStartPos(int startPos) {
this.startPos = startPos;
}
public interface Entry< T> {
void setTag( int tag);
void setSize( int size);
void setCount( int count);
void incCount( int count);
void setOffset( int offset);
void setValues( T values);
T getValues();
int getTag();
int getType();
int getOffset( int offset);
int size();
boolean ready();
void read( ByteBuffer buffer);
void write( ByteBuffer buffer);
void index( ByteBuffer buffer, int position);
}
public abstract class AbstractEntry< T> implements Entry< T> {
protected int size;
protected int tag;
protected int count;
protected int offset;
protected T values;
public void setTag( Tag tag) { this.tag = tag.getCode(); }
public void setTag( int tag) { this.tag = tag; }
public void setSize( int size) { this.size = size; }
public void setCount( int count) { this.count = count; }
public void incCount( int count) { this.count += count; }
public void setOffset( int offset) { this.offset = offset; }
/**
* Fails fast if Tag and T are not compatible.
* @param values
* @throws ClassCastException - if the type of values is not compatible with the type
* required by tag.type()
*/
protected abstract void typeCheck(T values);
/**
* @param values
* @throws ClassCastException - if the type of values is not compatible with the type
* required by tag.type()
*/
public void setValues( T values) {
if (values.getClass().isArray()) {
typeCheck(values);
}
this.values = values;
}
public T getValues() { return values; }
public int getTag() { return tag; }
public int getOffset( int offset) { return offset; }
/**
* Returns true if this entry is ready to write, indicated by the presence of
* a set of values.
* @return true if ready
*/
public boolean ready() { return values != null; }
/**
* Returns the data type of this entry.
*/
public abstract int getType();
/**
* Returns the size this entry will need in the provided data buffer to write
* it's contents, corrected for any trailing zeros to fill to a boundary.
*/
public abstract int size();
/**
* Reads this entries value from the provided buffer using the set count.
*/
public abstract void read( final ByteBuffer buffer);
/**
* Writes this entries index to the index buffer and its values to the output
* channel provided.
*/
public abstract void write( final ByteBuffer data);
/**
* Writes the index entry into the provided buffer at the current position.
*/
public void index( final ByteBuffer index, final int position) {
index.putInt( tag).putInt( getType()).putInt( position).putInt( count);
}
public String toString() {
StringBuilder builder = new StringBuilder();
if ( tags.containsKey( tag)) builder.append( tags.get( tag).getName());
else builder.append( super.toString());
builder.append( "[tag=").append( tag);
builder.append( ",type=").append( getType());
builder.append( ",count=").append( count);
builder.append( ",size=").append( size());
builder.append( ",offset=").append( offset);
builder.append( "]");
return builder.toString();
}
}
class NullEntry extends AbstractEntry< Object> {
public int getType() { return 0; }
public int size() { return 0; }
public void read( final ByteBuffer buffer) {}
public void write( final ByteBuffer data) {}
@Override
protected void typeCheck(Object values) {
return;
}
}
class CharEntry extends AbstractEntry< byte[]> {
public int getType() { return Tag.CHAR_ENTRY; }
public int size() { return count ; }
public void read( final ByteBuffer buffer) {
byte[] values = new byte[ count];
for ( int x = 0; x < count; x++) values[ x] = buffer.get();
setValues( values);
}
public void write( final ByteBuffer data) {
for ( byte c : values) data.put( c);
}
public String toString() {
StringBuilder builder = new StringBuilder( super.toString());
for ( byte c : values) builder.append( c);
builder.append( "\n\t");
return builder.toString();
}
@Override
protected void typeCheck(byte[] values) {
for ( @SuppressWarnings("unused") byte c : values) {/*intentionally do nothing*/}
}
}
class Int8Entry extends AbstractEntry< byte[]> {
public int getType() { return Tag.INT8_ENTRY; }
public int size() { return count; }
public void read( final ByteBuffer buffer) {
byte[] values = new byte[ count];
for ( int x = 0; x < count; x++) values[ x] = buffer.get();
setValues( values);
}
public void write( final ByteBuffer data) {
for ( byte b : values) data.put( b);
}
@Override
protected void typeCheck(byte[] values) {
for ( @SuppressWarnings("unused") byte c : values) {/*intentionally do nothing*/}
}
public String toString() {
StringBuilder builder = new StringBuilder( super.toString());
builder.append( "\n\t");
for ( byte b : values) builder.append( b).append( ", ");
return builder.toString();
}
}
class Int16Entry extends AbstractEntry< short[]> {
public int getOffset( int offset) { return Util.round( offset, 1); }
public int getType() { return Tag.INT16_ENTRY; }
public int size() { return count * ( Short.SIZE / 8); }
public void read( final ByteBuffer buffer) {
short[] values = new short[ count];
for ( int x = 0; x < count; x++) values[ x] = buffer.getShort();
setValues( values);
}
public void write( final ByteBuffer data) {
for ( short s : values) data.putShort( s);
}
public String toString() {
StringBuilder builder = new StringBuilder( super.toString());
builder.append( "\n\t");
for ( short s : values) builder.append( s & 0xFFFF).append( ", ");
return builder.toString();
}
@Override
protected void typeCheck(short[] values) {
for ( @SuppressWarnings("unused") short c : values) {/*intentionally do nothing*/}
}
}
class Int32Entry extends AbstractEntry< int[]> {
public int getOffset( int offset) { return Util.round( offset, 3); }
public int getType() { return Tag.INT32_ENTRY; }
public int size() { return count * ( Integer.SIZE / 8); }
public void read( final ByteBuffer buffer) {
int[] values = new int[ count];
for ( int x = 0; x < count; x++) values[ x] = buffer.getInt();
setValues( values);
}
public void write( final ByteBuffer data) {
for ( int i : values) data.putInt( i);
}
public String toString() {
StringBuilder builder = new StringBuilder( super.toString());
builder.append( "\n\t");
for ( int i : values) builder.append( i).append( ", ");
return builder.toString();
}
@Override
protected void typeCheck(int[] values) {
for ( @SuppressWarnings("unused") int c : values) {/*intentionally do nothing*/}
}
}
class Int64Entry extends AbstractEntry< long[]> {
public int getOffset( int offset) { return Util.round( offset, 7); }
public int getType() { return Tag.INT64_ENTRY; }
public int size() { return count * ( Long.SIZE / 8); }
public void read( final ByteBuffer buffer) {
long[] values = new long[ count];
for ( int x = 0; x < count; x++) values[ x] = buffer.getLong();
setValues( values);
}
public void write( final ByteBuffer data) {
for ( long l : values) data.putLong( l);
}
public String toString() {
StringBuilder builder = new StringBuilder( super.toString());
builder.append( "\n\t");
for ( long l : values) builder.append( l).append( ", ");
return builder.toString();
}
@Override
protected void typeCheck(long[] values) {
for ( @SuppressWarnings("unused") long c : values) {/*intentionally do nothing*/}
}
}
/**
* According to early documentation it should be illegal for this type of
* entry to store more than one string value, but other recent documents
* indicate that this may not longer be the case.
*/
class StringEntry extends AbstractEntry< String[]> {
public int getType() { return Tag.STRING_ENTRY; }
public int size() {
if ( size != 0) return size;
for ( String s : values) size += Charset.forName( "UTF-8").encode( s).remaining() + 1;
return size;
}
public void read( final ByteBuffer buffer) {
String[] values = new String[ count];
for ( int x = 0; x < count; x++) {
int length = 0;
while ( buffer.get( buffer.position() + length) != 0) length++;
final ByteBuffer slice = buffer.slice();
buffer.position( buffer.position() + length + 1);
slice.limit( length);
values[ x] = Charset.forName( "UTF-8").decode( slice).toString();
}
setValues( values);
}
public void write( final ByteBuffer data) {
for ( String s : values) data.put( Charset.forName( "UTF-8").encode( s)).put(( byte) 0);
}
public String toString() {
StringBuilder builder = new StringBuilder( super.toString());
if ( values != null) {
for ( String s : values) {
builder.append( "\n\t");
builder.append( s);
}
}
return builder.toString();
}
@Override
protected void typeCheck(String[] values) {
for ( @SuppressWarnings("unused") String c : values) {/*intentionally do nothing*/}
}
}
class BinEntry extends AbstractEntry< byte[]> {
public int getType() { return Tag.BIN_ENTRY; }
public int size() { return count; }
public void read( final ByteBuffer buffer) {
byte[] values = new byte[ count];
buffer.get( values);
setValues( values);
}
public void write( final ByteBuffer data) {
data.put( values);
}
public String toString() {
StringBuilder builder = new StringBuilder( super.toString());
if ( values != null) {
builder.append( "\n");
Util.dump( values, builder);
}
return builder.toString();
}
@Override
protected void typeCheck(byte[] values) {
for ( @SuppressWarnings("unused") byte c : values) {/*intentionally do nothing*/}
}
}
class StringArrayEntry extends StringEntry {
public int getType() { return Tag.STRING_ARRAY_ENTRY; }
}
class I18NStringEntry extends StringEntry {
public int getType() { return Tag.I18NSTRING_ENTRY; }
}
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append( "Start Header ( ").append( getClass()).append( ")").append( "\n");
int count = 0;
for ( int tag : entries.keySet()) {
builder.append( count++).append( ": ").append( entries.get( tag)).append( "\n");
}
return builder.toString();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy