package org.jruby;
import org.jcodings.Encoding;
import org.jcodings.specific.ASCIIEncoding;
import org.jcodings.specific.USASCIIEncoding;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.ast.util.ArgsUtil;
import org.jruby.compiler.Constantizable;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.ArgumentDescriptor;
import org.jruby.runtime.Binding;
import org.jruby.runtime.Block;
import org.jruby.runtime.BlockBody;
import org.jruby.runtime.CallSite;
import org.jruby.runtime.CallType;
import org.jruby.runtime.ClassIndex;
import org.jruby.runtime.ContextAwareBlockBody;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.Signature;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.callsite.RefinedCachingCallSite;
import org.jruby.runtime.encoding.EncodingCapable;
import org.jruby.runtime.encoding.MarshalEncoding;
import org.jruby.runtime.marshal.UnmarshalStream;
import org.jruby.runtime.opto.OptoFactory;
import org.jruby.util.ByteList;
import org.jruby.util.ByteListHelper;
import org.jruby.util.PerlHash;
import org.jruby.util.SipHashInline;
import org.jruby.util.StringSupport;
import java.lang.ref.WeakReference;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import static org.jruby.util.RubyStringBuilder.str;
import static org.jruby.util.RubyStringBuilder.ids;
import static org.jruby.util.StringSupport.CR_7BIT;
import static org.jruby.util.StringSupport.codeLength;
import static org.jruby.util.StringSupport.codePoint;
import static org.jruby.util.StringSupport.codeRangeScan;
@JRubyClass(name = "Symbol", include = "Enumerable")
public class RubySymbol extends RubyObject implements MarshalEncoding, EncodingCapable, Constantizable {
@Deprecated
public static final long symbolHashSeedK0 = 5238926673095087190l;
private final String symbol;
private final int id;
private final ByteList symbolBytes;
private final int hashCode;
private Object constant;
private RubySymbol(Ruby runtime, String internedSymbol, ByteList symbolBytes) {
super(runtime, runtime.getSymbol(), false);
assert internedSymbol == internedSymbol.intern() : internedSymbol + " is not interned";
this.symbol = internedSymbol;
if (symbolBytes.getEncoding() != USASCIIEncoding.INSTANCE &&
codeRangeScan(symbolBytes.getEncoding(), symbolBytes) == CR_7BIT) {
symbolBytes = symbolBytes.dup();
symbolBytes.setEncoding(USASCIIEncoding.INSTANCE);
}
this.symbolBytes = symbolBytes;
this.id = runtime.allocSymbolId();
long k0 = Helpers.hashStart(runtime, id);
long hash = runtime.isSiphashEnabled() ? SipHashInline.hash24(
k0, 0, symbolBytes.getUnsafeBytes(),
symbolBytes.getBegin(), symbolBytes.getRealSize()) :
PerlHash.hash(k0, symbolBytes.getUnsafeBytes(),
symbolBytes.getBegin(), symbolBytes.getRealSize());
this.hashCode = (int) hash;
setFrozen(true);
}
private RubySymbol(Ruby runtime, String internedSymbol) {
this(runtime, internedSymbol, symbolBytesFromString(runtime, internedSymbol));
}
public static RubyClass createSymbolClass(Ruby runtime) {
RubyClass symbolClass = runtime.defineClass("Symbol", runtime.getObject(), ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
runtime.setSymbol(symbolClass);
RubyClass symbolMetaClass = symbolClass.getMetaClass();
symbolClass.setClassIndex(ClassIndex.SYMBOL);
symbolClass.setReifiedClass(RubySymbol.class);
symbolClass.kindOf = new RubyModule.JavaClassKindOf(RubySymbol.class);
symbolClass.defineAnnotatedMethods(RubySymbol.class);
symbolMetaClass.undefineMethod("new");
symbolClass.includeModule(runtime.getComparable());
return symbolClass;
}
@Override
public ClassIndex getNativeClassIndex() {
return ClassIndex.SYMBOL;
}
@Override
public final String asJavaString() {
return symbol;
}
public String idString() {
return symbol;
}
@Override
public final String toString() {
return RubyEncoding.decodeISO(symbolBytes);
}
public final ByteList getBytes() {
return symbolBytes;
}
public RubySymbol asWriter() {
ByteList bytes = getBytes().dup();
bytes.append((byte) '=');
return newIDSymbol(metaClass.runtime, bytes);
}
public RubySymbol asInstanceVariable() {
ByteList bytes = getBytes().dup();
bytes.prepend((byte) '@');
return newIDSymbol(metaClass.runtime, bytes);
}
public static RubySymbol retrieveIDSymbol(IRubyObject name) {
return name instanceof RubySymbol ?
(RubySymbol) name : newIDSymbol(name.getRuntime(), name.convertToString().getByteList());
}
public static RubySymbol retrieveIDSymbol(IRubyObject name, ObjBooleanConsumer<RubySymbol> handler) {
if (name instanceof RubySymbol) {
RubySymbol sym = (RubySymbol) name;
handler.accept(sym, false);
return sym;
}
return newIDSymbol(name.getRuntime(), name.convertToString().getByteList(), handler);
}
public void associateEncoding(Encoding encoding) {
symbolBytes.setEncoding(encoding);
}
@Override
public final boolean eql(IRubyObject other) {
return other == this;
}
public boolean validConstantName() {
boolean valid = ByteListHelper.eachCodePoint(getBytes(), (int index, int codepoint, Encoding encoding) ->
index == 0 && encoding.isUpper(codepoint) ||
index != 0 && (encoding.isAlnum(codepoint) || !Encoding.isAscii(codepoint) || codepoint == '_'));
return valid && getBytes().length() >= 1;
}
public boolean validInstanceVariableName() {
boolean valid = ByteListHelper.eachCodePoint(getBytes(), (int index, int codepoint, Encoding encoding) ->
index == 0 && codepoint == '@' ||
index == 1 && (!encoding.isDigit(codepoint)) && (encoding.isAlnum(codepoint) || !Encoding.isAscii(codepoint) || codepoint == '_') ||
index > 1 && (encoding.isAlnum(codepoint) || !Encoding.isAscii(codepoint) || codepoint == '_'));
return valid && getBytes().length() >= 2;
}
public boolean validClassVariableName() {
boolean valid = ByteListHelper.eachCodePoint(getBytes(), (int index, int codepoint, Encoding encoding) ->
index == 0 && codepoint == '@' ||
index == 1 && codepoint == '@' ||
index == 2 && (!encoding.isDigit(codepoint)) && (encoding.isAlnum(codepoint) || !Encoding.isAscii(codepoint) || codepoint == '_') ||
index > 2 && (encoding.isAlnum(codepoint) || !Encoding.isAscii(codepoint) || codepoint == '_'));
return valid && getBytes().length() >= 3;
}
public boolean validLocalVariableName() {
boolean valid = ByteListHelper.eachCodePoint(getBytes(), (int index, int codepoint, Encoding encoding) ->
index == 0 && (!encoding.isDigit(codepoint) && (encoding.isAlnum(codepoint) || !Encoding.isAscii(codepoint) || codepoint == '_')) ||
index != 0 && (encoding.isAlnum(codepoint) || !Encoding.isAscii(codepoint) || codepoint == '_'));
return valid && getBytes().length() >= 1;
}
@Override
public boolean isImmediate() {
return true;
}
@Override
public RubyClass getSingletonClass() {
throw getRuntime().newTypeError("can't define singleton");
}
public static RubySymbol getSymbolLong(Ruby runtime, long id) {
return runtime.getSymbolTable().lookup(id);
}
@Deprecated
public static RubySymbol newSymbol(Ruby runtime, IRubyObject name) {
if (name instanceof RubySymbol) {
return runtime.getSymbolTable().getSymbol(((RubySymbol) name).getBytes(), false);
} else if (name instanceof RubyString) {
return runtime.getSymbolTable().getSymbol(((RubyString) name).getByteList(), false);
} else {
return newSymbol(runtime, name.asString().getByteList());
}
}
public static RubySymbol newHardSymbol(Ruby runtime, IRubyObject name) {
if (name instanceof RubySymbol) {
return runtime.getSymbolTable().getSymbol(((RubySymbol) name).getBytes(), true);
} else if (name instanceof RubyString) {
return runtime.getSymbolTable().getSymbol(((RubyString) name).getByteList(), true);
}
return newSymbol(runtime, name.asString().getByteList());
}
public static RubySymbol newSymbol(Ruby runtime, String name) {
return runtime.getSymbolTable().getSymbol(name, false);
}
public static RubySymbol newSymbol(Ruby runtime, ByteList bytes) {
return runtime.getSymbolTable().getSymbol(bytes, false);
}
public static RubySymbol newHardSymbol(Ruby runtime, ByteList bytes) {
return runtime.getSymbolTable().getSymbol(bytes, true);
}
public static RubySymbol newHardSymbol(Ruby runtime, ByteList bytes, ObjBooleanConsumer<RubySymbol> handler) {
return runtime.getSymbolTable().getSymbol(bytes, handler, true);
}
public static RubySymbol newHardSymbol(Ruby runtime, String name) {
return runtime.getSymbolTable().getSymbol(name, true);
}
public static RubySymbol newSymbol(Ruby runtime, ByteList bytes, ObjBooleanConsumer<RubySymbol> handler) {
return runtime.getSymbolTable().getSymbol(bytes, handler, false);
}
@FunctionalInterface
public interface ObjBooleanConsumer<T> {
void accept(T t, boolean b);
}
public static RubySymbol newIDSymbol(Ruby runtime, ByteList bytes) {
return newHardSymbol(runtime, bytes);
}
public static RubySymbol newIDSymbol(Ruby runtime, ByteList bytes, ObjBooleanConsumer<RubySymbol> handler) {
return newHardSymbol(runtime, bytes, handler);
}
public static RubySymbol newConstantSymbol(Ruby runtime, IRubyObject fqn, ByteList bytes) {
if (bytes.length() == 0) {
throw runtime.newNameError(str(runtime, "wrong constant name ", ids(runtime, fqn)), "");
}
RubySymbol symbol = runtime.newSymbol(bytes);
if (!symbol.validConstantName()) {
throw runtime.newNameError(str(runtime, "wrong constant name ", ids(runtime, fqn)), symbol.idString());
}
return symbol;
}
public static RubySymbol newSymbol(Ruby runtime, String name, Encoding encoding) {
RubySymbol newSymbol = runtime.getSymbolTable().getSymbol(RubyString.encodeBytelist(name, encoding));
return newSymbol;
}
public static RubySymbol newHardSymbol(Ruby runtime, String name, Encoding encoding) {
RubySymbol newSymbol = runtime.getSymbolTable().getSymbol(RubyString.encodeBytelist(name, encoding));
return newSymbol;
}
@Override
public Object constant() {
return constant == null ?
constant = OptoFactory.newConstantWrapper(IRubyObject.class, this) :
constant;
}
@Override
public IRubyObject inspect() {
return inspect(metaClass.runtime);
}
@JRubyMethod(name = "inspect")
public IRubyObject inspect(ThreadContext context) {
return inspect(context.runtime);
}
final RubyString inspect(final Ruby runtime) {
Encoding resenc = runtime.getDefaultInternalEncoding();
if (resenc == null) resenc = runtime.getDefaultExternalEncoding();
RubyString str = RubyString.newString(runtime, symbolBytes);
if (!(isPrintable(runtime) && (resenc.equals(symbolBytes.getEncoding()) || str.isAsciiOnly()) && isSymbolName19(symbol))) {
str = str.inspect(runtime);
}
ByteList result = new ByteList(str.getByteList().getRealSize() + 1);
result.setEncoding(str.getEncoding());
result.append((byte)':');
result.append(str.getBytes());
return RubyString.newString(runtime, result);
}
@Deprecated
public IRubyObject inspect19(ThreadContext context) {
return inspect(context);
}
@Override
public IRubyObject to_s() {
return to_s(metaClass.runtime);
}
@JRubyMethod
public IRubyObject to_s(ThreadContext context) {
return to_s(context.runtime);
}
final RubyString to_s(Ruby runtime) {
return RubyString.newStringShared(runtime, symbolBytes);
}
public IRubyObject id2name() {
return to_s(metaClass.runtime);
}
@JRubyMethod
public IRubyObject id2name(ThreadContext context) {
return to_s(context);
}
@Override
public RubyString asString() {
return to_s(metaClass.runtime);
}
@JRubyMethod(name = "===", required = 1)
@Override
public IRubyObject op_eqq(ThreadContext context, IRubyObject other) {
return context.runtime.newBoolean(this == other);
}
@JRubyMethod(name = "==", required = 1)
@Override
public IRubyObject op_equal(ThreadContext context, IRubyObject other) {
return context.runtime.newBoolean(this == other);
}
@Deprecated
@Override
public RubyFixnum hash() {
return metaClass.runtime.newFixnum(hashCode());
}
@JRubyMethod
public RubyFixnum hash(ThreadContext context) {
return context.runtime.newFixnum(hashCode());
}
@Override
public int hashCode() {
return hashCode;
}
public int getId() {
return id;
}
@Override
public boolean equals(Object other) {
return other == this;
}
@Override
public int compareTo(final IRubyObject that) {
if ( that instanceof RubySymbol ) {
return this.symbol.compareTo( ((RubySymbol) that).symbol );
}
return 0;
}
@JRubyMethod(name = { "to_sym", "intern" })
public IRubyObject to_sym() { return this; }
@Deprecated
public IRubyObject to_sym19() { return this; }
@Override
public IRubyObject taint(ThreadContext context) {
return this;
}
private RubyString newShared(Ruby runtime) {
return RubyString.newStringShared(runtime, symbolBytes);
}
@JRubyMethod(name = {"succ", "next"})
public IRubyObject succ(ThreadContext context) {
Ruby runtime = context.runtime;
return newSymbol(runtime, newShared(runtime).succ(context).asString().getByteList());
}
@JRubyMethod(name = "<=>")
@Override
public IRubyObject op_cmp(ThreadContext context, IRubyObject other) {
Ruby runtime = context.runtime;
return !(other instanceof RubySymbol) ? context.nil :
newShared(runtime).op_cmp(context, ((RubySymbol)other).newShared(runtime));
}
@JRubyMethod
public IRubyObject casecmp(ThreadContext context, IRubyObject other) {
Ruby runtime = context.runtime;
return !(other instanceof RubySymbol) ? context.nil :
newShared(runtime).casecmp(context, ((RubySymbol) other).newShared(runtime));
}
@JRubyMethod(name = "casecmp?")
public IRubyObject casecmp_p(ThreadContext context, IRubyObject other) {
Ruby runtime = context.runtime;
return !(other instanceof RubySymbol) ? context.nil :
newShared(runtime).casecmp_p(context, ((RubySymbol) other).newShared(runtime));
}
@JRubyMethod(name = "=~")
@Override
public IRubyObject op_match(ThreadContext context, IRubyObject other) {
return newShared(context.runtime).op_match(context, other);
}
@JRubyMethod(name = "match")
public IRubyObject match_m(ThreadContext context, IRubyObject other, Block block) {
return newShared(context.runtime).match19(context, other, block);
}
@JRubyMethod(name = "match")
public IRubyObject match_m(ThreadContext context, IRubyObject other, IRubyObject pos, Block block) {
return newShared(context.runtime).match19(context, other, pos, block);
}
@JRubyMethod(name = "match", required = 1, rest = true)
public IRubyObject match_m(ThreadContext context, IRubyObject[] args, Block block) {
return newShared(context.runtime).match19(context, args, block);
}
@JRubyMethod(name = "match?")
public IRubyObject match_p(ThreadContext context, IRubyObject other) {
return newShared(context.runtime).match_p(context, other);
}
@JRubyMethod(name = "match?")
public IRubyObject match_p(ThreadContext context, IRubyObject other, IRubyObject pos) {
return newShared(context.runtime).match_p(context, other, pos);
}
@JRubyMethod(name = {"[]", "slice"})
public IRubyObject op_aref(ThreadContext context, IRubyObject arg) {
return newShared(context.runtime).op_aref(context, arg);
}
@JRubyMethod(name = {"[]", "slice"})
public IRubyObject op_aref(ThreadContext context, IRubyObject arg1, IRubyObject arg2) {
return newShared(context.runtime).op_aref(context, arg1, arg2);
}
@JRubyMethod(name = {"length", "size"})
public IRubyObject length() {
final Ruby runtime = metaClass.runtime;
return RubyFixnum.newFixnum(runtime, newShared(runtime).strLength());
}
@JRubyMethod(name = "empty?")
public IRubyObject empty_p(ThreadContext context) {
return newShared(context.runtime).empty_p(context);
}
@JRubyMethod
public IRubyObject upcase(ThreadContext context) {
Ruby runtime = context.runtime;
return newSymbol(runtime, newShared(runtime).upcase(context).getByteList());
}
@JRubyMethod
public IRubyObject upcase(ThreadContext context, IRubyObject arg) {
Ruby runtime = context.runtime;
return newSymbol(runtime, newShared(runtime).upcase(context, arg).getByteList());
}
@JRubyMethod
public IRubyObject upcase(ThreadContext context, IRubyObject arg0, IRubyObject arg1) {
Ruby runtime = context.runtime;
return newSymbol(runtime, newShared(runtime).upcase(context, arg0, arg1).getByteList());
}
@JRubyMethod
public IRubyObject downcase(ThreadContext context) {
Ruby runtime = context.runtime;
return newSymbol(runtime, newShared(runtime).downcase(context).getByteList());
}
@JRubyMethod
public IRubyObject downcase(ThreadContext context, IRubyObject arg) {
Ruby runtime = context.runtime;
return newSymbol(runtime, newShared(runtime).downcase(context, arg).getByteList());
}
@JRubyMethod
public IRubyObject downcase(ThreadContext context, IRubyObject arg0, IRubyObject arg1) {
Ruby runtime = context.runtime;
return newSymbol(runtime, newShared(runtime).downcase(context, arg0, arg1).getByteList());
}
@JRubyMethod
public IRubyObject swapcase(ThreadContext context) {
Ruby runtime = context.runtime;
return newSymbol(runtime, newShared(runtime).swapcase(context).getByteList());
}
@JRubyMethod
public IRubyObject swapcase(ThreadContext context, IRubyObject arg) {
Ruby runtime = context.runtime;
return newSymbol(runtime, newShared(runtime).swapcase(context, arg).getByteList());
}
@JRubyMethod
public IRubyObject swapcase(ThreadContext context, IRubyObject arg0, IRubyObject arg1) {
Ruby runtime = context.runtime;
return newSymbol(runtime, newShared(runtime).swapcase(context, arg0, arg1).getByteList());
}
@JRubyMethod
public IRubyObject capitalize(ThreadContext context) {
Ruby runtime = context.runtime;
return newSymbol(runtime, newShared(runtime).capitalize(context).getByteList());
}
@JRubyMethod
public IRubyObject capitalize(ThreadContext context, IRubyObject arg) {
Ruby runtime = context.runtime;
return newSymbol(runtime, newShared(runtime).capitalize(context, arg).getByteList());
}
@JRubyMethod
public IRubyObject capitalize(ThreadContext context, IRubyObject arg0, IRubyObject arg1) {
Ruby runtime = context.runtime;
return newSymbol(runtime, newShared(runtime).capitalize(context, arg0, arg1).getByteList());
}
@JRubyMethod
public IRubyObject encoding(ThreadContext context) {
return context.runtime.getEncodingService().getEncoding(getEncoding());
}
@JRubyMethod
public IRubyObject to_proc(ThreadContext context) {
BlockBody body = new SymbolProcBody(context.runtime, symbol);
return RubyProc.newProc(context.runtime,
new Block(body, Block.NULL_BLOCK.getBinding()),
Block.Type.PROC);
}
public IRubyObject toRefinedProc(ThreadContext context, StaticScope scope) {
BlockBody body = new SymbolProcBody(context.runtime, symbol, scope);
return RubyProc.newProc(context.runtime,
new Block(body, Block.NULL_BLOCK.getBinding()),
Block.Type.PROC);
}
private static boolean isIdentStart(char c) {
return ((c >= 'a' && c <= 'z')|| (c >= 'A' && c <= 'Z') || c == '_' || !(c < 128));
}
private static boolean isIdentChar(char c) {
return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || c == '_' || !(c < 128));
}
private static boolean isIdentifier(String s) {
if (s.isEmpty() || !isIdentStart(s.charAt(0))) return false;
for (int i = 1; i < s.length(); i++) {
if (!isIdentChar(s.charAt(i))) return false;
}
return true;
}
private static boolean isSpecialGlobalName(String s) {
if (s.isEmpty()) return false;
int length = s.length();
switch (s.charAt(0)) {
case '~': case '*': case '$': case '?': case '!': case '@': case '/': case '\\':
case ';': case ',': case '.': case '=': case ':': case '<': case '>': case '\"':
case '&': case '`': case '\'': case '+': case '0':
return length == 1;
case '-':
return (length == 1 || (length == 2 && isIdentChar(s.charAt(1))));
default:
for (int i = 0; i < length; i++) {
if (!Character.isDigit(s.charAt(i))) return false;
}
}
return true;
}
private boolean isPrintable(final Ruby runtime) {
int p = symbolBytes.getBegin();
int end = p + symbolBytes.getRealSize();
byte[] bytes = symbolBytes.getUnsafeBytes();
Encoding enc = symbolBytes.getEncoding();
while (p < end) {
int c = codePoint(runtime, enc, bytes, p, end);
if (!enc.isPrint(c)) return false;
p += codeLength(enc, c);
}
return true;
}
private static boolean isSymbolName19(String s) {
if (s == null || s.length() < 1) return false;
int length = s.length();
char c = s.charAt(0);
return isSymbolNameCommon(s, c, length) ||
(c == '!' && (length == 1 ||
(length == 2 && (s.charAt(1) == '~' || s.charAt(1) == '=')))) ||
isSymbolLocal(s, c, length);
}
private static boolean isSymbolNameCommon(String s, char c, int length) {
switch (c) {
case '$':
if (length > 1 && isSpecialGlobalName(s.substring(1))) return true;
return isIdentifier(s.substring(1));
case '@':
int offset = 1;
if (length >= 2 && s.charAt(1) == '@') offset++;
return isIdentifier(s.substring(offset));
case '<':
return (length == 1 || (length == 2 && (s.equals("<<") || s.equals("<="))) ||
(length == 3 && s.equals("<=>")));
case '>':
return (length == 1) || (length == 2 && (s.equals(">>") || s.equals(">=")));
case '=':
return ((length == 2 && (s.equals("==") || s.equals("=~"))) ||
(length == 3 && s.equals("===")));
case '*':
return (length == 1 || (length == 2 && s.equals("**")));
case '+':
return (length == 1 || (length == 2 && s.equals("+@")));
case '-':
return (length == 1 || (length == 2 && s.equals("-@")));
case '|': case '^': case '&': case '/': case '%': case '~': case '`':
return length == 1;
case '[':
return s.equals("[]") || s.equals("[]=");
}
return false;
}
private static boolean isSymbolLocal(String s, char c, int length) {
if (!isIdentStart(c)) return false;
boolean localID = (c >= 'a' && c <= 'z');
int last = 1;
for (; last < length; last++) {
char d = s.charAt(last);
if (!isIdentChar(d)) break;
}
if (last == length) return true;
if (localID && last == length - 1) {
char d = s.charAt(last);
return d == '!' || d == '?' || d == '=';
}
return false;
}
@JRubyMethod(meta = true)
public static IRubyObject all_symbols(ThreadContext context, IRubyObject recv) {
return context.runtime.getSymbolTable().all_symbols();
}
@Deprecated
public static IRubyObject all_symbols(IRubyObject recv) {
return recv.getRuntime().getSymbolTable().all_symbols();
}
public static RubySymbol unmarshalFrom(UnmarshalStream input, UnmarshalStream.MarshalState state) throws java.io.IOException {
ByteList byteList = input.unmarshalString();
if (RubyString.scanForCodeRange(byteList) == CR_7BIT) {
byteList.setEncoding(USASCIIEncoding.INSTANCE);
}
RubySymbol result = newSymbol(input.getRuntime(), byteList,
(sym, newSym) -> {
input.registerLinkTarget(sym);
if (state.isIvarWaiting()) {
try {
input.unmarshalInt();
Encoding enc = input.getEncodingFromUnmarshaled(input.unmarshalObject());
if (enc == null) throw new RuntimeException("BUG: No encoding found in marshal stream");
if (newSym) sym.getBytes().setEncoding(enc);
state.setIvarWaiting(false);
} catch (Throwable t) {
Helpers.throwException(t);
}
}
});
return result;
}
@Override
public <T> T toJava(Class<T> target) {
if (target == String.class || target == CharSequence.class) {
return target.cast(symbol);
}
return super.toJava(target);
}
public static ByteList symbolBytesFromString(Ruby runtime, String internedSymbol) {
return new ByteList(ByteList.plain(internedSymbol), USASCIIEncoding.INSTANCE, false);
}
@Override
public Encoding getEncoding() {
return symbolBytes.getEncoding();
}
@Override
public void setEncoding(Encoding e) {
symbolBytes.setEncoding(e);
}
public static final class SymbolTable {
static final int DEFAULT_INITIAL_CAPACITY = 1 << 10;
static final int MAXIMUM_CAPACITY = 1 << 16;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
private final ReentrantLock tableLock = new ReentrantLock();
private volatile SymbolEntry[] symbolTable;
private int size;
private int threshold;
private final float loadFactor;
private final Ruby runtime;
public SymbolTable(Ruby runtime) {
this.runtime = runtime;
this.loadFactor = DEFAULT_LOAD_FACTOR;
this.threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
this.symbolTable = new SymbolEntry[DEFAULT_INITIAL_CAPACITY];
}
static final class SymbolEntry {
final int hash;
final String name;
final ByteList bytes;
final WeakReference<RubySymbol> symbol;
RubySymbol hardReference;
SymbolEntry next;
SymbolEntry(int hash, String name, ByteList bytes, RubySymbol symbol, SymbolEntry next, boolean hard) {
this.hash = hash;
this.name = name;
this.bytes = bytes;
this.symbol = new WeakReference<RubySymbol>(symbol);
this.next = next;
if (hard) hardReference = symbol;
}
public void setHardReference() {
if (hardReference == null) {
hardReference = symbol.get();
}
}
}
public RubySymbol getSymbol(String name) {
return getSymbol(name, false);
}
public RubySymbol getSymbol(String name, boolean hard) {
int hash = javaStringHashCode(name);
RubySymbol symbol = null;
for (SymbolEntry e = getEntryFromTable(symbolTable, hash); e != null; e = e.next) {
if (isSymbolMatch(name, hash, e)) {
if (hard) e.setHardReference();
symbol = e.symbol.get();
break;
}
}
if (symbol == null) symbol = createSymbol(name, symbolBytesFromString(runtime, name), hash, hard);
return symbol;
}
public RubySymbol getSymbol(ByteList bytes) {
return getSymbol(bytes, false);
}
public RubySymbol getSymbol(ByteList bytes, boolean hard) {
int hash = javaStringHashCode(bytes);
RubySymbol symbol = findSymbol(bytes, hash, hard);
if (symbol == null) {
bytes = bytes.dup();
symbol = createSymbol(
RubyEncoding.decodeISO(bytes),
bytes,
hash,
hard);
}
return symbol;
}
public RubySymbol getSymbol(ByteList bytes, ObjBooleanConsumer<RubySymbol> handler, boolean hard) {
int hash = javaStringHashCode(bytes);
RubySymbol symbol = findSymbol(bytes, hash, hard);
if (symbol == null) {
bytes = bytes.dup();
return createSymbol(bytes.toString(), bytes, handler, hash, hard);
}
handler.accept(symbol, false);
return symbol;
}
private RubySymbol findSymbol(ByteList bytes, int hash, boolean hard) {
RubySymbol symbol = null;
for (SymbolEntry e = getEntryFromTable(symbolTable, hash); e != null; e = e.next) {
if (isSymbolMatch(bytes, hash, e)) {
if (hard) e.setHardReference();
symbol = e.symbol.get();
break;
}
}
return symbol;
}
public RubySymbol fastGetSymbol(String internedName) {
return fastGetSymbol(internedName, false);
}
public RubySymbol fastGetSymbol(String internedName, boolean hard) {
RubySymbol symbol = null;
int hash = javaStringHashCode(internedName);
for (SymbolEntry e = getEntryFromTable(symbolTable, hash); e != null; e = e.next) {
if (isSymbolMatch(internedName, hash, e)) {
if (hard) e.setHardReference();
symbol = e.symbol.get();
break;
}
}
if (symbol == null) {
symbol = fastCreateSymbol(internedName, hard);
}
return symbol;
}
private static SymbolEntry getEntryFromTable(SymbolEntry[] table, int hash) {
return table[getIndex(hash, table)];
}
private static boolean isSymbolMatch(String name, int hash, SymbolEntry entry) {
return hash == entry.hash && name.equals(entry.name);
}
private static boolean isSymbolMatch(ByteList bytes, int hash, SymbolEntry entry) {
return hash == entry.hash && bytes.equals(entry.bytes);
}
private RubySymbol createSymbol(final String name, final ByteList value, final int hash, boolean hard) {
ReentrantLock lock = tableLock;
lock.lock();
try {
final SymbolEntry[] table = getTableForCreate();
final int index = getIndex(hash, table);
RubySymbol symbol = lookupSymbol(name, table, hash, index);
if (symbol == null) {
String internedName = name.intern();
symbol = new RubySymbol(runtime, internedName, value);
storeSymbol(value, hash, hard, table, index, symbol, internedName);
}
return symbol;
} finally {
lock.unlock();
}
}
private RubySymbol createSymbol(final String name, final ByteList value, ObjBooleanConsumer<RubySymbol> handler, final int hash, boolean hard) {
ReentrantLock lock = tableLock;
lock.lock();
try {
final SymbolEntry[] table = getTableForCreate();
final int index = getIndex(hash, table);
RubySymbol symbol = lookupSymbol(name, table, hash, index);
if (symbol == null) {
String internedName = name.intern();
symbol = new RubySymbol(runtime, internedName, value);
handler.accept(symbol, true);
storeSymbol(value, hash, hard, table, index, symbol, internedName);
} else {
handler.accept(symbol, false);
}
return symbol;
} finally {
lock.unlock();
}
}
private void storeSymbol(ByteList value, int hash, boolean hard, SymbolEntry[] table, int index, RubySymbol symbol, String internedName) {
table[index] = new SymbolEntry(hash, internedName, value, symbol, table[index], hard);
size++;
symbolTable = table;
}
private RubySymbol fastCreateSymbol(final String internedName, boolean hard) {
ReentrantLock lock;
(lock = tableLock).lock();
try {
final SymbolEntry[] table = getTableForCreate();
final int hash = internedName.hashCode();
final int index = getIndex(hash, table);
RubySymbol symbol = lookupSymbolByString(internedName, table, index);
if (symbol == null) {
symbol = new RubySymbol(runtime, internedName);
storeSymbol(symbol.getBytes(), hash, hard, table, index, symbol, internedName);
}
return symbol;
} finally {
lock.unlock();
}
}
private static int getIndex(int hash, SymbolEntry[] table) {
return hash & (table.length - 1);
}
private SymbolEntry[] getTableForCreate() {
return size > threshold ? rehash() : symbolTable;
}
private RubySymbol lookupSymbol(String name, SymbolEntry[] table, int hash, int index) {
RubySymbol symbol = null;
for (SymbolEntry last = null, curr = table[index]; curr != null; curr = curr.next) {
RubySymbol localSymbol = curr.symbol.get();
if (localSymbol == null) {
removeDeadEntry(table, index, last, curr);
if (hash != curr.hash || !name.equals(curr.name)) continue;
}
last = curr;
if (hash == curr.hash && name.equals(curr.name)) {
symbol = localSymbol;
break;
}
}
return symbol;
}
private RubySymbol lookupSymbolByString(String internedName, SymbolEntry[] table, int index) {
RubySymbol symbol = null;
for (SymbolEntry last = null, curr = table[index]; curr != null; curr = curr.next) {
RubySymbol localSymbol = curr.symbol.get();
if (localSymbol == null) {
removeDeadEntry(table, index, last, curr);
if (internedName != curr.name) continue;
}
last = curr;
if (internedName == curr.name) {
symbol = localSymbol;
break;
}
}
return symbol;
}
private void removeDeadEntry(SymbolEntry[] table, int index, SymbolEntry last, SymbolEntry e) {
if (last == null) {
table[index] = e.next;
} else {
last.next = e.next;
}
size--;
}
public RubySymbol lookup(long id) {
SymbolEntry[] table = symbolTable;
RubySymbol symbol;
for (int i = table.length; --i >= 0; ) {
for (SymbolEntry e = table[i]; e != null; e = e.next) {
symbol = e.symbol.get();
if (symbol != null && id == symbol.id) return symbol;
}
}
return null;
}
public RubyArray all_symbols() {
SymbolEntry[] table = this.symbolTable;
RubyArray array = runtime.newArray(this.size);
RubySymbol symbol;
for (int i = table.length; --i >= 0; ) {
for (SymbolEntry e = table[i]; e != null; e = e.next) {
symbol = e.symbol.get();
if (symbol != null) array.append(symbol);
}
}
return array;
}
public int size() {
return size;
}
private SymbolEntry[] rehash() {
SymbolEntry[] oldTable = symbolTable;
int oldCapacity = oldTable.length;
if (oldCapacity >= MAXIMUM_CAPACITY) return oldTable;
int newCapacity = oldCapacity << 1;
SymbolEntry[] newTable = new SymbolEntry[newCapacity];
threshold = (int)(newCapacity * loadFactor);
int sizeMask = newCapacity - 1;
SymbolEntry e;
for (int i = oldCapacity; --i >= 0; ) {
e = oldTable[i];
if (e == null) continue;
SymbolEntry next = e.next;
int idx = e.hash & sizeMask;
if (next == null) {
newTable[idx] = e;
} else {
SymbolEntry lastRun = e;
int lastIdx = idx;
for (SymbolEntry last = next;
last != null;
last = last.next) {
int k = last.hash & sizeMask;
if (k != lastIdx) {
lastIdx = k;
lastRun = last;
}
}
newTable[lastIdx] = lastRun;
for (SymbolEntry p = e; p != lastRun; p = p.next) {
int k = p.hash & sizeMask;
SymbolEntry n = newTable[k];
newTable[k] = new SymbolEntry(p.hash, p.name, p.bytes, p.symbol.get(), n, p.hardReference != null);
}
}
}
symbolTable = newTable;
return newTable;
}
@Deprecated
public RubySymbol lookup(String name) {
int hash = name.hashCode();
SymbolEntry[] table = symbolTable;
RubySymbol symbol = null;
SymbolEntry e = table[getIndex(hash, table)];
while (e != null) {
if (hash == e.hash && name.equals(e.name)) {
symbol = e.symbol.get();
if (symbol != null) break;
}
e = e.next;
}
return symbol;
}
@Deprecated
public void store(RubySymbol symbol) {
throw new UnsupportedOperationException();
}
}
private static int javaStringHashCode(String str) {
return str.hashCode();
}
public static int javaStringHashCode(ByteList iso8859) {
int h = 0;
int begin = iso8859.begin();
int end = begin + iso8859.realSize();
byte[] bytes = iso8859.unsafeBytes();
for (int i = begin; i < end; i++) {
int v = bytes[i] & 0xFF;
h = 31 * h + v;
}
return h;
}
@Override
public boolean shouldMarshalEncoding() {
Encoding enc = getMarshalEncoding();
return enc != USASCIIEncoding.INSTANCE && enc != ASCIIEncoding.INSTANCE;
}
@Override
public Encoding getMarshalEncoding() {
return symbolBytes.getEncoding();
}
public static String objectToSymbolString(IRubyObject object) {
if (object instanceof RubySymbol) {
return ((RubySymbol) object).idString();
}
if (object instanceof RubyString) {
return ((RubyString) object).getByteList().toString();
}
return object.convertToString().getByteList().toString();
}
private static final class SymbolProcBody extends ContextAwareBlockBody {
private final CallSite site;
public SymbolProcBody(Ruby runtime, String symbol) {
super(runtime.getStaticScopeFactory().getDummyScope(), Signature.OPTIONAL);
this.site = MethodIndex.getFunctionalCallSite(symbol);
}
public SymbolProcBody(Ruby runtime, String symbol, StaticScope scope) {
super(scope, Signature.OPTIONAL);
this.site = new RefinedCachingCallSite(symbol, scope, CallType.FUNCTIONAL);
}
private IRubyObject yieldInner(ThreadContext context, RubyArray array, Block blockArg) {
if (array.isEmpty()) {
throw context.runtime.newArgumentError("no receiver given");
}
final IRubyObject self = array.shift(context);
return site.call(context, self, self, array.toJavaArray(), blockArg);
}
@Override
public IRubyObject yield(ThreadContext context, Block block, IRubyObject[] args, IRubyObject self, Block blockArg) {
RubyProc.prepareArgs(context, block.type, this, args);
return yieldInner(context, RubyArray.newArrayMayCopy(context.runtime, args), blockArg);
}
@Override
public IRubyObject yield(ThreadContext context, Block block, IRubyObject value, Block blockArg) {
return yieldInner(context, ArgsUtil.convertToRubyArray(context.runtime, value, false), blockArg);
}
@Override
protected IRubyObject doYield(ThreadContext context, Block block, IRubyObject value) {
return yieldInner(context, ArgsUtil.convertToRubyArray(context.runtime, value, false), Block.NULL_BLOCK);
}
@Override
protected IRubyObject doYield(ThreadContext context, Block block, IRubyObject[] args, IRubyObject self) {
return yieldInner(context, RubyArray.newArrayMayCopy(context.runtime, args), Block.NULL_BLOCK);
}
@Override
public IRubyObject yieldSpecific(ThreadContext context, Block block, IRubyObject arg0) {
return site.call(context, arg0, arg0);
}
@Override
public IRubyObject yieldSpecific(ThreadContext context, Block block, IRubyObject arg0, IRubyObject arg1) {
return site.call(context, arg0, arg0, arg1);
}
@Override
public IRubyObject yieldSpecific(ThreadContext context, Block block, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) {
return site.call(context, arg0, arg0, arg1, arg2);
}
@Override
public String getFile() {
return site.methodName;
}
@Override
public int getLine() {
return -1;
}
@Override
public ArgumentDescriptor[] getArgumentDescriptors() {
return ArgumentDescriptor.ANON_REST;
}
}
}