/*
* Copyright 2016 Nabarun Mondal
* Licensed under the Apache 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.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.noga.njexl.lang.extension.oop;
import com.noga.njexl.lang.extension.TypeUtility;
import com.noga.njexl.lang.Debugger;
import com.noga.njexl.lang.extension.datastructures.ListSet;
import com.noga.njexl.lang.parser.ASTClassDef;
import com.noga.njexl.lang.parser.ASTMethodDef;
import com.noga.njexl.lang.parser.JexlNode;
import com.noga.njexl.lang.Interpreter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import com.noga.njexl.lang.extension.oop.ScriptClassBehaviour.TypeAware;
import com.noga.njexl.lang.extension.oop.ScriptClassBehaviour.Executable;
/**
* Created by noga on 08/04/15.
*/
public class ScriptClass implements TypeAware, Executable {
public static final String _ANCESTOR_ = "__anc__" ;
/**
* A wrapper for instance method instances
*/
public static final class MethodInstance implements Executable {
/**
* The instance which to pass as *me* pointer
*/
public final Executable executable ;
/**
* The name of the method
*/
public final String methodName ;
/**
* Creates such a wrapper
* @param exec executable object
* @param method method name
*/
public MethodInstance(Executable exec, String method){
executable = exec ;
methodName = method ;
}
@Override
public Object get(String prop) {
return this;
}
/**
* Calls the methodName
* @param method name of the method - ignored
* @param args to be passed as argument
* @return result object
*/
@Override
public Object execMethod(String method, Interpreter interpreter, Object[] args) {
return executable.execMethod(methodName,interpreter,args );
}
}
public static final String _INIT_ = "__new__";
public static final String _CL_INIT_ = "__class__";
public static final String SELF = "me";
Interpreter interpreter;
final Map<String,Object> statics;
public Map<String,Object> statics(){
return statics;
}
public Object get(String prop){
if ( statics.containsKey(prop )) {
return statics.get(prop);
}
try {
ScriptMethod sm = getMethod(prop);
if ( sm != null ){
if ( sm.instance ){
statics.put(prop,null); // can not return instance method...
return null;
}
Object o = new MethodInstance(this,prop);
statics.put(prop,o);
return o;
}
}catch (Exception e){
}
return null;
}
public Object set(String prop, Object value){
return statics.put(prop, value);
}
final Map<String, ScriptMethod> methods;
public Map getMethods() {
return methods;
}
ScriptMethod classInit;
protected void classInit(){
try{
classInit.invoke(this,this.interpreter,new Object[]{});
}catch (Exception e){
throw new Error(e);
}
}
ScriptMethod constructor;
public final boolean callAncestors;
final Map<String, ScriptClass> supers;
Set<ScriptClass> parents;
public Set<ScriptClass> parents(){
if ( parents == null ){
parents = new ListSet<>( supers.values());
}
return parents;
}
public Map getSupers() {
return supers;
}
protected void findMethods(JexlNode node) {
if (node instanceof ASTMethodDef) {
ScriptMethod methodDef = new ScriptMethod((ASTMethodDef) node);
methodDef.declaringObject = this;
methods.put(methodDef.name, methodDef);
} else {
int numChild = node.jjtGetNumChildren();
for (int i = 0; i < numChild; i++) {
findMethods(node.jjtGetChild(i));
}
}
}
public ScriptMethod getMethod(String method) throws Exception {
if (methods.containsKey(method)) {
return methods.get(method);
} else {
ScriptMethod m1;
ScriptMethod m2 = null;
for (ScriptClass sup : parents() ) {
m1 = sup.getMethod(method);
if (m1 != null) {
if (m2 == null) {
m2 = m1;
} else {
// m2 exists, we have diamond issue
throw new Exception("Ambiguous Method : '" + method + "' !");
}
}
}
return m2;
}
}
public final String ns;
public final String name;
public final String hash;
public String getName() {
return name;
}
public ScriptClassInstance instance(Interpreter interpreter, Object[] args) throws Exception {
ScriptClassInstance instance = new ScriptClassInstance(this, interpreter);
Set<String> superKeys = new HashSet<>(supers.keySet());
// invoke default constructors as
for (String n : superKeys) {
//direct call...
ScriptClass superClass = supers.get(n);
if (superClass == null) {
superClass = interpreter.resolveJexlClassName(n);
if (superClass == null) {
throw new ClassNotFoundException("Superclass : '" + n + "' not found!");
}
//if already added, do not add
if ( !supers.values().contains(superClass ) ){
// one time resolving of this
addSuper(superClass.ns, superClass.name, superClass);
}
}
if ( !callAncestors ) {
if (superClass.clazz == null) {
// this is a nJexl super class
//create only when we have no super of same type
boolean exists = instance.supers.containsKey( superClass.name ) ;
if ( !exists ) {
ScriptClassInstance superClassInstance = superClass.instance(interpreter, args);
instance.addSuper(superClassInstance);
}
} else {
// this is Java Super class
instance.addJSuper(superClass.name, superClass.clazz, args);
}
}
}
if (constructor != null) {
instance.execMethod(_INIT_, interpreter, args);
}
// return
return instance ;
}
protected void addSuper(String ns, String superName, ScriptClass value){
if ( this.ns.equals(ns)) {
supers.put(superName, value);
}
supers.put( ns +":" + superName, value);
}
public ScriptClass(Interpreter interpreter, ASTClassDef ref,String ns) {
this.interpreter = interpreter ;
this.ns = ns ;
name = ref.jjtGetChild(0).image;
methods = new HashMap<>();
supers = new HashMap<>();
int numChild = ref.jjtGetNumChildren();
for (int i = 1; i < numChild - 1; i++) {
int n = ref.jjtGetChild(i).jjtGetNumChildren();
String supNs = this.ns;
String superName = ref.jjtGetChild(i).jjtGetChild(0).image;
if (n > 1) {
supNs = superName;
superName = ref.jjtGetChild(i).jjtGetChild(1).image;
}
addSuper(supNs, superName, null);
}
findMethods(ref.jjtGetChild(numChild - 1));
constructor = methods.get(_INIT_);
String bodyText = Debugger.getText(ref);
// hack at best
callAncestors = bodyText.contains(_ANCESTOR_);
hash = TypeUtility.hash(bodyText);
clazz = null ;
statics = new ConcurrentHashMap<>();
this.interpreter.getContext().set(this.name,this);
classInit = methods.get(_CL_INIT_);
if ( classInit != null ){
classInit();
}
}
public final Class clazz;
public ScriptClass(Interpreter interpreter, String name, Object o,String ns){
this.interpreter = interpreter ;
Class c ;
if (o instanceof Class){
c = (Class)o ;
}else{
c = o.getClass();
}
clazz = c ;
this.ns = ns ;
this.name = name ;
hash = ns;
// we will do something about them later
methods = new HashMap<>();
supers = new HashMap<>();
statics = new ConcurrentHashMap<>();
callAncestors = false ;
}
@Override
public boolean isa(Object o) {
if ( o == null ){
return false ;
}
ScriptClass that = null;
if ( o instanceof ScriptClass){
that = (ScriptClass)o;
}
else if ( o instanceof ScriptClassInstance){
that = ((ScriptClassInstance)o).$;
}
if ( that != null){
// match body hash
if ( this.hash.equals(that.hash) ){
return true ;
}
}
else{
// do we have Java Supers?
Class clazz;
if ( o instanceof Class){
clazz = (Class)o;
}else{
clazz = o.getClass();
}
if ( this.clazz != null && clazz.isAssignableFrom(this.clazz)){
return true ;
}
}
// nothing, continue upward : may end in loop
for ( String n : supers.keySet()){
boolean s = supers.get(n).isa(o);
if ( s ){
return true ;
}
}
return false;
}
@Override
public String toString(){
return String.format("nClass %s:%s", ns, name) ;
}
@Override
public Object execMethod(String methodName, Interpreter i, Object[] args) {
try {
ScriptMethod scriptMethod = getMethod(methodName);
if ( scriptMethod == null ) {
throw new Error(new NoSuchMethodException(methodName));
}
interpreter.addFunctionNamespace(SELF,this);
return scriptMethod.invoke(this, interpreter, args);
}catch (Exception e){
throw new Error(e);
}
finally {
// do some housekeeping
interpreter.removeFunctionNamespace(SELF) ;
}
}
}