blob: a7922286a506fc75a49c2f3ade03d73f707adce7 [file] [log] [blame]
// Copyright (C) 2014 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package binary
import (
"fmt"
"path"
"reflect"
"strings"
"unicode"
)
// Entity represents the encodable type information for an object.
// In it's compact mode, the entity contains only the information strictly required to generate it's signature, and not
// any of metadata or display names.
type Entity struct {
Package string // The package that declared the struct.
Display string // The display name of the class, not set in compact form.
Identity string // The true name of the class.
Version string // The version string of the class, if set.
Exported bool // Whether the class is exported from it's package, not set in compact form.
Fields FieldList // Descriptions of the fields of the class.
Metadata []Object // The metadata for the class, not set in compact form
subspace *Subspace // Cache of the sub-types of this entity
// signature is the cached result of a call to Signature.
// It is never encoded to a stream.
signature string
}
func spaceToUnderscore(r rune) rune {
if unicode.IsSpace(r) {
return '_'
}
return r
}
// TypeName returns a string for the typename in a form compatible with
// the name codegen would use for this schema type. If t is in the package
// pkg a relative name should be used.
func TypeName(t reflect.Type, pkg string) string {
pkgPath := t.PkgPath()
if pkgPath == "" || pkg == pkgPath {
return t.Name()
}
fullname := pkgPath + "." + t.Name()
return strings.Map(spaceToUnderscore, path.Base(fullname))
}
// MakeTypeFun is a function type for building schema types. The function
// makes a schema type corresponding to t. If t has sub-types then
// makeType will be called to build them. Names given to schema sub-types
// should be relative to pkg.
type MakeTypeFun func(
t reflect.Type, tag reflect.StructTag, makeType MakeTypeFun, pkg string) Type
// InitEntity, initializes a schema entity 'e' from the Go reflect type 't'.
// 'makeType' is used to build schema types for sub-types.
func InitEntity(t reflect.Type, makeType MakeTypeFun, e *Entity) {
if t.Kind() != reflect.Struct {
panic(fmt.Errorf("Type %v is not struct-kind (kind = %v)", t, t.Kind()))
}
pkg := t.PkgPath()
e.Package = path.Base(pkg)
name := t.Name()
runes := ([]rune)(name)
if len(runes) != 0 {
e.Exported = unicode.IsUpper(runes[0])
}
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
if i == 0 && f.Anonymous && f.Type.Kind() == reflect.Struct &&
f.Type.NumField() == 0 {
e.Version = f.Tag.Get("version")
e.Identity = f.Tag.Get("identity")
continue
}
sf := Field{Declared: f.Name, Type: makeType(f.Type, f.Tag, makeType, pkg)}
e.Fields = append(e.Fields, sf)
}
if e.Identity == "" {
// Always set the Identity.
e.Identity = name
} else if e.Identity != name {
// Only set display if it differs from Identity.
e.Display = name
}
e.Signature() // This caches the signature in the entity
}
// Name returns the name of the Entity.
func (e *Entity) Name() string {
if e.Display != "" {
return e.Display
}
return e.Identity
}
type Signature string
// Signature returns a canonical string representations of an entities signature.
// If two entities have the same Signature, the are assumed to represent the same type
func (e *Entity) Signature() Signature {
if e.signature == "" {
// Internally signature is implemented by the fmt.Formatter interface with the format specifier 'z'
e.signature = fmt.Sprintf("%z", e)
}
return Signature(e.signature)
}
// IsPOD checks if the entity is a valid POD type
func (e *Entity) IsPOD() bool {
for _, field := range e.Fields {
if !field.Type.IsPOD() {
return false
}
}
return true
}
// IsSimple checks if the entity is a valid Simple type
func (e *Entity) IsSimple() bool {
for _, field := range e.Fields {
if !field.Type.IsSimple() {
return false
}
}
return true
}
// Format implements the fmt.Formatter interface
func (e *Entity) Format(f fmt.State, c rune) {
// if c is 'z' then we are printing in signature format.
// this is an internal implementation detail, the only code that should ever do this is the Signature method.
fmt.Fprint(f, e.Package, ".", e.Identity)
if e.Version != "" {
fmt.Fprint(f, '@', e.Version)
}
if c != 'z' && e.Display != "" {
fmt.Fprint(f, '(', e.Display, ")")
}
fmt.Fprint(f, "{")
for i, field := range e.Fields {
if i != 0 {
fmt.Fprint(f, ",")
}
if c != 'z' && field.Declared != "" {
fmt.Fprint(f, field.Declared, " ")
}
field.Type.Format(f, c)
}
fmt.Fprint(f, "}")
}
// appendSubTypes, appends the sub-types of 't' to the list 'l' and returns
// it. Inlines are expanded. If 't' is inline then append the expansion
// of its sub-types to the list 'l' and return it. If 't' is not inline
// just append 't' and return it.
func appendSubTypes(l TypeList, t SubspaceType) TypeList {
if !t.HasSubspace() {
return l
}
s := t.Subspace()
if !s.Inline {
return append(l, t)
}
for _, sub := range s.SubTypes {
l = appendSubTypes(l, sub)
}
return l
}
// Subspace, returns the subspace for the entity. The subspace of the
// entity is the the catenation of the field subtypes with inlines expanded.
func (e *Entity) Subspace() *Subspace {
if e.subspace != nil {
return e.subspace
}
sub := TypeList{}
for _, f := range e.Fields {
if f.Type.HasSubspace() {
sub = appendSubTypes(sub, f.Type)
}
}
e.subspace = &Subspace{SubTypes: sub}
return e.subspace
}
// ExpandSubTypes, returns the list of sub-types of this subspace with
// any inline sub-types expanded.
func (s *Subspace) ExpandSubTypes() TypeList {
if s.expanded != nil {
return s.expanded
}
l := TypeList{}
for _, t := range s.SubTypes {
l = appendSubTypes(l, t)
}
s.expanded = l
return l
}
// FieldList is a slice of fields.
type FieldList []Field
// Field represents a name/type pair for a field in an Object.
type Field struct {
Declared string // The name of the field, not set in compact form.
Type Type // The type stored in the field.
}
// Subspace represents the sub-types which need decoder support for nested types.
type Subspace struct {
Inline bool // true if the schema object inlines the encoding (arrays)
Counted bool // true if the schema type is a counted (slice, map)
SubTypes TypeList // the complete list of subtypes
expanded TypeList // cache of the expanded list of subtypes
}
// SubspaceType is the interface provides by schema types in order for them
// to provide sub-type information to the decoder.
type SubspaceType interface {
// Returns true if Subspace() will return non-nil.
HasSubspace() bool
// Subspace returns the subspace for this type. The subspace represents
// the sub-types which need decoder support for nested types.
// Nil means that this kind of schema object never has subtypes.
Subspace() *Subspace
}
// Type represents the common interface to all type objects in the schema.
type Type interface {
SubspaceType
String() string // The true name of the type.
Representation() string // The encoded representation of the type.
EncodeValue(e Encoder, value interface{})
DecodeValue(d Decoder) interface{}
Format(f fmt.State, c rune)
IsPOD() bool
IsSimple() bool
}
// TypeList used to represent the entities of any composed subtypes which
// need decoding.
type TypeList []SubspaceType
func trimPackage(n string) string {
i := strings.LastIndex(n, ".")
if i < 0 {
return n
}
return n[i+1:]
}
func (f Field) Name() string {
if f.Declared == "" {
return trimPackage(f.Type.String())
}
return f.Declared
}
// Find searches the field list of the field with the specified name, returning
// the index of the field if found, otherwise -1.
func (l FieldList) Find(name string) int {
for i, f := range l {
if f.Name() == name {
return i
}
}
return -1
}