/*
**** BEGIN LICENSE BLOCK *****
* Version: EPL 2.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Eclipse Public
* License Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.eclipse.org/legal/epl-v20.html
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* Copyright (C) 2001 Chad Fowler
* Copyright (C) 2001 Alan Moore
* Copyright (C) 2001-2002 Benoit Cerrina
* Copyright (C) 2001-2004 Jan Arne Petersen
* Copyright (C) 2002-2004 Anders Bengtsson
* Copyright (C) 2004 Thomas E Enebo
* Copyright (C) 2004-2005 Charles O Nutter
* Copyright (C) 2004 Stefan Matthias Aust
* Copyright (C) 2006-2007 Miguel Covarrubias
* Copyright (C) 2007 William N Dortch
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the EPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the EPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****/
package org.jruby;
import com.headius.invokebinder.Binder;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.Consumer;
import jnr.constants.Constant;
import org.jcodings.Encoding;
import org.jcodings.specific.USASCIIEncoding;
import org.jruby.anno.AnnotationBinder;
import org.jruby.anno.AnnotationHelper;
import org.jruby.anno.FrameField;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyConstant;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JavaMethodDescriptor;
import org.jruby.anno.TypePopulator;
import org.jruby.api.Convert;
import org.jruby.api.JRubyAPI;
import org.jruby.api.Warn;
import org.jruby.embed.Extension;
import org.jruby.exceptions.LoadError;
import org.jruby.exceptions.RaiseException;
import org.jruby.exceptions.RuntimeError;
import org.jruby.internal.runtime.AbstractIRMethod;
import org.jruby.internal.runtime.methods.AliasMethod;
import org.jruby.internal.runtime.methods.AttrReaderMethod;
import org.jruby.internal.runtime.methods.AttrWriterMethod;
import org.jruby.internal.runtime.methods.DefineMethodMethod;
import org.jruby.internal.runtime.methods.DelegatingDynamicMethod;
import org.jruby.internal.runtime.methods.DynamicMethod;
import org.jruby.internal.runtime.methods.JavaMethod;
import org.jruby.internal.runtime.methods.NativeCallMethod;
import org.jruby.internal.runtime.methods.PartialDelegatingMethod;
import org.jruby.internal.runtime.methods.ProcMethod;
import org.jruby.internal.runtime.methods.RefinedMarker;
import org.jruby.internal.runtime.methods.RefinedWrapper;
import org.jruby.internal.runtime.methods.SynchronizedDynamicMethod;
import org.jruby.internal.runtime.methods.UndefinedMethod;
import org.jruby.ir.IRClosure;
import org.jruby.ir.IRMethod;
import org.jruby.ir.Interp;
import org.jruby.ir.JIT;
import org.jruby.javasupport.JavaUtil;
import org.jruby.javasupport.binding.MethodGatherer;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.CallType;
import org.jruby.runtime.ClassIndex;
import org.jruby.runtime.Constants;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.IRBlockBody;
import org.jruby.runtime.JavaSites;
import org.jruby.runtime.MethodFactory;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.PositionAware;
import org.jruby.runtime.Signature;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.backtrace.RubyStackTraceElement;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.callsite.CacheEntry;
import org.jruby.runtime.callsite.CachingCallSite;
import org.jruby.runtime.load.LoadService;
import org.jruby.runtime.marshal.MarshalDumper;
import org.jruby.runtime.marshal.MarshalLoader;
import org.jruby.runtime.opto.Invalidator;
import org.jruby.runtime.opto.OptoFactory;
import org.jruby.runtime.profile.MethodEnhancer;
import org.jruby.util.RubyStringBuilder;
import org.jruby.util.ByteList;
import org.jruby.util.ClassProvider;
import org.jruby.util.IdUtil;
import org.jruby.util.StringSupport;
import org.jruby.util.cli.Options;
import org.jruby.util.io.RubyInputStream;
import org.jruby.util.io.RubyOutputStream;
import org.jruby.util.log.Logger;
import org.jruby.util.log.LoggerFactory;
import static org.jruby.RubySymbol.newHardSymbol;
import static org.jruby.anno.FrameField.BACKREF;
import static org.jruby.anno.FrameField.BLOCK;
import static org.jruby.anno.FrameField.CLASS;
import static org.jruby.anno.FrameField.FILENAME;
import static org.jruby.anno.FrameField.LASTLINE;
import static org.jruby.anno.FrameField.LINE;
import static org.jruby.anno.FrameField.METHODNAME;
import static org.jruby.anno.FrameField.SCOPE;
import static org.jruby.anno.FrameField.SELF;
import static org.jruby.anno.FrameField.VISIBILITY;
import static org.jruby.api.Access.basicObjectClass;
import static org.jruby.api.Access.instanceConfig;
import static org.jruby.api.Access.loadService;
import static org.jruby.api.Access.objectClass;
import static org.jruby.api.Check.checkID;
import static org.jruby.api.Convert.asBoolean;
import static org.jruby.api.Convert.asFixnum;
import static org.jruby.api.Convert.asSymbol;
import static org.jruby.api.Convert.castAsHash;
import static org.jruby.api.Convert.castAsModule;
import static org.jruby.api.Create.allocArray;
import static org.jruby.api.Create.dupString;
import static org.jruby.api.Create.newArray;
import static org.jruby.api.Create.newEmptyArray;
import static org.jruby.api.Create.newString;
import static org.jruby.api.Define.defineModule;
import static org.jruby.api.Error.argumentError;
import static org.jruby.api.Error.frozenError;
import static org.jruby.api.Error.nameError;
import static org.jruby.api.Error.runtimeError;
import static org.jruby.api.Error.typeError;
import static org.jruby.api.Warn.warn;
import static org.jruby.api.Warn.warnDeprecated;
import static org.jruby.api.Warn.warning;
import static org.jruby.api.Warn.warningDeprecated;
import static org.jruby.runtime.Visibility.MODULE_FUNCTION;
import static org.jruby.runtime.Visibility.PRIVATE;
import static org.jruby.runtime.Visibility.PROTECTED;
import static org.jruby.runtime.Visibility.PUBLIC;
import static org.jruby.runtime.Visibility.UNDEFINED;
import static org.jruby.util.CommonByteLists.COLON_COLON;
import static org.jruby.util.RubyStringBuilder.str;
import static org.jruby.util.RubyStringBuilder.ids;
import static org.jruby.util.RubyStringBuilder.types;
@JRubyClass(name="Module")
public class RubyModule extends RubyObject {
private static final Logger LOG = LoggerFactory.getLogger(RubyModule.class);
// static { LOG.setDebugEnable(true); } // enable DEBUG output
private byte moduleFlags;
static final byte CACHEPROXY = 0b00000001;
static final byte NEEDSIMPL = 0b00000010;
static final byte REFINED_MODULE = 0b00000100;
static final byte IS_OVERLAID = 0b00001000;
static final byte SHARED_MODULE = 0b00010000;
static final byte INCLUDED_IN_REFINEMENT = 0b00100000;
static final byte TEMP_NAME = 0b01000000;
/** @deprecated external access to global object flags is going away */
@Deprecated(since = "10.0.3.0")
public static final int CACHEPROXY_F = CACHEPROXY;
/** @deprecated external access to global object flags is going away */
@Deprecated(since = "10.0.3.0")
public static final int NEEDSIMPL_F = NEEDSIMPL;
/** @deprecated external access to global object flags is going away */
@Deprecated(since = "10.0.3.0")
public static final int REFINED_MODULE_F = REFINED_MODULE;
/** @deprecated external access to global object flags is going away */
@Deprecated(since = "10.0.3.0")
public static final int IS_OVERLAID_F = IS_OVERLAID;
/** @deprecated external access to global object flags is going away */
@Deprecated(since = "10.0.3.0")
public static final int OMOD_SHARED = SHARED_MODULE;
/** @deprecated external access to global object flags is going away */
@Deprecated(since = "10.0.3.0")
public static final int INCLUDED_INTO_REFINEMENT = INCLUDED_IN_REFINEMENT;
/** @deprecated external access to global object flags is going away */
@Deprecated(since = "10.0.3.0")
public static final int TEMPORARY_NAME = TEMP_NAME;
public static final String BUILTIN_CONSTANT = "";
public static final ObjectAllocator MODULE_ALLOCATOR = RubyModule::new;
public static void finishModuleClass(RubyClass Module) {
Module.reifiedClass(RubyModule.class).
kindOf(new RubyModule.JavaClassKindOf(RubyModule.class)).
classIndex(ClassIndex.MODULE);
}
public static void finishCreateModuleClass(ThreadContext context, RubyClass Module) {
Module.defineMethods(context, RubyModule.class);
}
public void checkValidBindTargetFrom(ThreadContext context, RubyModule originModule, boolean fromBind) throws RaiseException {
// Module methods can always be transplanted
if (!originModule.isModule() && !hasModuleInHierarchy(originModule)) {
if (originModule instanceof MetaClass) {
throw typeError(context, "can't bind singleton method to a different class");
} else {
String thing = fromBind ? "an instance" : "a subclass"; // bind : define_method
throw typeError(context, "bind argument must be " + thing + " of ", originModule, "");
}
}
}
/**
* Get the ClassIndex for this class. Will be NO_CLASS for non-core types.
*/
public ClassIndex getClassIndex() {
return classIndex;
}
void set(int flag, boolean set) {
if (set) {
moduleFlags |= flag;
} else {
moduleFlags &= ~flag;
}
}
private boolean get(int flag) {
return (moduleFlags & flag) != 0;
}
/**
* Set the ClassIndex for this core class. Only used at boot time for core types.
*
* @param classIndex the ClassIndex for this type
* @deprecated Use {@link org.jruby.RubyModule#classIndex(ClassIndex)} instead.
*/
@Deprecated(since = "10.0.0.0")
void setClassIndex(ClassIndex classIndex) {
classIndex(classIndex);
}
@JRubyMethod
public IRubyObject autoload(ThreadContext context, IRubyObject symbol, IRubyObject file) {
final RubyString fileString = RubyFile.get_path(context, file);
if (fileString.isEmpty()) throw argumentError(context, "empty file name");
final String symbolStr = symbol.asJavaString();
if (!IdUtil.isValidConstantName(symbol)) {
throw nameError(context, "autoload must be constant name", symbol);
}
IRubyObject existingValue = fetchConstant(context, symbolStr);
if (existingValue != null && existingValue != RubyObject.UNDEF) return context.nil;
defineAutoload(context, symbolStr, fileString);
return context.nil;
}
@JRubyMethod(name = "autoload?")
public IRubyObject autoload_p(ThreadContext context, IRubyObject symbol) {
return hasAutoload(context, checkID(context, symbol).idString(), true);
}
@JRubyMethod(name = "autoload?")
public IRubyObject autoload_p(ThreadContext context, IRubyObject symbol, IRubyObject inherit) {
return hasAutoload(context, checkID(context, symbol).idString(), inherit.isTrue());
}
public IRubyObject hasAutoload(ThreadContext context, String idString, boolean inherit) {
for (RubyModule mod = this; mod != null; mod = mod.getSuperClass()) {
final IRubyObject loadedValue = mod.fetchConstant(context, idString);
if (loadedValue == UNDEF) {
final RubyString file;
Autoload autoload = mod.getAutoloadMap().get(idString);
// autoload has been evacuated
if (autoload == null) return context.nil;
// autoload has been completed
if (autoload.getValue() != null) return context.nil;
file = autoload.getPath();
// autoload is in progress on another thread
if (autoload.ctx != null && !autoload.isSelf(context)) return file;
// file load is in progress or file is already loaded
if (!loadService(context).featureAlreadyLoaded(file.asJavaString())) return file;
}
if (!inherit) break;
}
return context.nil;
}
@Override
public ClassIndex getNativeClassIndex() {
return ClassIndex.MODULE;
}
@Override
public boolean isModule() {
return true;
}
@Override
public boolean isClass() {
return false;
}
public boolean isSingleton() {
return false;
}
public static class KindOf {
public static final KindOf DEFAULT_KIND_OF = new KindOf();
public boolean isKindOf(IRubyObject obj, RubyModule type) {
RubyModule cl = obj.getMetaClass();
// Not sure how but this happens in test_objects_are_released_by_cache_map
if (cl == null) return false;
return cl.hasAncestor(type);
}
}
public static final class JavaClassKindOf extends RubyModule.KindOf {
private final Class klass;
public JavaClassKindOf(Class klass) {
this.klass = klass;
}
@Override
public boolean isKindOf(IRubyObject obj, RubyModule type) {
return klass.isInstance(obj);
}
}
public boolean isInstance(IRubyObject object) {
return kindOf.isKindOf(object, this);
}
public boolean hasAncestor(RubyModule type) {
return searchAncestor(type.getDelegate().getOrigin()) != null;
}
public Map getConstantMap() {
Map constants = getOrigin().constants;
return constants == null ? Collections.EMPTY_MAP : constants;
}
public Map getConstantMapForWrite() {
Map constants = this.constants;
if (constants == null) {
if (!CONSTANTS_HANDLE.compareAndSet(this, null, constants = new ConcurrentHashMap(2, 0.9f, 1))) {
constants = this.constants;
}
}
return constants;
}
/**
* AutoloadMap must be accessed after checking ConstantMap. Checking UNDEF value in constantMap works as a guard.
* For looking up constant, check constantMap first then try to get an Autoload object from autoloadMap.
* For setting constant, update constantMap first and remove an Autoload object from autoloadMap.
*/
protected Map getAutoloadMap() {
Map autoloads = this.autoloads;
return autoloads == null ? Collections.EMPTY_MAP : autoloads;
}
protected Map getAutoloadMapForWrite() {
Map autoloads = this.autoloads;
if (autoloads == null) {
if (!AUTOLOADS_HANDLE.compareAndSet(this, null, autoloads = new ConcurrentHashMap(2, 0.9f, 1))) {
autoloads = this.autoloads;
}
}
return autoloads;
}
private RubyClass.RubyClassSet getIncludingHierarchiesForRead() {
RubyClass.RubyClassSet includingHierarchies = this.includingHierarchies;
return includingHierarchies == null ? RubyClass.EMPTY_RUBYCLASS_SET : includingHierarchies;
}
protected RubyClass.RubyClassSet getIncludingHierarchiesForWrite() {
RubyClass.RubyClassSet includingHierarchies = this.includingHierarchies;
if (includingHierarchies == null) {
if (!INCLUDING_HIERARCHIES_HANDLE.compareAndSet(this, null, includingHierarchies = new RubyClass.WeakRubyClassSet(4))) {
includingHierarchies = this.includingHierarchies;
}
}
return includingHierarchies;
}
@SuppressWarnings("unchecked")
public void addIncludingHierarchy(IncludedModule hierarchy) {
getIncludingHierarchiesForWrite().addClass(hierarchy);
}
public final MethodHandle getIdTest() {
MethodHandle idTest = this.idTest;
if (idTest != null) return idTest;
return this.idTest = newIdTest();
}
protected final MethodHandle newIdTest() {
return Binder.from(boolean.class, ThreadContext.class, IRubyObject.class)
.insert(2, id)
.invoke(testModuleMatch);
}
/** separate path for MetaClass construction
*
*/
protected RubyModule(Ruby runtime, RubyClass metaClass, boolean objectSpace) {
super(runtime, metaClass, objectSpace);
id = runtime.allocModuleId();
// track module instances separately, since they don't descend from BasicObject
if (metaClass == runtime.getModule()) {
runtime.addModule(this);
}
// if (parent == null) parent = runtime.getObject();
set(NEEDSIMPL, !isClass());
updateGeneration(runtime);
if (runtime.getInstanceConfig().isProfiling()) {
cacheEntryFactory = new ProfilingCacheEntryFactory(runtime, NormalCacheEntryFactory);
} else {
cacheEntryFactory = NormalCacheEntryFactory;
}
// set up an invalidator for use in new optimization strategies
methodInvalidator = OptoFactory.newMethodInvalidator(this);
}
/** used by MODULE_ALLOCATOR and RubyClass constructors
*
*/
protected RubyModule(Ruby runtime, RubyClass metaClass) {
this(runtime, metaClass, runtime.isObjectSpaceEnabled());
}
/** standard path for Module construction
*
*/
public RubyModule(Ruby runtime) {
this(runtime, runtime.getModule());
}
public boolean needsImplementer() {
return get(NEEDSIMPL);
}
/** rb_module_new
*
*/
@Deprecated(since = "10.0.0.0")
public static RubyModule newModule(Ruby runtime) {
return new RubyModule(runtime);
}
@Deprecated(since = "10.0.0.0")
public static RubyModule newModule(Ruby runtime, String name, RubyModule parent, boolean setParent, String file, int line) {
return newModule(runtime.getCurrentContext(), name, parent, setParent, file, line);
}
/** rb_module_new/rb_define_module_id/rb_name_class/rb_set_class_path
*
* This is used by IR to define a new module.
*/
public static RubyModule newModule(ThreadContext context, String name, RubyModule parent, boolean setParent, String file, int line) {
RubyModule module = defineModule(context).baseName(name);
if (setParent) module.setParent(parent);
if (file != null) {
parent.setConstant(context, name, module, file, line);
} else {
parent.setConstant(context, name, module);
}
return module;
}
@Deprecated(since = "10.0.0.0")
public static RubyModule newModule(Ruby runtime, String name, RubyModule parent, boolean setParent) {
var context = runtime.getCurrentContext();
RubyModule module = new RubyModule(runtime).baseName(name);
if (setParent) module.setParent(parent);
parent.setConstant(context, name, module);
return module;
}
public static RubyModule newModuleBootstrap(Ruby runtime, String name, RubyModule parent) {
var module = new RubyModule(runtime).baseName(name);
parent.defineConstantBootstrap(name, module);
return module;
}
/**
* Return the class providers set for read operations.
*
* @return the class provider set or an empty set if there are no class providers
*/
private Set getClassProvidersForRead() {
Set classProviders = this.classProviders;
return classProviders == null ? Collections.EMPTY_SET : classProviders;
}
/**
* Return the class providers set for write operations.
*
* @return the class provider set suitable for updating
*/
private Set getClassProvidersForWrite() {
Set classProviders = this.classProviders;
if (classProviders == null) {
if (!CLASS_PROVIDERS_HANDLE.compareAndSet(this, null, classProviders = new CopyOnWriteArraySet())) {
classProviders = this.classProviders;
}
}
return classProviders;
}
// synchronized method per JRUBY-1173 (unsafe Double-Checked Locking)
// FIXME: synchronization is still wrong in CP code
public final synchronized void addClassProvider(ClassProvider provider) {
getClassProvidersForWrite().add(provider);
}
public final synchronized void removeClassProvider(ClassProvider provider) {
getClassProvidersForWrite().remove(provider);
}
private void checkForCyclicInclude(ThreadContext context, RubyModule m) throws RaiseException {
if (isSameOrigin(m)) throw argumentError(context, "cyclic include detected");
}
protected void checkForCyclicPrepend(ThreadContext context, RubyModule m) throws RaiseException {
if (isSameOrigin(m)) throw argumentError(context, getName(context) + " cyclic prepend detected " + m.getName(context));
}
private RubyClass searchProvidersForClass(ThreadContext context, String name, RubyClass superClazz) {
Set classProviders = this.classProviders;
if (classProviders == null) return null;
RubyClass clazz;
for (ClassProvider classProvider: classProviders) {
if ((clazz = classProvider.defineClassUnder(context, this, name, superClazz)) != null) {
return clazz;
}
}
return null;
}
private RubyModule searchProvidersForModule(ThreadContext context, String name) {
Set classProviders = this.classProviders;
if (classProviders == null) return null;
RubyModule module;
for (ClassProvider classProvider: classProviders) {
if ((module = classProvider.defineModuleUnder(context, this, name)) != null) {
return module;
}
}
return null;
}
/** Getter for property superClass.
* @return Value of property superClass.
*/
public RubyClass getSuperClass() {
return superClass();
}
/**
* @param superClass
* @deprecated Use {@link RubyModule#superClass(RubyClass)} instead.
*/
@Deprecated(since = "10.0.0.0")
public void setSuperClass(RubyClass superClass) {
superClass(superClass);
}
public RubyModule getParent() {
return parent;
}
public void setParent(RubyModule parent) {
this.parent = parent;
}
public RubyModule getMethodLocation() {
RubyModule methodLocation = this.methodLocation;
return methodLocation == null ? this : methodLocation;
}
public void setMethodLocation(RubyModule module){
methodLocation = module;
}
public Map getMethods() {
Map methods = this.methods;
return methods == null ? Collections.EMPTY_MAP : methods;
}
public Map getMethodsForWrite() {
Map methods = this.methods;
if (methods == null) {
if (!METHODS_HANDLE.compareAndSet(this, null, methods = new ConcurrentHashMap(2, 0.9f, 1))) {
methods = this.methods;
}
}
return methods;
}
@Deprecated(since = "10.0.0.0")
public DynamicMethod putMethod(Ruby runtime, String id, DynamicMethod method) {
return putMethod(getCurrentContext(), id, method);
}
/**
* Note: Internal API - only public as its used by generated code!
* @param context the current thread context
* @param id identifier string (8859_1). Matching entry in symbol table.
* @param method
* @return method
*/ // NOTE: used by AnnotationBinder
public DynamicMethod putMethod(ThreadContext context, String id, DynamicMethod method) {
RubyModule methodLocation = getMethodLocation();
if (hasPrepends()) {
method = method.dup();
method.setImplementationClass(methodLocation);
}
DynamicMethod oldMethod = methodLocation.getMethodsForWrite().put(id, method);
if (oldMethod != null && oldMethod.isRefined()) {
methodLocation.getMethodsForWrite().put(id, new RefinedWrapper(method.getImplementationClass(), method.getVisibility(), id, method));
}
context.runtime.addProfiledMethod(id, method);
context.runtime.invalidateBuiltin(methodLocation, id);
return method;
}
/**
* Is this module one that in an included one (e.g. an {@link IncludedModuleWrapper}).
* @see IncludedModule
*/
public boolean isIncluded() {
return false;
}
public boolean isPrepended() {
return false;
}
/**
* In an included or prepended module what is the ACTUAL module it represents?
* @return the actual module of an included/prepended module.
*/
public RubyModule getOrigin() {
return this;
}
public RubyModule getDelegate() {
return this;
}
/**
* Get the "real" module, either the current one or the nearest ancestor that is not a singleton or include wrapper.
*
* See {@link RubyClass#getRealClass()}.
*
* @return the nearest non-singleton non-include module in the hierarchy
*/
public RubyModule getRealModule() {
RubyModule cl = this;
while (cl != null &&
(cl.isSingleton() || cl.isIncluded())) {
cl = cl.superClass;
}
return cl;
}
/**
* Get the base name of this class, or null if it is an anonymous class.
*
* @return base name of the class
*/
public String getBaseName() {
return baseName;
}
/**
* Set the base name of the class. If null, the class effectively becomes
* anonymous (though constants elsewhere may reference it).
* @param name the new base name of the class
* @deprecated Use {@link org.jruby.RubyModule#baseName(String)} instead.
*/
@Deprecated(since = "10.0.0.0")
public void setBaseName(String name) {
baseName(name);
}
@Deprecated(since = "10.0.0.0")
public String getName() {
return getName(getCurrentContext());
}
/**
* Generate a fully-qualified class name or a #-style name for anonymous and singleton classes.
*
* Ruby C equivalent = "classname"
*
* @return The generated class name
*/
public String getName(ThreadContext context) {
return cachedName != null ? cachedName : calculateName(context);
}
@Deprecated(since = "10.0.0.0")
public RubyString rubyName() {
return rubyName(getCurrentContext());
}
/**
* Generate a fully-qualified class name or a #-style name for anonymous and singleton classes which
* is properly encoded. The returned string is always frozen.
*
* @return a properly encoded class name.
*
* Note: getId() is only really valid for ASCII values. This should be favored over using it.
*/
public RubyString rubyName(ThreadContext context) {
return cachedRubyName != null ? cachedRubyName : calculateRubyName(context);
}
@Deprecated(since = "10.0.0.0")
public RubySymbol symbolName() {
return symbolName(getCurrentContext());
}
/**
* Generate a fully-qualified class name or a #-style name as a Symbol. If any element of the path is anonymous,
* returns null.
*
* @return a properly encoded non-anonymous class path as a Symbol, or null.
*/
public RubySymbol symbolName(ThreadContext context) {
IRubyObject symbolName = cachedSymbolName;
if (symbolName == null) {
cachedSymbolName = symbolName = calculateSymbolName(context);
}
return symbolName == UNDEF ? null : (RubySymbol) symbolName;
}
@Deprecated(since = "10.0.0.0")
public RubyString rubyBaseName() {
return rubyBaseName(getCurrentContext());
}
public RubyString rubyBaseName(ThreadContext context) {
String baseName = getBaseName();
return baseName == null ? null : (RubyString) asSymbol(context, baseName).to_s(context);
}
private RubyString calculateAnonymousRubyName(ThreadContext context) {
RubyString anonBase = newString(context, "#"));
return (RubyString) anonBase.freeze(context);
}
private RubyString calculateRubyName(ThreadContext context) {
boolean cache = true;
if (getBaseName() == null) return calculateAnonymousRubyName(context); // no name...anonymous!
if (usingTemporaryName()) { // temporary name
cachedRubyName = (RubyString) asSymbol(context, baseName).toRubyString(context).freeze(context);
return cachedRubyName;
}
RubyClass Object = objectClass(context);
List parents = new ArrayList(5);
for (RubyModule p = getParent();
p != null && p != Object && p != this; // Break out of cyclic namespaces like C::A = C2; C2::A = C (jruby/jruby#2314)
p = p.getParent()) {
RubyString name = p.rubyBaseName(context);
// This is needed when the enclosing class or module is a singleton.
// In that case, we generated a name such as null::Foo, which broke
// Marshalling, among others. The correct thing to do in this situation
// is to insert the generate the name of form # if
// it's a singleton module/class, which this code accomplishes.
if (name == null) {
cache = false;
name = p.rubyName(context);
}
parents.add(name);
}
RubyString fullName = buildPathString(context, parents);
if (cache) cachedRubyName = (RubyString) fullName.freeze(context);
return fullName;
}
/**
* Calculate the module's name path as a Symbol. If any element in the path is anonymous, this will return null.
*
* Used primarily by Marshal.dump for class names.
*
* @return a non-anonymous class path as a Symbol, or null
*/
private IRubyObject calculateSymbolName(ThreadContext context) {
if (getBaseName() == null) return UNDEF;
if (usingTemporaryName()) return asSymbol(context, baseName);
RubyClass Object = objectClass(context);
List parents = new ArrayList(5);
for (RubyModule p = getParent();
p != null && p != Object && p != this; // Break out of cyclic namespaces like C::A = C2; C2::A = C (jruby/jruby#2314)
p = p.getParent()) {
RubyString name = p.rubyBaseName(context);
if (name == null) {
return UNDEF;
}
parents.add(name);
}
RubyString fullName = buildPathString(context, parents);
return fullName.intern(context);
}
private RubyString buildPathString(ThreadContext context, List parents) {
RubyString colons = newString(context, "::");
RubyString fullName = context.runtime.newString(); // newString creates empty ByteList which ends up as
fullName.setEncoding(USASCIIEncoding.INSTANCE); // ASCII-8BIT. 8BIT is unfriendly to string concats.
for (int i = parents.size() - 1; i >= 0; i--) {
RubyString rubyString = fullName.catWithCodeRange(parents.get(i));
rubyString.catWithCodeRange(colons);
}
fullName.catWithCodeRange(rubyBaseName(context));
fullName.setFrozen(true);
return fullName;
}
@Deprecated(since = "10.0.0.0")
public String getSimpleName() {
return getSimpleName(getCurrentContext());
}
/**
* Get the "simple" name for the class, which is either the "base" name or
* the "anonymous" class name.
*
* @return the "simple" name of the class
*/
public String getSimpleName(ThreadContext context) {
return baseName != null ? baseName : calculateAnonymousName(context);
}
/**
* Recalculate the fully-qualified name of this class/module.
*/
private String calculateName(ThreadContext context) {
boolean cache = true;
// we are anonymous, use anonymous name
if (getBaseName() == null) return calculateAnonymousName(context);
String name = getBaseName();
var Object = objectClass(context);
// First, we count the parents
int parentCount = 0;
for (RubyModule p = getParent() ; p != null && p != Object ; p = p.getParent()) {
// Break out of cyclic namespaces like C::A = C2; C2::A = C (jruby/jruby#2314)
if (p == this) break;
parentCount++;
}
// Allocate a String array for all of their names and populate it
String[] parentNames = new String[parentCount];
int i = parentCount - 1;
int totalLength = name.length() + parentCount * 2; // name length + enough :: for all parents
for (RubyModule p = getParent() ; p != null && p != Object ; p = p.getParent(), i--) {
// Break out of cyclic namespaces like C::A = C2; C2::A = C (jruby/jruby#2314)
if (p == this) break;
String pName = p.getBaseName();
// This is needed when the enclosing class or module is a singleton.
// In that case, we generated a name such as null::Foo, which broke
// Marshalling, among others. The correct thing to do in this situation
// is to insert the generate the name of form # if
// it's a singleton module/class, which this code accomplishes.
if(pName == null) {
cache = false;
pName = p.getName(context);
}
parentNames[i] = pName;
totalLength += pName.length();
}
// Then build from the front using a StringBuilder
StringBuilder builder = new StringBuilder(totalLength);
for (String parentName : parentNames) {
builder.append(parentName).append("::");
}
builder.append(name);
String fullName = builder.toString();
if (cache) cachedName = fullName;
return fullName;
}
private String calculateAnonymousName(ThreadContext context) {
String cachedName = this.cachedName;
if (cachedName == null) {
// anonymous classes get the # format
cachedName = this.cachedName = "#