All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.redline_rpm.header.AbstractHeader Maven / Gradle / Ivy

Go to download

Redline is a pure Java library for manipulating RPM Package Manager packages.

There is a newer version: 1.2.10
Show newest version
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 - 2024 Weber Informatics LLC | Privacy Policy