package org.jruby.javasupport;
import org.jruby.IncludedModuleWrapper;
import org.jruby.MetaClass;
import org.jruby.Ruby;
import org.jruby.RubyBoolean;
import org.jruby.RubyClass;
import org.jruby.RubyModule;
import org.jruby.RubyString;
import org.jruby.RubySymbol;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.RaiseException;
import org.jruby.internal.runtime.methods.DynamicMethod;
import org.jruby.internal.runtime.methods.NullMethod;
import org.jruby.runtime.*;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ClassProvider;
import org.jruby.util.TypeConverter;
import static org.jruby.runtime.Visibility.PRIVATE;
@JRubyClass(name="Java::JavaPackage", parent="Module")
public class JavaPackage extends RubyModule {
static RubyModule createJavaPackageClass(final Ruby runtime, final RubyModule Java) {
RubyClass superClass = new BlankSlateWrapper(runtime, runtime.getModule(), runtime.getKernel());
RubyClass JavaPackage = RubyClass.newClass(runtime, superClass);
JavaPackage.setMetaClass(runtime.getModule());
JavaPackage.setAllocator(ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
((MetaClass) JavaPackage.makeMetaClass(superClass)).setAttached(JavaPackage);
JavaPackage.setBaseName("JavaPackage");
JavaPackage.setParent(Java);
Java.setConstant("JavaPackage", JavaPackage);
JavaPackage.defineAnnotatedMethods(JavaPackage.class);
return JavaPackage;
}
static RubyModule newPackage(Ruby runtime, CharSequence name, RubyModule parent) {
final JavaPackage pkgModule = new JavaPackage(runtime, name);
pkgModule.addClassProvider( JavaClassProvider.INSTANCE );
return pkgModule;
}
static CharSequence buildPackageName(final RubyModule parentPackage, final String name) {
return ((JavaPackage) parentPackage).packageRelativeName(name);
}
final String packageName;
private JavaPackage(final Ruby runtime, final CharSequence packageName) {
super(runtime, runtime.getJavaSupport().getJavaPackageClass());
this.packageName = packageName.toString();
}
@JRubyMethod(name = "package_name", alias = "to_s")
public RubyString package_name() {
return getRuntime().newString(packageName);
}
@Override
public RubyString to_s() { return package_name(); }
@Override
@JRubyMethod
public IRubyObject inspect() {
return getRuntime().newString(getName());
}
@Override
@JRubyMethod(name = "===")
public RubyBoolean op_eqq(ThreadContext context, IRubyObject obj) {
return context.runtime.newBoolean(obj == this || isInstance(obj));
}
@JRubyMethod(name = "const_missing", required = 1)
public IRubyObject const_missing(final ThreadContext context, final IRubyObject name) {
return relativeJavaClassOrPackage(context, name, false);
}
@JRubyMethod(name = "const_get", required = 1)
public final IRubyObject const_get(final ThreadContext context, final IRubyObject name) {
IRubyObject constant = getConstantNoConstMissing(name.toString(), false, false);
if ( constant != null ) return constant;
return relativeJavaClassOrPackage(context, name, false);
}
@JRubyMethod(name = "const_get", required = 2)
public final IRubyObject const_get(final ThreadContext context,
final IRubyObject name, final IRubyObject inherit) {
IRubyObject constant = getConstantNoConstMissing(name.toString(), inherit.isTrue(), false);
if ( constant != null ) return constant;
return relativeJavaClassOrPackage(context, name, false);
}
@Override
public final IRubyObject storeConstant(String name, IRubyObject value) {
assert value != null : "value is null";
ensureConstantsSettable();
return constantTableStore(name, value);
}
@Override
public final boolean hasConstant(String name) {
return constantTableContains(name);
}
@Override
public final IRubyObject fetchConstant(String name, boolean includePrivate) {
ConstantEntry entry = constantEntryFetch(name);
if (entry == null) return null;
if (entry.hidden && !includePrivate) {
throw getRuntime().newNameError("private constant " + getName() + "::" + name + " referenced", name);
}
return entry.value;
}
@Override
public final IRubyObject deleteConstant(String name) {
assert name != null : "name is null";
ensureConstantsSettable();
return constantTableRemove(name);
}
final CharSequence packageRelativeName(final CharSequence name) {
final int length = packageName.length();
final StringBuilder fullName = new StringBuilder(length + 1 + name.length());
fullName.append(packageName);
if ( length > 0 ) fullName.append('.');
return fullName.append(name);
}
private RubyModule relativeJavaClassOrPackage(final ThreadContext context,
final IRubyObject name, final boolean cacheMethod) {
return Java.getProxyOrPackageUnderPackage(context, this, name.toString(), cacheMethod);
}
RubyModule relativeJavaProxyClass(final Ruby runtime, final IRubyObject name) {
final String fullName = packageRelativeName( name.toString() ).toString();
final JavaClass javaClass = JavaClass.forNameVerbose(runtime, fullName);
return Java.getProxyClass(runtime, javaClass);
}
@JRubyMethod(name = "respond_to?")
public IRubyObject respond_to_p(final ThreadContext context, IRubyObject name) {
return respond_to(context, name, false);
}
@JRubyMethod(name = "respond_to?")
public IRubyObject respond_to_p(final ThreadContext context, IRubyObject name, IRubyObject includePrivate) {
return respond_to(context, name, includePrivate.isTrue());
}
private IRubyObject respond_to(final ThreadContext context, IRubyObject mname, final boolean includePrivate) {
RubySymbol name = TypeConverter.checkID(mname);
if (getMetaClass().respondsToMethod(name.idString(), !includePrivate)) return context.tru;
return context.nil;
}
private RubyBoolean checkMetaClassBoundMethod(final ThreadContext context, final String name, final boolean includePrivate) {
DynamicMethod method = getMetaClass().searchMethod(name);
if ( ! method.isUndefined() && ! method.isNotImplemented() ) {
if ( ! includePrivate && method.getVisibility() == PRIVATE ) {
return context.fals;
}
return context.tru;
}
return null;
}
@JRubyMethod(name = "respond_to_missing?")
public IRubyObject respond_to_missing_p(final ThreadContext context, IRubyObject name) {
return respond_to_missing(context, name, false);
}
@JRubyMethod(name = "respond_to_missing?")
public IRubyObject respond_to_missing_p(final ThreadContext context, IRubyObject name, IRubyObject includePrivate) {
return respond_to_missing(context, name, includePrivate.isTrue());
}
private RubyBoolean respond_to_missing(final ThreadContext context, IRubyObject mname, final boolean includePrivate) {
return context.runtime.newBoolean(BlankSlateWrapper.handlesMethod(TypeConverter.checkID(mname).idString()) == null);
}
@JRubyMethod(name = "method_missing")
public IRubyObject method_missing(ThreadContext context, final IRubyObject name) {
return Java.getProxyOrPackageUnderPackage(context, this, name.toString(), true);
}
@JRubyMethod(name = "method_missing", rest = true)
public IRubyObject method_missing(ThreadContext context, final IRubyObject[] args) {
if (args.length > 1) {
throw packageMethodArgumentMismatch(context.runtime, this, args[0].toString(), args.length - 1);
}
return method_missing(context, args[0]);
}
static RaiseException packageMethodArgumentMismatch(final Ruby runtime, final RubyModule pkg,
final String method, final int argsLength) {
String packageName = ((JavaPackage) pkg).packageName;
return runtime.newArgumentError(
"Java package '" + packageName + "' does not have a method `" +
method + "' with " + argsLength + (argsLength == 1 ? " argument" : " arguments")
);
}
public final boolean isAvailable() {
return Package.getPackage(packageName) != null;
}
@JRubyMethod(name = "available?")
public IRubyObject available_p(ThreadContext context) {
return context.runtime.newBoolean(isAvailable());
}
@JRubyMethod(name = "sealed?")
public IRubyObject sealed_p(ThreadContext context) {
final Package pkg = Package.getPackage(packageName);
if ( pkg == null ) return context.nil;
return context.runtime.newBoolean(pkg.isSealed());
}
@Override
@SuppressWarnings("unchecked")
public <T> T toJava(Class<T> target) {
if ( target.isAssignableFrom( Package.class ) ) {
return target.cast(Package.getPackage(packageName));
}
return super.toJava(target);
}
private static class JavaClassProvider implements ClassProvider {
static final JavaClassProvider INSTANCE = new JavaClassProvider();
public RubyClass defineClassUnder(RubyModule pkg, String name, RubyClass superClazz) {
if ( superClazz != null ) return null;
final String subPackageName = JavaPackage.buildPackageName(pkg, name).toString();
final Ruby runtime = pkg.getRuntime();
JavaClass javaClass = JavaClass.forNameVerbose(runtime, subPackageName);
return (RubyClass) Java.getProxyClass(runtime, javaClass);
}
public RubyModule defineModuleUnder(RubyModule pkg, String name) {
final String subPackageName = JavaPackage.buildPackageName(pkg, name).toString();
final Ruby runtime = pkg.getRuntime();
JavaClass javaClass = JavaClass.forNameVerbose(runtime, subPackageName);
return Java.getInterfaceModule(runtime, javaClass);
}
}
static final class BlankSlateWrapper extends IncludedModuleWrapper {
BlankSlateWrapper(Ruby runtime, RubyClass superClass, RubyModule delegate) {
super(runtime, superClass, delegate);
}
@Override
protected DynamicMethod searchMethodCommon(String id) {
if ("superclass".equals(id)) {
return new MethodValue(id, superClass);
}
return (id = handlesMethod(id)) != null ? superClass.searchMethodInner(id) : NullMethod.INSTANCE;
}
private static class MethodValue extends DynamicMethod {
private final IRubyObject value;
MethodValue(final String name, final IRubyObject value) {
super(name);
this.value = value;
}
public final IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args, Block block) {
return call(context, self, clazz, name);
}
@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule klazz, String name) {
return value;
}
@Override
public DynamicMethod dup() {
try {
return (DynamicMethod) super.clone();
}
catch (CloneNotSupportedException ex) {
throw new AssertionError(ex);
}
}
@Override
public Arity getArity() { return Arity.NO_ARGUMENTS; }
}
private static String handlesMethod(final String name) {
switch (name) {
case "class" : case "singleton_class" : return name;
case "object_id" : case "name" : return name;
case "const_set" : return name;
case "inspect" : case "to_s" : return name;
case "throw" : case "catch" :
return name;
case "singleton_method_added" :
case "singleton_method_undefined" :
case "singleton_method_removed" :
case "define_singleton_method" :
return name;
case "__constants__" : return "constants";
case "__methods__" : return "methods";
}
final int last = name.length() - 1;
if ( last >= 0 ) {
switch (name.charAt(last)) {
case '?' : case '!' : case '=' :
return name;
}
switch (name.charAt(0)) {
case '<' : case '>' : case '=' :
return name;
case '_' :
if ( last > 0 && name.charAt(1) == '_' ) {
return name;
}
}
}
return null;
}
@Override
public void addSubclass(RubyClass subclass) { }
}
}