232 行
8.0 KiB
Go
232 行
8.0 KiB
Go
package props
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/cjslep/activity/tools/exp/codegen"
|
|
"github.com/dave/jennifer/jen"
|
|
"sync"
|
|
)
|
|
|
|
const (
|
|
managerName = "Manager"
|
|
managerInitVarName = "mgr"
|
|
)
|
|
|
|
// managerInitName returns the package variable name for the manager.
|
|
func managerInitName() string {
|
|
return managerInitVarName
|
|
}
|
|
|
|
// Generates the ActivityStreamManager that handles the creation of
|
|
// ActivityStream Core, Extended, and any extension types.
|
|
//
|
|
// This is implicitly used by Application code, but Application code usually
|
|
// won't need to manually use this Manager.
|
|
//
|
|
// This also provides interfaces to break the recursive/cyclic dependencies
|
|
// between properties and types. The previous version of this tool did not
|
|
// attempt to solve this problem, and instead just created one big and bloated
|
|
// library in order to avoid having to break the dependence. This version of
|
|
// the tool instead will generate interfaces for all of the required types.
|
|
//
|
|
// This means that developers will only ever need to interact with these
|
|
// interfaces, and could switch out using this implementation for another one of
|
|
// their own choosing.
|
|
//
|
|
// Also note that the manager links against all the implementations to generate
|
|
// a comprehensive registry. So while individual properties and types are able
|
|
// to be compiled separately, this generated output will link against all of
|
|
// these libraries.
|
|
//
|
|
// TODO: Improve the code generation to examine specific Golang code to
|
|
// determine which types to actually generate, and prune the unneeded types.
|
|
// This would cut down on the bloat on a per-program basis.
|
|
type ManagerGenerator struct {
|
|
pkg Package
|
|
tg []*TypeGenerator
|
|
fp []*FunctionalPropertyGenerator
|
|
nfp []*NonFunctionalPropertyGenerator
|
|
// Constructed at creation time. These rely on pointer stability,
|
|
// which should happen as none of these generators are treated as
|
|
// values.
|
|
tgManagedMethods map[*TypeGenerator]*managedMethods
|
|
fpManagedMethods map[*FunctionalPropertyGenerator]*managedMethods
|
|
nfpManagedMethods map[*NonFunctionalPropertyGenerator]*managedMethods
|
|
// Cached during manager code generation.
|
|
cacheOnce sync.Once
|
|
cachedStruct *codegen.Struct
|
|
}
|
|
|
|
// managedMethods caches the specific methods and interfaces mapped to specific
|
|
// properties and types.
|
|
type managedMethods struct {
|
|
deserializor *codegen.Method
|
|
}
|
|
|
|
// NewManagerGenerator creates a new manager system.
|
|
//
|
|
// This generator should be constructed in the third pass, after types and
|
|
// property generators are all constructed.
|
|
func NewManagerGenerator(pkg Package,
|
|
tg []*TypeGenerator,
|
|
fp []*FunctionalPropertyGenerator,
|
|
nfp []*NonFunctionalPropertyGenerator) (*ManagerGenerator, error) {
|
|
mg := &ManagerGenerator{
|
|
pkg: pkg,
|
|
tg: tg,
|
|
fp: fp,
|
|
nfp: nfp,
|
|
tgManagedMethods: make(map[*TypeGenerator]*managedMethods, len(tg)),
|
|
fpManagedMethods: make(map[*FunctionalPropertyGenerator]*managedMethods, len(fp)),
|
|
nfpManagedMethods: make(map[*NonFunctionalPropertyGenerator]*managedMethods, len(nfp)),
|
|
}
|
|
// Pass 1: Get all deserializor-like methods created. Further passes may
|
|
// rely on already having this data available in the manager.
|
|
for _, t := range tg {
|
|
mg.tgManagedMethods[t] = &managedMethods{
|
|
deserializor: mg.createDeserializationMethodForType(t),
|
|
}
|
|
}
|
|
for _, p := range fp {
|
|
mg.fpManagedMethods[p] = &managedMethods{
|
|
deserializor: mg.createDeserializationMethodForFuncProperty(p),
|
|
}
|
|
}
|
|
for _, p := range nfp {
|
|
mg.nfpManagedMethods[p] = &managedMethods{
|
|
deserializor: mg.createDeserializationMethodForNonFuncProperty(p),
|
|
}
|
|
}
|
|
// Pass 2: Inform the type of this ManagerGenerator so that it can keep
|
|
// all of its bookkeeping straight.
|
|
for _, t := range tg {
|
|
if e := t.apply(mg); e != nil {
|
|
return nil, e
|
|
}
|
|
}
|
|
return mg, nil
|
|
}
|
|
|
|
// getDeserializationMethodForType obtains the deserialization method for a
|
|
// type.
|
|
func (m *ManagerGenerator) getDeserializationMethodForType(t *TypeGenerator) *codegen.Method {
|
|
return m.tgManagedMethods[t].deserializor
|
|
}
|
|
|
|
// getDeserializationMethodForProperty obtains the deserialization method for a
|
|
// property regardless whether it is functional or non-functional.
|
|
func (m *ManagerGenerator) getDeserializationMethodForProperty(p Property) *codegen.Method {
|
|
switch v := p.(type) {
|
|
case *FunctionalPropertyGenerator:
|
|
return m.fpManagedMethods[v].deserializor
|
|
case *NonFunctionalPropertyGenerator:
|
|
return m.nfpManagedMethods[v].deserializor
|
|
default:
|
|
panic("unknown property type")
|
|
}
|
|
}
|
|
|
|
// Definition creates a manager implementation that works with the interface
|
|
// types required by the other PropertyGenerators and TypeGenerators for
|
|
// serializing and deserializing.
|
|
//
|
|
// Applications will implicitly use this manager and be isolated from the
|
|
// underlying specific go-fed implementation. If another alternative to go-fed
|
|
// were to be created, it could target those interfaces and be a drop-in
|
|
// replacement for an application.
|
|
//
|
|
// It is necessary to have this to acheive isolation without cyclic
|
|
// dependencies: types and properties can each belong in their own package (if
|
|
// desired) to minimize binary bloat.
|
|
func (m *ManagerGenerator) Definition() *codegen.Struct {
|
|
var methods []*codegen.Method
|
|
for _, tg := range m.tgManagedMethods {
|
|
methods = append(methods, tg.deserializor)
|
|
}
|
|
for _, fp := range m.fpManagedMethods {
|
|
methods = append(methods, fp.deserializor)
|
|
}
|
|
for _, nfp := range m.nfpManagedMethods {
|
|
methods = append(methods, nfp.deserializor)
|
|
}
|
|
s := codegen.NewStruct(
|
|
fmt.Sprintf("%s manages interface types and deserializations for use by generated code. Application code implicitly uses this manager at run-time to create concrete implementations of the interfaces.", managerName),
|
|
managerName,
|
|
methods,
|
|
/*functions=*/ nil,
|
|
/*members=*/ nil)
|
|
return s
|
|
}
|
|
|
|
// createDeserializationMethodForType creates a new deserialization method for
|
|
// a type.
|
|
func (m *ManagerGenerator) createDeserializationMethodForType(tg *TypeGenerator) *codegen.Method {
|
|
return m.createDeserializationMethod(
|
|
tg.deserializationFnName(),
|
|
tg.PublicPackage(),
|
|
tg.PrivatePackage(),
|
|
tg.InterfaceName(),
|
|
tg.VocabName())
|
|
}
|
|
|
|
// createDeserializationMethodForFuncProperty creates a new deserialization
|
|
// method for a functional property.
|
|
func (m *ManagerGenerator) createDeserializationMethodForFuncProperty(fp *FunctionalPropertyGenerator) *codegen.Method {
|
|
return m.createDeserializationMethod(
|
|
fp.DeserializeFnName(),
|
|
fp.GetPublicPackage(),
|
|
fp.GetPrivatePackage(),
|
|
fp.InterfaceName(),
|
|
fp.VocabName())
|
|
}
|
|
|
|
// createDeserializationMethodForNonFuncProperty creates a new deserialization
|
|
// method for a non-functional property.
|
|
func (m *ManagerGenerator) createDeserializationMethodForNonFuncProperty(nfp *NonFunctionalPropertyGenerator) *codegen.Method {
|
|
return m.createDeserializationMethod(
|
|
nfp.DeserializeFnName(),
|
|
nfp.GetPublicPackage(),
|
|
nfp.GetPrivatePackage(),
|
|
nfp.InterfaceName(),
|
|
nfp.VocabName())
|
|
}
|
|
|
|
// createDeserializationMethod returns a function
|
|
func (m *ManagerGenerator) createDeserializationMethod(deserName string, pubPkg, privPkg Package, interfaceName, vocabName string) *codegen.Method {
|
|
name := fmt.Sprintf("%s%s", deserName, vocabName)
|
|
return codegen.NewCommentedValueMethod(
|
|
m.pkg.Path(),
|
|
name,
|
|
managerName,
|
|
/*param=*/ nil,
|
|
[]jen.Code{
|
|
jen.Func().Params(
|
|
jen.Map(jen.String()).Interface(),
|
|
).Params(
|
|
jen.Qual(pubPkg.Path(), interfaceName),
|
|
jen.Error(),
|
|
),
|
|
},
|
|
[]jen.Code{
|
|
jen.Return(
|
|
jen.Func().Params(
|
|
jen.Id("m").Map(jen.String()).Interface(),
|
|
).Params(
|
|
jen.Qual(pubPkg.Path(), interfaceName),
|
|
jen.Error(),
|
|
).Block(
|
|
jen.List(
|
|
// Note: this "i" must be the same as the "i" in the deserialization definition.
|
|
jen.Id("i"),
|
|
jen.Err(),
|
|
).Op(":=").Qual(privPkg.Path(), deserName).Call(jen.Id("m")),
|
|
jen.Return(jen.List(
|
|
jen.Id("i"),
|
|
jen.Err(),
|
|
)),
|
|
),
|
|
),
|
|
},
|
|
fmt.Sprintf("%s returns the deserialization method for the %q non-functional property in the vocabulary %q", name, interfaceName, vocabName))
|
|
}
|