activity/astool/gen/manager.go

238 行
8.1 KiB
Go

package gen
import (
"fmt"
"github.com/dave/jennifer/jen"
"github.com/go-fed/activity/astool/codegen"
"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(),
jen.Map(jen.String()).String(),
).Params(
jen.Qual(pubPkg.Path(), interfaceName),
jen.Error(),
),
},
[]jen.Code{
jen.Return(
jen.Func().Params(
jen.Id("m").Map(jen.String()).Interface(),
jen.Id("aliasMap").Map(jen.String()).String(),
).Params(
jen.Qual(pubPkg.Path(), interfaceName),
jen.Error(),
).Block(
jen.List(
jen.Id("i"),
jen.Err(),
).Op(":=").Qual(privPkg.Path(), deserName).Call(jen.Id("m"), jen.Id("aliasMap")),
jen.If(
jen.Id("i").Op("==").Nil(),
).Block(
jen.Return(jen.Nil(), jen.Err()),
),
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))
}