# Java back end # Responsible for turning a jarfile name into an object holding a description/signature of # the jarfile contents suitable for reflecting on - this is roughly similar to a .Net Assembly. # Supporting this requires delving through java jar files and obtaining class info which we cant easily # do in a .Net program # so until we have a java implementation of the cobra compiler we instead rely on a java helper program # (see pkgSig.java and pkgSig.class, pkgSig.jar in the cobra distribution) # This is expected to have been run and creates what we call a pkgSignature file (pkgSig File) that is # named the same as a jarfile (or the same as a java package) with a '.sig' extension' and # contains an easily parseable text description of the classes and interfaces in the jarfile or package # this sigfile is expected to be in an obvious place, currently the cwd or same place as the cobra compiler executable.... # # e.g. 'rt.jar' (java runtime classes) has a sigfile 'rt.jar.sig' # 'java.lang' (java package) has a sigfile 'java.lang.sig' class JarSig var name = '' # jarfile or pkg var location = '' # abs pathname of sigFile var _javaTypes as List # lists of classes, interfaces or Enums in this jarfile or pkg shared var classByNameCache = Dictionary() """All classes found in all jars keyed by canonical/full name.""" var virtualNameRemaps = { 'java.lang.Decimal' : 'java.lang.Double', # BigDecimal eventually with (lotta) codegen support 'java.lang.UByte' : 'java.lang.Short', 'java.lang.UShort' : 'java.lang.Integer', 'java.lang.UInteger': 'java.lang.Long', 'java.lang.ULong' : 'java.lang.Long', } """ Remap unique virtualised java class name to an actual existing Java class name. """ def reset """Clear all static caches""" JarSig.classByNameCache.clear def lookupClassByCobraName(fullCobraName as String) as JavaClassType """ This is intended to be the only way to access the JarSig classCache contents from outside this file. """ assert fullCobraName[0].isUpper #fullCobraName = .virtualNameRemaps.get(fullCobraName, fullCobraName) fullName = _convertCobraName(fullCobraName) #print 'Lookup "[fullCobraName]" as "[fullName]"' if not JarSig.classByNameCache.containsKey(fullName) # dbg print 'Class [fullName] not in JarSig ClassName cache' #for k in JarSig.classByNameCache.keys, print k throw Exception('Cannot find class [fullName]') return JarSig.classByNameCache[fullName] def _convertCobraName(fullCobraName as String) as String #if fullCobraName.contains('<') # genericInstDef Aaa.Bbb.C # genParts = fullCobraName.split('<') # for i in 0 : genParts.length # genParts[i] = _xvertName(genParts[i]) # fullName = genParts.join('<') #else # fullName = _xvertName(fullCobraName) #return fullName # #def _xvertName(fullCobraName as String) as String #"""Turn names of form Aaa.Bbb.Class to aaa.bbb.Class. Cobra form to java.""" parts = fullCobraName.split('.') for i in 0: parts.length-1 parts[i] = parts[i][0].toLower.toString + parts[i][1:] fullName = parts.join('.') return fullName def lookupClass(fullName as String) as JavaClassType """ Lookup class by full/canonicalName. Used only for class resolution from within this file. """ if not JarSig.classByNameCache.containsKey(fullName) if fullName.startsWith(r'[') # [a.b.c; .addArrayClass(fullName) else if fullName.contains(r'<') # Generic a.b.c or a.b.c or a.b.c paramList = fullName.after('<') if _hasBoundParam(paramList), .addGenericInstClass(fullName) # shld this be a genericDefn ? else if paramList.contains('.'), .addGenericInstClass(fullName) else, return .findAliasGenericDefnClass(fullName) else if fullName.startsWith('?') or _hasBoundParam(fullName) or not fullName.contains('.') .addGenericParamClass(fullName) else print 'Dbg:JarSig.lookupClass - unknown class:"[fullName]".' #print 'Lookup class', fullName return JarSig.classByNameCache[fullName] # java bounded generic param '' or '' def _hasBoundParam(name as String) as bool return name.contains(' extends ') or name.contains(' super ') or name.contains('?') def addArrayClass(aName as String) """Array classes are synthesized on reference assuming element class already exists.""" #print 'Dbg crt ArrayClass "[aName]"' elName = aName.replace(r'[','') branch elName on 'Z', elName = 'boolean' on 'B', elName = 'byte' on 'I', elName = 'int' on 'S', elName = 'short' on 'J', elName = 'long' on 'C', elName = 'char' on 'D', elName = 'double' on 'F', elName = 'float' else if elName.startsWith('L') and elName.endsWith(';') elName = elName[1:-1] # e.g. Ljava.lang.Integer; else print 'Unknown arrayName [elName]' assert false, 'arrayName [elName]' if not JarSig.classByNameCache.containsKey(elName) if elName.contains('<') # a generic elNameRaw = elName.before('<') if JarSig.classByNameCache.containsKey(elNameRaw) elName = elNameRaw # rawType is superclass of Generic so find that else print 'Dbg: neither class "[elName]" nor class "[elNameRaw]" are in classByNameCache - element for "[aName]"' else print 'Dbg: class "[elName]" not in classByNameCache - element for "[aName]"' #elType = JarSig.classByNameCache[elName] elType = JarSig.lookupClass(elName) arrayCls = JavaClassType(aName, elType) # ArrayType JarSig.classByNameCache[aName] = arrayCls #print 'Dbg: added ArrayOf_[elName] "[aName]"' def addGenericInstClass(fullName as String) """ Generic class being instantiated. e.g. com.x.MyClass Synthesized (here) as a result of being referenced. """ #print 'Dbg crt GenericInstClass "[fullName]"' assert fullName.contains('<') and fullName.endsWith('>') canonName = fullName.before('<') assert canonName.contains('.') idx = canonName.lastIndexOf('.') gName = canonName[idx+1:] pkg = canonName[0:idx] paramTypes = fullName.after('<')[0:-1] # drop end '>' #print 'Dbg: GenericInstanceClass "[pkg]" "[gName]" "[paramTypes]' giCls = JavaClassType(gName, pkg, paramTypes) JarSig.classByNameCache[fullName] = giCls #JarSig.registerClassType(fullName, giCls) #print 'Dbg: added GenericInstanceClass "[fullName]"' def findAliasGenericDefnClass(fullName as String) as JavaClassType """ Generic class being used as a param where declared with different param list Generic class defn redeclared with a specific bound paramList e.g. com.x.MyClass, com.x.MyClass com.x.myClass, vs dcl as com.x.myClass Synthesized (here) as a result of being referenced. """ assert fullName.contains('<') and fullName.endsWith('>') canonName = fullName.before('<') #params = fullName.after('<')[0:-1] # drop end '>' #nParams = 1 + params.count(c',') # params.countOf(c',') #assert canonName.contains('.') #idx = canonName.lastIndexOf('.') #gDefName = canonName[idx+1:] + '`[nParams]' #pkg = canonName[0:idx] #print 'Dbg: GenericDefnClass "[canonName]" "[params]"' return JarSig.lookupClass(canonName) # Shld already exist but prob with different paramNames in paramList #gDefCls = JavaClassType(gDefName, pkg, params) #JarSig.classByNameCache[fullName] = gDefCls #JarSig._registerClassType(fullName, giCls) #print 'Dbg: added GenericDefinitionClass "[fullName]"' def addGenericParamClass(name as String) #print 'Dbg crt Generic Param Class "[name]"' gparamCls = JavaClassType(name, JavaType.GenericParam, '') JarSig.classByNameCache[name] = gparamCls #print 'Dbg: added Generic Parameter Class "[name]"' def sigFile(name as String) as String is shared """ Check that a sig file exists for the given name (jarfile or pkgname). Return the sig filename if found, InvalidOperationException if not """ assert not name.endsWith('.sig') sigFile = name + '.sig' if File.exists(sigFile) fileName = sigFile #if not Path.IsPathRooted(sigFile) # fileName = Path.getFullPath(sigFile) else ccPath = Path.getDirectoryName(CobraCore.exePath) to ! absName = Path.combine(ccPath, sigFile) if File.exists(absName) fileName = absName else throw InvalidOperationException('Cannot find a PkgSig file for "[name]" ("[sigFile]") in cwd or [ccPath].') return fileName cue init(jarName as String) base.init .name = jarName _javaTypes = List() _genTypesList _aliasPrimitives if jarName == 'rt.jar' # special for system jarfile # these name remaps will probably need some codegen special handling. _dupVirtuals _nonGenericCommon def _genTypesList jarName = if(.name.endsWith('.sig'), .name[:-4], .name) fileName = JarSig.sigFile(jarName) .location = fileName desc = List() for line in File.readAllLines(fileName) if line.trim.startsWith('#'), continue if line.length == 0 if desc.count == 0, continue jct = JavaClassType.makeFromDesc(desc) _registerClassType(jct) desc.clear continue desc.add(line) if desc.count >0 jct = JavaClassType.makeFromDesc(desc) _registerClassType(jct) def _registerClassType(jct as JavaClassType) canonName = jct.canonicalName if '`' in canonName # jct.isGeneric canonName = canonName.before('`') # canonName = canonName.split('`')[0] _registerClassType(canonName, jct) def _registerClassType(canonName as String, jct as JavaClassType) _javaTypes.add(jct) JarSig.classByNameCache[canonName] = jct #print 'Dbg: register [canonName]' def _aliasPrimitives if JarSig.classByNameCache.containsKey('void') return # ['void', 'boolean','byte', 'short', 'int', 'long', 'float', 'double'] JarSig.classByNameCache['void'] = JarSig.classByNameCache['java.lang.Void'] JarSig.classByNameCache['boolean'] = JarSig.classByNameCache['java.lang.Boolean'] JarSig.classByNameCache['char'] = JarSig.classByNameCache['java.lang.Character'] JarSig.classByNameCache['byte'] = JarSig.classByNameCache['java.lang.Byte'] JarSig.classByNameCache['short'] = JarSig.classByNameCache['java.lang.Short'] JarSig.classByNameCache['int'] = JarSig.classByNameCache['java.lang.Integer'] JarSig.classByNameCache['long'] = JarSig.classByNameCache['java.lang.Long'] #l = JarSig.classByNameCache['java.lang.Long'] #l = l.copy('long','') #JarSig.classByNameCache['long'] = l #_javaTypes.add(l) JarSig.classByNameCache['float'] = JarSig.classByNameCache['java.lang.Float'] JarSig.classByNameCache['double'] = JarSig.classByNameCache['java.lang.Double'] def _dupVirtuals # Make cache and jar export entries for virtualised types that dont exist in java # but cobra has and expects to be distinct. They get remapped to an existing Java Type for dupName in .virtualNameRemaps.keys dupOf = .virtualNameRemaps[dupName] _dupClassTo(dupOf, dupName) def _nonGenericCommon # spoof generic Instances for cobra non generic Types that java genericises # 'java.util.Collection' : 'java.util.Collection', #_dupClassTo( 'java.util.Collection', 'java.util.Collection') _makeGenericInstance('java.util.Collection') # for ICollection/Java.Util.Collection # probably others yet def _dupClassTo(dupOfName as String, asName as String) if JarSig.classByNameCache.containsKey(asName) # already cached ct = JarSig.classByNameCache[asName] else ct = JarSig.classByNameCache[dupOfName] parts = asName.split('.') name = parts[parts.length-1] package = parts[0:-1].join('.') # all but last ct = ct.copy(name, package) JarSig.classByNameCache[asName] = ct _javaTypes.add(ct) def _makeGenericInstance(fullName as String) # cached AND exposed to namespace assert fullName.contains('<') if not JarSig.classByNameCache.containsKey(fullName) # already cached .addGenericInstClass(fullName) giCls = JarSig.classByNameCache[fullName] #print giCls.canonicalName _javaTypes.add(giCls) def getExportedTypes as JavaClassType* return _javaTypes def toString as String is override """ For testing dump rep of jarSig""" sb = StringBuilder('[.name]: ') sb.append(' [_javaTypes.count] types') return sb.toString # # Below are classes describing JavaTypes (class), JavaFields, Java Ctors and JavaMethods # They are temporary placeholders until get a native implementation and direct access to Java RTL equivalents. enum JavaType NoType, JavaClass, JavaInterface, JavaEnum, GenericParam class JavaClassType # Simplified System Type for java classes, interface, enum """ Descriptor for a java class - equivalent of java.lang.Class for cobra in .Net""" shared var emptyClassList = List() var _emptyFieldList = List() var _emptyCtorList = List() var _emptyMethodList = List() var _emptyStringList = List() def genClassTypeList(names as List) as List """ Generate JavaClassType List for list of names.""" classList = List() for name in names cls = JarSig.lookupClass(name) classList.add(cls) return classList var name = '' var package = '' # ->? FlyWeight this - save some string space? var type = JavaType.NoType #class,interface, enum var superclass = '' var modifiers as List var interfaceNames as List var _interfaces as List = JavaClassType.emptyClassList var _nestedTypes as List = JavaClassType.emptyClassList var _fields as List = JavaClassType._emptyFieldList # JavaFieldInfo* var _props as List = JavaClassType._emptyFieldList # JavaFieldInfo* var _ctors as List = JavaClassType._emptyCtorList # JavaCtorInfo* var _methods as List = JavaClassType._emptyMethodList # JavaMethodInfo* var _indexer as JavaFieldInfo? # TODO split these out as subclass var _isGenericDefn = false var _isGenericInst = false var _isGenericParam = false var _genParamNames as List = JavaClassType._emptyStringList var _genParams as List = JavaClassType.emptyClassList # generic params list set genParamNames from var var _isArray = false var _arrayDimension = 0 var _arrayComponentType as JavaClassType? = nil def makeFromDesc(descriptor as List) as JavaClassType is shared """ Generate a populated JavaClassType from a string descriptor. Descriptor entry for a Type (as per pkgSig output), single entry per line ( or '-' ), entries nl terminated # Class, Interface or Enum #simple Name #comma separated # spc separated 'static', 'default', 'public', 'protected' var # 0 or more fields ... ctor # 0 or more constructors # comma sep list of typeNames ... method name # 0 or more methods modifiers returnType paramList throwsList ... Descriptor is parsed and info and various field, ctor, method and property lists populated. """ fieldList = List() propList = List() props = Dictionary() # cache for inferred propertyNames ctorList = List() methodList = List() lines = descriptor assert lines.count >= 7 # header - fixed length and format; absName, type, pkg, name, superclass, interfacesList, ModifiersList absName = _popTop(lines) # absolute class name #CobraCore.noOp(absName) branch _popTop(lines).toLower on 'class', type = JavaType.JavaClass on 'interface', type = JavaType.JavaInterface on 'enum', type = JavaType.JavaEnum else, type = JavaType.NoType pkg = _popTop(lines) name = _popTop(lines) isGen = name.contains('`') super = _popTop(lines) if super == '-', super = '' interfaces = _parseList(_popTop(lines)) modifiers = _parseList(_popTop(lines), [c' ']) if isGen, gParamNames = _parseList(_popTop(lines)) while true et = _popTop(lines) if et.length == 0, break if et.startsWith('var ') varName = et[3:].trim field = JavaFieldInfo() field.name = varName assert lines.count >= 2 # procField(et, subline, field, fieldList) field.modifiers = _parseList(_popTop(lines), [c' ']) field.typeName = _popTop(lines) if type == JavaType.JavaEnum and field.typeName == absName #'[pkg].[name]' t = _peekTop(lines) if t.length - t.trimStart.length > 8 field.value = _popTop(lines) #field.attributes = _parseList(_popTop(lines)) fieldList.add(field) else if et.startsWith('ctor ') ctorName = et[4:].trim ctor = JavaCtorInfo() ctor.name = ctorName assert lines.count >= 1 ctor.paramNames = _parseList(_popTop(lines)) # attributes ?? ctorList.add(ctor) else if et.startsWith('method ') methodName = et[6:].trim if methodName.endsWith(r'[V]') # hack encoding flags methodName = methodName[:-3] isVari = true method = JavaMethodInfo() method.name = methodName method.modifiers = _parseList(_popTop(lines), [c' ']) method.returnTypeName = _popTop(lines) assert method.returnTypeName <> '-' method.paramNames = _parseList(_popTop(lines)) method.throwsList = _parseList(_popTop(lines)) # attributes ?? if isVari, method.attributes = ['IsVari'] methodList.add(method) # map method getters and setters to property propName = .chkForProp(methodName, method) if propName _genProp(method, propName, propList, props) else print 'Unknown item in class: [et]' jct = JavaClassType(name, pkg, type, super, interfaces, modifiers) if isGen, jct.genParamNames = gParamNames jct._fields = fieldList jct._props = propList jct._ctors = ctorList jct._methods = methodList jct._fixIdxr return jct def _parseList(et as String) as List is shared return _parseList(et, [c',']) def _parseList(et as String, seps as List ) as List is shared l = List() if et == '-' return l if et.contains('<') start = gCnt = 0 for i in 0 : et.length c = et[i] if c == c'<', gCnt += 1 if c == c'>', gCnt -= 1 if gCnt == 0 and c in seps l.add(et[start:i].trim) start = i+1 if start < et.length, l.add(et[start:].trim) else a = et.split(seps.toArray) for s in a, l.add(s.trim) return l def _popTop(content as List) as String is shared while true if content.count == 0 return '' e = content[0] content.removeAt(0) if e.trim.startsWith('#') # commentline continue idx = e.indexOf('#') # trailing comment if idx >= 0 e = e[:idx].trimEnd if not e.length, continue break return e.trim def _peekTop(content as List) as String is shared if content.count == 0 return '' return content[0] # property: get and no paramList and returnType is Property Type or # is and no paramList and returnType is bool # and set and 1 parameter (value to set) def chkForProp(methodName as String, method as JavaMethodInfo) as String? is shared tag = methodName[:3] if tag in [ 'get', 'set'] and methodName.length > 3 and method.modifiers.contains('public') if tag == 'set' and method.paramNames.count <> 1 # setXXXX without a single param return nil return methodName[3:] if methodName.startsWith('is') and method.paramNames.count == 0 and method.returnTypeName == 'boolean' and method.modifiers.contains('public') return methodName[2:] return nil # unused old obsolete def _genProp0(method as JavaMethodInfo, propName as String, propList as List, props as Dictionary) as JavaFieldInfo is shared """ Create or update property fm getter/setter method and update the given List and Dict. """ assert method.name.startsWith('get') or method.name.startsWith('set') or method.name.startsWith('is') isSetProp = method.name.startsWith('set') isGetProp = not isSetProp #print method.name, propName if props.containsKey(propName) prop = props[propName] else # no existing property prop = JavaFieldInfo() # JavaPropInfo prop.name = propName prop.isProp = true prop.isIndexer = false prop.isReadable = prop.isWritable = false props[propName] = prop propList.add(prop) method.prop = prop #print '-- new Prop' if isGetProp prop.isReadable = true prop.getter = method if not prop.typeName.length and method.returnTypeName.length, prop.typeName = method.returnTypeName #prop.name0 = method.name #print '-- isGetProp' else #isSetProp prop.isWritable = true prop.setter = method #print '-- isSetProp' if not prop.typeName.length and method.paramNames.count, prop.typeName = method.paramNames[0] if not prop.modifiers.count or not prop.modifiers[0].length, prop.modifiers = method.modifiers if not prop.attributes.count, prop.attributes = method.attributes assert prop.name.length assert prop.typeName.length return prop def _genProp(method as JavaMethodInfo, propName as String, propList as List, props as Dictionary) as JavaFieldInfo is shared """ Create or update property fm getter/setter method and update the given List and Dict. """ assert method.name.startsWith('get') or method.name.startsWith('set') or method.name.startsWith('is') isSetProp = method.name.startsWith('set') isGetProp = not isSetProp #print method.name, propName if not props.containsKey(propName) # No existing property prop = _genAProp(propName, if(isGetProp, method, nil), if(isSetProp, method, nil), false) props[propName] = prop propList.add(prop) #print '-- new Prop' else prop = props[propName] if isGetProp prop.isReadable = true prop.getter = method if not prop.typeName.length and method.returnTypeName.length prop.typeName = method.returnTypeName #prop.name0 = method.name #print '-- isGetProp' else #isSetProp prop.isWritable = true prop.setter = method #print '-- isSetProp' if not prop.typeName.length and method.paramNames.count prop.typeName = method.paramNames[0] assert prop.name.length assert prop.typeName.length return prop def _genAProp(name as String, getMethod as JavaMethodInfo?, setMethod as JavaMethodInfo?, isIndexer as bool ) as JavaFieldInfo is shared """ Create a property given either or both getter/setter methods. """ prop = JavaFieldInfo() # JavaPropInfo prop.name = name prop.isProp = true prop.isIndexer = isIndexer if getMethod prop.isReadable = true prop.getter = getMethod if getMethod.returnTypeName.length, prop.typeName = getMethod.returnTypeName getMethod.prop = prop if not prop.modifiers.count or not prop.modifiers[0].length, prop.modifiers = getMethod.modifiers if not prop.attributes.count, prop.attributes = getMethod.attributes if setMethod prop.isWritable = true prop.setter = setMethod if not prop.typeName.length if isIndexer if setMethod.paramNames.count > 1, prop.typeName = setMethod.paramNames[1] else if setMethod.paramNames.count, prop.typeName = setMethod.paramNames[0] setMethod.prop = prop if not prop.modifiers.count or not prop.modifiers[0].length, prop.modifiers = setMethod.modifiers if not prop.attributes.count, prop.attributes = setMethod.attributes assert prop.name.length assert prop.typeName.length return prop # support Indexers on Strings and things supporting (java.util.) Map and List Interfaces def _fixIdxr # assumed Default name for indexer methods getterName, setterName = 'getItem', 'setItem' # special case naming weirdnesses of Java lib classes if .canonicalName == 'java.lang.String' getterName, setterName = 'charAt', '' else if .canonicalName.startsWith('java.util.List`') getterName, setterName = 'get', 'set' else if .canonicalName.startsWith('java.util.Map`') getterName, setterName = 'get', 'put' else for ifcName in .interfaceNames # TODO also chase up interface inheritance tree if ifcName.startsWith('java.util.List`') getterName, setterName = 'get', 'set' else if ifcName.startsWith('java.util.Map`') getterName, setterName = 'get', 'put' #TODO also check for specially annotated methods assert getterName.length getMethod, setMethod = _lookForMethods(getterName, setterName) if getMethod or setMethod idxrProp = _genAProp('[.canonicalName]_synthesizedIdxr', getMethod, setMethod, true) _props.add(idxrProp) _indexer = idxrProp #sb = StringBuilder('= ') #if getMethod # sb.append(getterName) # if setMethod, sb.append(',') #if setMethod # sb.append(setterName) #print 'dbg: [.canonicalName] [.getHashCode] indexerMethods [sb.toString] ' #else # print 'dbg: No IndexerMethods [getterName]/[setterName] on [.canonicalName]' #/ def _lookForMethods( getterName as String, setterName as String) as List getter as JavaMethodInfo? = nil setter as JavaMethodInfo? = nil for m in _methods if m.name == getterName, getter = m if m.name == setterName, setter = m return [getter, setter] def lookupIndexer as JavaFieldInfo? """ Search this class and superclass chain for an indexer. Return first hit or nil. Used to map indexing '[]' cobra code to java method calls. """ #depth=0 #print depth, .getHashCode, .canonicalName, .superclass idxr = .indexer if idxr return idxr super = .getSuperClass # otherwise try superclass chain while super idxr = super.indexer if idxr break super = super.getSuperClass return idxr cue init(name as String, pkg as String, type as JavaType, super as String, ifcs as List, modifiers as List ) """Create a normal Class or GenericClass Defn. MyClass or MyClass""" base.init assert not name.contains(".") .name = name .package = pkg .type = type .superclass = super .interfaceNames = ifcs .modifiers = modifiers _isGenericDefn = name.contains('`') # Defn Generic types have name suffix ` #print '[type] [pkg].[name]' assert .name.length assert .type <> JavaType.NoType if .type == JavaType.JavaClass and .canonicalName <> 'java.lang.Object' assert '.' in .superclass # canonical form assert .package.length cue init(name as String, elementType as JavaClassType) """Create an ArrayType.""" base.init .name = name .package = elementType.package .type = JavaType.JavaClass .superclass = 'java.lang.Object' # not really - closest is javax.openmanagment.mbean.ArrayType # maybe should fake up an Abstract ArrayBaseType providing methods, etc synthesized below .modifiers = elementType.modifiers .interfaceNames = JavaClassType._emptyStringList _isArray = true _arrayComponentType = elementType _arrayDimension = name.count(c'[') # has properties 'length' and method 'getLength' and indexer for get and set methodList = List() method = JavaMethodInfo() method.name = 'getLength' method.modifiers = ['public'] method.returnTypeName = 'int' method.paramNames = JavaClassType._emptyStringList methodList.add(method) propList = List() prop = JavaFieldInfo() prop.name = 'length' prop.typeName = 'int' prop.modifiers = ['public'] prop.isProp = true prop.isIndexer = false prop.isReadable = true method.prop = prop prop.getter = method prop.isWritable = false propList.add(prop) idxr = JavaFieldInfo() idxr.name = r'[]' idxr.typeName = name.replace(r'[', r'') idxr.modifiers = ['public'] idxr.isProp = true idxr.isIndexer = true idxr.isReadable = true idxr.isWritable = true # TODO getter return type same as typeName # TODO setter params List 'int' propList.add(idxr) _props = propList _methods = methodList assert .name.length assert .type <> JavaType.NoType if .type == JavaType.JavaClass and .canonicalName <> 'java.lang.Object' assert '.' in .superclass # canonical form assert .package.length assert _arrayDimension >0 assert _arrayComponentType assert .name.contains(r'[') cue init(name as String, pkg as String, paramTypes as String) """ Create a Generic Instantiation Type. Assumes Generic Defn Type already exists Like MyClass for MyClass. """ base.init #print 'JavaClassType.init([name])' assert not name.contains('.') #assert pkg.contains('.') gPart = '[pkg].[name]' paramList = JavaClassType._parseList(paramTypes) #print 'Looking for Generic Class [gPart]' genType = JarSig.lookupClass(gPart) assert genType.isGenericTypeDefinition .name = name .package = pkg # genType.package .type = genType.type .superclass = genType.canonicalName # not really but thats how we'll fake it #.superclass = 'java.lang.Object' .modifiers = genType.modifiers .package = genType.package _isGenericInst = true .interfaceNames = JavaClassType._emptyStringList _genParamNames= paramList #print '[type] [pkg].[name]' # print paramList assert .name.length assert .type <> JavaType.NoType assert '`' in .superclass # fake parentage to the Generic Definition assert .package.length assert genType.genericArgsCount == _genParamNames.count cue init(name as String, jType as JavaType, pkg as String) """Create a Generic Parameter Type. Like the T of MyClass""" base.init assert jType == JavaType.GenericParam assert not name.contains("<") and not name.contains(">") .name = name .type = jType .superclass = 'java.lang.Object' # not really but thats how we'll fake it .package = pkg if not .package.length, .package = 'java.lang' .modifiers = ['public'] _isGenericParam = true .interfaceNames = JavaClassType._emptyStringList #print '[type] [pkg].[name]' assert .name.length assert .type <> JavaType.NoType assert 'Object' in .superclass assert .package.length def copy( name as String, package as String) as JavaClassType c = .memberwiseClone to JavaClassType c.name = name c.package = package return c #pro name from var #pro package from var # ... get canonicalName as String """package and name""" assert .name.length return if(.package.length, '[.package].[.name]', .name) get packageAsCobra as String """ package name in Cobra Form - i.e each element Capitalized""" # cache this map in a class var rather than recalc each time parts = .package.split(c'.') cobraParts = List() for p in parts cobraParts.add(p.capitalized) return cobraParts.join('.') get isClass as bool return .type == JavaType.JavaClass get isInterface as bool return .type == JavaType.JavaInterface get isEnum as bool return .type == JavaType.JavaEnum get isNested as bool #return .flags.contains('N') return .name.contains('$') and not .name.startsWith('$') # temporary hack def addNestedType(type as JavaClassType) if _nestedTypes == JavaClassType.emptyClassList _nestedTypes = List() _nestedTypes.add(type) def getNestedTypes as JavaClassType* #List return _nestedTypes get declaringType as JavaClassType? if .isNested assert .name.contains('$') lidx = .canonicalName.lastIndexOf('$') declName = .canonicalName[:lidx] #trace .canonicalName, declName if JarSig.classByNameCache.containsKey(declName) return JarSig.lookupClass(declName) return nil get nestedName as String require .isNested assert .name.contains('$') lidx = .canonicalName.lastIndexOf('$') nestName = .canonicalName[lidx+1:] #trace .canonicalName, declName return nestName get isPublic return .modifiers.contains('public') get isProtected return .modifiers.contains('protected') get isPrivate return .modifiers.contains('private') get isDefault return .modifiers.contains('default') # = protected + package get isStatic return .modifiers.contains('static') get isFinal # sealed return .modifiers.contains('final') get isAbstract return .modifiers.contains('abstract') def getSuperClass as JavaClassType? if not .superclass.length return nil #return JvmNativeType(_type.getSuperClass) if JarSig.classByNameCache.containsKey(.superclass) return JarSig.lookupClass(.superclass) else if .superclass.contains('`') # isGeneric key = .superclass.before('`') return JarSig.lookupClass(key) else return JarSig.lookupClass('java.lang.Object') # e.g. Abstract base classes with default access - java.lang.AbstractStringBuilder #else # synth = SyntheticObject(.superclass) # JarSig.classByNameCache[.superclass] = synth # return synth get isValueType as bool return false # TODO true for unboxed primitives... get isArray as bool return _isArray get arrayComponentType as JavaClassType? #if not .isArray, return nil assert .isArray return _arrayComponentType get arrayDimension as int require .isArray assert _arrayDimension > 0 return _arrayDimension # Three forms of Generic Types: Definition ( MyClass) and Instantiation ( MyClass) and Param ( T, java.lang.String) # In java with runtime generic Type instantiation all we see from the jarfile directly are definitions (:-( # Walking the classes and getting the class(names) referenced (params, intefaces, returnTypes,...) # gives us the names of the instantiations which we synthesize as Types or Params for our purposes... # or at least thats the plan get isGenericType as bool return _isGenericDefn or _isGenericInst #or _isGenericParam get genericArgsCount as int assert .isGenericType #return _genArgNames.count # number of Generic args associated with this Generic Type # We use dotNet naming here where generic (defn) classes are named with trailing '`' if _isGenericDefn parts = .name.split(c'`') return int.parse(parts[1]) else if _isGenericInst return _genParamNames.count else return 0 get isGenericParameter as bool """ assumes generic parameters are instantiated as Types. Things like T,U,E,.. - basically simple names or qualified name java.lang.String or bounded (possibly wildcard) type - ? extends Type, T super Type , T extends Type """ return _isGenericParam get isGenericTypeDefinition as bool """ Can use this generic type to create new types if this type is a template/Defn form. e.g myClass defn allows creating new type like MyClass myClass is a generic definition new type like MyClass is a generic instance T,U and String, int are their respective generic params """ assert .isGenericType return _isGenericDefn def getGenericArguments as JavaClassType* #List assert .genericArgsCount assert _genParamNames.count if _genParamNames.count and not _genParams.count _genParams = JavaClassType.genClassTypeList(_genParamNames) return _genParams def getInterfaces as JavaClassType* #List if .interfaceNames.count and not _interfaces.count _interfaces = JavaClassType.genClassTypeList(.interfaceNames) return _interfaces def getFields as JavaFieldInfo* return _fields def getConstructors as JavaCtorInfo* return _ctors def getMethods as JavaMethodInfo* return _methods def getProperties as JavaFieldInfo* return _props def getAnnotations as IList # TODO when store annotations in .sigfile - return as a list of classes #return List() return @[] to IList get fields from var get props from var get ctors from var get methods from var get indexer from var #dbg def toMinString as String return '[.type] [.canonicalName]' def toString as String is override sb = StringBuilder() sb.append('[.type] [.canonicalName]') if _genParamNames.count sb.append('<') sep = '' for pName in _genParamNames sb.append(sep) sb.append(pName) sep = ',' sb.append('>') sb.append(' ') if .modifiers.count sb.append('is ') sb.append(.modifiers.join(',')) sb.append(' ') if .superclass.length sb.append('inherits [.superclass] ') if .interfaceNames.count sb.append('implements ') sb.append(.interfaceNames.join(',')) #sb.append(String.format(' -- {0} fields {1} props {2} ctors {3} methods', _fields.count, _props.count, _ctors.count, _methods.count)) return sb.toString def dumpFields for field in .getFields print field def dumpCtors for ctor in .getConstructors print 'ctor ', ctor.toString def dumpMethods for mthd in .getMethods print mthd def dumpProps for prop in .getProperties print prop def dumpIndexers assert false, 'dump Indexers NYI' class JavaMemberInfo """Baseclass for java fields and properties, constructors and methods""" var name = '' var _attributes as List? var modifiers = List() # public, default (=protected+pkg), protected, private pro attributes get if not _attributes _attributes = List() return _attributes set _attributes = value get isPublic return .modifiers.contains('public') get isProtected return .modifiers.contains('protected') get isPrivate return .modifiers.contains('private') get isDefault return .modifiers.contains('default') # = protected + package get isStatic return .modifiers.contains('static') get isFinal return .modifiers.contains('final') get isAbstract return .modifiers.contains('abstract') get isOverride # in java this is an annotation (at best), - via pkgSig treated as strings return .attributes.contains('Override') get isNonNullable if .isFinal return true # arbitrarily declare final members non nilable till get attributes working. return .attributes.contains('NotNull') or .attributes.contains('NonNull') or .attributes.contains('Nonnull') #for name in .attributes # in java these are annotations, - via pkgSig treated as strings # if name.endsWith == 'NotNull' or name.endsWith == 'NonNull' or name.endsWith == 'Nonnull' # return true #return false get isVari """ VarArgs arglist to a a method. Java sets this as a flag on the method but we convert it to a faked attribute on the methodInfo """ return .attributes.contains('IsVari') class JavaFieldInfo inherits JavaMemberInfo """Info for fields and properties.""" var typeName = '' var value = '' # pkgsig set value if final and initted var isProp = false var isIndexer = false var isReadable = true var isWritable = true var getter as JavaMethodInfo? var setter as JavaMethodInfo? #var name0 = '' # original name before tweaked for propertyname var _type as JavaClassType? get type as JavaClassType assert .typeName.length if .typeName.length and not _type _type = JarSig.lookupClass(.typeName) return _type to ! def getValue as Object # ?? return .value def getIndexParameters as List """ List of Types for indexes for an indexer Property.""" if not .isIndexer return JavaClassType.emptyClassList assert .getter return .getter.getParameters #if .setter # pars = List() var _params = JavaClassType.emptyClassList def getParameters as List if .paramNames.count and not _params.count _params = JavaClassType.genClassTypeList(.paramNames) return _params def toString as String is override sb = StringBuilder(.name) if .paramNames.count and .paramNames[0].length sb.append('(') sep='' for p in .paramNames sb.append(sep) sep =', ' sb.append(p) sb.append(')') # TODO attributes return sb.toString class JavaMethodInfo inherits JavaCtorInfo var returnTypeName = '' var throwsList = List() # list of String var prop as JavaFieldInfo? # if this is a get or set method for a prop refer to the property def returnType as JavaClassType? if not .returnTypeName.length return nil return JarSig.lookupClass(.returnTypeName) def getGenericArguments as List #print 'TODO getGenArgs on method [.name]' return JavaClassType.emptyClassList # TODO: support generics on Method def toString as String is override nampar = base.toString sb = StringBuilder('def [nampar] ') if .returnTypeName.length sb.append('as [.returnTypeName] ') else sb.append('as ??? ') if .modifiers.count sb.append('is ') sep='' for mod in .modifiers sb.append(sep) sep =',' sb.append(mod) if .prop getset = .name[:3] sb.append(" {[getset]ter for prop '[.prop.name]'} ") if .throwsList.count sb.append(' #throws') sep='' for thrw in .throwsList sb.append(sep) sep =',' sb.append(thrw) # TODO attributes return sb.toString