Updates to hpack for hpack-09 draft
This commit is contained in:
parent
a14b2be0e6
commit
9c3eedfea8
|
@ -20,6 +20,8 @@ package org.eclipse.jetty.http;
|
|||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** A HTTP Field
|
||||
*/
|
||||
|
@ -67,6 +69,11 @@ public class HttpField
|
|||
return _value;
|
||||
}
|
||||
|
||||
public long getLongValue()
|
||||
{
|
||||
return StringUtil.toLong(_value);
|
||||
}
|
||||
|
||||
public String[] getValues()
|
||||
{
|
||||
ArrayList<String> list = new ArrayList<>();
|
||||
|
@ -349,19 +356,6 @@ public class HttpField
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
|
@ -420,4 +414,28 @@ public class HttpField
|
|||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static class LongValueHttpField extends HttpField
|
||||
{
|
||||
final long _long;
|
||||
|
||||
public LongValueHttpField(HttpHeader header, long value)
|
||||
{
|
||||
super(header,Long.toString(value));
|
||||
_long=value;
|
||||
}
|
||||
|
||||
public LongValueHttpField(HttpHeader header, String value)
|
||||
{
|
||||
super(header,value);
|
||||
_long=StringUtil.toLong(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLongValue()
|
||||
{
|
||||
return _long;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -474,7 +474,7 @@ public class HttpFields implements Iterable<HttpField>
|
|||
public long getLongField(String name) throws NumberFormatException
|
||||
{
|
||||
HttpField field = getField(name);
|
||||
return field==null?-1L:StringUtil.toLong(field.getValue());
|
||||
return field==null?-1L:field.getLongValue();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -51,7 +51,7 @@ public class HeadersGenerator extends FrameGenerator
|
|||
if (streamId < 0)
|
||||
throw new IllegalArgumentException("Invalid stream id: " + streamId);
|
||||
|
||||
encoder.encode(metaData, lease);
|
||||
encoder.encode(metaData, lease,Frame.MAX_LENGTH);
|
||||
|
||||
long length = lease.getTotalLength();
|
||||
if (length > Frame.MAX_LENGTH)
|
||||
|
|
|
@ -53,7 +53,7 @@ public class PushPromiseGenerator extends FrameGenerator
|
|||
if (promisedStreamId < 0)
|
||||
throw new IllegalArgumentException("Invalid promised stream id: " + promisedStreamId);
|
||||
|
||||
encoder.encode(metaData, lease);
|
||||
encoder.encode(metaData, lease, Frame.MAX_LENGTH);
|
||||
|
||||
// The promised streamId.
|
||||
int fixedLength = 4;
|
||||
|
|
|
@ -117,20 +117,20 @@ public class HpackContext
|
|||
switch(i)
|
||||
{
|
||||
case 2:
|
||||
entry=new StaticEntry(i,new StaticValueHttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1],HttpMethod.GET),true);
|
||||
entry=new StaticEntry(i,new StaticValueHttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1],HttpMethod.GET));
|
||||
break;
|
||||
case 3:
|
||||
entry=new StaticEntry(i,new StaticValueHttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1],HttpMethod.POST),false);
|
||||
entry=new StaticEntry(i,new StaticValueHttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1],HttpMethod.POST));
|
||||
break;
|
||||
case 6:
|
||||
entry=new StaticEntry(i,new StaticValueHttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1],HttpScheme.HTTP),true);
|
||||
entry=new StaticEntry(i,new StaticValueHttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1],HttpScheme.HTTP));
|
||||
break;
|
||||
case 7:
|
||||
entry=new StaticEntry(i,new StaticValueHttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1],HttpScheme.HTTPS),false);
|
||||
entry=new StaticEntry(i,new StaticValueHttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1],HttpScheme.HTTPS));
|
||||
break;
|
||||
case 8:
|
||||
case 11:
|
||||
entry=new StaticEntry(i,new StaticValueHttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1],Integer.valueOf(STATIC_TABLE[i][1])),true);
|
||||
entry=new StaticEntry(i,new StaticValueHttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1],Integer.valueOf(STATIC_TABLE[i][1])));
|
||||
break;
|
||||
|
||||
case 9:
|
||||
|
@ -138,11 +138,11 @@ public class HpackContext
|
|||
case 12:
|
||||
case 13:
|
||||
case 14:
|
||||
entry=new StaticEntry(i,new StaticValueHttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1],Integer.valueOf(STATIC_TABLE[i][1])),false);
|
||||
entry=new StaticEntry(i,new StaticValueHttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1],Integer.valueOf(STATIC_TABLE[i][1])));
|
||||
break;
|
||||
|
||||
default:
|
||||
entry=new StaticEntry(i,new HttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1]),true);
|
||||
entry=new StaticEntry(i,new HttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1]));
|
||||
}
|
||||
|
||||
__staticTable[i]=entry;
|
||||
|
@ -412,9 +412,8 @@ public class HpackContext
|
|||
public static class StaticEntry extends Entry
|
||||
{
|
||||
private final byte[] _huffmanValue;
|
||||
private final boolean _useRefSet;
|
||||
|
||||
StaticEntry(int index,HttpField field,boolean useRefSet)
|
||||
StaticEntry(int index,HttpField field)
|
||||
{
|
||||
super(index,field);
|
||||
String value = field.getValue();
|
||||
|
@ -434,7 +433,6 @@ public class HpackContext
|
|||
}
|
||||
else
|
||||
_huffmanValue=null;
|
||||
_useRefSet=useRefSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -442,11 +440,6 @@ public class HpackContext
|
|||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean useRefSet()
|
||||
{
|
||||
return _useRefSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getStaticHuffmanValue()
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.eclipse.jetty.http.HttpHeader;
|
|||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
|
@ -41,6 +42,7 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
public class HpackDecoder
|
||||
{
|
||||
public static final Logger LOG = Log.getLogger(HpackDecoder.class);
|
||||
public final static HttpField.LongValueHttpField CONTENT_LENGTH_0 = new HttpField.LongValueHttpField(HttpHeader.CONTENT_LENGTH,0L);
|
||||
|
||||
private final HpackContext _context;
|
||||
private final MetaDataBuilder _builder;
|
||||
|
@ -79,7 +81,7 @@ public class HpackDecoder
|
|||
byte b = buffer.get();
|
||||
if (b<0)
|
||||
{
|
||||
// indexed
|
||||
// 7.1 indexed if the high bit is set
|
||||
int index = NBitInteger.decode(buffer,7);
|
||||
Entry entry=_context.get(index);
|
||||
if (entry==null)
|
||||
|
@ -107,126 +109,143 @@ public class HpackDecoder
|
|||
else
|
||||
{
|
||||
// look at the first nibble in detail
|
||||
int f=(b&0xF0)>>4;
|
||||
byte f= (byte)((b&0xF0)>>4);
|
||||
String name;
|
||||
HttpHeader header;
|
||||
String value;
|
||||
|
||||
boolean indexed;
|
||||
int name_index;
|
||||
|
||||
if (f<=1 || f>=4)
|
||||
switch (f)
|
||||
{
|
||||
// literal
|
||||
boolean indexed=f>=4;
|
||||
int bits=indexed?6:4;
|
||||
boolean huffmanName=false;
|
||||
case 2: // 7.3
|
||||
case 3: // 7.3
|
||||
// change table size
|
||||
int size = NBitInteger.decode(buffer,5);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("decode resize="+size);
|
||||
if (size>_localMaxHeaderTableSize)
|
||||
throw new IllegalArgumentException();
|
||||
_context.resize(size);
|
||||
continue;
|
||||
|
||||
// decode the name
|
||||
int name_index=NBitInteger.decode(buffer,bits);
|
||||
if (name_index>0)
|
||||
{
|
||||
Entry name_entry=_context.get(name_index);
|
||||
name=name_entry.getHttpField().getName();
|
||||
header=name_entry.getHttpField().getHeader();
|
||||
}
|
||||
else
|
||||
{
|
||||
huffmanName = (buffer.get()&0x80)==0x80;
|
||||
int length = NBitInteger.decode(buffer,7);
|
||||
_builder.checkSize(length,huffmanName);
|
||||
if (huffmanName)
|
||||
name=Huffman.decode(buffer,length);
|
||||
else
|
||||
name=toASCIIString(buffer,length);
|
||||
for (int i=0;i<name.length();i++)
|
||||
{
|
||||
char c=name.charAt(i);
|
||||
if (c>='A'&&c<='Z')
|
||||
{
|
||||
throw new BadMessageException(400,"Uppercase header name");
|
||||
}
|
||||
}
|
||||
header=HttpHeader.CACHE.get(name);
|
||||
}
|
||||
|
||||
// decode the value
|
||||
boolean huffmanValue = (buffer.get()&0x80)==0x80;
|
||||
case 0: // 7.2.2
|
||||
case 1: // 7.2.3
|
||||
indexed=false;
|
||||
name_index=NBitInteger.decode(buffer,4);
|
||||
break;
|
||||
|
||||
|
||||
case 4: // 7.2.1
|
||||
case 5: // 7.2.1
|
||||
case 6: // 7.2.1
|
||||
case 7: // 7.2.1
|
||||
indexed=true;
|
||||
name_index=NBitInteger.decode(buffer,6);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
|
||||
boolean huffmanName=false;
|
||||
|
||||
// decode the name
|
||||
if (name_index>0)
|
||||
{
|
||||
Entry name_entry=_context.get(name_index);
|
||||
name=name_entry.getHttpField().getName();
|
||||
header=name_entry.getHttpField().getHeader();
|
||||
}
|
||||
else
|
||||
{
|
||||
huffmanName = (buffer.get()&0x80)==0x80;
|
||||
int length = NBitInteger.decode(buffer,7);
|
||||
_builder.checkSize(length,huffmanValue);
|
||||
if (huffmanValue)
|
||||
value=Huffman.decode(buffer,length);
|
||||
_builder.checkSize(length,huffmanName);
|
||||
if (huffmanName)
|
||||
name=Huffman.decode(buffer,length);
|
||||
else
|
||||
value=toASCIIString(buffer,length);
|
||||
|
||||
// Make the new field
|
||||
HttpField field;
|
||||
switch(name)
|
||||
name=toASCIIString(buffer,length);
|
||||
for (int i=0;i<name.length();i++)
|
||||
{
|
||||
case ":method":
|
||||
HttpMethod method=HttpMethod.CACHE.get(value);
|
||||
if (method!=null)
|
||||
field = new StaticValueHttpField(header,name,method.asString(),method);
|
||||
else
|
||||
field = new AuthorityHttpField(value);
|
||||
break;
|
||||
|
||||
case ":status":
|
||||
Integer code = Integer.valueOf(value);
|
||||
field = new StaticValueHttpField(header,name,value,code);
|
||||
break;
|
||||
|
||||
case ":scheme":
|
||||
HttpScheme scheme=HttpScheme.CACHE.get(value);
|
||||
if (scheme!=null)
|
||||
field = new StaticValueHttpField(header,name,scheme.asString(),scheme);
|
||||
else
|
||||
field = new AuthorityHttpField(value);
|
||||
break;
|
||||
|
||||
case ":authority":
|
||||
char c=name.charAt(i);
|
||||
if (c>='A'&&c<='Z')
|
||||
{
|
||||
throw new BadMessageException(400,"Uppercase header name");
|
||||
}
|
||||
}
|
||||
header=HttpHeader.CACHE.get(name);
|
||||
}
|
||||
|
||||
// decode the value
|
||||
boolean huffmanValue = (buffer.get()&0x80)==0x80;
|
||||
int length = NBitInteger.decode(buffer,7);
|
||||
_builder.checkSize(length,huffmanValue);
|
||||
if (huffmanValue)
|
||||
value=Huffman.decode(buffer,length);
|
||||
else
|
||||
value=toASCIIString(buffer,length);
|
||||
|
||||
// Make the new field
|
||||
HttpField field;
|
||||
switch(name)
|
||||
{
|
||||
case ":method":
|
||||
HttpMethod method=HttpMethod.CACHE.get(value);
|
||||
if (method!=null)
|
||||
field = new StaticValueHttpField(header,name,method.asString(),method);
|
||||
else
|
||||
field = new AuthorityHttpField(value);
|
||||
break;
|
||||
|
||||
case ":status":
|
||||
Integer code = Integer.valueOf(value);
|
||||
field = new StaticValueHttpField(header,name,value,code);
|
||||
break;
|
||||
|
||||
case ":scheme":
|
||||
HttpScheme scheme=HttpScheme.CACHE.get(value);
|
||||
if (scheme!=null)
|
||||
field = new StaticValueHttpField(header,name,scheme.asString(),scheme);
|
||||
else
|
||||
field = new AuthorityHttpField(value);
|
||||
break;
|
||||
|
||||
case ":path":
|
||||
// TODO is this needed
|
||||
/*
|
||||
if (indexed)
|
||||
field = new StaticValueHttpField(header,name,value,new HttpURI(value));
|
||||
else*/
|
||||
field = new HttpField(header,name,value);
|
||||
break;
|
||||
|
||||
default:
|
||||
field = new HttpField(header,name,value);
|
||||
break;
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("decoded '"+field+"' by Lit"+(name_index>0?"IdxName":(huffmanName?"HuffName":"LitName"))+(huffmanValue?"HuffVal":"LitVal")+(indexed?"Idx":""));
|
||||
|
||||
// emit the field
|
||||
_builder.emit(field);
|
||||
|
||||
// if indexed
|
||||
if (indexed)
|
||||
{
|
||||
// add to header table
|
||||
_context.add(field);
|
||||
}
|
||||
break;
|
||||
|
||||
case ":authority":
|
||||
field = new AuthorityHttpField(value);
|
||||
break;
|
||||
|
||||
case ":path":
|
||||
field = new HttpField(header,name,value);
|
||||
break;
|
||||
|
||||
case "content-length":
|
||||
if ("0".equals(value))
|
||||
field = CONTENT_LENGTH_0;
|
||||
else
|
||||
field = new HttpField.LongValueHttpField(header,value);
|
||||
break;
|
||||
|
||||
default:
|
||||
field = new HttpField(header,name,value);
|
||||
break;
|
||||
}
|
||||
else if (f==2)
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("decoded '"+field+"' by Lit"+(name_index>0?"IdxName":(huffmanName?"HuffName":"LitName"))+(huffmanValue?"HuffVal":"LitVal")+(indexed?"Idx":""));
|
||||
|
||||
// emit the field
|
||||
_builder.emit(field);
|
||||
|
||||
// if indexed
|
||||
if (indexed)
|
||||
{
|
||||
// change table size
|
||||
int size = NBitInteger.decode(buffer,4);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("decode resize="+size);
|
||||
if (size>_localMaxHeaderTableSize)
|
||||
throw new IllegalArgumentException();
|
||||
_context.resize(size);
|
||||
// add to header table
|
||||
_context.add(field);
|
||||
}
|
||||
else if (f==3)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("unused");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -42,9 +42,7 @@ public class HpackEncoder
|
|||
|
||||
|
||||
private final static EnumSet<HttpHeader> __DO_NOT_HUFFMAN =
|
||||
EnumSet.of(HttpHeader.COOKIE,
|
||||
HttpHeader.SET_COOKIE,
|
||||
HttpHeader.SET_COOKIE2,
|
||||
EnumSet.of(
|
||||
HttpHeader.AUTHORIZATION,
|
||||
HttpHeader.CONTENT_MD5,
|
||||
HttpHeader.PROXY_AUTHENTICATE,
|
||||
|
@ -113,11 +111,11 @@ public class HpackEncoder
|
|||
_localMaxHeaderTableSize=localMaxHeaderTableSize;
|
||||
}
|
||||
|
||||
public void encode(MetaData metadata,Lease lease)
|
||||
// TODO better handling of buffer size
|
||||
public void encode(MetaData metadata,Lease lease,int buffersize)
|
||||
{
|
||||
ByteBuffer buffer = lease.acquire(8*1024,false); // TODO make size configurable
|
||||
ByteBuffer buffer = lease.acquire(buffersize,false);
|
||||
lease.append(buffer,true);
|
||||
// TODO handle multiple buffers if large size configured.
|
||||
BufferUtil.clearToFill(buffer);
|
||||
encode(buffer,metadata);
|
||||
BufferUtil.flipToFlush(buffer,0);
|
||||
|
@ -170,7 +168,7 @@ public class HpackEncoder
|
|||
if (maxHeaderTableSize>_remoteMaxHeaderTableSize)
|
||||
throw new IllegalArgumentException();
|
||||
buffer.put((byte)0x20);
|
||||
NBitInteger.encode(buffer,4,maxHeaderTableSize);
|
||||
NBitInteger.encode(buffer,5,maxHeaderTableSize);
|
||||
_context.resize(maxHeaderTableSize);
|
||||
}
|
||||
|
||||
|
@ -188,87 +186,67 @@ public class HpackEncoder
|
|||
|
||||
if (entry!=null)
|
||||
{
|
||||
// Is this as static field
|
||||
if (entry.isStatic())
|
||||
{
|
||||
// entries like :status: 200 and :method: GET are worthwhile putting into ref set.
|
||||
// as they are likely to be repeated.
|
||||
encoding="StaticIndexed";
|
||||
int index=_context.index(entry);
|
||||
buffer.put((byte)0x80);
|
||||
NBitInteger.encode(buffer,7,index);
|
||||
|
||||
// TODO Copy?
|
||||
// entry=_context.add(entry.getHttpField());
|
||||
}
|
||||
else
|
||||
{
|
||||
// So we can emit the index and add the entry to the reference Set
|
||||
int index=_context.index(entry);
|
||||
if (p>=0)
|
||||
encoding="IdxField"+(1+NBitInteger.octectsNeeded(7,index));
|
||||
buffer.put((byte)0x80);
|
||||
NBitInteger.encode(buffer,7,index);
|
||||
}
|
||||
int index=_context.index(entry);
|
||||
if (p>=0)
|
||||
encoding="Indexed"+index;
|
||||
buffer.put((byte)0x80);
|
||||
NBitInteger.encode(buffer,7,index);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Must be a new entry, so we will have to send literally.
|
||||
// TODO Strategy decision to make!
|
||||
// What fields will we put in the reference set and what fields will we huffman encode?
|
||||
|
||||
// Let's make these decisions by lookup of known fields
|
||||
HttpHeader header = field.getHeader();
|
||||
final boolean never_index;
|
||||
final boolean huffman;
|
||||
final boolean indexed;
|
||||
final int name_bits;
|
||||
final byte mask;
|
||||
if (header==null)
|
||||
{
|
||||
// unknown header.
|
||||
never_index=false;
|
||||
huffman=true;
|
||||
indexed=true;
|
||||
name_bits = 6;
|
||||
mask=(byte)0x40;
|
||||
buffer.put((byte)0x40);
|
||||
}
|
||||
else if (__DO_NOT_INDEX.contains(header))
|
||||
{
|
||||
// Non indexed field
|
||||
indexed=false;
|
||||
never_index=__NEVER_INDEX.contains(header);
|
||||
huffman=!__DO_NOT_HUFFMAN.contains(header);
|
||||
name_bits = 4;
|
||||
mask=never_index?(byte)0x01:(byte)0x00;
|
||||
buffer.put(never_index?(byte)0x10:(byte)0x00);
|
||||
}
|
||||
else if (header==HttpHeader.CONTENT_LENGTH && field.getValue().length()>1)
|
||||
{
|
||||
// Non indexed content length for non zero value
|
||||
indexed=false;
|
||||
never_index=false;
|
||||
huffman=true;
|
||||
name_bits = 4;
|
||||
mask=(byte)0x00;
|
||||
buffer.put((byte)0x00);
|
||||
}
|
||||
else
|
||||
{
|
||||
// indexed
|
||||
indexed=true;
|
||||
never_index=false;
|
||||
huffman=!__DO_NOT_HUFFMAN.contains(header);
|
||||
name_bits = 6;
|
||||
mask=(byte)0x40;
|
||||
buffer.put((byte)0x40);
|
||||
}
|
||||
|
||||
// Add the mask bits
|
||||
buffer.put(mask);
|
||||
|
||||
// Look for a name Index
|
||||
Entry name_entry = _context.get(field.getName());
|
||||
|
||||
if (p>=0)
|
||||
{
|
||||
encoding="Lit"+
|
||||
((name_entry==null)?"HuffName":("IdxName"+(_context.index(name_entry)<64?'1':'X')))+
|
||||
(huffman?"HuffVal":"LitVal")+
|
||||
(indexed?"Idxd":(never_index?"NeverIdx":""));
|
||||
((name_entry==null)?"HuffN":"IdxN")+
|
||||
(huffman?"HuffV":"LitV")+
|
||||
(indexed?"Idx":(never_index?"!!Idx":"!Idx"));
|
||||
}
|
||||
|
||||
if (name_entry!=null)
|
||||
|
|
|
@ -114,9 +114,9 @@ public class MetaDataBuilder
|
|||
break;
|
||||
|
||||
case ":path":
|
||||
_path=field.getValue();
|
||||
_path = field.getValue();
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
if (field.getName().charAt(0)!=':')
|
||||
_fields.add(field);
|
||||
|
|
|
@ -35,6 +35,14 @@ public class StaticValueHttpField extends HttpField
|
|||
_value=value;
|
||||
}
|
||||
|
||||
public StaticValueHttpField(HttpHeader header,String valueString, Object value)
|
||||
{
|
||||
super(header,header.asString(),valueString);
|
||||
if (value==null)
|
||||
throw new IllegalArgumentException();
|
||||
_value=value;
|
||||
}
|
||||
|
||||
public StaticValueHttpField(String name, String valueString, Object value)
|
||||
{
|
||||
super(name,valueString);
|
||||
|
|
Loading…
Reference in New Issue