package org.scribble.codegen.java.endpointapi; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import org.scribble.ast.Module; import org.scribble.ast.global.GProtocolDecl; import org.scribble.codegen.java.util.JavaBuilder; import org.scribble.codegen.java.util.ClassBuilder; import org.scribble.codegen.java.util.ConstructorBuilder; import org.scribble.codegen.java.util.FieldBuilder; import org.scribble.codegen.java.util.MethodBuilder; import org.scribble.del.ModuleDel; import org.scribble.main.Job; import org.scribble.main.ScribbleException; import org.scribble.sesstype.name.GProtocolName; import org.scribble.sesstype.name.MessageId; import org.scribble.sesstype.name.Role; import org.scribble.visit.util.MessageIdCollector; public class SessionApiGenerator extends ApiGenerator { public static final String GPROTOCOLNAME_CLASS = "org.scribble.sesstype.name.GProtocolName"; public static final String OP_CLASS = "org.scribble.sesstype.name.Op"; public static final String ROLE_CLASS = "org.scribble.sesstype.name.Role"; public static final String SESSION_CLASS = "org.scribble.net.session.Session"; public static final String SESSIONTYPEFACTORY_CLASS = "org.scribble.sesstype.SessionTypeFactory"; private static final String IMPATH_FIELD = "IMPATH"; private static final String SOURCE_FIELD = "SOURCE"; private static final String PROTO_FIELD = "PROTO"; private final Set<Role> roles = new HashSet<>(); private final Set<MessageId<?>> mids = new HashSet<>(); private final ClassBuilder cb = new ClassBuilder(); private final Map<String, ClassBuilder> classes = new HashMap<>(); // All classes in same package, for protected constructor access public SessionApiGenerator(Job job, GProtocolName fullname) throws ScribbleException { super(job, fullname); constructRoleClasses(); constructOpClasses(); constructSessionClass(); // Depends on the above two being done first } @Override public Map<String, String> generateApi() { String simpname = getSessionClassName(this.gpn); //String path = getPackageName(this.gpn).replace('.', '/') + "/" + simpname + ".java"; String path = makePath(this.gpn, simpname); StringBuilder sb = new StringBuilder(this.cb.build()); //this.roles.values().forEach((cb) -> sb.append("\n\n").append(cb.generate()) ); //this.mids.values().forEach((cb) -> sb.append("\n\n").append(cb.generate()) ); Map<String, String> map = new HashMap<>(); map.put(path, sb.toString()); this.classes.keySet().stream().forEach((n) -> map.put(n, this.classes.get(n).build())); return map; } private static String makePath(GProtocolName gpn, String simpname) { return getEndpointApiRootPackageName(gpn).replace('.', '/') + "/" + simpname + ".java"; } /*public Map<MessageId<?>, String> getOpClasses() { return this.mids; }*/ private void constructSessionClass() { String packname = getEndpointApiRootPackageName(this.gpn); String simpname = getSessionClassName(this.gpn); this.cb.setName(simpname); this.cb.setPackage(packname); this.cb.addImports(/*"java.io.IOException", */"java.util.Arrays", "java.util.Collections", "java.util.LinkedList", "java.util.List"); //this.cb.addImports("org.scribble.main.ScribbleRuntimeException", "org.scribble.net.session.SessionEndpoint", "org.scribble.net.ScribMessageFormatter"); this.cb.addImports("org.scribble.sesstype.name.Role"); this.cb.addImports(getRolesPackageName(this.gpn) + ".*"); if (!this.mids.isEmpty()) { this.cb.addImports(getOpsPackageName(this.gpn) + ".*"); } this.cb.addModifiers(JavaBuilder.PUBLIC, JavaBuilder.FINAL); this.cb.setSuperClass(SessionApiGenerator.SESSION_CLASS); FieldBuilder fb1 = this.cb.newField(SessionApiGenerator.IMPATH_FIELD); fb1.setType("List<String>"); fb1.addModifiers(JavaBuilder.PUBLIC, JavaBuilder.STATIC, JavaBuilder.FINAL); fb1.setExpression("new LinkedList<>()"); FieldBuilder fb2 = this.cb.newField(SessionApiGenerator.SOURCE_FIELD); fb2.setType("String"); fb2.addModifiers(JavaBuilder.PUBLIC, JavaBuilder.STATIC, JavaBuilder.FINAL); fb2.setExpression("\"getSource\""); FieldBuilder fb3 = this.cb.newField(SessionApiGenerator.PROTO_FIELD); fb3.setType(SessionApiGenerator.GPROTOCOLNAME_CLASS); fb3.addModifiers(JavaBuilder.PUBLIC, JavaBuilder.STATIC, JavaBuilder.FINAL); fb3.setExpression(SessionApiGenerator.SESSIONTYPEFACTORY_CLASS + ".parseGlobalProtocolName(\"" + gpn + "\")"); this.roles.stream().forEach((r) -> addRoleField(this.cb, r)); this.mids.stream().forEach((mid) -> addOpField(this.cb, mid)); ConstructorBuilder ctor = this.cb.newConstructor(); ctor.addModifiers(JavaBuilder.PUBLIC); //ctor.setName(simpname); // ? ctor.addBodyLine(JavaBuilder.SUPER + "(" + simpname + "." + SessionApiGenerator.IMPATH_FIELD + ", " + simpname + "." + SessionApiGenerator.SOURCE_FIELD + ", " + simpname + "." + SessionApiGenerator.PROTO_FIELD + ");"); FieldBuilder fb4 = this.cb.newField("ROLES"); fb4.setType("List<Role>"); fb4.addModifiers(JavaBuilder.PUBLIC, JavaBuilder.STATIC, JavaBuilder.FINAL); String roles = "Collections.unmodifiableList(Arrays.asList(new Role[] {"; roles += this.roles.stream().map((r) -> r.toString()).collect(Collectors.joining(", ")); roles += "}))"; fb4.setExpression(roles); /*for (Role r : this.roles) { String role = getRoleClassName(r); MethodBuilder mb = this.cb.newMethod("project"); // No: use new on SessionEndpoint directly for Eclipse resource leak warning mb.addModifiers(ClassBuilder.PUBLIC); mb.setReturn("SessionEndpoint<" + simpname + ", " + role + ">"); mb.addParameters(role + " role", "ScribMessageFormatter smf"); mb.addExceptions("ScribbleRuntimeException", "IOException"); mb.addBodyLine(ClassBuilder.RETURN + " " + ClassBuilder.SUPER + ".project(role, smf);"); }*/ MethodBuilder mb = this.cb.newMethod("getRoles"); mb.addAnnotations("@Override"); mb.addModifiers(JavaBuilder.PUBLIC); mb.setReturn("List<Role>"); mb.addParameters(); mb.addBodyLine(JavaBuilder.RETURN + " " + simpname + ".ROLES;"); } private void addRoleField(ClassBuilder cb, Role role) { addSingletonConstant(cb, "roles", getRoleClassName(role)); // FIXME: factor out } private void addOpField(ClassBuilder cb, MessageId<?> mid) { addSingletonConstant(cb, "ops", getOpClassName(mid)); // FIXME: factor out } private void addSingletonConstant(ClassBuilder cb, String subpack, String type) { FieldBuilder fb = cb.newField(type); fb.setType(type); fb.addModifiers(JavaBuilder.PUBLIC, JavaBuilder.STATIC, JavaBuilder.FINAL); //fb.setExpression(ClassBuilder.NEW + " " + type + "()"); fb.setExpression(getEndpointApiRootPackageName(this.gpn) + "." + subpack + "." + type + "." + type); // Currently requires source Scribble to be in a package that is not the root -- can fix by generating to a subpackage based on Module and/or protocol } private void constructOpClasses() throws ScribbleException { Module mod = this.job.getContext().getModule(this.gpn.getPrefix()); GProtocolName simpname = this.gpn.getSimpleName(); GProtocolDecl gpd = (GProtocolDecl) mod.getProtocolDecl(simpname); MessageIdCollector coll = new MessageIdCollector(this.job, ((ModuleDel) mod.del()).getModuleContext()); gpd.accept(coll); for (MessageId<?> mid : coll.getNames()) { //constructOpClass(this.cb.newClass(), mid); //constructOpClass(new ClassBuilder(), getEndpointApiRootPackageName(this.gpn), mid); constructOpClass(new ClassBuilder(), getOpsPackageName(this.gpn), mid); this.mids.add(mid); } } private void constructRoleClasses() throws ScribbleException { Module mod = this.job.getContext().getModule(this.gpn.getPrefix()); GProtocolName simpname = this.gpn.getSimpleName(); GProtocolDecl gpd = (GProtocolDecl) mod.getProtocolDecl(simpname); for (Role r : gpd.header.roledecls.getRoles()) { //constructRoleClass(this.cb.newClass(), r); //constructRoleClass(new ClassBuilder(), getEndpointApiRootPackageName(this.gpn), r); constructRoleClass(new ClassBuilder(), getRolesPackageName(this.gpn), r); this.roles.add(r); } } private ClassBuilder constructRoleClass(ClassBuilder cb, String pack, Role r) { return constructSingletonClass(cb, pack, SessionApiGenerator.ROLE_CLASS, getRoleClassName(r)); } private ClassBuilder constructOpClass(ClassBuilder cb, String pack, MessageId<?> mid) { return constructSingletonClass(cb, pack, SessionApiGenerator.OP_CLASS, getOpClassName(mid)); } private ClassBuilder constructSingletonClass(ClassBuilder cb, String pack, String superc, String type) { cb.setName(type); cb.addModifiers(JavaBuilder.PUBLIC, JavaBuilder.FINAL); cb.setPackage(pack); cb.setSuperClass(superc); FieldBuilder fb = cb.newField("serialVersionUID"); fb.addModifiers(JavaBuilder.PRIVATE, JavaBuilder.STATIC, JavaBuilder.FINAL); fb.setType("long"); fb.setExpression("1L"); FieldBuilder fb2 = cb.newField(type); fb2.addModifiers(JavaBuilder.PUBLIC, JavaBuilder.STATIC, JavaBuilder.FINAL); fb2.setType(type); fb2.setExpression(JavaBuilder.NEW + " " + type + "()"); ConstructorBuilder mb = cb.newConstructor(); mb.addModifiers(JavaBuilder.PRIVATE); mb.addBodyLine(JavaBuilder.SUPER + "(\"" + type + "\");"); this.classes.put(pack.replace('.', '/') + "/" + type + ".java", cb); return cb; } // Returns the simple Session Class name public static String getSessionClassName(GProtocolName gpn) { return gpn.getSimpleName().toString(); } // Returns the Java output package: currently the Scribble package excluding the Module public static String getEndpointApiRootPackageName(GProtocolName gpn) { //return gpn.getPrefix().getPrefix().toString(); // Java output package name (not Scribble package) // FIXME: factor out with StateChannelApiGenerator.generateClasses //return gpn.getPrefix().getPrefix().toString() + "." + gpn.getSimpleName(); // Java output package name (not Scribble package) return gpn.toString(); } public static String getRolesPackageName(GProtocolName gpn) { return getEndpointApiRootPackageName(gpn) + ".roles"; } public static String getOpsPackageName(GProtocolName gpn) { return getEndpointApiRootPackageName(gpn) + ".ops"; } public static String getStateChannelPackageName(GProtocolName gpn, Role self) { return getEndpointApiRootPackageName(gpn) + ".channels." + self; } // Returns the simple Role Class name public static String getRoleClassName(Role r) { return r.toString(); } // Returns the simple Op Class name: names starting with a digit are prefixed by '_' public static String getOpClassName(MessageId<?> mid) { String s = mid.toString(); return (s.isEmpty() || s.charAt(0) < 65) ? "_" + s : s; // Hacky? } }