Skip to content

Commit

Permalink
Generate a writeReplace() for classes with all value constructors
Browse files Browse the repository at this point in the history
Part of #2354
  • Loading branch information
tombentley committed Oct 14, 2015
1 parent 87b4dc2 commit 77283c0
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ public static Symtab instance(Context context) {
public Type ceylonUnresolvedCompilationErrorType;
public Type ceylonAbstractCallableType;
public Type ceylonAbstractTypeConstructorType;
public Type ceylonSerializationProxyType;
public Type ceylonVariableBoxType;
public Type ceylonVariableBoxLongType;
public Type ceylonVariableBoxIntType;
Expand Down Expand Up @@ -950,6 +951,7 @@ public void loadCeylonSymbols() {
ceylonUnresolvedCompilationErrorType = enterClass("com.redhat.ceylon.compiler.java.language.UnresolvedCompilationError");
ceylonAbstractCallableType = enterClass("com.redhat.ceylon.compiler.java.language.AbstractCallable");
ceylonAbstractTypeConstructorType = enterClass("com.redhat.ceylon.compiler.java.language.AbstractTypeConstructor");
ceylonSerializationProxyType = enterClass("com.redhat.ceylon.compiler.java.language.SerializationProxy");
ceylonVariableBoxType = enterClass("com.redhat.ceylon.compiler.java.language.VariableBox");
ceylonVariableBoxLongType = enterClass("com.redhat.ceylon.compiler.java.language.VariableBoxLong");
ceylonVariableBoxIntType = enterClass("com.redhat.ceylon.compiler.java.language.VariableBoxInt");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ protected void transformSingletonConstructor(
adb.userAnnotations(gen.expressionGen().transformAnnotations(true, OutputElement.GETTER, ctor));
adb.fieldAnnotations(gen.expressionGen().transformAnnotations(false, OutputElement.FIELD, ctor));
adb.immutable();// not setter
SyntheticName field = gen.naming.getValueConstructorFieldName(singletonModel);
if (clz.isToplevel()) {
adb.modifiers((singletonModel.isShared() ? PUBLIC : PRIVATE) | STATIC | FINAL);
adb.initialValue(gen.make().NewClass(null, null,
Expand All @@ -292,7 +293,6 @@ protected void transformSingletonConstructor(
} else if (clz.isClassMember()){
adb.modifiers(singletonModel.isShared() ? 0 : PRIVATE);
// lazy
SyntheticName field = gen.naming.synthetic(Prefix.$instance$, clz.getName(), singletonModel.getName());
adb.initialValue(gen.makeNull());
List<JCStatement> l = List.<JCStatement>of(
gen.make().If(gen.make().Binary(JCTree.EQ, field.makeIdent(), gen.makeNull()),
Expand All @@ -311,15 +311,15 @@ protected void transformSingletonConstructor(
} else {
// LOCAL

classBuilder.after(gen.makeVar(FINAL, gen.naming.synthetic(Prefix.$instance$, clz.getName(), singletonModel.getName()),
classBuilder.after(gen.makeVar(FINAL, field,
gen.naming.makeTypeDeclarationExpression(null, Decl.getConstructedClass(ctor.getEnumerated())),
gen.make().NewClass(null, null,
gen.naming.makeTypeDeclarationExpression(null, Decl.getConstructedClass(ctor.getEnumerated())),
List.<JCExpression>of(
gen.make().TypeCast(
gen.naming.makeNamedConstructorType(ctor.getEnumerated(), false),
gen.makeNull())), null)));
gen.naming.addVariableSubst(singletonModel, gen.naming.synthetic(Prefix.$instance$, clz.getName(), singletonModel.getName()).getName());
gen.naming.addVariableSubst(singletonModel, field.getName());
}
}

Expand Down
62 changes: 59 additions & 3 deletions src/com/redhat/ceylon/compiler/java/codegen/ClassTransformer.java
Original file line number Diff line number Diff line change
Expand Up @@ -277,10 +277,14 @@ public List<JCTree> transform(final Tree.ClassOrInterface def) {

if (model instanceof Class
&& !(model instanceof ClassAlias)) {
if (Strategy.introduceJavaIoSerializable((Class)model, typeFact().getJavaIoSerializable())) {
Class c = (Class)model;
if (Strategy.introduceJavaIoSerializable(c, typeFact().getJavaIoSerializable())) {
classBuilder.introduce(make().QualIdent(syms().serializableType.tsym));
if (Strategy.useSerializationProxy(c)) {
addWriteReplace(c, classBuilder);
}
}
serialization((Class)model, classBuilder);
serialization(c, classBuilder);
}

// reset position before initializer constructor is generated.
Expand All @@ -299,9 +303,61 @@ public List<JCTree> transform(final Tree.ClassOrInterface def) {
result = classBuilder.build();
}

return result;
}

/**
* Adds a write replace method which replaces value constructor instances
* with a SerializationProxy
* @param model
* @param classBuilder
*/
protected void addWriteReplace(final Class model,
ClassDefinitionBuilder classBuilder) {
MethodDefinitionBuilder mdb = MethodDefinitionBuilder.systemMethod(this, "writeReplace");
mdb.resultType(null, make().Type(syms().objectType));
mdb.modifiers(PRIVATE | FINAL);
ListBuffer<JCStatement> stmts = ListBuffer.<JCStatement>lb();
SyntheticName name = naming.synthetic(Unfix.$name$);
stmts.add(makeVar(FINAL, name, make().Type(syms().stringType), null));
JCStatement tail;
if (Decl.hasOnlyValueConstructors(model)) {
tail = make().Throw(statementGen().makeNewEnumeratedTypeError("Instance not of any constructor"));
} else {
tail = make().Return(naming.makeThis());
}
for (Declaration member : model.getMembers()) {
if (Decl.isValueConstructor(member) ) {
Value val = (Value)member;
tail = make().If(
make().Binary(JCTree.EQ, naming.makeThis(),
naming.getValueConstructorFieldName(val).makeIdent()),
make().Block(0, List.<JCStatement>of(make().Exec(make().Assign(name.makeIdent(), make().Literal(Naming.getGetterName(member)))))),
tail);
}
}

stmts.add(tail);
// final String name;
// if(this == instA) {
// name = "getInstA";
// } // ... else { throw new

return result;
// return new SerializationProxy(outer, Foo.clazz, name);
List<JCExpression> args = List.<JCExpression>of(name.makeIdent());
if (model.isMember()) {
ClassOrInterface outer = (ClassOrInterface)model.getContainer();
args = args.prepend(makeClassLiteral(outer.getType()));
args = args.prepend(naming.makeQualifiedThis(naming.makeTypeDeclarationExpression(null, outer, DeclNameFlag.QUALIFIED)));
} else {
args = args.prepend(makeClassLiteral(model.getType()));
}
stmts.add(make().Return(make().NewClass(null, null,
make().QualIdent(syms().ceylonSerializationProxyType.tsym),
args,
null)));
mdb.body(stmts.toList());
classBuilder.method(mdb);
}

protected void buildJpaConstructor(Class model, ClassDefinitionBuilder classBuilder) {
Expand Down
28 changes: 24 additions & 4 deletions src/com/redhat/ceylon/compiler/java/codegen/Decl.java
Original file line number Diff line number Diff line change
Expand Up @@ -898,21 +898,36 @@ public static boolean hasAbstractConstructor(Class cls) {
}

public static boolean hasOnlyValueConstructors(Class cls) {
if (cls.getParameterList() == null) {
if (cls.hasEnumerated()) {
for (Declaration d : cls.getMembers()) {
if (d instanceof Constructor &&
!((Constructor) d).isValueConstructor()) {
return false;
}
}
return true;
}
return false;
} else {
return false;
}
}

public static boolean hasAnyValueConstructors(Class cls) {
if (cls.hasEnumerated()) {
for (Declaration d : cls.getMembers()) {
if (d instanceof Constructor &&
((Constructor) d).isValueConstructor()) {
return true;
}
}
return false;
} else {
return false;
}
}

/** Is the given constructor an enumerated ("singleton") constructor */
public static boolean isEnumeratedConstructor(Constructor ctor) {
return ctor != null && ctor.getContainer().getDirectMember(ctor.getName(), null, false) instanceof Value;
return ctor != null && ctor.getParameterList() == null;
}

/** Is the given value the result of an enumerated ("singleton") constructor */
Expand Down Expand Up @@ -990,4 +1005,9 @@ public static boolean skipContainer(Scope container) {
}
return false;
}

public static boolean isValueConstructor(Declaration member) {
return member instanceof Value
&& ((Value)member).getTypeDeclaration() instanceof Constructor;
}
}
12 changes: 12 additions & 0 deletions src/com/redhat/ceylon/compiler/java/codegen/Naming.java
Original file line number Diff line number Diff line change
Expand Up @@ -2118,6 +2118,18 @@ public static boolean isAmbiguousGetterName(String name) {
int s = name.codePointAt(1);
return Character.isUpperCase(s);
}

protected SyntheticName getValueConstructorFieldName(Value singletonModel) {
Class clz = (Class)singletonModel.getContainer();
if (clz.isToplevel()) {
return synthetic(Naming.quoteFieldName(singletonModel.getName()));
}
else if (clz.isClassMember()){
return synthetic(Prefix.$instance$, clz.getName(), singletonModel.getName());
} else {
return synthetic(Prefix.$instance$, clz.getName(), singletonModel.getName());
}
}
}


Original file line number Diff line number Diff line change
Expand Up @@ -3979,22 +3979,19 @@ protected JCStatement transformElse(Naming.SyntheticName selectorAlias, Tree.Swi
!canUnbox(expectedType),
CodegenUtil.getBoxingStrategy(outerExpression),
expectedType))));
stmts = stmts.prepend(make().Exec(utilInvocation().rethrow(makeNewEnumeratedTypeError())));
stmts = stmts.prepend(make().Exec(utilInvocation().rethrow(makeNewEnumeratedTypeError(exhasutedExhaustiveSwitch))));
return make().Block(0L, stmts);
}else{
return makeThrowEnumeratedTypeError();
}
}
}
protected JCStatement makeThrowEnumeratedTypeError() {
return make().Throw(makeNewEnumeratedTypeError());
}
protected JCExpression makeNewEnumeratedTypeError() {
return make().NewClass(null, List.<JCExpression>nil(),
makeIdent(syms().ceylonEnumeratedTypeErrorType),
List.<JCExpression>of(make().Literal(
"Supposedly exhaustive switch was not exhaustive")), null);
return make().Throw(makeNewEnumeratedTypeError(exhasutedExhaustiveSwitch));
}

String exhasutedExhaustiveSwitch = "Supposedly exhaustive switch was not exhaustive";

public abstract JCStatement transformSwitch(Node node, Tree.SwitchClause switchClause, Tree.SwitchCaseList caseList,
String tmpVar, Tree.Term outerExpression, Type expectedType);

Expand All @@ -4005,6 +4002,14 @@ protected boolean isDefinitelyReturns(CaseClause caseClause) {
return false;
}
}

JCExpression makeNewEnumeratedTypeError(String msg) {
return make().NewClass(null, List.<JCExpression>nil(),
makeIdent(syms().ceylonEnumeratedTypeErrorType),
List.<JCExpression>of(make().Literal(
msg)), null);
}

/**
* Switch transformation which produces a Java {@code switch},
* suitable for a switch whose cases are all String literals,
Expand Down
14 changes: 10 additions & 4 deletions src/com/redhat/ceylon/compiler/java/codegen/Strategy.java
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ && isNullary((Class)s)) {
boolean constrained =
(cls.getCaseValues() != null
&& !cls.getCaseValues().isEmpty())
|| Decl.hasOnlyValueConstructors(cls);
|| cls.hasEnumerated() && Decl.hasOnlyValueConstructors(cls);

return hasDelegatableSuper
&& !constrained;
Expand Down Expand Up @@ -477,10 +477,11 @@ protected static boolean hasNullaryNonJpaConstructor(Class c) {
public static boolean introduceJavaIoSerializable(
Class cls, Interface ser) {
if (!(cls instanceof ClassAlias)) {
if ((!Decl.hasOnlyValueConstructors(cls) || (cls.isAnonymous()))
&& cls.getExtendedType() != null
if ((Decl.hasOnlyValueConstructors(cls)
|| cls.isAnonymous()
|| (cls.getExtendedType() != null
&& (cls.getExtendedType().isBasic()
|| cls.getExtendedType().isObject())
|| cls.getExtendedType().isObject())))
&& !cls.getSatisfiedTypes().contains(ser.getType())) {
return true;
}
Expand Down Expand Up @@ -516,5 +517,10 @@ public static boolean addReadResolve(Class cls, Interface ser) {
}
return false;
}

public static boolean useSerializationProxy(Class model) {
return model.hasEnumerated()
&& (model.isToplevel() || model.isMember());
}

}

0 comments on commit 77283c0

Please sign in to comment.