RubyClass.java

/*
 * Copyright (c) 2013, 2014 Oracle and/or its affiliates. All rights reserved. This
 * code is released under a tri EPL/GPL/LGPL license. You can use it,
 * redistribute it and/or modify it under the terms of the:
 *
 * Eclipse Public License version 1.0
 * GNU General Public License version 2
 * GNU Lesser General Public License version 2.1
 */
package org.jruby.truffle.runtime.core;

import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.nodes.Node;
import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.nodes.objects.Allocator;
import org.jruby.truffle.runtime.RubyContext;

import java.util.Collections;
import java.util.Set;
import java.util.WeakHashMap;

/**
 * Represents the Ruby {@code Class} class. Note that most of the functionality you might associate
 * with {@code Class} is actually in {@code Module}, implemented by {@link RubyModule}.
 */
public class RubyClass extends RubyModule {

    // TODO(CS): is this compilation final needed? Is it a problem for correctness?
    @CompilationFinal Allocator allocator = new RubyBasicObject.BasicObjectAllocator();

    private boolean isSingleton;
    private final Set<RubyClass> subClasses = Collections.newSetFromMap(new WeakHashMap<RubyClass, Boolean>());

    /**
     * This constructor supports initialization and solves boot-order problems and should not
     * normally be used from outside this class.
     */
    public static RubyClass createBootClass(RubyContext context, String name) {
        return new RubyClass(context, null, null, name, false);
    }

    public RubyClass(RubyContext context, RubyModule lexicalParent, RubyClass superclass, String name) {
        this(context, lexicalParent, superclass, name, false);
        // Always create a class singleton class for normal classes for consistency.
        ensureSingletonConsistency();
    }

    protected static RubyClass createSingletonClassOfObject(RubyContext context, RubyClass superclass, String name) {
        // We also need to create the singleton class of a singleton class for proper lookup and consistency.
        // See rb_singleton_class() documentation in MRI.
        return new RubyClass(context, null, superclass, name, true).ensureSingletonConsistency();
    }

    protected RubyClass(RubyContext context, RubyModule lexicalParent, RubyClass superclass, String name, boolean isSingleton) {
        super(context, context.getCoreLibrary().getClassClass(), lexicalParent, name, null);
        this.isSingleton = isSingleton;

        if (superclass != null) {
            unsafeSetSuperclass(superclass);
        }
    }

    public void setAllocator(Allocator allocator) {
        this.allocator = allocator;
    }

    public void initialize(RubyClass superclass) {
        unsafeSetSuperclass(superclass);
        ensureSingletonConsistency();
        allocator = superclass.allocator;
    }

    @Override
    public void initCopy(RubyModule other) {
        super.initCopy(other);
        assert other instanceof RubyClass;
    }

    private RubyClass ensureSingletonConsistency() {
        createOneSingletonClass();
        return this;
    }

    @Override
    public RubyClass getSingletonClass(Node currentNode) {
        // We also need to create the singleton class of a singleton class for proper lookup and consistency.
        // See rb_singleton_class() documentation in MRI.
        return createOneSingletonClass().ensureSingletonConsistency();
    }

    private RubyClass createOneSingletonClass() {
        CompilerAsserts.neverPartOfCompilation();

        if (metaClass.isSingleton()) {
            return metaClass;
        }

        RubyClass singletonSuperclass;

        if (getSuperClass() == null) {
            singletonSuperclass = getLogicalClass();
        } else {
            singletonSuperclass = getSuperClass().createOneSingletonClass();
        }

        metaClass = new RubyClass(getContext(),
                null, singletonSuperclass, String.format("#<Class:%s>", getName()), true);

        return metaClass;
    }

    /**
     * This method supports initialization and solves boot-order problems and should not normally be
     * used.
     */
    public void unsafeSetSuperclass(RubyClass newSuperclass) {
        RubyNode.notDesignedForCompilation();

        assert parentModule == null;

        unsafeSetParent(newSuperclass);
        newSuperclass.subClasses.add(this);

        newVersion();
    }

    public RubyBasicObject allocate(RubyNode currentNode) {
        return allocator.allocate(getContext(), this, currentNode);
    }

    public boolean isSingleton() {
        return isSingleton;
    }

    public RubyClass getSuperClass() {
        CompilerAsserts.neverPartOfCompilation();

        for (RubyModule ancestor : parentAncestors()) {
            if (ancestor instanceof RubyClass) {
                return (RubyClass) ancestor;
            }
        }

        return null;
    }

    public Allocator getAllocator() {
        return allocator;
    }

    public static class ClassAllocator implements Allocator {

        @Override
        public RubyBasicObject allocate(RubyContext context, RubyClass rubyClass, RubyNode currentNode) {
            return new RubyClass(context, null, null, null, false);
        }

    }

}