All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ochafik.lang.jnaerator.ObjectiveCGenerator Maven / Gradle / Ivy

Go to download

JNAerator (pronounce "generator") simply parses C and Objective-C headers and generates the corresponding JNA and Rococoa Java interfaces (it also has a very limited support for C++). This lets Java programmers access native libraries transparently, with full IDE support and little to no hand-tweaking. Users who are looking for ready-to-use libraries should check the NativeLibs4Java project instead.

There is a newer version: 0.12
Show newest version
/*
	Copyright (c) 2009-2011 Olivier Chafik, All Rights Reserved
	
	This file is part of JNAerator (http://jnaerator.googlecode.com/).
	
	JNAerator is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.
	
	JNAerator is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.
	
	You should have received a copy of the GNU Lesser General Public License
	along with JNAerator.  If not, see .
*/
package com.ochafik.lang.jnaerator;

import java.io.IOException;
import java.io.InputStream;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import com.ochafik.util.string.StringUtils;

import org.rococoa.cocoa.foundation.NSObject;
//import org.rococoa.cocoa.foundation.NSString;
import org.rococoa.Rococoa;
import org.rococoa.ObjCObject;

import static com.ochafik.lang.jnaerator.parser.ElementsHelper.*;

import com.ochafik.io.ReadText;
import com.ochafik.lang.jnaerator.parser.Arg;
import com.ochafik.lang.jnaerator.parser.Declaration;
import com.ochafik.lang.jnaerator.parser.DeclarationsHolder;
import com.ochafik.lang.jnaerator.parser.Declarator;
import com.ochafik.lang.jnaerator.parser.Enum;
import com.ochafik.lang.jnaerator.parser.Expression;
import com.ochafik.lang.jnaerator.parser.Function;
import com.ochafik.lang.jnaerator.parser.Identifier;
import com.ochafik.lang.jnaerator.parser.Modifier;
import com.ochafik.lang.jnaerator.parser.ModifierType;
import com.ochafik.lang.jnaerator.parser.Statement;
import com.ochafik.lang.jnaerator.parser.StoredDeclarations;
import com.ochafik.lang.jnaerator.parser.Struct;
import com.ochafik.lang.jnaerator.parser.TaggedTypeRefDeclaration;
import com.ochafik.lang.jnaerator.parser.TypeRef;
import com.ochafik.lang.jnaerator.parser.VariablesDeclaration;
import com.ochafik.lang.jnaerator.parser.Expression.AssignmentOp;
import com.ochafik.lang.jnaerator.parser.Expression.AssignmentOperator;
import com.ochafik.lang.jnaerator.parser.Expression.BinaryOperator;
import com.ochafik.lang.jnaerator.parser.Expression.Constant;
import com.ochafik.lang.jnaerator.parser.Expression.MemberRefStyle;
import com.ochafik.lang.jnaerator.parser.Statement.Block;
import com.ochafik.lang.jnaerator.parser.StoredDeclarations.TypeDef;
import com.ochafik.lang.jnaerator.parser.Struct.Type;
import com.ochafik.lang.jnaerator.parser.TypeRef.FunctionSignature;
import com.ochafik.lang.jnaerator.parser.TypeRef.SimpleTypeRef;
import com.ochafik.lang.jnaerator.parser.TypeRef.TaggedTypeRef;
import java.util.*;
import org.rococoa.ObjCClass;
import org.rococoa.cocoa.foundation.NSClass;

/*
include com/ochafik/lang/jnaerator/ObjectiveCStaticForwardsExcludeList.data
include com/ochafik/lang/jnaerator/ObjectiveCProtocolsForcedInheritanceList.data
 */
public class ObjectiveCGenerator {
	String 
		classClassName = "_class_",
		classInterfaceNameInCategoriesAndProtocols = "_static_", 
		classInstanceName = "_NSCLASS_",
		classInstanceGetterName = "getNSClass";
	boolean AUTO_RELEASE_IN_FACTORIES = false;

	static Map> protocolsForcedInheritance;
	public static Set getForcedProtocolParents(String protocolName) {
		if (protocolsForcedInheritance == null) {
			protocolsForcedInheritance = new HashMap>();
			try {
				InputStream in = ObjectiveCGenerator.class.getClassLoader().getResourceAsStream("com/ochafik/lang/jnaerator/ObjectiveCProtocolsForcedInheritanceList.data");
				List lines = ReadText.readLines(in);
				for (String line : lines) {
					line = line.trim();
					if (line.startsWith("//") || line.startsWith("#") || line.length() == 0)
						continue;
					String[] tks = line.split(":");
					protocolsForcedInheritance.put(tks[0], new TreeSet(Arrays.asList(tks[1].split(","))));
				}
			} catch (IOException ex) {
				ex.printStackTrace();
			}
		}
		Set ret = protocolsForcedInheritance.get(protocolName);
		return ret == null ? Collections.EMPTY_SET : ret;

	}
 	static Map> methodsExcludedFromStaticForwarding;
	public static boolean isMethodExcludedFromStaticForwarding(Function m) {
		if (methodsExcludedFromStaticForwarding == null) {
			methodsExcludedFromStaticForwarding = new HashMap>();
			try {
				InputStream in = ObjectiveCGenerator.class.getClassLoader().getResourceAsStream("com/ochafik/lang/jnaerator/ObjectiveCStaticForwardsExcludeList.data");
//				InputStream in = new FileInputStream("/Users/ochafik/Prog/Java/sources/com/ochafik/lang/jnaerator/ObjectiveCStaticForwardsExcludeList.data");
				List lines = ReadText.readLines(in);
				for (String line : lines) {
					line = line.trim();
					if (line.startsWith("//") || line.startsWith("#") || line.length() == 0)
						continue;
					String[] tks = line.split("\\|");
					String className = tks[0].trim(), methodSignature = tks[1].trim();
					Set set = methodsExcludedFromStaticForwarding.get(className);
					if (set == null)
						methodsExcludedFromStaticForwarding.put(className, set = new HashSet());
					set.add(methodSignature);
				}
			} catch (Exception ex) {
				ex.printStackTrace();
			}
		}
		if (!(m.getParentElement() instanceof Struct))
			return false;
		
		Struct s = (Struct)m.getParentElement();
		Identifier n = s.getTag();
		if (n != null && n.equals("NSObject"))// || n.equals("NSClass"))
			return true;
		
		String sig = m.computeSignature(false);
		if (DeclarationsConverter.getMethodsAndTheirSignatures(NSObject.class).getSecond().contains(sig))
			return true;
		
		String cn = s.getTag() == null ? "" : s.getTag().toString();
		Set set = methodsExcludedFromStaticForwarding.get(cn);
		if (set != null && set.contains(sig))
			return true;
		
		set = methodsExcludedFromStaticForwarding.get("");
		if (set != null && set.contains(sig))
			return true;
		
		return false;
	}
	public ObjectiveCGenerator(Result result) {
		this.result = result;
	}

	final Result result;

	public Struct getStruct(Identifier className, Struct.Type type) {
		return Result.getMap(result.classes, type).get(className);
	}
	public Identifier getPackageName(Struct struct) {
		if (struct == null)
			return null;
		
		String library = result.getLibrary(struct);
		Identifier javaPackage = result.getLibraryPackage(library);
		
		//if (struct.getType() == Struct.Type.ObjCClass) {
		//	String name = String.valueOf(struct.getTag());
			//if (name.equals("NSObject"))
			//	javaPackage = ident(NSObject.class.getPackage().getName().split("\\."));
			//else if (name.equals("NSClass"))
			//	javaPackage = ident(NSClass.class.getPackage().getName().split("\\."));
			//else if (name.equals("NSString"))
			//	javaPackage = ident(NSString.class.getPackage().getName().split("\\."));
		//}
		
		if (struct.getType() == Type.ObjCProtocol)
			javaPackage = ident(javaPackage, "protocols");
		else if (struct.getCategoryName() != null)
			javaPackage = ident(javaPackage, "categories");
		return javaPackage;
	}
	public Identifier getFullClassName(Struct struct) {
		if (struct == null)
			return null;
		Identifier javaPackage = getPackageName(struct);
		Identifier tag = struct.getTag();
		String categ = struct.getCategoryName();
		return ident(javaPackage, categ == null ? tag.clone() : ident(categ));
	}
	public void generateObjectiveCClasses() throws IOException {
		for (Struct in : Result.getMap(result.classes, Type.ObjCClass).values()) {
			outputObjectiveCClass(in);
		}
		for (Struct protocol : Result.getMap(result.classes, Type.ObjCProtocol).values()) {
			for (String parent : getForcedProtocolParents(String.valueOf(protocol.getTag())))
				protocol.addParent(ident(parent));
			outputObjectiveCClass(protocol);
		}
	}
	public void outputObjectiveCClass(Struct in) throws IOException {
		Identifier fullClassName = getFullClassName(in);
		Signatures signatures = new Signatures();

		Struct s = generateObjectiveCClass(in, signatures);
		result.notifyBeforeWritingClass(fullClassName, s, signatures, result.getLibrary(in));
		
		PrintWriter out = result.classOutputter.getClassSourceWriter(fullClassName.toString());
		result.printJavaClass(getPackageName(in), s, out);
		out.close();
	}
	static Identifier 
		NSObjectIdent = ident(NSObject.class),
		ObjCObjectIdent = ident(ObjCObject.class),
		ObjCClassIdent = ident(ObjCClass.class);
		//NSClassIdent = ident(NSClass.class);
	public Struct generateObjectiveCClass(Struct in, Signatures signatures) throws IOException {
		boolean isProtocol = in.getType() == Type.ObjCProtocol, isCategory = in.getCategoryName() != null;
		
		Struct instanceStruct = new Struct().addModifiers(ModifierType.Public);
		
		instanceStruct.setCommentBefore(in.getCommentBefore());
		instanceStruct.addToCommentBefore(in.getCommentAfter());
		instanceStruct.setTag(isCategory ? ident(in.getCategoryName()) : in.getTag().clone());
		if (isProtocol || isCategory)
			instanceStruct.setType(Type.JavaInterface);
		else
			instanceStruct.addModifiers(ModifierType.Abstract).setType(Type.JavaClass);
		
		Struct classStruct = new Struct();
		classStruct.setTag(ident(classClassName));
		classStruct.setType(Struct.Type.JavaClass);
		classStruct.addModifiers(ModifierType.Public, ModifierType.Static, ModifierType.Abstract);
		
		List
			interfacesForInstance = new ArrayList();

		List
			parentsForInstance = new ArrayList(in.getParents());

		//for (Identifier p : parentsForInstance)
		//	parentsForClass
		boolean isNSObject = in.getTag().equals(NSObject.class.getSimpleName());
		/*if (parentsForInstance.isEmpty()) {
			if (isProtocol || isCategory)
				parentsForInstance.add(ObjCObjectIdent);
			else
			if (!isNSObject)
				parentsForInstance.add(NSObjectIdent);
		}*/
		//interfacesForClass.add(ObjCClassIdent);
		
		if (!(isCategory || isProtocol))
			for (Struct catIn : Result.getMap(result.objCCategoriesByTargetType, in.getTag()).values()) {
				Identifier catId = getFullClassName(catIn);

				Identifier sim = catId.resolveLastSimpleIdentifier();
				String categName = catIn.getTag() + "_" + sim;
				if (add(instanceStruct, createCastMethod(ident(categName), catId, false), signatures))
					classStruct.addDeclaration(createCastMethod(ident(categName), ident(catId, classInterfaceNameInCategoriesAndProtocols), true));

				//interfacesForInstance.add(catId);
				//interfacesForClass.add(ident(catId, classInterfaceNameInCategoriesAndProtocols));
				outputObjectiveCClass(catIn);
			}	

		for (SimpleTypeRef sp : parentsForInstance) {
            Identifier p = sp.getName();
			String ps = p.toString();
			boolean basic = ps.toString().equals(ObjCObject.class.getName()) || ps.equals(NSObject.class.getName());
			Identifier id = basic ? p : result.typeConverter.findObjCClassIdent(p);
			//Identifier id = result.typeConverter.findObjCClassIdent(p);
			if (id != null || (!p.isPlain() && (id = p) != null)) {
				if (ps.toString().equals("NSObject"))
					instanceStruct.addProtocol(ident(ObjCObject.class));
				else
					instanceStruct.addParent(id.clone());
				if (!basic)
					classStruct.addParent(ident(id, classClassName));
			}
		}
		/*for (Identifier p : interfacesForClass) {
			boolean basic = p == ObjCClassIdent || p == NSObjectIdent;
			Identifier id = basic ? p : result.typeConverter.findObjCClassIdent(p);
			if (id != null)
				classStruct.addProtocol(p);
		}*/
		boolean isInterface = isProtocol || isCategory;

		if (instanceStruct.getParents().isEmpty()) {
			if (isInterface)
				instanceStruct.addParent(ident(ObjCObject.class));
			else if (isNSObject)
				instanceStruct.addProtocol(ident(ObjCObject.class));
			else
				instanceStruct.addParent(ident(NSObject.class));
		}
		if (classStruct.getParents().isEmpty()) {
			if (isNSObject)
				classStruct.addParent(ident(NSClass.class));
			else
				classStruct.addParent(ident(ident(NSObject.class), classClassName));
				//classStruct.addProtocol(ident(ObjCClass.class));
		}

		for (SimpleTypeRef p : in.getProtocols()) {
			Identifier id = getFullClassName(getStruct(p.getName(), Type.ObjCProtocol));
			if (id != null)
				interfacesForInstance.add(typeRef(id));
		}
		for (SimpleTypeRef id : interfacesForInstance) {
			if (isProtocol || isCategory)
				instanceStruct.addParent(id);
			else
				instanceStruct.addProtocol(id);
		}
		
//		CompoundCollection declarations = new CompoundCollection();
//		declarations.addComponent(in.getDeclarations());
//		for (Struct catIn : Result.getMap(result.objCCategoriesByTargetType, in.getTag()).values()) {
//			for (Declaration d : catIn.getDeclarations())
//				d.addToCommentBefore("From category " + catIn.getCategoryName());
//			declarations.addComponent(catIn.getDeclarations());
//			
//			if (catIn.getCommentBefore() != null)
//				instanceStruct.addToCommentBefore("

Category " + catIn.getTag()+ " : " + catIn.getCommentBefore() +"

"); // } StoredDeclarations classHolder = new VariablesDeclaration(); if (!(isProtocol || isCategory)) classHolder.addModifiers(ModifierType.Private, ModifierType.Static); classHolder.setValueType(typeRef(classClassName)); Expression.FunctionCall call = methodCall(expr(typeRef(Rococoa.class)), MemberRefStyle.Dot, "createClass"); call.addArgument(expr(in.getTag().toString())); call.addArgument(memberRef(expr(typeRef(classClassName)), MemberRefStyle.Dot, "class")); Function classGetter; if (isProtocol || isCategory) { classGetter = null; classHolder.addDeclarator(new Declarator.DirectDeclarator(classInstanceName, call)); } else { classHolder.addDeclarator(new Declarator.DirectDeclarator(classInstanceName)); classGetter = new Function(Function.Type.JavaMethod, ident(classInstanceGetterName), typeRef(classClassName)); classGetter.addModifiers(ModifierType.Public, ModifierType.Static); classGetter.setBody(new Block( new Statement.If( expr( varRef(classInstanceName), BinaryOperator.IsEqual, Constant.newNull() ), new Statement.ExpressionStatement( new AssignmentOp( varRef(classInstanceName), AssignmentOperator.Equal, call ) ), null ), new Statement.Return(varRef(classInstanceName)) )); } Struct classInterfaceStruct = null, structThatReceivesStaticMethods; if (isProtocol || isCategory) { structThatReceivesStaticMethods = classInterfaceStruct = new Struct(); classInterfaceStruct.setType(Struct.Type.JavaInterface); classInterfaceStruct.setTag(ident(classInterfaceNameInCategoriesAndProtocols)); classInterfaceStruct.addParent(ident(ObjCClass.class)); classStruct.addProtocol(ident(classInterfaceNameInCategoriesAndProtocols)); } else structThatReceivesStaticMethods = classStruct; if (!(isProtocol || isCategory)) { addAllocIfMissing(in, "alloc"); addAllocIfMissing(in, "new_"); } outputMembers(signatures, in, instanceStruct, structThatReceivesStaticMethods, in.getDeclarations(), isProtocol || isCategory); Identifier fullClassName = getFullClassName(in); /* if (!isProtocol && !isCategory) { // Output static proxies for static category methods for (Struct catIn : Result.getMap(result.objCCategoriesByTargetType, in.getTag()).values()) { for (Declaration d : catIn.getDeclarations()) { if (!(d instanceof Function)) continue; Function f = (Function)d; if (!f.hasModifier(ModifierType.Static)) continue; List decls = new ArrayList(); result.declarationsConverter.convertFunction(f, null, false, new DeclarationsHolder.ListWrapper(decls), fullClassName); if (f.getModifiers().contains(ModifierType.Static)) { for (Declaration decl : decls) { if (!(decl instanceof Function)) continue; Function pf = (Function)decl; if (!signatures.methodsSignatures.add(pf.computeSignature(false))) continue; //if (!add(classStruct, decl, signatures, objSigs, clasSigs)) // continue; instanceStruct.addDeclaration(createProxyCopy(pf, (Function)decl)); } } } } }*/ instanceStruct.addDeclaration(decl(classInterfaceStruct)); if (!isCategory) {// && !structThatReceivesStaticMethods.getDeclarations().isEmpty()) { instanceStruct.addDeclaration(new TaggedTypeRefDeclaration(classStruct)); instanceStruct.addDeclaration(classGetter); instanceStruct.addDeclaration(classHolder); } return instanceStruct; } Function createCastMethod(Identifier name, Identifier classId, boolean isStatic) { Function m = new Function(); m.setType(Function.Type.JavaMethod); m.addModifiers(ModifierType.Public); m.setName(ident("as" + (isStatic ? "Static_" : "_") + name)); m.setValueType(typeRef(classId.clone())); m.setBody(block( new Statement.Return( methodCall( expr(typeRef(Rococoa.class)), MemberRefStyle.Dot, "cast", varRef("this"), result.typeConverter.typeLiteral(typeRef(classId.clone())) ) ) )); return m; } private void addAllocIfMissing(Struct in, String allocName) { Identifier n = in.getTag(); if (n.equals("NSObject") || n.equals("NSClass")) return; boolean hasAlloc = false; for (Declaration d : in.getDeclarations()) { if (d instanceof Function) { Function f = (Function)d; if (f.getArgs().isEmpty() && allocName.equals(f.getName())) { hasAlloc = true; break; } } } if (!hasAlloc) in.addDeclaration(new Function(Function.Type.ObjCMethod, ident(allocName), typeRef(in.getTag())).addModifiers(ModifierType.Static)); } private void outputMembers(Signatures signatures, Struct in, Struct instanceStruct, Struct classStruct, List declarations, boolean isProtocol) throws IOException { Identifier fullClassName = getFullClassName(in); Set objSigs = DeclarationsConverter.getMethodsAndTheirSignatures(NSObject.class).getSecond(), clasSigs = new HashSet();//DeclarationsConverter.getMethodsAndTheirSignatures(NSClass.class).getSecond(); int[] iChild = new int[1]; for (Declaration d : declarations) { if (d instanceof Function) { Function f = (Function)d; List decls = new ArrayList(); result.declarationsConverter.convertFunction(f, null/*signatures*/, false, new DeclarationsHolder.ListWrapper(decls), fullClassName, -1); if (f.hasModifier(ModifierType.Static)) { for (Declaration decl : decls) { if (!add(classStruct, decl, signatures, objSigs, clasSigs)) continue; if (!isProtocol && decl instanceof Function) { instanceStruct.addDeclaration(createProxyCopy(f, (Function)decl)); signatures.addMethod((Function)decl); } if (classStruct.getType() == Type.JavaClass) { decl.addModifiers(ModifierType.Public, ModifierType.Abstract); decl.reorganizeModifiers(); } } } else { for (Declaration decl : decls) { if (!add(instanceStruct, decl, signatures, objSigs)) continue; if (!isProtocol && decl instanceof Function) { // Function addedF = createCreateCopyFromInit((Function)decl, instanceStruct); signatures.addMethod((Function)decl); instanceStruct.addDeclaration(addedF); if (instanceStruct.getType() == Type.JavaClass) { decl.addModifiers(ModifierType.Public, ModifierType.Abstract); decl.reorganizeModifiers(); } } } } // } else if (d instanceof VariablesDeclaration) { // result.declarationsConverter.convertVariablesDeclaration((VariablesDeclaration)d, instanceStruct, iChild, fullClassName); } else if (d instanceof TaggedTypeRefDeclaration) { TaggedTypeRef tr = ((TaggedTypeRefDeclaration) d).getTaggedTypeRef(); if (tr instanceof Struct) { result.declarationsConverter.outputConvertedStruct((Struct)tr, signatures, instanceStruct, fullClassName, null, false); } else if (tr instanceof Enum) { result.declarationsConverter.convertEnum((Enum)tr, signatures, instanceStruct, fullClassName); } } else if (d instanceof TypeDef) { TypeDef td = (TypeDef)d; TypeRef tr = td.getValueType(); if (tr instanceof Struct) { result.declarationsConverter.outputConvertedStruct((Struct)tr, signatures, instanceStruct, fullClassName, null, false); } else if (tr instanceof FunctionSignature) { result.declarationsConverter.convertCallback((FunctionSignature)tr, signatures, instanceStruct, fullClassName); } } iChild[0]++; } } private boolean add(Struct classStruct, Declaration decl, Signatures signatures, Set... additionalMethodSignatures) { if (decl instanceof Function) { String sig = ((Function)decl).computeSignature(false); for (Set sigs : additionalMethodSignatures) if (sigs.contains(sig)) return false; if (signatures.addMethod(sig)) { classStruct.addDeclaration(decl); return true; } else return false; } classStruct.addDeclaration(decl); return true; } /** * Create a createXXXWithYYY factory * @param meth * @param instanceStruct * @return */ private Function createCreateCopyFromInit(Function meth, TaggedTypeRef instanceStruct) { String name = meth.getName().toString(); if (!name.matches("^init([A-Z].*|)$")) return null; //if (name.startsWith("init") && (name.equals("init") || !Character.isUpperCase(name.charAt("init".length())))) Function createCopy = meth.clone(); createCopy.setCommentBefore("Factory method"); createCopy.addToCommentBefore("@see #" + meth.computeSignature(false)); createCopy.setName(ident("create" + name.substring("init".length()))); createCopy.addModifiers(ModifierType.Public, ModifierType.Static); createCopy.reorganizeModifiers(); Expression[] args = new Expression[meth.getArgs().size()]; int i = 0; for (Arg arg : meth.getArgs()) args[i++] = varRef(arg.getName()); Expression val = methodCall( methodCall( methodCall(null, null, classInstanceGetterName), Expression.MemberRefStyle.Dot, "alloc" ), Expression.MemberRefStyle.Dot, meth.getName().toString(), args ); if (AUTO_RELEASE_IN_FACTORIES) { val = methodCall(val, MemberRefStyle.Dot, "autorelease"); val = methodCall(expr(typeRef(Rococoa.class)), MemberRefStyle.Dot, "cast", val, memberRef(expr(typeRef(instanceStruct.getTag())), MemberRefStyle.Dot, "class") ) ; } createCopy.setBody(new Block(new Statement.Return(val))); return createCopy; } private Function createProxyCopy(Function originalMethod, Function meth) { if (isMethodExcludedFromStaticForwarding(originalMethod)) return null; Function proxyCopy = meth.clone(); proxyCopy.addModifiers(ModifierType.Public, ModifierType.Static); proxyCopy.reorganizeModifiers(); Expression[] args = new Expression[meth.getArgs().size()]; int i = 0; for (Arg arg : meth.getArgs()) args[i++] = varRef(arg.getName()); Expression val = methodCall(methodCall(null, null, classInstanceGetterName), Expression.MemberRefStyle.Dot, meth.getName().toString(), args); proxyCopy.setBody(new Block( meth.getValueType() == null || "void".equals(meth.getValueType().toString()) ? stat(val) : new Statement.Return(val) )); return proxyCopy; } // protected static _class_ _CLASS_ = org.rococoa.Rococoa.createClass("NSURL", _class_.class); // public abstract class _class_ extends // org.rococoa.NSClass { // //org.rococoa.NSObject._class_ { // } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy