gt: checkU16(String field, int val) {
context: // Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
  
  package org.xbill.DNS;
  
  import java.io.*;
  import java.text.*;
  import java.lang.reflect.*;
  import java.util.*;
  import org.xbill.DNS.utils.*;
  
  /**
   * A generic DNS resource record.  The specific record types extend this class.
   * A record contains a name, type, class, and rdata.
   *
   * @author Brian Wellington
   */
  
  public abstract class Record implements Cloneable, Comparable {
  
  protected Name name;
  protected int type, dclass;
  protected long ttl;
  
  private static final Record [] knownRecords = new Record[256];
  private static final Record unknownRecord = new UNKRecord();
  private static final Class [] emptyClassArray = new Class[0];
  private static final Object [] emptyObjectArray = new Object[0];
  
  private static final DecimalFormat byteFormat = new DecimalFormat();
  
  static {
  	byteFormat.setMinimumIntegerDigits(3);
  }
  
  protected
  Record() {}
  
  Record(Name name, int type, int dclass, long ttl) {
  	if (!name.isAbsolute())
  		throw new RelativeNameException(name);
  	Type.check(type);
  	DClass.check(dclass);
  	TTL.check(ttl);
  	this.name = name;
  	this.type = type;
  	this.dclass = dclass;
  	this.ttl = ttl;
  }
  
  /**
   * Creates an empty record of the correct type; must be overriden
   */
  abstract Record
  getObject();
  
  private static final Record
  getTypedObject(int type) {
  	if (type < 0 || type > knownRecords.length)
  		return unknownRecord.getObject();
  	if (knownRecords[type] != null)
  		return knownRecords[type];
  
  	/* Construct the class name by putting the type before "Record". */
  	String s = Record.class.getPackage().getName() + "." +
  		   Type.string(type).replace('-', '_') + "Record";
  	try {
  		Class c = Class.forName(s);
  		Constructor m = c.getDeclaredConstructor(emptyClassArray);
  		knownRecords[type] = (Record) m.newInstance(emptyObjectArray);
  	}
  	catch (ClassNotFoundException e) {
  		/* This is normal; do nothing */
  	}
  	catch (Exception e) {
  		if (Options.check("verbose"))
  			System.err.println(e);
  	}
  	if (knownRecords[type] == null)
  		knownRecords[type] = unknownRecord.getObject();
  	return knownRecords[type];
  }
  
  private static final Record
  getEmptyRecord(Name name, int type, int dclass, long ttl, boolean hasData) {
  	Record rec;
  	if (hasData)
  		rec = getTypedObject(type).getObject();
  	else
  		rec = new EmptyRecord();
  	rec.name = name;
  	rec.type = type;
  	rec.dclass = dclass;
  	rec.ttl = ttl;
  	return rec;
  }
  
  /**
   * Converts the type-specific RR to wire format - must be overriden
   */
  abstract void
  rrFromWire(DNSInput in) throws IOException;
  
  private static Record
  newRecord(Name name, int type, int dclass, long ttl, int length, DNSInput in)
  throws IOException
  {
  	Record rec;
  	int recstart;
  	rec = getEmptyRecord(name, type, dclass, ttl, in != null);
  	if (in != null) {
  		if (in.remaining() < length)
  			throw new WireParseException("truncated record");
  		in.setActive(length);
  
  		rec.rrFromWire(in);
  
  		if (in.remaining() > 0)
  			throw new WireParseException("invalid record length");
  		in.clearActive();
  	}
  	return rec;
  }
  
  /**
   * Creates a new record, with the given parameters.
   * @param name The owner name of the record.
   * @param type The record's type.
   * @param dclass The record's class.
   * @param ttl The record's time to live.
   * @param length The length of the record's data.
   * @param data The rdata of the record, in uncompressed DNS wire format.  Only
   * the first length bytes are used.
   */
  public static Record
  newRecord(Name name, int type, int dclass, long ttl, int length, byte [] data) {
  	if (!name.isAbsolute())
  		throw new RelativeNameException(name);
  	Type.check(type);
  	DClass.check(dclass);
  	TTL.check(ttl);
  
  	DNSInput in;
  	if (data != null)
  		in = new DNSInput(data);
  	else
  		in = null;
  	try {
  		return newRecord(name, type, dclass, ttl, length, in);
  	}
  	catch (IOException e) {
  		return null;
  	}
  }
  
  /**
   * Creates a new record, with the given parameters.
   * @param name The owner name of the record.
   * @param type The record's type.
   * @param dclass The record's class.
   * @param ttl The record's time to live.
   * @param data The complete rdata of the record, in uncompressed DNS wire
   * format.
   */
  public static Record
  newRecord(Name name, int type, int dclass, long ttl, byte [] data) {
  	return newRecord(name, type, dclass, ttl, data.length, data);
  }
  
  /**
   * Creates a new empty record, with the given parameters.
   * @param name The owner name of the record.
   * @param type The record's type.
   * @param dclass The record's class.
   * @param ttl The record's time to live.
   * @return An object of a subclass of Record
   */
  public static Record
  newRecord(Name name, int type, int dclass, long ttl) {
  	if (!name.isAbsolute())
  		throw new RelativeNameException(name);
  	Type.check(type);
  	DClass.check(dclass);
  	TTL.check(ttl);
  
  	return getEmptyRecord(name, type, dclass, ttl, false);
  }
  
  /**
   * Creates a new empty record, with the given parameters.  This method is
   * designed to create records that will be added to the QUERY section
   * of a message.
   * @param name The owner name of the record.
   * @param type The record's type.
   * @param dclass The record's class.
   * @return An object of a subclass of Record
   */
  public static Record
  newRecord(Name name, int type, int dclass) {
  	return newRecord(name, type, dclass, 0);
  }
  
  static Record
  fromWire(DNSInput in, int section, boolean isUpdate) throws IOException {
  	int type, dclass;
  	long ttl;
  	int length;
  	Name name;
  	Record rec;
  
  	name = new Name(in);
  	type = in.readU16();
  	dclass = in.readU16();
  
  	if (section == Section.QUESTION)
  		return newRecord(name, type, dclass);
  
  	ttl = in.readU32();
  	length = in.readU16();
  	if (length == 0 && isUpdate)
  		return newRecord(name, type, dclass, ttl);
  	rec = newRecord(name, type, dclass, ttl, length, in);
  	return rec;
  }
  
  static Record
  fromWire(DNSInput in, int section) throws IOException {
  	return fromWire(in, section, false);
  }
  
  /**
   * Builds a Record from DNS uncompressed wire format.
   */
  public static Record
  fromWire(byte [] b, int section) throws IOException {
  	return fromWire(new DNSInput(b), section, false);
  }
  
  void
  toWire(DNSOutput out, int section, Compression c) {
  	name.toWire(out, c);
  	out.writeU16(type);
  	out.writeU16(dclass);
  	if (section == Section.QUESTION)
  		return;
  	out.writeU32(ttl);
  	int lengthPosition = out.current();
  	out.writeU16(0); /* until we know better */
  	rrToWire(out, c, false);
  	int rrlength = out.current() - lengthPosition - 2;
  	out.save();
  	out.jump(lengthPosition);
  	out.writeU16(rrlength);
  	out.restore();
  }
  
  /**
   * Converts a Record into DNS uncompressed wire format.
   */
  public byte []
  toWire(int section) {
  	DNSOutput out = new DNSOutput();
  	toWire(out, section, null);
  	return out.toByteArray();
  }
  
  private void
  toWireCanonical(DNSOutput out, boolean noTTL) {
  	name.toWireCanonical(out);
  	out.writeU16(type);
  	out.writeU16(dclass);
  	if (noTTL) {
  		out.writeU32(0);
  	} else {
  		out.writeU32(ttl);
  	}
  	int lengthPosition = out.current();
  	out.writeU16(0); /* until we know better */
  	rrToWire(out, null, true);
  	int rrlength = out.current() - lengthPosition - 2;
  	out.save();
  	out.jump(lengthPosition);
  	out.writeU16(rrlength);
  	out.restore();
  }
  
  /*
   * Converts a Record into canonical DNS uncompressed wire format (all names are
   * converted to lowercase), optionally ignoring the TTL.
   */
  private byte []
  toWireCanonical(boolean noTTL) {
  	DNSOutput out = new DNSOutput();
  	toWireCanonical(out, noTTL);
  	return out.toByteArray();
  }
  
  /**
   * Converts a Record into canonical DNS uncompressed wire format (all names are
   * converted to lowercase).
   */
  public byte []
  toWireCanonical() {
  	return toWireCanonical(false);
  }
  
  /**
   * Converts the rdata in a Record into canonical DNS uncompressed wire format
   * (all names are converted to lowercase).
   */
  public byte []
  rdataToWireCanonical() {
  	DNSOutput out = new DNSOutput();
  	rrToWire(out, null, true);
  	return out.toByteArray();
  }
  
  /**
   * Converts the type-specific RR to text format - must be overriden
   */
  abstract String rrToString();
  
  /**
   * Converts the rdata portion of a Record into a String representation
   */
  public String
  rdataToString() {
  	return rrToString();
  }
  
  /**
   * Converts a Record into a String representation
   */
  public String
  toString() {
  	StringBuffer sb = new StringBuffer();
  	sb.append(name);
  	if (sb.length() < 8)
  		sb.append("\t");
  	if (sb.length() < 16)
  		sb.append("\t");
  	sb.append("\t");
  	if (Options.check("BINDTTL"))
  		sb.append(TTL.format(ttl));
  	else
  		sb.append(ttl);
  	sb.append("\t");
  	if (dclass != DClass.IN || !Options.check("noPrintIN")) {
  		sb.append(DClass.string(dclass));
  		sb.append("\t");
  	}
  	sb.append(Type.string(type));
  	String rdata = rrToString();
  	if (!rdata.equals("")) {
  		sb.append("\t");
  		sb.append(rdata);
  	}
  	return sb.toString();
  }
  
  /**
   * Converts the text format of an RR to the internal format - must be overriden
   */
  abstract void
  rdataFromString(Tokenizer st, Name origin) throws IOException;
  
  /**
   * Converts a String into a byte array.
   */
  protected static byte []
  byteArrayFromString(String s) throws TextParseException {
  	byte [] array = s.getBytes();
  	boolean escaped = false;
  	boolean hasEscapes = false;
  
  	for (int i = 0; i < array.length; i++) {
  		if (array[i] == '\\') {
  			hasEscapes = true;
  			break;
  		}
  	}
  	if (!hasEscapes) {
  		if (array.length > 255) {
  			throw new TextParseException("text string too long");
  		}
  		return array;
  	}
  
  	ByteArrayOutputStream os = new ByteArrayOutputStream();
  
  	int digits = 0;
  	int intval = 0;
  	for (int i = 0; i < array.length; i++) {
  		byte b = array[i];
  		if (escaped) {
  			if (b >= '0' && b <= '9' && digits < 3) {
  				digits++; 
  				intval *= 10;
  				intval += (b - '0');
  				if (intval > 255)
  					throw new TextParseException
  								("bad escape");
  				if (digits < 3)
  					continue;
  				b = (byte) intval;
  			}
  			else if (digits > 0 && digits < 3)
  				throw new TextParseException("bad escape");
  			os.write(b);
  			escaped = false;
  		}
  		else if (array[i] == '\\') {
  			escaped = true;
  			digits = 0;
  			intval = 0;
  		}
  		else
  			os.write(array[i]);
  	}
  	if (digits > 0 && digits < 3)
  		throw new TextParseException("bad escape");
  	array = os.toByteArray();
  	if (array.length > 255) {
  		throw new TextParseException("text string too long");
  	}
  
  	return os.toByteArray();
  }
  
  /**
   * Converts a byte array into a String.
   */
  protected static String
  byteArrayToString(byte [] array, boolean quote) {
  	StringBuffer sb = new StringBuffer();
  	if (quote)
  		sb.append('"');
  	for (int i = 0; i < array.length; i++) {
  		int b = array[i] & 0xFF;
  		if (b < 0x20 || b >= 0x7f) {
  			sb.append('\\');
  			sb.append(byteFormat.format(b));
  		} else if (b == '"' || b == ';' || b == '\\') {
  			sb.append('\\');
  			sb.append((char)b);
  		} else
  			sb.append((char)b);
  	}
  	if (quote)
  		sb.append('"');
  	return sb.toString();
  }
  
  /**
   * Converts a byte array into the unknown RR format.
   */
  protected static String
  unknownToString(byte [] data) {
  	StringBuffer sb = new StringBuffer();
  	sb.append("\\# ");
  	sb.append(data.length);
  	sb.append(" ");
  	sb.append(base16.toString(data));
  	return sb.toString();
  }
  
  /**
   * Builds a new Record from its textual representation
   * @param name The owner name of the record.
   * @param type The record's type.
   * @param dclass The record's class.
   * @param ttl The record's time to live.
   * @param st A tokenizer containing the textual representation of the rdata.
   * @param origin The default origin to be appended to relative domain names.
   * @return The new record
   * @throws IOException The text format was invalid.
   */
  public static Record
  fromString(Name name, int type, int dclass, long ttl, Tokenizer st, Name origin)
  throws IOException
  {
  	Record rec;
  
  	if (!name.isAbsolute())
  		throw new RelativeNameException(name);
  	Type.check(type);
  	DClass.check(dclass);
  	TTL.check(ttl);
  
  	Tokenizer.Token t = st.get();
  	if (t.type == Tokenizer.IDENTIFIER && t.value.equals("\\#")) {
  		int length = st.getUInt16();
  		byte [] data = st.getHex();
  		if (data == null) {
  			data = new byte[0];
  		}
  		if (length != data.length)
  			throw st.exception("invalid unknown RR encoding: " +
  					   "length mismatch");
  		DNSInput in = new DNSInput(data);
  		return newRecord(name, type, dclass, ttl, length, in);
  	}
  	st.unget();
  	rec = getEmptyRecord(name, type, dclass, ttl, true);
  	rec.rdataFromString(st, origin);
  	t = st.get();
  	if (t.type != Tokenizer.EOL && t.type != Tokenizer.EOF) {
  		throw st.exception("unexpected tokens at end of record");
  	}
  	return rec;
  }
  
  /**
   * Builds a new Record from its textual representation
   * @param name The owner name of the record.
   * @param type The record's type.
   * @param dclass The record's class.
   * @param ttl The record's time to live.
   * @param s The textual representation of the rdata.
   * @param origin The default origin to be appended to relative domain names.
   * @return The new record
   * @throws IOException The text format was invalid.
   */
  public static Record
  fromString(Name name, int type, int dclass, long ttl, String s, Name origin)
  throws IOException
  {
  	return fromString(name, type, dclass, ttl, new Tokenizer(s), origin);
  }
  
  /**
   * Returns the record's name
   * @see Name
   */
  public Name
  getName() {
  	return name;
  }
  
  /**
   * Returns the record's type
   * @see Type
   */
  public int
  getType() {
  	return type;
  }
  
  /**
   * Returns the type of RRset that this record would belong to.  For all types
   * except SIGRecord, this is equivalent to getType().
   * @return The type of record, if not SIGRecord.  If the type is SIGRecord,
   * the type covered is returned.
   * @see Type
   * @see RRset
   * @see SIGRecord
   */
  public int
  getRRsetType() {
  	if (type == Type.SIG || type == Type.RRSIG) {
  		SIGBase sig = (SIGBase) this;
  		return sig.getTypeCovered();
  	}
  	return type;
  }
  
  /**
   * Returns the record's class
   */
  public int
  getDClass() {
  	return dclass;
  }
  
  /**
   * Returns the record's TTL
   */
  public long
  getTTL() {
  	return ttl;
  }
  
  /**
   * Converts the type-specific RR to wire format - must be overriden
   */
  abstract void
  rrToWire(DNSOutput out, Compression c, boolean canonical);
  
  /**
   * Determines if two Records could be part of the same RRset.
   * This compares the name, type, and class of the Records; the ttl and
   * rdata are not compared.
   */
  public boolean
  sameRRset(Record rec) {
  	return (getRRsetType() == rec.getRRsetType() &&
  		dclass == rec.dclass &&
  		name.equals(rec.name));
  }
  
  /**
   * Determines if two Records are identical.  This compares the name, type,
   * class, and rdata (with names canonicalized).  The TTLs are not compared.
   * @param arg The record to compare to
   * @return true if the records are equal, false otherwise.
   */
  public boolean
  equals(Object arg) {
  	if (arg == null || !(arg instanceof Record))
  		return false;
  	Record r = (Record) arg;
  	if (type != r.type || dclass != r.dclass || !name.equals(r.name))
  		return false;
  	byte [] array1 = rdataToWireCanonical();
  	byte [] array2 = r.rdataToWireCanonical();
  	return Arrays.equals(array1, array2);
  }
  
  /**
   * Generates a hash code based on the Record's data.
   */
  public int
  hashCode() {
  	byte [] array = toWireCanonical(true);
  	int code = 0;
  	for (int i = 0; i < array.length; i++)
  		code += ((code << 3) + (array[i] & 0xFF));
  	return code;
  }
  
  Record
  cloneRecord() {
  	try {
  		return (Record) clone();
  	}
  	catch (CloneNotSupportedException e) {
  		throw new IllegalStateException();
  	}
  }
  
  /**
   * Creates a new record identical to the current record, but with a different
   * name.  This is most useful for replacing the name of a wildcard record.
   */
  public Record
  withName(Name name) {
  	if (!name.isAbsolute())
  		throw new RelativeNameException(name);
  	Record rec = cloneRecord();
  	rec.name = name;
  	return rec;
  }
  
  /**
   * Creates a new record identical to the current record, but with a different
   * class and ttl.  This is most useful for dynamic update.
   */
  Record
  withDClass(int dclass, long ttl) {
  	Record rec = cloneRecord();
  	rec.dclass = dclass;
  	rec.ttl = ttl;
  	return rec;
  }
  
  /* Sets the TTL to the specified value.  This is intentionally not public. */
  void
  setTTL(long ttl) {
  	this.ttl = ttl;
  }
  
  /**
   * Compares this Record to another Object.
   * @param o The Object to be compared.
   * @return The value 0 if the argument is a record equivalent to this record;
   * a value less than 0 if the argument is less than this record in the
   * canonical ordering, and a value greater than 0 if the argument is greater
   * than this record in the canonical ordering.  The canonical ordering
   * is defined to compare by name, class, type, and rdata.
   * @throws ClassCastException if the argument is not a Record.
   */
  public int
  compareTo(Object o) {
  	Record arg = (Record) o;
  
  	if (this == arg)
  		return (0);
  
  	int n = name.compareTo(arg.name);
  	if (n != 0)
  		return (n);
  	n = dclass - arg.dclass;
  	if (n != 0)
  		return (n);
  	n = type - arg.type;
  	if (n != 0)
  		return (n);
  	byte [] rdata1 = rdataToWireCanonical();
  	byte [] rdata2 = arg.rdataToWireCanonical();
  	for (int i = 0; i < rdata1.length && i < rdata2.length; i++) {
  		n = (rdata1[i] & 0xFF) - (rdata2[i] & 0xFF);
  		if (n != 0)
  			return (n);
  	}
  	return (rdata1.length - rdata2.length);
  }
  
  /**
   * Returns the name for which additional data processing should be done
   * for this record.  This can be used both for building responses and
   * parsing responses.
   * @return The name to used for additional data processing, or null if this
   * record type does not require additional data processing.
   */
  public Name
  getAdditionalName() {
  	return null;
  }
  
  /* Checks that an int contains an unsigned 8 bit value */
  static int
  checkU8(String field, int val) {
  	if (val < 0 || val > 0xFF)
  		throw new IllegalArgumentException("\"" + field + "\" " + val + 
  						   " must be an unsigned 8 " +
  						   "bit value");
  	return val;
  }
  
  /* Checks that an int contains an unsigned 16 bit value */
  static int
  