[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/jruby/jruby/master/core/src/main/java/org/jruby/RubyData.java [Back]  [Original]

package org.jruby;

import org.jruby.anno.JRubyMethod;
import org.jruby.internal.runtime.methods.AttrReaderMethod;
import org.jruby.ir.operands.UndefinedValue;
import org.jruby.ir.runtime.IRRuntimeHelpers;
import org.jruby.runtime.Block;
import org.jruby.runtime.ClassIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.ivars.VariableAccessor;
import org.jruby.runtime.ivars.VariableTableManager;
import org.jruby.runtime.marshal.MarshalDumper;
import org.jruby.runtime.marshal.MarshalLoader;
import org.jruby.util.io.RubyInputStream;
import org.jruby.util.io.RubyOutputStream;

import java.util.LinkedHashSet;
import java.util.Map;
import java.util.stream.Collectors;

import static org.jruby.RubyBasicObject.rbInspect;
import static org.jruby.RubyHash.newSmallHash;
import static org.jruby.api.Convert.asFixnum;
import static org.jruby.api.Convert.toLong;
import static org.jruby.api.Convert.toSymbol;
import static org.jruby.api.Create.allocArray;
import static org.jruby.api.Create.newArray;
import static org.jruby.api.Create.newEmptyArray;
import static org.jruby.api.Create.newEmptyHash;
import static org.jruby.api.Create.newSmallHash;
import static org.jruby.api.Create.newString;
import static org.jruby.api.Define.defineClass;
import static org.jruby.api.Error.argumentError;
import static org.jruby.api.Error.keywordError;
import static org.jruby.api.Error.typeError;
import static org.jruby.ir.runtime.IRRuntimeHelpers.setCallInfo;
import static org.jruby.runtime.Arity.checkArgumentCount;
import static org.jruby.runtime.Helpers.invokedynamic;
import static org.jruby.runtime.ThreadContext.CALL_KEYWORD;
import static org.jruby.runtime.ThreadContext.hasNonemptyKeywords;
import static org.jruby.runtime.invokedynamic.MethodNames.HASH;
import static org.jruby.util.RubyStringBuilder.str;

public class RubyData {

    private static final String MEMBERS_KEY = "__members__";
    private static final String ACCESSORS_KEY = "__accessors__";

    public static RubyClass createDataClass(ThreadContext context, RubyClass Object) {
        RubyClass Data = defineClass(context, "Data", Object, ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR)
                .classIndex(ClassIndex.DATA)
                .defineMethods(context, RubyData.class);

        Data.getSingletonClass().undefMethods(context, "new");

        return Data;
    }

    @JRubyMethod(meta = true, rest = true)
    public static RubyClass define(ThreadContext context, IRubyObject self, IRubyObject[] args, Block block) {
        LinkedHashSet keySet = new LinkedHashSet();
        for (int i = 0 ; i < args.length ; i++) {
            RubySymbol mem = toSymbol(context, args[i]);
            if (mem.validAttrsetName()) {
                throw argumentError(context, "invalid data member: " + mem);
            }
            if (keySet.contains(mem)) {
                throw argumentError(context, "duplicate member: " + mem);
            }
            keySet.add(mem);
        }

        RubyClass dataClass = newDataStruct(context, (RubyClass) self, keySet);

        if (block.isGiven()) {
            dataClass.module_eval(context, block);
        }

        return dataClass;
    }

    @JRubyMethod(keywords = true, rest = true)
    public static void initialize(ThreadContext context, IRubyObject self, IRubyObject[] args) {
        ThreadContext.resetCallInfo(context); // we don't directly use callInfo here

        RubyBasicObject selfObj = (RubyBasicObject) self;

        selfObj.checkFrozen();
        RubyArray members = getStructMembers(self);
        int numMembers = members.size();

        if (args.length == 0) {
            if (numMembers > 0) {
                throw keywordError(context, "missing", members);
            }

            selfObj.setFrozen(true);
            return;
        }

        if (args.length > 1 || !(args[0] instanceof RubyHash)) {
            throw argumentError(context, args.length, 0, 0);
        }

        RubyHash hash = (RubyHash) args[0];

        if (hash.size() < numMembers) {
            RubyArray missing = (RubyArray) members.op_diff(context, hash.keys(context));
            throw keywordError(context, "missing", missing);
        }

        RubyArray[] unknownKeywordsPtr = {null};
        RubyClass metaClass = selfObj.getMetaClass();
        VariableTableManager vtm = getVariableManagerFromClass(metaClass);
        Map variableAccessors = vtm.getVariableAccessorsForRead();

        hash.visitAll(context, (c, h, k, v, i) -> {
            String keyString = toSymbol(context, k).idString();
            VariableAccessor variableAccessor = variableAccessors.get(keyString);
            if (variableAccessor != null) {
                variableAccessor.set(self, v);
            } else {
                RubyArray unknownKeywords = unknownKeywordsPtr[0];
                if (unknownKeywords == null) {
                    unknownKeywordsPtr[0] = unknownKeywords = newEmptyArray(context);
                }
                unknownKeywords.append(context, k);
            }
        });
        selfObj.setFrozen(true);
        if (unknownKeywordsPtr[0] != null) {
            throw keywordError(context, "unknown", unknownKeywordsPtr[0]);
        }
    }

    @JRubyMethod(name = "initialize_copy")
    public static IRubyObject initialize_copy(ThreadContext context, IRubyObject copy, IRubyObject original) {
        if (original != copy) {
            original.getMetaClass().getVariableTableManager().syncVariables((RubyBasicObject) copy, original);
        }

        copy.setFrozen(true);

        return copy;
    }

    @JRubyMethod(name = "==")
    public static IRubyObject op_equal(ThreadContext context, IRubyObject self, IRubyObject other) {
        return checkDataEquality(context, (RubyBasicObject) self, (RubyBasicObject) other, "==", RubyData::equalData);
    }

    @JRubyMethod(name = "eql?")
    public static IRubyObject eql(ThreadContext context, IRubyObject self, IRubyObject other) {
        return checkDataEquality(context, (RubyBasicObject) self, (RubyBasicObject) other, "eql?", RubyData::eqlData);
    }

    @JRubyMethod
    public static IRubyObject hash(ThreadContext context, IRubyObject self) {
        RubyBasicObject selfObj = (RubyBasicObject) self;

        int h = selfObj.getType().hashCode();
        VariableAccessor[] accessors = getStructAccessors(selfObj);
        for (int i = 0; i < accessors.length; i++) {
            h = (h  0) {
                str.catString(", ");
            } else if (first != '#') {
                str.catString(" ");
            }

            RubySymbol id = members.eltOk(i);
            if (id.validLocalVariableName() || id.validConstantName()) {
                str.append(id.fstring());
            } else {
                str.append(id.inspect(context));
            }
            str.catString("=");
            str.append(rbInspect(context, (IRubyObject) accessors[i].get(s)));
        }
        str.catString(">");

        return str;
    }

    private static RubyArray getStructMembers(IRubyObject s) {
        RubyClass metaClass = s.getMetaClass();
        return getMembersFromClass(metaClass);
    }

    private static RubyArray getMembersFromClass(RubyClass metaClass) {
        while (metaClass != null) {
            RubyArray members = (RubyArray) metaClass.getInternalVariable(MEMBERS_KEY);

            if (members != null) return members;

            metaClass = metaClass.getSuperClass();
        }

        throw new RuntimeException("non-Data attempted to access Data members");
    }

    private static VariableAccessor[] getStructAccessors(IRubyObject s) {
        RubyClass metaClass = s.getMetaClass();
        return getAccessorsFromClass(metaClass);
    }

    private static VariableAccessor[] getAccessorsFromClass(RubyClass metaClass) {
        while (metaClass != null) {
            VariableAccessor[] accessors = (VariableAccessor[]) metaClass.getInternalVariable(ACCESSORS_KEY);

            if (accessors != null) return accessors;

            metaClass = metaClass.getSuperClass();
        }

        throw new RuntimeException("non-Data attempted to access Data accessors");
    }

    private static VariableTableManager getVariableManagerFromClass(RubyClass metaClass) {
        while (metaClass != null) {
            VariableAccessor[] accessors = (VariableAccessor[]) metaClass.getInternalVariable(ACCESSORS_KEY);

            if (accessors != null) return metaClass.getVariableTableManager();

            metaClass = metaClass.getSuperClass();
        }

        throw new RuntimeException("non-Data attempted to access Data accessors");
    }

    private static IRubyObject hashData(ThreadContext ctx, RubyBasicObject state, IRubyObject obj, boolean recur) {
        return recur ? asFixnum(ctx, 0) : invokedynamic(ctx, obj, HASH);
    }

    private static IRubyObject eqlData(ThreadContext c, RubyBasicObject s, IRubyObject o, boolean recur) {
        if (recur) return c.tru;
        VariableAccessor[] accessors = getStructAccessors(s);
        for (int i = 0; i < accessors.length; i++) {
            VariableAccessor accessor = accessors[i];
            if (!RubyBasicObject.eqlInternal(c, ((RubyBasicObject) accessor.get(s)), (IRubyObject) accessor.get(o))) {
                return c.fals;
            }
        }

        return c.tru;
    }

    private static IRubyObject equalData(ThreadContext c, RubyBasicObject s, IRubyObject o, boolean recur) {
        if (recur) return c.tru;
        VariableAccessor[] accessors = getStructAccessors(s);
        for (int i = 0; i < accessors.length; i++) {
            VariableAccessor accessor = accessors[i];
            if (!RubyBasicObject.equalInternal(c, ((RubyBasicObject) accessor.get(s)), (IRubyObject) accessor.get(o))) {
                return c.fals;
            }
        }

        return c.tru;
    }
}

Web Proxy Viewer  |  New URL  |  Original Page