1676106010
This Xcode Source Code Extension will generate a Swift initializer based on the lines you've selected. Handy if you made a struct public and now you have to provide the initializer implementation yourself.
Select the lines with the attributes that should be included in the initializer. See below; >
is the start of the selection and <
is the end of the selection.
struct MyStruct {
> public var a: String
public var b: Int<
}
Run the extension's "Generate Swift Initializer". Voila! The code above is modified to:
struct MyStruct {
public var a: String
public var b: Int
public init(a: String, b: Int) {
self.a = a
self.b = b
}
}
On OS X 10.11 El Capitan, run the following command and restart your Mac:
sudo /usr/libexec/xpccachectl
Open SwiftInitializerGenerator.xcodeproj
Enable target signing for both the Application and the Source Code Extension using your own developer ID
Product > Archive
Right click archive > Show in Finder
Right click archive > Show Package Contents
Drag Swift Initializer Generator.app
to your Applications folder
Run Swift Initializer Generator.app
and exit again.
Go to System Preferences -> Extensions -> Xcode Source Editor and enable the extension
The menu-item should now be available from Xcode's Editor menu.
It will only parse attributes defined like (open|public|fileprivate|private|internal) [weak] (var|let) NAME: TYPE
.
Author: Bouke
Source Code: https://github.com/Bouke/SwiftInitializerGenerator
License: MIT license
1665877800
SwiftGen is a tool to automatically generate Swift code for resources of your projects (like images, localised strings, etc), to make them type-safe to use.
There are multiple benefits in using this:
Also, it's fully customizable thanks to Stencil templates, so even if it comes with predefined templates, you can make your own to generate whatever code fits your needs and your guidelines!
There are multiple possibilities to install SwiftGen on your machine or in your project, depending on your preferences and needs:
Download the ZIP for the latest release
swiftgen-x.y.z.zip
file associated with that releaseWe recommend that you unarchive the ZIP inside your project directory and commit its content to git. This way, all coworkers will use the same version of SwiftGen for this project.
If you unarchived the ZIP file in a folder e.g. called swiftgen
at the root of your project directory, you can then invoke SwiftGen in your Script Build Phase using:
"${PROJECT_DIR}/swiftgen/bin/swiftgen" …
Via CocoaPods
If you're using CocoaPods, simply add pod 'SwiftGen', '~> 6.0'
to your Podfile
.
Then execute pod install --repo-update
(or pod update SwiftGen
if you want to update an existing SwiftGen installation) to download and install the SwiftGen
binaries and dependencies in Pods/SwiftGen/bin/swiftgen
next to your project.
Given that you can specify an exact version for SwiftGen
in your Podfile
, this allows you to ensure all coworkers will use the same version of SwiftGen for this project.
You can then invoke SwiftGen in your Script Build Phase using:
if [[ -f "${PODS_ROOT}/SwiftGen/bin/swiftgen" ]]; then
"${PODS_ROOT}/SwiftGen/bin/swiftgen" …
else
echo "warning: SwiftGen is not installed. Run 'pod install --repo-update' to install it."
fi
Similarly, be sure to use
Pods/SwiftGen/bin/swiftgen
instead of justswiftgen
where we mention commands withswiftgen
in the rest of the documentation.
Note: SwiftGen isn't really a pod, as it's not a library your code will depend on at runtime; so the installation via CocoaPods is just a trick that installs the SwiftGen binaries in the Pods/ folder, but you won't see any swift files in the Pods/SwiftGen group in your Xcode's Pods.xcodeproj. That's normal; the SwiftGen binary is still present in that folder in the Finder.
Via Homebrew (system-wide installation)
To install SwiftGen via Homebrew, simply use:
$ brew update
$ brew install swiftgen
This will install SwiftGen system-wide. The same version of SwiftGen will be used for all projects on that machine, and you should make sure all your coworkers have the same version of SwiftGen installed on their machine too.
You can then invoke swiftgen
directly in your Script Build Phase (as it will be in your $PATH
already):
swiftgen …
Via Mint (system-wide installation)
❗️SwiftGen 6.0 or higher only.
To install SwiftGen via Mint, simply use:
$ mint install SwiftGen/SwiftGen
Compile from source (only recommended if you need features from the `stable` branch or want to test a PR)
This solution is when you want to build and install the latest version from stable
and have access to features which might not have been released yet.
homebrew
installed, you can use the following command to build and install the latest commit:brew install swiftgen --HEAD
rake cli:install
to build the tool and install it from any branch, which could be useful to test SwiftGen in a fork or a Pull Request branch.Some Ruby tools are used in the build process, and the system Ruby works well if you are running a recent macOS. However, if you are using rbenv
you can run rbenv install
to make sure you have a matching version of Ruby installed.
Then install the Ruby Gems:
# Install bundle if it isn't installed
gem install bundle
# Install the Ruby gems from Gemfile
bundle install
You can now install to the default locations (no parameter) or to custom locations:
# Binary is installed in `./.build/swiftgen/bin`
$ rake cli:install
# - OR -
# Binary will be installed in `~/swiftgen/bin``
$ rake cli:install[~/swiftgen/bin]
You can then invoke SwiftGen using the path to the binary where you installed it:
~/swiftgen/bin/swiftgen …
Or add the path to the bin
folder to your $PATH
and invoke swiftgen
directly.
Starting with SwiftGen 6.2.1, if you get an error similar to dyld: Symbol not found: _$s11SubSequenceSlTl
when running SwiftGen, you'll need to install the Swift 5 Runtime Support for Command Line Tools.
Alternatively, you can:
/Applications/Xcode.app
❗️ If you're migrating from older SwiftGen versions, don't forget to read the Migration Guide.
SwiftGen is provided as a single command-line tool which uses a configuration file to define the various parsers to run (depending on the type of input files you need to parse) and their parameters.
To create a sample configuration file as a starting point to adapt to your needs, run swiftgen config init
.
Each parser described in the configuration file (strings
, fonts
, ib
, …) typically corresponds to a type of input resources to parse (strings files, IB files, Font files, JSON files, …), allowing you to generate constants for each types of those input files.
To use SwiftGen, simply create a swiftgen.yml
YAML file (either manually or using swiftgen config init
) then edit it to adapt to your project. The config file should list all the parsers to invoke, and for each parser, the list of inputs/outputs/templates/parameters to use for it.
For example:
strings:
inputs: Resources/Base.lproj
outputs:
- templateName: structured-swift5
output: Generated/Strings.swift
xcassets:
inputs:
- Resources/Images.xcassets
- Resources/MoreImages.xcassets
- Resources/Colors.xcassets
outputs:
- templateName: swift5
output: Generated/Assets.swift
Then you just have to invoke swiftgen config run
, or even just swiftgen
for short, and it will execute what's described in the configuration file.
The dedicated documentation explains the syntax and possibilities in details – like how to pass custom parameters to your templates, use swiftgen config lint
to validate your config file, how to use alternate config files, and other tips.
There are also additional subcommands you can invoke from the command line to manage and configure SwiftGen:
swiftgen config
subcommand to help you with the configuration file, especially swiftgen config init
to create a starting point for your config and swiftgen config lint
to validate that your Config file is valid and has no errorsswiftgen template
subcommands to help you print, duplicate, find and manage templates bundled with SwiftGenLastly, you can use --help
on swiftgen
or one of its subcommand to see the detailed usage.
Directly invoking a parser without a config file
While we highly recommend the use a configuration file for performance reasons (especially if you have multiple outputs, but also because it's more flexible), it's also possible to directly invoke the available parsers individually using swiftgen run
:
swiftgen run colors [OPTIONS] DIRORFILE1 …
swiftgen run coredata [OPTIONS] DIRORFILE1 …
swiftgen run files [OPTIONS] DIRORFILE1 …
swiftgen run fonts [OPTIONS] DIRORFILE1 …
swiftgen run ib [OPTIONS] DIRORFILE1 …
swiftgen run json [OPTIONS] DIRORFILE1 …
swiftgen run plist [OPTIONS] DIRORFILE1 …
swiftgen run strings [OPTIONS] DIRORFILE1 …
swiftgen run xcassets [OPTIONS] DIRORFILE1 …
swiftgen run yaml [OPTIONS] DIRORFILE1 …
One rare cases where this might be useful — as opposed to using a config file — is if you are working on a custom template and want to quickly test the specific parser you're working on at each iteration/version of your custom template, until you're happy with it.
Each parser command generally accepts the same options and syntax, and they mirror the options and parameters from the configuration file:
--output FILE
or -o FILE
: set the file where to write the generated code. If omitted, the generated code will be printed on stdout
.--templateName NAME
or -n NAME
: define the Stencil template to use (by name, see here for more info) to generate the output.--templatePath PATH
or -p PATH
: define the Stencil template to use, using a full path.-t
or -p
but should not use both at the same time (it wouldn't make sense anyway and you'll get an error if you try)--filter REGEX
or -f REGEX
: the filter to apply to each input path. Filters are applied to actual (relative) paths, not just the filename. Each command has a default filter that you can override with this option..+
to match multiple characters (at least one), and don't forget to escape the dot (\.
) if you want to match a literal dot like for an extension. Add $
at the end to ensure the path ends with the extension you want. Regular expressions will be case sensitive by default, and not anchored to the start/end of a path. For example, use .+\.xib$
to match files with a .xib
extension. Use a tool such as RegExr to ensure you're using a valid regular expression.--help
flag to see what options a command accept, e.g. swiftgen run xcassets --help
.SwiftGen is based on templates (it uses Stencil as its template engine). This means that you can choose the template that fits the Swift version you're using — and also the one that best fits your preferences — to adapt the generated code to your own conventions and Swift version.
SwiftGen comes bundled with some templates for each of the parsers (colors
, coredata
, files
, fonts
, ib
, json
, plist
, strings
, xcassets
, yaml
), which will fit most needs; simply use the templateName
output option to specify the name of the template to use. But you can also create your own templates if the bundled ones don't suit your coding conventions or needs: just store them anywhere (like in your project repository) and use the templatePath
output option instead of templateName
, to specify their path.
💡 You can use the swiftgen template list
command to list all the available bundled templates for each parser, and use swiftgen template cat
to show a template's content and duplicate it to create your own variation.
For more information about how to create your own templates, see the dedicated documentation.
As explained above, you can use swiftgen template list
to list all templates bundled with SwiftGen. For most SwiftGen parsers, we provide, among others:
swift4
template, compatible with Swift 4swift5
template, compatible with Swift 5flat-swift4/5
and structured-swift4/5
templates for Strings, etc.You can find the documentation for each bundled template here in the repo, with documentation organized as one folder per SwiftGen parser, then one MarkDown file per template. You can also use swiftgen template doc
to open that documentation page in your browser directly from your terminal.
Each MarkDown file documents the Swift Version it's aimed for, the use case for that template (in which cases you might favor that template over others), the available parameters to customize it on invocation (using the params:
key in your config file), and some code examples.
Don't hesitate to make PRs to share your improvements suggestions on the bundled templates 😉
The SwiftGen.playground
available in this repository will allow you to play with the code that the tool typically generates, and see some examples of how you can take advantage of it.
This allows you to have a quick look at how typical code generated by SwiftGen looks like, and how you will then use the generated constants in your code.
There is a lot of documentation in the form of Markdown files in this repository, and in the related StencilSwiftKit repository as well.
Be sure to check the "Documentation" folder of each repository.
Especially, in addition to the previously mentioned Migration Guide and Configuration File documentation, the Documentation/
folder in the SwiftGen repository also includes:
templates
subdirectory that details the documentation for each of the templates bundled with SwiftGen (when to use each template, what the output will look like, and custom parameters to adjust them, …)SwiftGenKit Contexts
subdirectory that details the structure of the "Stencil Contexts", i.e. the Dictionary/YAML representation resulting of parsing your input files. This documentation is useful for people wanting to write their own templates, so that they know the structure and various keys available when writing their template, to construct the wanted generated output accordingly.You can also find other help & tutorial material on the internet, like this classroom about Code Generation I gave at FrenchKit in Sept'17 — and its wiki detailing a step-by-step tutorial about installing and using SwiftGen (and Sourcery too)
Available Parsers
xcassets:
inputs: /dir/to/search/for/imageset/assets
outputs:
templateName: swift5
output: Assets.swift
This will generate an enum Asset
with one static let
per asset (image set, color set, data set, …) in your assets catalog, so that you can use them as constants.
Example of code generated by the bundled template
internal enum Asset {
internal enum Files {
internal static let data = DataAsset(value: "Data")
internal static let readme = DataAsset(value: "README")
}
internal enum Food {
internal enum Exotic {
internal static let banana = ImageAsset(value: "Exotic/Banana")
internal static let mango = ImageAsset(value: "Exotic/Mango")
}
internal static let `private` = ImageAsset(value: "private")
}
internal enum Styles {
internal enum Vengo {
internal static let primary = ColorAsset(value: "Vengo/Primary")
internal static let tint = ColorAsset(value: "Vengo/Tint")
}
}
internal enum Symbols {
internal static let exclamationMark = SymbolAsset(name: "Exclamation Mark")
internal static let plus = SymbolAsset(name: "Plus")
}
internal enum Targets {
internal static let bottles = ARResourceGroupAsset(name: "Bottles")
internal static let paintings = ARResourceGroupAsset(name: "Paintings")
}
}
// You can create new images by referring to the enum instance and calling `.image` on it:
let bananaImage = Asset.Exotic.banana.image
let privateImage = Asset.private.image
// You can create colors by referring to the enum instance and calling `.color` on it:
let primaryColor = Asset.Styles.Vengo.primary.color
let tintColor = Asset.Styles.Vengo.tint.color
// You can create data items by referring to the enum instance and calling `.data` on it:
let data = Asset.data.data
let readme = Asset.readme.data
// You can load an AR resource group's items using:
let bottles = Asset.Targets.bottles.referenceObjects
let paintings = Asset.Targets.paintings.referenceImages
// You can create new symbol images by referring to the enum instance and calling `.image` on it (with or without configuration)
let plus = Asset.Symbols.plus.image
let style = UIImage.SymbolConfiguration(textStyle: .headline)
let styled = Asset.Symbols.exclamationMark.image(with: style)
❗️ We recommend to define your colors in your Assets Catalogs and use the
xcassets
parser (see above) to generate color constants, instead of using thiscolors
parser described below.
Thecolors
parser below is mainly useful if you support older versions of iOS where colors can't be defined in Asset Catalogs, or if you want to use Android'scolors.xml
files as input.
colors:
inputs: /path/to/colors-file.txt
outputs:
templateName: swift5
output: Colors.swift
This will generate a enum ColorName
with one static let
per color listed in the text file passed as argument.
The input file is expected to be either:
rrggbb
or rrggbbaa
, optionally prefixed by #
or 0x
) or the name of another color in the file. Whitespaces are ignored.<color name="AColorName">AColorHexRepresentation</color>
*.clr
file used by Apple's Color Palettes.For example you can use this command to generate colors from one of your system color lists:
colors:
inputs: ~/Library/Colors/MyColors.clr
outputs:
templateName: swift5
output: Colors.swift
Generated code will look the same as if you'd use a text file.
Example of code generated by the bundled template
Given the following colors.txt
file:
Cyan-Color : 0xff66ccff
ArticleTitle : #33fe66
ArticleBody : 339666
ArticleFootnote : ff66ccff
Translucent : ffffffcc
The generated code will look like this:
internal struct ColorName {
internal let rgbaValue: UInt32
internal var color: Color { return Color(named: self) }
/// <span style="display:block;width:3em;height:2em;border:1px solid black;background:#339666"></span>
/// Alpha: 100% <br/> (0x339666ff)
internal static let articleBody = ColorName(rgbaValue: 0x339666ff)
/// <span style="display:block;width:3em;height:2em;border:1px solid black;background:#ff66cc"></span>
/// Alpha: 100% <br/> (0xff66ccff)
internal static let articleFootnote = ColorName(rgbaValue: 0xff66ccff)
...
}
// You can create colors with the convenience constructor like this:
let title = UIColor(named: .articleBody) // iOS
let footnote = NSColor(named: .articleFootnote) // macOS
// Or as an alternative, you can refer to enum instance and call .color on it:
let sameTitle = ColorName.articleBody.color
let sameFootnote = ColorName.articleFootnote.color
This way, no need to enter the color red, green, blue, alpha values each time and create ugly constants in the global namespace for them.
coredata:
inputs: /path/to/model.xcdatamodeld
outputs:
templateName: swift5
output: CoreData.swift
This will parse the specified core data model(s), generate a class for each entity in your model containing all the attributes, and a few extensions if needed for relationships and predefined fetch requests.
Example of code generated by the bundled template
internal class MainEntity: NSManagedObject {
internal class var entityName: String {
return "MainEntity"
}
internal class func entity(in managedObjectContext: NSManagedObjectContext) -> NSEntityDescription? {
return NSEntityDescription.entity(forEntityName: entityName, in: managedObjectContext)
}
@nonobjc internal class func makeFetchRequest() -> NSFetchRequest<MainEntity> {
return NSFetchRequest<MainEntity>(entityName: entityName)
}
@NSManaged internal var attributedString: NSAttributedString?
@NSManaged internal var binaryData: Data?
@NSManaged internal var boolean: Bool
@NSManaged internal var date: Date?
@NSManaged internal var float: Float
@NSManaged internal var int64: Int64
internal var integerEnum: IntegerEnum {
get {
let key = "integerEnum"
willAccessValue(forKey: key)
defer { didAccessValue(forKey: key) }
guard let value = primitiveValue(forKey: key) as? IntegerEnum.RawValue,
let result = IntegerEnum(rawValue: value) else {
fatalError("Could not convert value for key '\(key)' to type 'IntegerEnum'")
}
return result
}
set {
let key = "integerEnum"
willChangeValue(forKey: key)
defer { didChangeValue(forKey: key) }
setPrimitiveValue(newValue.rawValue, forKey: key)
}
}
@NSManaged internal var manyToMany: Set<SecondaryEntity>
}
// MARK: Relationship ManyToMany
extension MainEntity {
@objc(addManyToManyObject:)
@NSManaged public func addToManyToMany(_ value: SecondaryEntity)
@objc(removeManyToManyObject:)
@NSManaged public func removeFromManyToMany(_ value: SecondaryEntity)
@objc(addManyToMany:)
@NSManaged public func addToManyToMany(_ values: Set<SecondaryEntity>)
@objc(removeManyToMany:)
@NSManaged public func removeFromManyToMany(_ values: Set<SecondaryEntity>)
}
// Fetch all the instances of MainEntity
let request = MainEntity.makeFetchRequest()
let mainItems = try myContext.execute(request)
// Type-safe relationships: `relatedItem` will be a `SecondaryEntity?` in this case
let relatedItem = myMainItem.manyToMany.first
files:
inputs: path/to/search
filter: .+\.mp4$
outputs:
templateName: structured-swift5
output: Files.swift
The files parser is intended to just list the name and mimetype of the files and subdirectories in a given directory. This will recursively search the specified directory using the given filter (default .*
), defining a struct File
for each matching file, and an hierarchical enum representing the directory structure of files.
Example of code generated by the bundled template
internal enum Files {
/// test.txt
internal static let testTxt = File(name: "test", ext: "txt", path: "", mimeType: "text/plain")
/// subdir/
internal enum Subdir {
/// subdir/A Video With Spaces.mp4
internal static let aVideoWithSpacesMp4 = File(name: "A Video With Spaces", ext: "mp4", path: "subdir", mimeType: "video/mp4")
}
}
// Access files using the `url` or `path` fields
let txt = Files.testTxt.url
let video = Files.Subdir.aVideoWithSpacesMp4.path
// In addition, there are `url(locale:)` and `path(locale:)` to specify a locale
let localeTxt = Files.testTxt.url(locale: Locale.current)
let localeVideo = Files.Subdir.aVideoWithSpacesMp4.path(locale: Locale.current)
SwiftGen also has a template if you're not interested in keeping the folder structure in the generated code.
Example of code generated by the flat bundled template
internal enum Files {
/// test.txt
internal static let testTxt = File(name: "test", ext: "txt", path: "", mimeType: "text/plain")
/// subdir/A Video With Spaces.mp4
internal static let aVideoWithSpacesMp4 = File(name: "A Video With Spaces", ext: "mp4", path: "subdir", mimeType: "video/mp4")
}
}
Given the same file and folder structure as above the usage will now be:
// Access files using the `url` or `path` fields
let txt = Files.testTxt.url
let video = Files.aVideoWithSpacesMp4.path
// In addition, there are `url(locale:)` and `path(locale:)` to specify a locale
let localeTxt = Files.testTxt.url(locale: Locale.current)
let localeVideo = Files.aVideoWithSpacesMp4.path(locale: Locale.current)
fonts:
inputs: /path/to/font/dir
outputs:
templateName: swift5
output: Fonts.swift
This will recursively go through the specified directory, finding any typeface files (TTF, OTF, …), defining a struct FontFamily
for each family, and an enum nested under that family that will represent the font styles.
Example of code generated by the bundled template
internal enum FontFamily {
internal enum SFNSDisplay: String, FontConvertible {
internal static let regular = FontConvertible(name: ".SFNSDisplay-Regular", family: ".SF NS Display", path: "SFNSDisplay-Regular.otf")
}
internal enum ZapfDingbats: String, FontConvertible {
internal static let regular = FontConvertible(name: "ZapfDingbatsITC", family: "Zapf Dingbats", path: "ZapfDingbats.ttf")
}
}
// You can create fonts with the convenience constructor like this:
let displayRegular = UIFont(font: FontFamily.SFNSDisplay.regular, size: 20.0) // iOS
let dingbats = NSFont(font: FontFamily.ZapfDingbats.regular, size: 20.0) // macOS
// Or as an alternative, you can refer to enum instance and call .font on it:
let sameDisplayRegular = FontFamily.SFNSDisplay.regular.font(size: 20.0)
let sameDingbats = FontFamily.ZapfDingbats.regular.font(size: 20.0)
ib:
inputs: /dir/to/search/for/storyboards
outputs:
- templateName: scenes-swift5
output: Storyboard Scenes.swift
- templateName: segues-swift5
output: Storyboard Segues.swift
This will generate an enum
for each of your NSStoryboard
/UIStoryboard
, with respectively one static let
per storyboard scene or segue.
Example of code generated by the bundled template
The generated code will look like this:
// output from the scenes template
internal enum StoryboardScene {
internal enum Dependency: StoryboardType {
internal static let storyboardName = "Dependency"
internal static let dependent = SceneType<UIViewController>(storyboard: Dependency.self, identifier: "Dependent")
}
internal enum Message: StoryboardType {
internal static let storyboardName = "Message"
internal static let messagesList = SceneType<UITableViewController>(storyboard: Message.self, identifier: "MessagesList")
}
}
// output from the segues template
internal enum StoryboardSegue {
internal enum Message: String, SegueType {
case customBack = "CustomBack"
case embed = "Embed"
case nonCustom = "NonCustom"
case showNavCtrl = "Show-NavCtrl"
}
}
// You can instantiate scenes using the `instantiate` method:
let vc = StoryboardScene.Dependency.dependent.instantiate()
// You can perform segues using:
vc.perform(segue: StoryboardSegue.Message.embed)
// or match them (in prepareForSegue):
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch StoryboardSegue.Message(segue) {
case .embed?:
// Prepare for your custom segue transition, passing information to the destination VC
case .customBack?:
// Prepare for your custom segue transition, passing information to the destination VC
default:
// Other segues from other scenes, not handled by this VC
break
}
}
json:
inputs: /path/to/json/dir-or-file
outputs:
templateName: runtime-swift5
output: JSON.swift
yaml:
inputs: /path/to/yaml/dir-or-file
outputs:
templateName: inline-swift5
output: YAML.swift
This will parse the given file, or when given a directory, recursively search for JSON and YAML files. It will define an enum
for each file (and documents in a file where needed), and type-safe constants for the content of the file.
Unlike other parsers, this one is intended to allow you to use more custom inputs (as the formats are quite open to your needs) to generate your code. This means that for these parsers (and the plist
one), you'll probably be more likely to use custom templates to generate code properly adapted/tuned to your inputs, rather than using the bundled templates. To read more about writing your own custom templates, see see the dedicated documentation.
Example of code generated by the bundled template
internal enum JSONFiles {
internal enum Info {
private static let _document = JSONDocument(path: "info.json")
internal static let key1: String = _document["key1"]
internal static let key2: String = _document["key2"]
internal static let key3: [String: Any] = _document["key3"]
}
internal enum Sequence {
internal static let items: [Int] = objectFromJSON(at: "sequence.json")
}
}
// This will be a dictionary
let foo = JSONFiles.Info.key3
// This will be an [Int]
let bar = JSONFiles.Sequence.items
plist:
inputs: /path/to/plist/dir-or-file
outputs:
templateName: runtime-swift5
output: Plist.swift
This will parse the given file, or when given a directory, recursively search for Plist files. It will define an enum
for each file (and documents in a file where needed), and type-safe constants for the content of the file.
Unlike other parsers, this one is intended to allow you to use more custom inputs (as the format is quite open to your needs) to generate your code. This means that for this parser (and the json
and yaml
ones), you'll probably be more likely to use custom templates to generate code properly adapted/tuned to your inputs, rather than using the bundled templates. To read more about writing your own custom templates, see see the dedicated documentation.
Example of code generated by the bundled template
internal enum PlistFiles {
internal enum Test {
internal static let items: [String] = arrayFromPlist(at: "array.plist")
}
internal enum Stuff {
private static let _document = PlistDocument(path: "dictionary.plist")
internal static let key1: Int = _document["key1"]
internal static let key2: [String: Any] = _document["key2"]
}
}
// This will be an array
let foo = PlistFiles.Test.items
// This will be an Int
let bar = PlistFiles.Stuff.key1
strings:
inputs: /path/to/language.lproj
outputs:
templateName: structured-swift5
output: Strings.swift
This will generate a Swift enum L10n
that will map all your Localizable.strings
and Localizable.stringsdict
(or other tables) keys to a static let
constant. And if it detects placeholders like %@
,%d
,%f
, it will generate a static func
with the proper argument types instead, to provide type-safe formatting. By default it will add comments to the generated constants and functions using the comments from the strings file if present, or the default translation of the string.
Note that all dots within the key names are converted to dots in code (by using nested enums). You can provide a different separator than
.
to split key names into substructures by using a parser option – see the parser documentation.
Example of code generated by the structured bundled template
Given the following Localizable.strings
file:
/* Title for an alert */
"alert_title" = "Title of the alert";
"alert_message" = "Some alert body there";
/* A comment with no space above it */
"bananas.owner" = "Those %d bananas belong to %@.";
And the following Localizable.stringsdict
file:
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>apples.count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@apples@</string>
<key>apples</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string>You have no apples</string>
<key>one</key>
<string>You have one apple</string>
<key>other</key>
<string>You have %d apples. Wow that is a lot!</string>
</dict>
</dict>
</dict>
</plist>
Reminder: Don't forget to end each line in your
*.strings
files with a semicolon;
! Now that in Swift code we don't need semi-colons, it's easy to forget it's still required by theLocalizable.strings
file format 😉
The generated code will contain this:
internal enum L10n {
/// Some alert body there
internal static let alertMessage = L10n.tr("Localizable", "alert__message", fallback: #"Some alert body there"#)
/// Title for an alert
internal static let alertTitle = L10n.tr("Localizable", "alert__title", fallback: #"Title of the alert"#)
internal enum Apples {
/// You have %d apples
internal static func count(_ p1: Int) -> String {
return L10n.tr("Localizable", "apples.count", p1, fallback: #"You have %d apples"#)
}
}
internal enum Bananas {
/// A comment with no space above it
internal static func owner(_ p1: Int, _ p2: Any) -> String {
return L10n.tr("Localizable", "bananas.owner", p1, String(describing: p2), fallback: #"Those %d bananas belong to %@."#)
}
}
}
Note that if the same key is present in both the .strings
and the .stringsdict
files, SwiftGen will only consider the one in the .stringsdict
file, as that's also how Foundation behaves at runtime.
Once the code has been generated by the script, you can use it this way in your Swift code:
// Simple strings
let message = L10n.alertMessage
let title = L10n.alertTitle
// with parameters, note that each argument needs to be of the correct type
let apples = L10n.Apples.count(3)
let bananas = L10n.Bananas.owner(5, "Olivier")
SwiftGen also has a template to support flat strings files (i.e. without splitting the keys in substructures using "dot syntax"). The advantage is that your keys won't be mangled in any way; the disadvantage is that auto-completion won't be as nice.
Example of code generated by the flat bundled template
internal enum L10n {
/// Some alert body there
internal static let alertMessage = L10n.tr("Localizable", "alert__message", fallback: #"Some alert body there"#)
/// Title for an alert
internal static let alertTitle = L10n.tr("Localizable", "alert__title", fallback: #"Title of the alert"#)
/// You have %d apples
internal static func applesCount(_ p1: Int) -> String {
return L10n.tr("Localizable", "apples.count", p1, fallback: #"You have %d apples"#)
}
/// A comment with no space above it
internal static func bananasOwner(_ p1: Int, _ p2: Any) -> String {
return L10n.tr("Localizable", "bananas.owner", p1, String(describing: p2), fallback: #"Those %d bananas belong to %@."#)
}
}
Given the same Localizable.strings
and Localizable.stringsdict
as above the usage will now be:
// Simple strings
let message = L10n.alertMessage
let title = L10n.alertTitle
// with parameters, note that each argument needs to be of the correct type
let apples = L10n.applesCount(3)
let bananas = L10n.bananasOwner(5, "Olivier")
Licence
This code and tool is under the MIT Licence. See the LICENCE
file in this repository.
This tool is powered by
It is currently mainly maintained by @AliSoftware and @djbe. But I couldn't thank enough all the other contributors to this tool along the different versions which helped make SwiftGen awesome! 🎉
If you want to contribute, don't hesitate to open a Pull Request, or even join the team!
If you want to also get rid of String-based APIs not only for your resources, but also for UITableViewCell
, UICollectionViewCell
and XIB-based views, you should take a look at my Mixin Reusable.
If you want to generate Swift code from your own Swift code (so meta!), like generate Equatable
conformance to your types and a lot of other similar things, use Sourcery.
SwiftGen and Sourcery are complementary tools. In fact, Sourcery uses Stencil
too, as well as SwiftGen's StencilSwiftKit
so you can use the exact same syntax for your templates for both!
You can also follow me on twitter for news/updates about other projects I am creating, or read my blog.
Author: SwiftGen
Source Code: https://github.com/SwiftGen/SwiftGen
License: MIT license
1654780380
Java 16 introduces Records. While this version of records is fantastic, it's currently missing some important features normally found in data classes: a builder and "with"ers. This project is an annotation processor that creates:
Details:
@RecordBuilder
public record NameAndAge(String name, int age){}
This will generate a builder class that can be used ala:
// build from components
NameAndAge n1 = NameAndAgeBuilder.builder().name(aName).age(anAge).build();
// generate a copy with a changed value
NameAndAge n2 = NameAndAgeBuilder.builder(n1).age(newAge).build(); // name is the same as the name in n1
// pass to other methods to set components
var builder = new NameAndAgeBuilder();
setName(builder);
setAge(builder);
NameAndAge n3 = builder.build();
// use the generated static constructor/builder
import static NameAndAgeBuilder.NameAndAge;
...
var n4 = NameAndAge("hey", 42);
@RecordBuilder
public record NameAndAge(String name, int age) implements NameAndAgeBuilder.With {}
In addition to creating a builder, your record is enhanced by "wither" methods ala:
NameAndAge r1 = new NameAndAge("foo", 123);
NameAndAge r2 = r1.withName("bar");
NameAndAge r3 = r2.withAge(456);
// access the builder as well
NameAndAge r4 = r3.with().age(101).name("baz").build();
// alternate method of accessing the builder (note: no need to call "build()")
NameAndAge r5 = r4.with(b -> b.age(200).name("whatever"));
// perform some logic in addition to changing values
NameAndAge r5 = r4.with(b -> {
if (b.age() > 13) {
b.name("Teen " + b.name());
} else {
b.name("whatever"));
}
});
// or, if you cannot add the "With" interface to your record...
NameAndAge r6 = NameAndAgeBuilder.from(r5).with(b -> b.age(200).name("whatever"));
NameAndAge r7 = NameAndAgeBuilder.from(r5).withName("boop");
Hat tip to Benji Weber for the Withers idea.
(Note: you can see a builder class built using @RecordBuilderFull
here: FullRecordBuilder.java)
The full builder class is defined as:
public class NameAndAgeBuilder {
private String name;
private int age;
private NameAndAgeBuilder() {
}
private NameAndAgeBuilder(String name, int age) {
this.name = name;
this.age = age;
}
/**
* Static constructor/builder. Can be used instead of new NameAndAge(...)
*/
public static NameAndAge NameAndAge(String name, int age) {
return new NameAndAge(name, age);
}
/**
* Return a new builder with all fields set to default Java values
*/
public static NameAndAgeBuilder builder() {
return new NameAndAgeBuilder();
}
/**
* Return a new builder with all fields set to the values taken from the given record instance
*/
public static NameAndAgeBuilder builder(NameAndAge from) {
return new NameAndAgeBuilder(from.name(), from.age());
}
/**
* Return a "with"er for an existing record instance
*/
public static NameAndAgeBuilder.With from(NameAndAge from) {
return new _FromWith(from);
}
/**
* Return a stream of the record components as map entries keyed with the component name and the value as the component value
*/
public static Stream<Map.Entry<String, Object>> stream(NameAndAge record) {
return Stream.of(new AbstractMap.SimpleImmutableEntry<>("name", record.name()),
new AbstractMap.SimpleImmutableEntry<>("age", record.age()));
}
/**
* Return a new record instance with all fields set to the current values in this builder
*/
public NameAndAge build() {
return new NameAndAge(name, age);
}
@Override
public String toString() {
return "NameAndAgeBuilder[name=" + name + ", age=" + age + "]";
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public boolean equals(Object o) {
return (this == o) || ((o instanceof NameAndAgeBuilder r)
&& Objects.equals(name, r.name)
&& (age == r.age));
}
/**
* Set a new value for the {@code name} record component in the builder
*/
public NameAndAgeBuilder name(String name) {
this.name = name;
return this;
}
/**
* Return the current value for the {@code name} record component in the builder
*/
public String name() {
return name;
}
/**
* Set a new value for the {@code age} record component in the builder
*/
public NameAndAgeBuilder age(int age) {
this.age = age;
return this;
}
/**
* Return the current value for the {@code age} record component in the builder
*/
public int age() {
return age;
}
/**
* Add withers to {@code NameAndAge}
*/
public interface With {
/**
* Return the current value for the {@code name} record component in the builder
*/
String name();
/**
* Return the current value for the {@code age} record component in the builder
*/
int age();
/**
* Return a new record builder using the current values
*/
default NameAndAgeBuilder with() {
return new NameAndAgeBuilder(name(), age());
}
/**
* Return a new record built from the builder passed to the given consumer
*/
default NameAndAge with(Consumer<NameAndAgeBuilder> consumer) {
NameAndAgeBuilder builder = with();
consumer.accept(builder);
return builder.build();
}
/**
* Return a new instance of {@code NameAndAge} with a new value for {@code name}
*/
default NameAndAge withName(String name) {
return new NameAndAge(name, age());
}
/**
* Return a new instance of {@code NameAndAge} with a new value for {@code age}
*/
default NameAndAge withAge(int age) {
return new NameAndAge(name(), age);
}
}
private static final class _FromWith implements NameAndAgeBuilder.With {
private final NameAndAge from;
private _FromWith(NameAndAge from) {
this.from = from;
}
@Override
public String name() {
return from.name();
}
@Override
public int age() {
return from.age();
}
}
}
@RecordInterface
public interface NameAndAge {
String name();
int age();
}
This will generate a record ala:
@RecordBuilder
public record NameAndAgeRecord(String name, int age) implements
NameAndAge, NameAndAgeRecordBuilder.With {}
Note that the generated record is annotated with @RecordBuilder
so a record builder is generated for the new record as well.
Notes:
@IgnoreDefaultMethod
@RecordInterface(addRecordBuilder = false)
getThing()
, isThing()
) the "get" and "is" prefixes are stripped and forwarding methods are added.An alternate method of generation is to use the Include variants of the annotations. These variants act on lists of specified classes. This allows the source classes to be pristine or even come from libraries where you are not able to annotate the source.
E.g.
import some.library.code.ImportedRecord
import some.library.code.ImportedInterface
@RecordBuilder.Include({
ImportedRecord.class // generates a record builder for ImportedRecord
})
@RecordInterface.Include({
ImportedInterface.class // generates a record interface for ImportedInterface
})
public void Placeholder {
}
@RecordBuilder.Include
also supports a packages
attribute that includes all records in the listed packages.
The target package for generation is the same as the package that contains the "Include" annotation. Use packagePattern
to change this (see Javadoc for details).
Add a dependency that contains the discoverable annotation processor:
<dependency>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder-processor</artifactId>
<version>${record.builder.version}</version>
<scope>provided</scope>
</dependency>
Add the following to your build.gradle file:
dependencies {
annotationProcessor 'io.soabase.record-builder:record-builder-processor:$version-goes-here'
compileOnly 'io.soabase.record-builder:record-builder-core:$version-goes-here'
}
Depending on your IDE you are likely to need to enable Annotation Processing in your IDE settings.
RecordBuilder can be customized to your needs and you can even create your own custom RecordBuilder annotations. See Customizing RecordBuilder for details.
Download Details:
Author: Randgalt
Source Code: https://github.com/Randgalt/record-builder
License: Apache-2.0 license
1654780200
JavaPoet
is a Java API for generating .java
source files.
Source file generation can be useful when doing things such as annotation processing or interacting with metadata files (e.g., database schemas, protocol formats). By generating code, you eliminate the need to write boilerplate while also keeping a single source of truth for the metadata.
Here's a (boring) HelloWorld
class:
package com.example.helloworld;
public final class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, JavaPoet!");
}
}
And this is the (exciting) code to generate it with JavaPoet:
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
javaFile.writeTo(System.out);
To declare the main method, we've created a MethodSpec
"main" configured with modifiers, return type, parameters and code statements. We add the main method to a HelloWorld
class, and then add that to a HelloWorld.java
file.
In this case we write the file to System.out
, but we could also get it as a string (JavaFile.toString()
) or write it to the file system (JavaFile.writeTo()
).
The Javadoc catalogs the complete JavaPoet API, which we explore below.
Most of JavaPoet's API uses plain old immutable Java objects. There's also builders, method chaining and varargs to make the API friendly. JavaPoet offers models for classes & interfaces (TypeSpec
), fields (FieldSpec
), methods & constructors (MethodSpec
), parameters (ParameterSpec
) and annotations (AnnotationSpec
).
But the body of methods and constructors is not modeled. There's no expression class, no statement class or syntax tree nodes. Instead, JavaPoet uses strings for code blocks:
MethodSpec main = MethodSpec.methodBuilder("main")
.addCode(""
+ "int total = 0;\n"
+ "for (int i = 0; i < 10; i++) {\n"
+ " total += i;\n"
+ "}\n")
.build();
Which generates this:
void main() {
int total = 0;
for (int i = 0; i < 10; i++) {
total += i;
}
}
The manual semicolons, line wrapping, and indentation are tedious and so JavaPoet offers APIs to make it easier. There's addStatement()
which takes care of semicolons and newline, and beginControlFlow()
+ endControlFlow()
which are used together for braces, newlines, and indentation:
MethodSpec main = MethodSpec.methodBuilder("main")
.addStatement("int total = 0")
.beginControlFlow("for (int i = 0; i < 10; i++)")
.addStatement("total += i")
.endControlFlow()
.build();
This example is lame because the generated code is constant! Suppose instead of just adding 0 to 10, we want to make the operation and range configurable. Here's a method that generates a method:
private MethodSpec computeRange(String name, int from, int to, String op) {
return MethodSpec.methodBuilder(name)
.returns(int.class)
.addStatement("int result = 1")
.beginControlFlow("for (int i = " + from + "; i < " + to + "; i++)")
.addStatement("result = result " + op + " i")
.endControlFlow()
.addStatement("return result")
.build();
}
And here's what we get when we call computeRange("multiply10to20", 10, 20, "*")
:
int multiply10to20() {
int result = 1;
for (int i = 10; i < 20; i++) {
result = result * i;
}
return result;
}
Methods generating methods! And since JavaPoet generates source instead of bytecode, you can read through it to make sure it's right.
Some control flow statements, such as if/else
, can have unlimited control flow possibilities. You can handle those options using nextControlFlow()
:
MethodSpec main = MethodSpec.methodBuilder("main")
.addStatement("long now = $T.currentTimeMillis()", System.class)
.beginControlFlow("if ($T.currentTimeMillis() < now)", System.class)
.addStatement("$T.out.println($S)", System.class, "Time travelling, woo hoo!")
.nextControlFlow("else if ($T.currentTimeMillis() == now)", System.class)
.addStatement("$T.out.println($S)", System.class, "Time stood still!")
.nextControlFlow("else")
.addStatement("$T.out.println($S)", System.class, "Ok, time still moving forward")
.endControlFlow()
.build();
Which generates:
void main() {
long now = System.currentTimeMillis();
if (System.currentTimeMillis() < now) {
System.out.println("Time travelling, woo hoo!");
} else if (System.currentTimeMillis() == now) {
System.out.println("Time stood still!");
} else {
System.out.println("Ok, time still moving forward");
}
}
Catching exceptions using try/catch
is also a use case for nextControlFlow()
:
MethodSpec main = MethodSpec.methodBuilder("main")
.beginControlFlow("try")
.addStatement("throw new Exception($S)", "Failed")
.nextControlFlow("catch ($T e)", Exception.class)
.addStatement("throw new $T(e)", RuntimeException.class)
.endControlFlow()
.build();
Which produces:
void main() {
try {
throw new Exception("Failed");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
The string-concatenation in calls to beginControlFlow()
and addStatement
is distracting. Too many operators. To address this, JavaPoet offers a syntax inspired-by but incompatible-with String.format()
. It accepts $L
to emit a literal value in the output. This works just like Formatter
's %s
:
private MethodSpec computeRange(String name, int from, int to, String op) {
return MethodSpec.methodBuilder(name)
.returns(int.class)
.addStatement("int result = 0")
.beginControlFlow("for (int i = $L; i < $L; i++)", from, to)
.addStatement("result = result $L i", op)
.endControlFlow()
.addStatement("return result")
.build();
}
Literals are emitted directly to the output code with no escaping. Arguments for literals may be strings, primitives, and a few JavaPoet types described below.
When emitting code that includes string literals, we can use $S
to emit a string, complete with wrapping quotation marks and escaping. Here's a program that emits 3 methods, each of which returns its own name:
public static void main(String[] args) throws Exception {
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(whatsMyName("slimShady"))
.addMethod(whatsMyName("eminem"))
.addMethod(whatsMyName("marshallMathers"))
.build();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
javaFile.writeTo(System.out);
}
private static MethodSpec whatsMyName(String name) {
return MethodSpec.methodBuilder(name)
.returns(String.class)
.addStatement("return $S", name)
.build();
}
In this case, using $S
gives us quotation marks:
public final class HelloWorld {
String slimShady() {
return "slimShady";
}
String eminem() {
return "eminem";
}
String marshallMathers() {
return "marshallMathers";
}
}
We Java programmers love our types: they make our code easier to understand. And JavaPoet is on board. It has rich built-in support for types, including automatic generation of import
statements. Just use $T
to reference types:
MethodSpec today = MethodSpec.methodBuilder("today")
.returns(Date.class)
.addStatement("return new $T()", Date.class)
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(today)
.build();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
javaFile.writeTo(System.out);
That generates the following .java
file, complete with the necessary import
:
package com.example.helloworld;
import java.util.Date;
public final class HelloWorld {
Date today() {
return new Date();
}
}
We passed Date.class
to reference a class that just-so-happens to be available when we're generating code. This doesn't need to be the case. Here's a similar example, but this one references a class that doesn't exist (yet):
ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");
MethodSpec today = MethodSpec.methodBuilder("tomorrow")
.returns(hoverboard)
.addStatement("return new $T()", hoverboard)
.build();
And that not-yet-existent class is imported as well:
package com.example.helloworld;
import com.mattel.Hoverboard;
public final class HelloWorld {
Hoverboard tomorrow() {
return new Hoverboard();
}
}
The ClassName
type is very important, and you'll need it frequently when you're using JavaPoet. It can identify any declared class. Declared types are just the beginning of Java's rich type system: we also have arrays, parameterized types, wildcard types, and type variables. JavaPoet has classes for building each of these:
ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");
ClassName list = ClassName.get("java.util", "List");
ClassName arrayList = ClassName.get("java.util", "ArrayList");
TypeName listOfHoverboards = ParameterizedTypeName.get(list, hoverboard);
MethodSpec beyond = MethodSpec.methodBuilder("beyond")
.returns(listOfHoverboards)
.addStatement("$T result = new $T<>()", listOfHoverboards, arrayList)
.addStatement("result.add(new $T())", hoverboard)
.addStatement("result.add(new $T())", hoverboard)
.addStatement("result.add(new $T())", hoverboard)
.addStatement("return result")
.build();
JavaPoet will decompose each type and import its components where possible.
package com.example.helloworld;
import com.mattel.Hoverboard;
import java.util.ArrayList;
import java.util.List;
public final class HelloWorld {
List<Hoverboard> beyond() {
List<Hoverboard> result = new ArrayList<>();
result.add(new Hoverboard());
result.add(new Hoverboard());
result.add(new Hoverboard());
return result;
}
}
JavaPoet supports import static
. It does it via explicitly collecting type member names. Let's enhance the previous example with some static sugar:
...
ClassName namedBoards = ClassName.get("com.mattel", "Hoverboard", "Boards");
MethodSpec beyond = MethodSpec.methodBuilder("beyond")
.returns(listOfHoverboards)
.addStatement("$T result = new $T<>()", listOfHoverboards, arrayList)
.addStatement("result.add($T.createNimbus(2000))", hoverboard)
.addStatement("result.add($T.createNimbus(\"2001\"))", hoverboard)
.addStatement("result.add($T.createNimbus($T.THUNDERBOLT))", hoverboard, namedBoards)
.addStatement("$T.sort(result)", Collections.class)
.addStatement("return result.isEmpty() ? $T.emptyList() : result", Collections.class)
.build();
TypeSpec hello = TypeSpec.classBuilder("HelloWorld")
.addMethod(beyond)
.build();
JavaFile.builder("com.example.helloworld", hello)
.addStaticImport(hoverboard, "createNimbus")
.addStaticImport(namedBoards, "*")
.addStaticImport(Collections.class, "*")
.build();
JavaPoet will first add your import static
block to the file as configured, match and mangle all calls accordingly and also import all other types as needed.
package com.example.helloworld;
import static com.mattel.Hoverboard.Boards.*;
import static com.mattel.Hoverboard.createNimbus;
import static java.util.Collections.*;
import com.mattel.Hoverboard;
import java.util.ArrayList;
import java.util.List;
class HelloWorld {
List<Hoverboard> beyond() {
List<Hoverboard> result = new ArrayList<>();
result.add(createNimbus(2000));
result.add(createNimbus("2001"));
result.add(createNimbus(THUNDERBOLT));
sort(result);
return result.isEmpty() ? emptyList() : result;
}
}
Generated code is often self-referential. Use $N
to refer to another generated declaration by its name. Here's a method that calls another:
public String byteToHex(int b) {
char[] result = new char[2];
result[0] = hexDigit((b >>> 4) & 0xf);
result[1] = hexDigit(b & 0xf);
return new String(result);
}
public char hexDigit(int i) {
return (char) (i < 10 ? i + '0' : i - 10 + 'a');
}
When generating the code above, we pass the hexDigit()
method as an argument to the byteToHex()
method using $N
:
MethodSpec hexDigit = MethodSpec.methodBuilder("hexDigit")
.addParameter(int.class, "i")
.returns(char.class)
.addStatement("return (char) (i < 10 ? i + '0' : i - 10 + 'a')")
.build();
MethodSpec byteToHex = MethodSpec.methodBuilder("byteToHex")
.addParameter(int.class, "b")
.returns(String.class)
.addStatement("char[] result = new char[2]")
.addStatement("result[0] = $N((b >>> 4) & 0xf)", hexDigit)
.addStatement("result[1] = $N(b & 0xf)", hexDigit)
.addStatement("return new String(result)")
.build();
Code blocks may specify the values for their placeholders in a few ways. Only one style may be used for each operation on a code block.
Pass an argument value for each placeholder in the format string to CodeBlock.add()
. In each example, we generate code to say "I ate 3 tacos"
CodeBlock.builder().add("I ate $L $L", 3, "tacos")
Place an integer index (1-based) before the placeholder in the format string to specify which argument to use.
CodeBlock.builder().add("I ate $2L $1L", "tacos", 3)
Use the syntax $argumentName:X
where X
is the format character and call CodeBlock.addNamed()
with a map containing all argument keys in the format string. Argument names use characters in a-z
, A-Z
, 0-9
, and _
, and must start with a lowercase character.
Map<String, Object> map = new LinkedHashMap<>();
map.put("food", "tacos");
map.put("count", 3);
CodeBlock.builder().addNamed("I ate $count:L $food:L", map)
All of the above methods have a code body. Use Modifiers.ABSTRACT
to get a method without any body. This is only legal if the enclosing class is either abstract or an interface.
MethodSpec flux = MethodSpec.methodBuilder("flux")
.addModifiers(Modifier.ABSTRACT, Modifier.PROTECTED)
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.addMethod(flux)
.build();
Which generates this:
public abstract class HelloWorld {
protected abstract void flux();
}
The other modifiers work where permitted. Note that when specifying modifiers, JavaPoet uses javax.lang.model.element.Modifier
, a class that is not available on Android. This limitation applies to code-generating-code only; the output code runs everywhere: JVMs, Android, and GWT.
Methods also have parameters, exceptions, varargs, Javadoc, annotations, type variables, and a return type. All of these are configured with MethodSpec.Builder
.
MethodSpec
is a slight misnomer; it can also be used for constructors:
MethodSpec flux = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(String.class, "greeting")
.addStatement("this.$N = $N", "greeting", "greeting")
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC)
.addField(String.class, "greeting", Modifier.PRIVATE, Modifier.FINAL)
.addMethod(flux)
.build();
Which generates this:
public class HelloWorld {
private final String greeting;
public HelloWorld(String greeting) {
this.greeting = greeting;
}
}
For the most part, constructors work just like methods. When emitting code, JavaPoet will place constructors before methods in the output file.
Declare parameters on methods and constructors with either ParameterSpec.builder()
or MethodSpec
's convenient addParameter()
API:
ParameterSpec android = ParameterSpec.builder(String.class, "android")
.addModifiers(Modifier.FINAL)
.build();
MethodSpec welcomeOverlords = MethodSpec.methodBuilder("welcomeOverlords")
.addParameter(android)
.addParameter(String.class, "robot", Modifier.FINAL)
.build();
Though the code above to generate android
and robot
parameters is different, the output is the same:
void welcomeOverlords(final String android, final String robot) {
}
The extended Builder
form is necessary when the parameter has annotations (such as @Nullable
).
Like parameters, fields can be created either with builders or by using convenient helper methods:
FieldSpec android = FieldSpec.builder(String.class, "android")
.addModifiers(Modifier.PRIVATE, Modifier.FINAL)
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC)
.addField(android)
.addField(String.class, "robot", Modifier.PRIVATE, Modifier.FINAL)
.build();
Which generates:
public class HelloWorld {
private final String android;
private final String robot;
}
The extended Builder
form is necessary when a field has Javadoc, annotations, or a field initializer. Field initializers use the same String.format()
-like syntax as the code blocks above:
FieldSpec android = FieldSpec.builder(String.class, "android")
.addModifiers(Modifier.PRIVATE, Modifier.FINAL)
.initializer("$S + $L", "Lollipop v.", 5.0d)
.build();
Which generates:
private final String android = "Lollipop v." + 5.0;
JavaPoet has no trouble with interfaces. Note that interface methods must always be PUBLIC ABSTRACT
and interface fields must always be PUBLIC STATIC FINAL
. These modifiers are necessary when defining the interface:
TypeSpec helloWorld = TypeSpec.interfaceBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC)
.addField(FieldSpec.builder(String.class, "ONLY_THING_THAT_IS_CONSTANT")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.initializer("$S", "change")
.build())
.addMethod(MethodSpec.methodBuilder("beep")
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.build())
.build();
But these modifiers are omitted when the code is generated. These are the defaults so we don't need to include them for javac
's benefit!
public interface HelloWorld {
String ONLY_THING_THAT_IS_CONSTANT = "change";
void beep();
}
Use enumBuilder
to create the enum type, and addEnumConstant()
for each value:
TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo")
.addModifiers(Modifier.PUBLIC)
.addEnumConstant("ROCK")
.addEnumConstant("SCISSORS")
.addEnumConstant("PAPER")
.build();
To generate this:
public enum Roshambo {
ROCK,
SCISSORS,
PAPER
}
Fancy enums are supported, where the enum values override methods or call a superclass constructor. Here's a comprehensive example:
TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo")
.addModifiers(Modifier.PUBLIC)
.addEnumConstant("ROCK", TypeSpec.anonymousClassBuilder("$S", "fist")
.addMethod(MethodSpec.methodBuilder("toString")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addStatement("return $S", "avalanche!")
.returns(String.class)
.build())
.build())
.addEnumConstant("SCISSORS", TypeSpec.anonymousClassBuilder("$S", "peace")
.build())
.addEnumConstant("PAPER", TypeSpec.anonymousClassBuilder("$S", "flat")
.build())
.addField(String.class, "handsign", Modifier.PRIVATE, Modifier.FINAL)
.addMethod(MethodSpec.constructorBuilder()
.addParameter(String.class, "handsign")
.addStatement("this.$N = $N", "handsign", "handsign")
.build())
.build();
Which generates this:
public enum Roshambo {
ROCK("fist") {
@Override
public String toString() {
return "avalanche!";
}
},
SCISSORS("peace"),
PAPER("flat");
private final String handsign;
Roshambo(String handsign) {
this.handsign = handsign;
}
}
In the enum code, we used TypeSpec.anonymousInnerClass()
. Anonymous inner classes can also be used in code blocks. They are values that can be referenced with $L
:
TypeSpec comparator = TypeSpec.anonymousClassBuilder("")
.addSuperinterface(ParameterizedTypeName.get(Comparator.class, String.class))
.addMethod(MethodSpec.methodBuilder("compare")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addParameter(String.class, "a")
.addParameter(String.class, "b")
.returns(int.class)
.addStatement("return $N.length() - $N.length()", "a", "b")
.build())
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addMethod(MethodSpec.methodBuilder("sortByLength")
.addParameter(ParameterizedTypeName.get(List.class, String.class), "strings")
.addStatement("$T.sort($N, $L)", Collections.class, "strings", comparator)
.build())
.build();
This generates a method that contains a class that contains a method:
void sortByLength(List<String> strings) {
Collections.sort(strings, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
});
}
One particularly tricky part of defining anonymous inner classes is the arguments to the superclass constructor. In the above code we're passing the empty string for no arguments: TypeSpec.anonymousClassBuilder("")
. To pass different parameters use JavaPoet's code block syntax with commas to separate arguments.
Simple annotations are easy:
MethodSpec toString = MethodSpec.methodBuilder("toString")
.addAnnotation(Override.class)
.returns(String.class)
.addModifiers(Modifier.PUBLIC)
.addStatement("return $S", "Hoverboard")
.build();
Which generates this method with an @Override
annotation:
@Override
public String toString() {
return "Hoverboard";
}
Use AnnotationSpec.builder()
to set properties on annotations:
MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent")
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.addAnnotation(AnnotationSpec.builder(Headers.class)
.addMember("accept", "$S", "application/json; charset=utf-8")
.addMember("userAgent", "$S", "Square Cash")
.build())
.addParameter(LogRecord.class, "logRecord")
.returns(LogReceipt.class)
.build();
Which generates this annotation with accept
and userAgent
properties:
@Headers(
accept = "application/json; charset=utf-8",
userAgent = "Square Cash"
)
LogReceipt recordEvent(LogRecord logRecord);
When you get fancy, annotation values can be annotations themselves. Use $L
for embedded annotations:
MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent")
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.addAnnotation(AnnotationSpec.builder(HeaderList.class)
.addMember("value", "$L", AnnotationSpec.builder(Header.class)
.addMember("name", "$S", "Accept")
.addMember("value", "$S", "application/json; charset=utf-8")
.build())
.addMember("value", "$L", AnnotationSpec.builder(Header.class)
.addMember("name", "$S", "User-Agent")
.addMember("value", "$S", "Square Cash")
.build())
.build())
.addParameter(LogRecord.class, "logRecord")
.returns(LogReceipt.class)
.build();
Which generates this:
@HeaderList({
@Header(name = "Accept", value = "application/json; charset=utf-8"),
@Header(name = "User-Agent", value = "Square Cash")
})
LogReceipt recordEvent(LogRecord logRecord);
Note that you can call addMember()
multiple times with the same property name to populate a list of values for that property.
Fields, methods and types can be documented with Javadoc:
MethodSpec dismiss = MethodSpec.methodBuilder("dismiss")
.addJavadoc("Hides {@code message} from the caller's history. Other\n"
+ "participants in the conversation will continue to see the\n"
+ "message in their own history unless they also delete it.\n")
.addJavadoc("\n")
.addJavadoc("<p>Use {@link #delete($T)} to delete the entire\n"
+ "conversation for all participants.\n", Conversation.class)
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.addParameter(Message.class, "message")
.build();
Which generates this:
/**
* Hides {@code message} from the caller's history. Other
* participants in the conversation will continue to see the
* message in their own history unless they also delete it.
*
* <p>Use {@link #delete(Conversation)} to delete the entire
* conversation for all participants.
*/
void dismiss(Message message);
Use $T
when referencing types in Javadoc to get automatic imports.
Download the latest .jar or depend via Maven:
<dependency>
<groupId>com.squareup</groupId>
<artifactId>javapoet</artifactId>
<version>1.13.0</version>
</dependency>
or Gradle:
compile 'com.squareup:javapoet:1.13.0'
Snapshots of the development version are available in Sonatype's snapshots
repository.
Download Details:
Author: square
Source Code: https://github.com/square/javapoet
License: Apache-2.0 license
1654772940
Java::Geci
Read "how to contribute".
Java::Geci is a library for generating Java code. You can execute code generation programs using Java::Geci to generate new source code or modify existing Java source files. This way, the programmer can use metaprogramming to express code shorter and more expressively than it would be possible in pure Java.
The framework:
There are several occasions when we need generated code. IDEs (Eclipse, NetBeans, IntelliJ) support the simplest of such scenarios. They can create setters, getters, constructors, equals()
and hashCode()
methods in different ways. There are two significant problems with that solution.
Java::Geci eliminates these two problems. The mode of the execution ensures that all the generated code is up-to-date. The code generation runs from a unit test, that you can start from your favourite IDE or from the build tool If you forgot to update the generated code after the program affecting the generated code changed, the test would fail. This is not a problem in this case, because the test also updates the generated code. The next time you run the test it will see the up-to-date generated code, and it will not fail.
Java::Geci also has a straightforward API supporting code generators, so it is simple to create new code generators that are project-specific. You do not even need to package your code generator classes. Just put them into a test package and execute Java::Geci during the test phase of the build process.
Java::Geci already includes several readily available code generators. Some of them are simple and may be found in other tools, some of them are unique. The generators packaged with the core package are:
Map
and backAdd the Java::Geci modules that you want to use in your project as a dependency. To do that with Maven, use the following dependencies:
<dependency>
<!-- This is optional, you can use your own annotations or comment config -->
<groupId>com.javax0.geci</groupId>
<artifactId>javageci-annotation</artifactId>
<version>1.6.7-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.javax0.geci</groupId>
<artifactId>javageci-engine</artifactId>
<scope>test</scope>
<version>1.6.7-SNAPSHOT</version>
</dependency>
This documentation contains the latest development version. You can see the latest non-SNAPSHOT version in the link for release versions release history documentation.
There are other modules, but you do not need to declare a dependency on them as the engine module has a transitive dependency, and thus Maven automatically will use them.
Note | Note that this does not include out-of-the-box generators. Check the individual documentations to see what additional dependencies you might need for those. |
Since Java::Geci works during test execution, the dependencies have a test scope. The exception is the annotation module. This module defines an annotation that you can use to configure the code generation during test time (using reflection). Because of that, the retention of this module is run-time. Although the annotation is not used in production, it remains in the byte code, and thus it has to be the default maven scope.
If for any reason production dependency must not include Java::Geci, you can configure the generators using comments, or you can use the annotation interfaces that you can define inside your project. Copy Geci.java
and Gecis.java
to your project into any package, and Java::Geci will recognize that they are to there configure a generator. Java::Geci is very flexible and recognizes any annotation so long as long the simple name of the annotation is Geci
(and even a bit more).
If you have a look at the test code TestAccessor.java
in the test module you can see the following sample code:
@Test
void testAccessor() throws Exception {
Geci geci;
Assertions.assertFalse(
(geci = new Geci()).source(maven().module("javageci-examples").mainSource())
.register(Accessor.builder().build())
.generate(),
geci.failed());
}
The test runs during the build process, and it generates code whenever that is needed. The return value answers the question: "Was code generated?". It is true
when new code was generated or false
if everything was up-to-date. The Assertions.assertFalse
checks this return value, and if Java::Geci generated new code, your test (and your build) fails. In this case, you have to restart your build. Java::Geci will recognize that the code it could generate is already there, and the test passes.
Download Details:
Author: verhas
Source Code: https://github.com/verhas/javageci
License: Apache-2.0 license
1654765560
Automatic generation of the Builder pattern for Java 1.8+
The Builder pattern is a good choice when designing classes whose constructors or static factories would have more than a handful of parameters. — Effective Java, Second Edition, page 39
Implementing the Builder pattern in Java is tedious, error-prone and repetitive. Who hasn't seen a ten-argument constructor, thought cross thoughts about the previous maintainers of the class, then added "just one more"? Even a simple four-field class requires 39 lines of code for the most basic builder API, or 72 lines if you don't use a utility like AutoValue to generate the value boilerplate.
FreeBuilder produces all the boilerplate for you, as well as free extras like JavaDoc, getter methods, mapper methods, collections support, nested builders, and partial values (used in testing), which are highly useful, but would very rarely justify their creation and maintenance burden in hand-crafted code. (We also reserve the right to add more awesome methods in future!)
[The Builder pattern] is more verbose…so should only be used if there are enough parameters, say, four or more. But keep in mind that you may want to add parameters in the future. If you start out with constructors or static factories, and add a builder when the class evolves to the point where the number of parameters starts to get out of hand, the obsolete constructors or static factories will stick out like a sore thumb. Therefore, it's often better to start with a builder in the first place. — Effective Java, Second Edition, page 39
See Build tools and IDEs for how to add FreeBuilder to your project's build and/or IDE.
Create your value type (e.g. Person
) as an interface or abstract class, containing an abstract accessor method for each desired field. Add the @FreeBuilder
annotation to your class, and it will automatically generate an implementing class and a package-visible builder API (Person_Builder
), which you must subclass. For instance:
import org.inferred.freebuilder.FreeBuilder;
@FreeBuilder
public interface Person {
/** Returns this person's full (English) name. */
String name();
/** Returns this person's age in years, rounded down. */
int age();
/** Returns a new {@link Builder} with the same property values as this person. */
Builder toBuilder();
/** Builder of {@link Person} instances. */
class Builder extends Person_Builder { }
}
The toBuilder()
method here is optional but highly recommended. You may also wish to make the builder's constructor package-protected and manually provide instead a static builder()
method on the value type (though Effective Java does not do this).
If you write the Person interface shown above, you get:
IllegalStateException
for unset fields)mergeFrom
and static from
methods to copy data from existing values or buildersbuild
method that verifies all fields have been setPerson
with:toString
equals
and hashCode
Person
for unit tests with:UnsupportedOperationException
-throwing getters for unset fieldstoString
equals
and hashCode
Person person = new Person.Builder()
.name("Phil")
.age(31)
.build();
System.out.println(person); // Person{name=Phil, age=31}
If you prefer your value types to follow the JavaBean naming convention, just prefix your accessor methods with 'get' (or, optionally, 'is' for boolean accessors). FreeBuilder will follow suit, and additionally add 'set' prefixes on setter methods, as well as dropping the prefix from its toString output.
@FreeBuilder
public interface Person {
/** Returns the person's full (English) name. */
String getName();
/** Returns the person's age in years, rounded down. */
int getAge();
/** Builder of {@link Person} instances. */
class Builder extends Person_Builder { }
}
Person person = new Person.Builder()
.setName("Phil")
.setAge(31)
.build();
System.out.println(person); // Person{name=Phil, age=31}
For each property foo
, the builder gets:
Method | Description |
---|---|
A setter method, foo | Throws a NullPointerException if provided a null. (See the sections on Optional and Nullable for ways to store properties that can be missing.) |
A getter method, foo | Throws an IllegalStateException if the property value has not yet been set. |
A mapper method, mapFoo | Takes a [UnaryOperator]. Replaces the current property value with the result of invoking the unary operator on it. Throws a NullPointerException if the operator, or the value it returns, is null. Throws an IllegalStateException if the property value has not yet been set. |
The mapper methods are very useful when modifying existing values, e.g.
Person olderPerson = person.toBuilder().mapAge(age -> age + 1).build();
We use method overrides to add customization like default values and constraint checks. For instance:
@FreeBuilder
public interface Person {
/** Returns the person's full (English) name. */
String name();
/** Returns the person's age in years, rounded down. */
int age();
/** Returns a human-readable description of the person. */
String description();
/** Builder class for {@link Person}. */
class Builder extends Person_Builder {
public Builder() {
// Set defaults in the builder constructor.
description("Indescribable");
}
@Override Builder age(int age) {
// Check single-field (argument) constraints in the setter method.
checkArgument(age >= 0);
return super.age(age);
}
@Override public Person build() {
// Check cross-field (state) constraints in the build method.
Person person = super.build();
checkState(!person.description().contains(person.name()));
return person;
}
}
}
If a property is optional—that is, has no reasonable default—then use the Java Optional type (or the Guava Optional type for backwards-compatibility).
/** Returns an optional human-readable description of the person. */
Optional<String> description();
This property will now default to Optional.empty(), and the Builder will gain additional convenience setter methods:
Method | Description |
---|---|
description(String value) | Sets the property to Optional.of(value) . Throws a NullPointerException if value is null; this avoids users accidentally clearing an optional value in a way that peer review is unlikely to catch. |
clearDescription() | Sets the property to Optional.empty() . |
description(Optional<String> value) | Sets the property to value . |
nullableDescription(String value) | Sets the property to Optional.ofNullable(value) . |
mapDescription(UnaryOperator<String> mapper | If the property value is not empty, this replaces the value with the result of invoking mapper with the existing value, or clears it if mapper returns null. Throws a NullPointerException if mapper is null. |
Prefer to use explicit defaults where meaningful, as it avoids the need for edge-case code; but prefer Optional to ad-hoc 'not set' defaults, like -1 or the empty string, as it forces the user to think about those edge cases.
@Nullable
As Java currently stands, you should strongly prefer Optional to returning nulls. Using null to represent unset properties is the classic example of Hoare's billion-dollar mistake: a silent source of errors that users won't remember to write test cases for, and which won't be spotted in code reviews. The large "air gap" between the declaration of the getter and the usage is the cause of this problem. Optional uses the compiler to force the call sites to perform explicit null handling, giving reviewers a better chance of seeing mistakes. See also Using and Avoiding Null.
Obviously, greenfield code can trivially adopt Optional, but even existing APIs can be converted to Optional via a simple refactoring sequence; see below. However, if you have compelling legacy reasons that mandate using nulls, you can disable null-checking by marking the getter method @Nullable
. (Any annotation type named "Nullable" will do, but you may wish to use javax.annotation.Nullable
, as used in Google Guava.)
/** Returns an optional title to use when addressing the person. */
@Nullable String title();
This property will now default to null, and the Builder's setter methods will change their null-handling behaviour:
Method | Description |
---|---|
title(@Nullable String title) | Sets the property to title . |
title() | Returns the current value of the property. May be null. |
mapTitle(UnaryOperator<String> mapper) | Takes a UnaryOperator. Replaces the current property value, if it is not null, with the result of invoking mapper on it. Throws a NullPointerException if mapper is null. mapper may return a null. |
@Nullable
This is the O(1), non-tedious, non–error-prone way we recomment converting @Nullable
to Optional:
@Nullable
getters to nullableX()
(or getNullableX()
if you use JavaBean naming conventions).x()
(or getX()
)return x().orElse(null)
return x().orNull()
)nullableX()
methodsAt this point, you have effectively performed an automatic translation of a @Nullable
method to an Optional-returning one. Of course, your code is not optimal yet (e.g. if (foo.x().orElse(null) != null)
instead of if (foo.x().isPresent())
). Search-and-replace should get most of these issues.
@Nullable
setters to nullableX
(or setNullableX
).Your API is now FreeBuilder-compatible :)
FreeBuilder has special support for collection and map properties, removing the foo
accessor method and generating new ones appropriate to the type. Collection and map properties default to an empty collection/map and cannot hold nulls.
/** Returns a list of descendents for this person. **/
List<String> descendants();
A List
, Set
, SortedSet
or Multiset
property called 'descendants' would generate:
Method | Description |
---|---|
addDescendants(String element) | Appends element to the collection of descendants. If descendants is a set and the element is already present, it is ignored. Throws a NullPointerException if element is null. |
addDescendants(String... elements) | Appends all elements to the collection of descendants. If descendants is a set, any elements already present are ignored. Throws a NullPointerException if elements, or any of the values it holds, is null. |
addAllDescendants(Iterable<String> elements) addAllDescendants(Stream<String> elements) addAllDescendants(Spliterator<String> elements) | Appends all elements to the collection of descendants. If descendants is a set, any elements already present are ignored. Throws a NullPointerException if elements, or any of the values it holds, is null. |
mutateDescendants(Consumer<...<String>> mutator) | Invokes the [Consumer] mutator with the collection of descendants. (The mutator takes a list, set or map as appropriate.) Throws a NullPointerException if mutator is null. As mutator is a void consumer, any value returned from a lambda will be ignored, so be careful not to call pure functions like [stream()] expecting the returned collection to replace the existing collection. |
clearDescendants() | Removes all elements from the collection of descendants, leaving it empty. |
descendants() | Returns an unmodifiable view of the collection of descendants. Changes to the collection held by the builder will be reflected in the view. |
setComparatorForDescendants(Comparator<? super String> comparator) | SortedSet only A protected method that sets the [comparator] to keep the set elements ordered by. Must be called before any other accessor method for this property. Defaults to the [natural ordering] of the set's elements. |
/** Returns a map of favourite albums by year. **/
Map<Integer, String> albums();
A Map
property called 'albums' would generate:
Method | Description |
---|---|
putAlbums(int key, String value) | Associates key with value in albums. Throws a NullPointerException if either parameter is null. Replaces any existing entry. |
putAllAlbums(Map<? extends Integer, ? extends String> map) | Associates all of map 's keys and values in albums. Throws a NullPointerException if the map is null or contains a null key or value. Replaces any existing mapping for all keys in map . |
removeAlbums(int key) | Removes the mapping for key from albums. Throws a NullPointerException if the parameter is null. Does nothing if the key is not present. |
mutateAlbums(Consumer<Map<Integer, String>> mutator) | Invokes the [Consumer] mutator with the map of albums. Throws a NullPointerException if mutator is null. As mutator is a void consumer, any value returned from a lambda will be ignored, so be careful not to call pure functions like [stream()] expecting the returned map to replace the existing map. |
clearAlbums() | Removes all mappings from albums, leaving it empty. |
albums() | Returns an unmodifiable view of the map of albums. Changes to the map held by the builder will be reflected in this view. |
/** Returns a bimap of favourite albums by year. **/
BiMap<Integer, String> albums();
A BiMap
property called 'albums' would generate:
Method | Description |
---|---|
putAlbums(int key, String value) | Associates key with value in albums. Throws a NullPointerException if either parameter is null, or an IllegalArgumentException if value is already bound to a different key. Replaces any existing entry for key . |
forcePutAlbums(int key, String value) | Associates key with value in albums. Throws a NullPointerException if either parameter is null. Replaces any existing entry for both key and value . Override this method to implement constraint checks. |
putAllAlbums(Map<? extends Integer, ? extends String> map) | Associates all of map 's keys and values in albums. Throws a NullPointerException if the map is null or contains a null key or value. Replaces any existing mapping for all keys in map . Throws an IllegalArgumentException if an attempt to put any entry fails. |
removeKeyFromAlbums(int key) | Removes the mapping for key from albums. Throws a NullPointerException if the parameter is null. Does nothing if the key is not present. |
removeValueFromAlbums(String value) | Removes the mapping for value from albums. Throws a NullPointerException if the parameter is null. Does nothing if the value is not present. |
mutateAlbums(Consumer<BiMap<Integer, String>> mutator) | Invokes the [Consumer] mutator with the bimap of albums. Throws a NullPointerException if mutator is null. As mutator is a void consumer, any value returned from a lambda will be ignored, so be careful not to call pure functions like [stream()] expecting the returned map to replace the existing map. |
clearAlbums() | Removes all mappings from albums, leaving it empty. |
albums() | Returns an unmodifiable view of the bimap of albums. Changes to the bimap held by the builder will be reflected in this view. |
/** Returns a multimap of all awards by year. **/
SetMultimap<Integer, String> awards();
A Multimap
property called 'awards' would generate:
Method | Description |
---|---|
putAwards(int key, String value) | Associates key with value in awards. Throws a NullPointerException if either parameter is null. |
putAllAwards(int key, Iterable<? extends String> values) | Associates key with every element of values in awards. Throws a NullPointerException if either parameter, or any value, is null. |
putAllAwards(Map<? extends Integer, ? extends String> map) | Associates all of map 's keys and values in awards. Throws a NullPointerException if the map is null or contains a null key or value. If awards is a map, an IllegalArgumentException will be thrown if any key is already present. |
removeAwards(int key, String value) | Removes the single pair key -value from awards. If multiple pairs match, which is removed is unspecified. Throws a NullPointerException if either parameter is null. |
removeAllAwards(int key) | Removes all values associated with key from awards. Throws a NullPointerException if the key is null. |
mutateAwards(Consumer<Map<Integer, String>> mutator) | Invokes the [Consumer] mutator with the multimap of awards. Throws a NullPointerException if mutator is null. As mutator is a void consumer, any value returned from a lambda will be ignored, so be careful not to call pure functions like [stream()] expecting the returned multimap to replace the existing multimap. |
clearAwards() | Removes all mappings from awards, leaving it empty. |
awards() | Returns an unmodifiable view of the multimap of awards. Changes to the multimap held by the builder will be reflected in this view. |
In all cases, the value type will return immutable objects from its getter.
The mutator methods are useful for invoking methods not directly exposed on the builder, like subList, or methods that take a mutable collection, like sort:
personBuilder
// Delete the fourth and fifth descendants in the list
.mutateDescendants(d -> d.subList(3,5).clear())
// Sort the remaining descendants
.mutateDescendants(Collections::sort);
/** Returns the person responsible for this project. */
Person owner();
FreeBuilder has special support for buildable types like protos and other FreeBuilder types. A buildable property called 'owner' would generate:
Method | Description |
---|---|
owner(Person owner) | Sets the owner. Throws a NullPointerException if provided a null. |
owner(Person.Builder builder) | Calls build() on builder and sets the owner to the result. Throws a NullPointerException if builder or the result of calling build() is null. |
ownerBuilder() | Returns a builder for the owner property. Unlike other getter methods in FreeBuilder-generated API, this object is mutable, and modifying it will modify the underlying property. |
mutateOwner(Consumer<Person.Builder> mutator) | Invokes the [Consumer] mutator with the builder for the property. Throws a NullPointerException if mutator is null. As mutator is a void consumer, any value returned from a lambda will be ignored. |
The mutate method allows the buildable property to be set up or modified succinctly and readably:
Project project = new Project.Builder()
.mutateOwner($ -> $
.name("Phil")
.department("HR"))
.build();
FreeBuilder has special support for lists of buildable types, too. It maintains a list of builders, to allow elements of the list to be built incrementally. (For better performance, if given a built instance for the list, it will lazily convert it to a Builder on demand. This may cause problems if your buildable types continue to be mutable after construction; to avoid unpredictable aliasing, we recommend disabling buildable list support, as described below.)
A list of buildable properties called 'owners' would generate:
Method | Description |
---|---|
addOwners(Person element) | Appends element to the collection of owners. Throws a NullPointerException if element is null. The element may be lazily converted to/from a Builder. |
addOwners(Person.Builder builder) | Appends the value built by builder to the collection of owners. Throws a NullPointerException if builder is null. Only a copy of the builder will be stored; changes made to it after this method returns will have no effect on the list. The copied builder's build() method will not be called immediately, so if this builder's state is not legal, you will not get failures until you build the final immutable object. |
addOwners(Person... elements) | Appends all elements to the collection of owners. Throws a NullPointerException if elements, or any of the values it holds, is null. Each element may be lazily converted to/from a Builder. |
addOwners(Person.Builder... builders) | Appends the values built by builders to the collection of owners. Throws a NullPointerException if builders, or any of the values it holds, is null. Only copies of the builders will be stored, and build() methods will not be called immediately. |
addAllOwners(Iterable<Person> elements) addAllOwners(Stream<Person> elements) addAllOwners(Spliterator<Person> elements) | Appends all elements to the collection of owners. Throws a NullPointerException if elements, or any of the values it holds, is null. Each element may be lazily converted to/from a Builder. |
addAllBuildersOfOwners(Iterable<Person.Builder> builders) addAllBuildersOfOwners(Stream<Person.Builder> builders) addAllBuildersOfOwners(Spliterator<Person.Builder> builders) | Appends the values built by builders to the collection of owners. Throws a NullPointerException if builders, or any of the values it holds, is null. Only copies of the builders will be stored, and build() methods will not be called immediately. |
mutateOwners(Consumer<? super List<Person.Builder>> mutator) | Invokes the Consumer mutator with the list of owner builders. Throws a NullPointerException if mutator is null. As mutator is a void consumer, any value returned from a lambda will be ignored, so be careful not to call pure functions like stream() expecting the returned collection to replace the existing collection. |
clearOwners() | Removes all elements from the collection of owners, leaving it empty. |
buildersOfOwners() | Returns an unmodifiable view of the list of owner builders. Changes to the list held by the builder will be reflected in the view, and changes made to any of the returned builders will be reflected in the final list of owners. |
Note that mutateOwners
and buildersOfOwners
are the only methods which can cause lazy convertion of an inserted value to a Builder, and then only upon accessing the element, so avoid these actions if possible to avoid unexpected performance hits.
You can force FreeBuilder to use vanilla list support, rather than converting elements to/from Builders under the hood, by declaring a vanilla getter in the Builder. For instance, to force owners
to drop Builder support:
class Builder extends Foo_Builder {
@Override
public List<Person> owners() {
// Disable FreeBuilder's lazy conversion to/from Person.Builder by declaring
// a non-Builder-compatible getter.
return super.owners();
}
}
FreeBuilder will now generate the methods described in Collections and Maps.
FreeBuilder will only generate toString, hashCode and equals methods if they are left abstract, so to customise them, just implement them. (Due to language constraints, you will need to first convert your type to an abstract class if it was previously an interface.)
abstract class Person {
public abstract String name();
public abstract int age();
@Override public String toString() {
return name() + " (" + age() + " years old)";
}
public static class Builder extends Person_Builder {}
}
Note that it is a compile error to leave hashCode abstract if equals is implemented, and vice versa, as FreeBuilder has no reasonable way to ensure the consistency of any implementation it might generate.
If you have a small set of properties you wish to exclude from equals or toString without losing the generated code entirely, you can annotate them @IgnoredByEquals
and/or @NotInToString
.
Warning: It is rarely a good idea to redefine equality on a value type, as it makes testing very hard. For instance, assertEquals
in JUnit relies on equality; it will not know to check individual fields, and as a result, tests may be failing to catch bugs that, on the face of it, they looks like they should be. If you are only testing a subset of your fields for equality, consider separating your class in two, as you may have accidentally combined the key and the value of a map into a single object, and you may find your code becomes healthier after the separation. Alternatively, creating a custom Comparator will make it explicit that you are not using the natural definition of equality.
If for any reason your types cannot use the conventional method names (build
, buildPartial
, clear
and mergeFrom
), you can force FreeBuilder to generate package protected implementations, and even select alternative fallback names if necessary, by declaring an alternative visibility and/or incompatible signature. If the default name is not available, FreeBuilder will prepend an underscore and append "Impl" (and, if necessary, a number), e.g. build
becomes _buildImpl
.
public interface MyType {
class Builder extends MyType_Builder {
public OtherDataType build() {
// This signature is not compatible with the default build method.
// FreeBuilder will instead declare a package-scoped _buildImpl.
...
}
public DataType buildMyType() {
return _buildImpl();
}
}
}
Note that this will, unfortunately, disable FreeBuilder's enhanced support for nested builders for this type, as it needs to be able to call these methods.
FreeBuilder's generated map and mutate methods take UnaryOperator or Consumer functional interfaces. If you need to use a different functional interface, you can override the generated methods in your Builder and change the parameter type. FreeBuilder will spot the incompatible override and change the code it generates to match:
public interface MyType {
String property();
class Builder extends MyType_Builder {
@Override public Builder mapProperty(
com.google.common.base.Function<Integer, Integer> mapper) {
return super.mapProperty(mapper);
}
}
}
Effective Java recommends passing required parameters in to the Builder constructor. While we follow most of the recommendations therein, we explicitly do not follow this one: while you gain compile-time verification that all parameters are set, you lose flexibility in client code, as well as opening yourself back up to the exact same subtle usage bugs as traditional constructors and factory methods. For the default FreeBuilder case, where all parameters are required, this does not scale.
If you want to follow Effective Java more faithfully in your own types, however, just create the appropriate constructor in your builder subclass:
public Builder(String name, int age) {
// Set all initial values in the builder constructor
name(name);
age(age);
}
Implementation note: in javac, we spot these fields being set in the constructor, and do not check again at runtime.
A partial value is an implementation of the value type which does not conform to the type's state constraints. It may be missing required fields, or it may violate a cross-field constraint.
Person person = new Person.Builder()
.name("Phil")
.buildPartial(); // build() would throw an IllegalStateException here
System.out.println(person); // prints: partial Person{name=Phil}
person.age(); // throws UnsupportedOperationException
As partials violate the (legitimate) expectations of your program, they must not be created in production code. (They may also affect the performance of your program, as the JVM cannot make as many optimizations.) However, when testing a component which does not rely on the full state restrictions of the value type, partials can reduce the fragility of your test suite, allowing you to add new required fields or other constraints to an existing value type without breaking swathes of test code.
To allow robust tests of modify-rebuild code, Builders created from partials (either via the static Builder.from
method or the optional toBuilder()
method) will override build()
to instead call buildPartial()
.
Person anotherPerson = person.toBuilder().name("Bob").build();
System.out.println(anotherPerson); // prints: partial Person{name=Bob}
This "infectious" behavior of partials is another reason to confine them to test code.
(Note the mergeFrom
method does not behave this way; instead, it will throw an UnsupportedOperationException if given a partial.)
To create types compatible with the Jackson JSON serialization library, use the builder property of @JsonDeserialize to point Jackson at your Builder class. For instance:
// This type can be freely converted to and from JSON with Jackson
@JsonDeserialize(builder = Address.Builder.class)
interface Address {
String city();
String state();
class Builder extends Address_Builder {}
}
FreeBuilder will generate appropriate @JsonProperty annotations on the builder. (If you use Java 8 or Guava types, you may need to include the relevant Jackson extension modules, jackson-datatype-jdk8 and jackson-datatype-guava.)
To enable GWT serialization of the generated Value subclass, just add @GwtCompatible(serializable = true)
to your @FreeBuilder
-annotated type, and extend/implement Serializable
. This will generate a CustomFieldSerializer, and ensure all necessary types are whitelisted.
Download the latest FreeBuilder JAR and add it to the classpath (or processorpath, if you supply one) on the command line. If Guava is available, FreeBuilder will use it to generate cleaner, more interoperable implementation code (e.g returning immutable collections).
Add the FreeBuilder artifact as an optional dependency to your Maven POM:
<dependencies>
<dependency>
<groupId>org.inferred</groupId>
<artifactId>freebuilder</artifactId>
<version>[current version]</version>
<optional>true</optional>
</dependency>
</dependencies>
If Guava is available, FreeBuilder will use it to generate cleaner, more interoperable implementation code (e.g returning immutable collections).
Add the following lines to your project's build.gradle file:
dependencies {
annotationProcessor 'org.inferred:freebuilder:<current version>'
compileOnly 'org.inferred:freebuilder:<current version>'
}
If Guava is available, FreeBuilder will use it to generate cleaner, more interoperable implementation code (e.g returning immutable collections). You may also wish to use the org.inferred.processors plugin to correctly configure code generation in your IDE.
Add the following lines to your project's build.gradle file:
plugins {
id 'org.inferred.processors' version '1.2.10'
}
dependencies {
processor 'org.inferred:freebuilder:<current version>'
}
This uses the org.inferred.processors plugin to correctly configure code generation in your IDE. Alternatively, you can drop the plugin and replace processor
with compileOnly
, or compile
if you are on Gradle 2.11 or earlier—you will lose IDE integration—or use your own favourite Gradle annotation processor plugin.
If Guava is available, FreeBuilder will use it to generate cleaner, more interoperable implementation code (e.g returning immutable collections).
Condensed from Eclipse Indigo's documentation.
Download the latest FreeBuilder JAR and add it to your project. Select it, right-click and choose Build path > Add to Build path.
In your projects properties dialog, go to Java Compiler > Annotation Processing and ensure Enable annotation processing is checked. Next, go to Java Compiler > Annotation Processing > Factory Path, select Add JARs, and select the FreeBuilder JAR.
Condensed from the IntelliJ 14.0.3 documentation and Auto Issue #106.
Download the latest FreeBuilder JAR, add it to your project, right-click it and select Use as Project Library.
In your Settings, go to Build, Execution, Deployment > Compiler > Annotation Processors and ensure Enable annotation processing is selected, and Store generated sources relative to is set to Module content root. (If you have specified a processor path, ensure you add the new JAR to it. Similarly, if you choose to specify processors explicitly, add org.inferred.freebuilder.processor.Processor
to the list.)
Run Build > Rebuild Project, then right-click the new generated
folder (this may have a different name if you have changed the Production sources directory setting) and select Mark Directory As > Generated Sources Root.
If you are using Maven to build your project within IntelliJ, you can alternatively configure the POM in a way that enables IntelliJ to automatically detect annotation processors when the POM is reloaded:
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessors>
<annotationProcessor>org.inferred.freebuilder.processor.Processor</annotationProcessor>
</annotationProcessors>
<annotationProcessorPaths>
<path>
<groupId>org.inferred</groupId>
<artifactId>freebuilder</artifactId>
<version>2.6.2</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
Click Reload All Maven Projects in the Maven tool window to force a reload if you have not configured IntelliJ for automatic reloading. The Annotation processor should appear in Build, Execution, Deployment > Compiler > Annotation Processors automatically, with all needed settings applied.
In case you need to use more than one annotation processor, you'll find an example at https://github.com/inferred/FreeBuilder/issues/435 .
FreeBuilder 2.3 now allows partials to be passed to the static Builder.from
method. Previously this would have thrown an UnsupportedOperationException if any field was unset; now, as with the optional toBuilder
method, a Builder subclass will be returned that redirects build
to buildPartial
. This allows unit tests to be written that won't break if new constraints or required fields are later added to the datatype. You can restore the old behaviour by overriding the from
method to delegate to mergeFrom
.
Note that use of partials outside of tests is considered undefined behaviour by FreeBuilder, as documented here and on the buildPartial
method. Incomplete values should always be represented by Builder instances, not partials.
FreeBuilder 2.2 extends its optional value API customization to OptionalInt, OptionalLong and OptionalDouble. This is, for mutate methods, a non-binary-backwards-compatible change. If you have existing properties that you do not want this to affect, you can force FreeBuilder to adhere to your existing API with custom functional interfaces. To do this, use your IDE to override all mapper methods taking an OptionalInt
, OptionalLong
or OptionalDouble
(the implementations can just delegate to super). One of these will have been generated for each primitive optional property. Once all such methods are overridden, upgrading to 2.2 should now leave your APIs unaltered.
This change also alters the default behavior of the Optional-accepting setter method to delegate to a new primitive-accepting setter method, to allow constraints to be added through overriding. If you have previously added such a primitive-accepting setter method that delegates to the Optional-accepting setter method, this will of course now result in a stack overflow at runtime. FreeBuilder will attempt to flag this as a compiler error, but please double-check your builders when upgrading.
FreeBuilder 2.1 adds more extensive API customization for lists of buildable types, storing Builder instances internally until build is called, cascading buildPartial automatically, and adding overloads accepting Builder instances.
This is a behavioural and, for the get and mutate methods, a non-binary-backwards-compatible change. If you have existing properties that you do not want this to affect, see disabling buildable lists for instructions on restoring the 2.0 behaviour on a case-by-case basis.
There are three API-breaking changes between v1 and v2 of FreeBuilder:
Mapper methods use primitive, not boxed, functional interfaces for int
, long
and double
properties.
This will likely break binary backwards compatibility for any library using FreeBuilder to generate its builders. We apologise profusely for the hassle this causes. If you simply cannot break your clients, but want to upgrade to v2, you can force FreeBuilder to adhere to your existing API with custom functional interfaces. To do this, use your IDE to override all mapper methods taking a UnaryOperator<Integer>
, UnaryOperator<Long>
or UnaryOperator<Double>
(the implementations can just delegate to super). One of these will have been generated for each int
, long
and double
property. Once all such methods are overridden, upgrading to v2 should now leave your APIs unaltered.
No more support for Java 6/7
If you are still on Java 6/7, please continue to use the v1 releases of FreeBuilder.
No longer ships with javax.annotation.Nullable
FreeBuilder treats any annotation named Nullable the same way, so you can either explicitly compile against the old annotation by including the JSR-305 jar, or use a more modern alternative, like org.jetbrains.annotations. (Or use optionals! See Converting from @Nullable
for advice.)
If you make a mistake in your code (e.g. giving your value type a private constructor), FreeBuilder is designed to output a Builder superclass anyway, with as much of the interface intact as possible, so the only errors you see are the ones output by the annotation processor.
Unfortunately, javac
has a broken design: if any annotation processor outputs any error whatsoever, all generated code is discarded, and all references to that generated code are flagged as broken references. Since your Builder API is generated, that means every usage of a FreeBuilder builder becomes an error. This deluge of false errors swamps the actual error you need to find and fix. (See also the related issue.)
If you find yourself in this situation, search the output of javac
for the string "@FreeBuilder type"; nearly all errors include this in their text.
Eclipse manages, somehow, to be worse than javac
here. It will never output annotation processor errors unless there is another error of some kind; and, even then, only after an incremental, not clean, compile. In practice, most mistakes are made while editing the FreeBuilder type, which means the incremental compiler will flag the errors. If you find a generated superclass appears to be missing after a clean compile, however, try touching the relevant file to trigger the incremental compiler. (Or run javac.)
Where is Immutables better than FreeBuilder?
Immutables provides many of the same features as FreeBuilder, plus a whole host more. Some are optional ways to potentially enhance performance, like derived and lazy attributes, singleton and interned instances, and hash code precomputation. Some are API-changing: strict builders provide a compile-time guarantee that all fields are set (but limit the use of builders as a consequence), while copy methods provide a concise way to clone a value with a single field changed (but require you to reference the generated ImmutableFoo type, not Foo). It provides advanced binary serialization options and GSON support. It lets you easily customize the conventional method prefixes. As Immutables is an active project, this list is likely incomplete.
Where is FreeBuilder better than Immutables?
FreeBuilder provides some features that are missing from, or not the default in, Immutables:
The first three points are increasingly useful as your interfaces grow in complexity and usage. Partials greatly reduce the fragility of your tests by only setting the values the code being tested actually uses. Your interfaces are liable accumulate more constraints, like new required fields, or cross-field constraints, and while these will be vitally important across the application as a whole, they create a big maintenance burden when they break unit tests for existing code that does not rely on those constraints. Builder getter, mapper and mutation methods and nested builders empower the modify-rebuild pattern, where code changes a small part of an object without affecting the remainder.
The last two points arise because Immutables is type-generating: you write your value type as a prototype, but you always use the generated class. This type is final, meaning you can't proxy it, which unfortunately breaks tools like Mockito and its wonderful smart nulls. The generated builder is hard to customize as you cannot override methods, explaining why Immutables has so many annotations: for instance, the @Value.Check
annotation is unnecessary in FreeBuilder, as you can simply override the setters or build method to perform field or cross-field validation.
1: But note that you can write a Builder subclass in your Foo type, enabling FreeBuilder-style override-based customization, though that is not the default approach recommended in the guide, and you will break all your callers if you change between the two.
Immutables is a very active project, frequently adding new features, and future releases may address some or all of these deficiencies.
Why is FreeBuilder better than AutoValue?
It’s not! AutoValue provides an implementing class with a package-visible constructor, so you can easily implement the Factory pattern. If you’re writing an immutable type that needs a small number of values to create (Effective Java suggests at most three), and is not likely to require more in future, use the Factory pattern.
How about if you want a builder? AutoValue.Builder lets you explicitly specify a minimal Builder interface that will then be implemented by generated code, while FreeBuilder provides a generated builder API. AutoValue.Builder is better if you must have a minimal API—for instance, in an Android project, where every method is expensive—or strongly prefer a visible-in-source API at the expense of many useful methods. Otherwise, consider using FreeBuilder to implement the Builder pattern.
I used AutoValue, but now have more than three properties! How do I migrate to FreeBuilder?
@FreeBuilder
.You can always skip step 3 and have both factory and builder methods, if that seems cleaner!
Can I use both AutoValue and FreeBuilder?
Not really. You can certainly use both annotations, but you will end up with two different implementing classes that never compare equal, even if they have the same values.
Protocol buffers have provided builders for ages. Why should I use FreeBuilder?
Protocol buffers are cross-platform, backwards- and forwards-compatible, and have a very efficient wire format. Unfortunately, they do not support custom validation logic; nor can you use appropriate Java domain types, such as Instant or Range. Generally, it will be clear which one is appropriate for your use-case.
Download Details:
Author: inferred
Source Code: https://github.com/inferred/FreeBuilder
License: Apache-2.0 license
1654758120
A collection of source code generators for Java.
Java is full of code that is mechanical, repetitive, typically untested and sometimes the source of subtle bugs. Sounds like a job for robots!
The Auto subprojects are a collection of code generators that automate those types of tasks. They create the code you would have written, but without the bugs.
Save time. Save code. Save sanity.
AutoFactory - JSR-330-compatible factories
AutoService - Provider-configuration files for ServiceLoader
AutoValue - Immutable value-type code generation for Java 7+.
Common - Helper utilities for writing annotation processors.
Download Details:
Author: google
Source Code: https://github.com/google/auto
License: Apache-2.0 license