├── .bazelrc ├── .bazelrc.travis ├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── WORKSPACE ├── checkstyle.xml ├── flagz-etcd └── src │ ├── main │ └── java │ │ └── org │ │ └── flagz │ │ ├── BUILD │ │ ├── EtcdFlagFieldUpdater.java │ │ ├── EtcdFlagFieldUpdaterException.java │ │ └── EtcdRetryPolicy.java │ └── test │ └── java │ └── org │ └── flagz │ ├── BUILD │ └── EtcdFlagFieldUpdaterTest.java ├── flagz-java └── src │ ├── main │ └── java │ │ └── org │ │ └── flagz │ │ ├── BUILD │ │ ├── BaseFlag.java │ │ ├── ContainerFlagField.java │ │ ├── Flag.java │ │ ├── FlagException.java │ │ ├── FlagField.java │ │ ├── FlagFieldRegistry.java │ │ ├── FlagFieldScanner.java │ │ ├── FlagInfo.java │ │ ├── FlagProperty.java │ │ ├── FlagPropertySyncer.java │ │ ├── Flagz.java │ │ ├── FlagzUnused.java │ │ ├── JmxFlagFieldRegistrar.java │ │ ├── PrimitiveFlagField.java │ │ ├── ReflectionsCache.java │ │ ├── Utils.java │ │ └── Validators.java │ └── test │ └── java │ └── org │ └── flagz │ ├── BUILD │ ├── BaseFlagTest.java │ ├── ContainerFlagFieldTest.java │ ├── FlagFieldScannerTest.java │ ├── FlagPropertySyncerTest.java │ ├── PrimitiveFlagFieldTest.java │ ├── UnusedFlagTest.java │ └── testclasses │ ├── BUILD │ ├── SomeEnum.java │ └── StaticFlags.java ├── flagz-scala └── src │ ├── main │ └── scala │ │ └── org │ │ └── flagz │ │ ├── BUILD │ │ ├── FlagContainer.scala │ │ ├── ScalaCollectionsFlagField.scala │ │ ├── ScalaFlagz.scala │ │ ├── ScalaObjectScanner.java │ │ └── package.scala │ └── test │ └── scala │ └── org │ └── flagz │ ├── BUILD │ ├── FlagObjectRegistryTest.scala │ ├── ScalaCollectionsFlagFieldTest.scala │ └── testclasses │ ├── BUILD │ ├── TestCollectionObject.scala │ ├── TestObjectOne.scala │ └── TestObjectTwo.scala ├── samples └── src │ └── main │ └── java │ └── org │ └── flagz │ └── samples │ ├── BUILD │ ├── EtcdSampleApp.java │ └── JmxSampleApp.java └── third_party ├── etcd4j ├── BUILD └── etcd4j.LICENSE ├── guava ├── BUILD └── guava.LICENSE ├── javassist ├── BUILD └── javassist.LICENSE ├── jsr305 ├── BUILD └── guava.LICENSE ├── netty ├── BUILD └── netty.LICENSE ├── reflections ├── BUILD └── reflections.LICENSE ├── slf4j ├── BUILD └── slf4j.LICENSE └── testing ├── BUILD ├── junit.LICENSE └── mockito.LICENSE /.bazelrc: -------------------------------------------------------------------------------- 1 | test --test_output=errors 2 | test --legacy_bazel_java_test 3 | test --test_size_filters=-large,-enormous 4 | 5 | build --javacopt="-extra_checks:off" 6 | build --javacopt="-source 8" 7 | build --javacopt="-target 8" 8 | -------------------------------------------------------------------------------- /.bazelrc.travis: -------------------------------------------------------------------------------- 1 | # This is from Bazel's former travis setup, to avoid blowing up the RAM usage. 2 | startup --host_jvm_args=-Xmx2500m 3 | startup --host_jvm_args=-Xms2500m 4 | startup --batch 5 | test --ram_utilization_factor=10 6 | 7 | # This is so we understand failures better 8 | build --verbose_failures 9 | 10 | # This is so we don't use sandboxed execution. Sandboxed execution 11 | # runs stuff in a container, and since Travis already runs its script 12 | # in a container (unless you require sudo in your .travis.yml) this 13 | # fails to run tests. 14 | build --spawn_strategy=standalone --genrule_strategy=standalone 15 | test --test_strategy=standalone 16 | 17 | # Below this line, .travis.yml will cat the default bazelrc. 18 | # This is needed so Bazel starts with the base workspace in its 19 | # package path. 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | // Bazel 2 | bazel-* 3 | output 4 | 5 | 6 | # SBT 7 | dist/* 8 | boot/ 9 | project/boot/ 10 | project/plugins/project/ 11 | lib_managed/ 12 | src_managed/ 13 | target/ 14 | .history 15 | 16 | # IntelliJ 17 | .idea/ 18 | *.iml 19 | *.ipr 20 | *.iws 21 | out/ 22 | 23 | # Eclipse 24 | .cache 25 | .classpath 26 | .loadpath 27 | .metadata 28 | .project 29 | .scala_dependencies 30 | .settings 31 | .target/ 32 | *.launch 33 | 34 | # NetBeans 35 | nbproject/ 36 | nbbuild/ 37 | dist/ 38 | nbdist/ 39 | nbactions.xml 40 | nb-configuration.xml 41 | 42 | # TextMate 43 | *.tmproj 44 | *.tmproject 45 | tmtags 46 | 47 | # Sublime Text 48 | *.sublime-workspace 49 | 50 | # vim 51 | .*.s[a-w][a-z] 52 | *.un~ 53 | Session.vim 54 | .netrwhist 55 | *~ 56 | 57 | # Emacs 58 | *~ 59 | \#*\# 60 | /.emacs.desktop 61 | /.emacs.desktop.lock 62 | .elc 63 | auto-save-list 64 | tramp 65 | .\#* 66 | .org-id-locations 67 | *_archive 68 | 69 | # Mac OS X 70 | .DS_Store 71 | .AppleDouble 72 | .LSOverride 73 | Icon 74 | ._* 75 | .Spotlight-V100 76 | .Trashes 77 | 78 | # Windows 79 | Thumbs.db 80 | ehthumbs.db 81 | Desktop.ini 82 | $RECYCLE.BIN/ 83 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | 3 | jdk: 4 | - oraclejdk8 5 | 6 | env: 7 | - BAZEL_VERSION=0.3.2 8 | 9 | addons: 10 | apt: 11 | packages: 12 | - wget 13 | 14 | cache: 15 | directories: 16 | - $HOME/bazel/install 17 | - $HOME/bazel/outbase 18 | 19 | before_install: 20 | - mkdir -p ${HOME}/bazel/install 21 | - cd ${HOME}/bazel/install 22 | - wget --no-clobber "https://github.com/bazelbuild/bazel/releases/download/${BAZEL_VERSION}/bazel_${BAZEL_VERSION}-linux-x86_64.deb" 23 | - chmod +x bazel_${BAZEL_VERSION}-linux-x86_64.deb 24 | - sudo dpkg -i bazel_${BAZEL_VERSION}-linux-x86_64.deb 25 | - cd ${TRAVIS_BUILD_DIR} 26 | - mv .bazelrc .bazelrc.orig 27 | - cat .bazelrc.travis .bazelrc.orig > .bazelrc 28 | 29 | script: 30 | - bazel --output_base=${HOME}/bazel/outbase test //... 31 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2012 Kenny Yu 3 | Copyright (c) 2014-2016 Michal Witkowski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 19 | AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # FlagZ - modern flag library for Java/Scala 3 | 4 | [![Travis Build](https://travis-ci.org/mwitkow/java-flagz.svg)](https://travis-ci.org/mwitkow/java-flagz) 5 | [![MIT License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) 6 | 7 | A modern, annotation-based flags library for Java and Scala that's tailored for large codebases. 8 | 9 | 10 | ## Key features 11 | 12 | * No more passing around config classes, and punching-through parameters. 13 | * Like [args4j](http://args4j.kohsuke.org/) or [JCommander](http://jcommander.org/) uses `@FlagInfo` annotation-based mapping of flag names to class fields. 14 | * *Unlike* [args4j](http://args4j.kohsuke.org/) or [JCommander](http://jcommander.org/) allows flags to be specified *anywhere* on the classpath. 15 | * Support for simple types, e.g. `Boolean`, `Integer`, `String`, `Double`... 16 | * Support for generic container types, e.g. `List`, `Map`, `Set` 17 | * All flags are *thread-safe* and dynamically modifiable at runtime through: 18 | - JMX MBeans - a standard Java mechanism for server debugging/tuning - see [`JmxSampleApp`](samples/src/main/java/org/flagz/samples/JmxSampleApp.java) example 19 | - [etcd](https://coreos.com/etcd/docs/latest/getting-started-with-etcd.html) - a distributed key-value store, allowing for multiple servers to have their dynamic flags changed in sync - see [`EtcdSampleApp`](samples/src/main/java/org/flagz/samples/EtcdSampleApp.java) 20 | * Compatibility with existing `System.properties`-based libraries through [`@FlagProperty`](flagz-java/src/main/java/org/flagz/FlagProperty.java) annotation that syncs a flag with a property name. 21 | * `withValidator` - All flags can have a set of validators attached, that prevent bad values (e.g. out of range) from being set. 22 | * `withNotifier` - All flags have callabacks that are triggered when flags are modified dynamically. 23 | * Extensible - just extend [`FlagField`](flagz-java/src/main/java/org/flagz/FlagField.java) and define your own types, e.g. JSON flags, protobuf flags. 24 | * Scala support 25 | 26 | 27 | ## Paint me a code picture 28 | 29 | Let's say your project is more than just a simple CLI utility and consists of multiple packages. 30 | 31 | You can define parameters that relate to given functionality *inside* the relevant package. 32 | ```java 33 | package com.example.rpc.client 34 | ... 35 | 36 | public class MyRpcClientFactory { 37 | @FlagInfo(name="rpc_client_max_threadpool_size", help="Max threadpool size for RPC client callbacks") 38 | private static final Flag maxThreadPool = Flagz.valueOf(10); 39 | 40 | @FlagInfo(name="rpc_client_default_timeout_s", help="Default timeout (seconds) for RPCs.") 41 | private static final Flag defaultRpcTimeoutSec = Flagz.valueOf(1.5); 42 | ... 43 | } 44 | ``` 45 | 46 | All `@FlagInfo`-annotated fields on the classpath will automatically be discovered. Let's say that the server's main 47 | looks like: 48 | 49 | 50 | ```java 51 | package com.example.superserver 52 | ... 53 | 54 | public class Main { 55 | @FlagInfo(name="daemonize", altName="d", help="Whether to fork off the process,") 56 | private static final Flag daemonize = Flagz.valueOf(false); 57 | 58 | @FlagInfo(name="bind_addr", help="Bind addresses to listen on") 59 | private static final Flag> bindList = Flagz.valueOf(ImmutableList.of("0.0.0.0:80")); 60 | 61 | ... 62 | public static void main(String[] args) { 63 | Flagz.parse(args); 64 | ... 65 | for (bind in bindList.Get()) { 66 | server.BindTo(bind); 67 | } 68 | if (daemonize.Get()) { 69 | server.Fork() 70 | } 71 | ... 72 | } 73 | 74 | ... 75 | } 76 | ``` 77 | 78 | This means you can start the server as: 79 | 80 | $ java com.example.superserver.Main --rpc_client_max_threadpool_size=15 -d 81 | 82 | This will start the server in daemon mode, and change the the `maxThreadPool` field in `MyRpcClientFactory` without 83 | the need for punching through massive configuration objects. 84 | 85 | 86 | ## Installing 87 | 88 | ### Maven Packages 89 | 90 | To use this flag library, include this in your `pom.xml`: 91 | 92 | ```xml 93 | 94 | ... 95 | 96 | mwitkow-github-repo 97 | https://raw.github.com/mwitkow/maven-repos/master 98 | 99 | ... 100 | 101 | 102 | ... 103 | 104 | org.flagz 105 | flagz 106 | 2.2 107 | 108 | ... 109 | 110 | ``` 111 | 112 | ### Development 113 | 114 | Obviously git-clone this repo. Then install [bazel](https://www.bazel.io/versions/master/docs/install.html)>=0.3.2, which is a fantastic build system that this project uses. 115 | Bazel takes care of downloading all other dependencies (Scala, etcd), the only system requirement is JDK>=8.0. 116 | 117 | To run all the tests: 118 | 119 | ```bash 120 | bazel test //... 121 | ``` 122 | 123 | #### Publishing (internal stuff) 124 | 125 | The way that bazel `BUILD` files are laid out in this project is along the lines of old Maven packages. This means that each target 126 | is a maven-deliverable. 127 | 128 | To build all of them using: 129 | 130 | ``` 131 | bazel build //flagz-java/src/main/java/org/flagz 132 | bazel build //flagz-etcd/src/main/java/org/flagz 133 | bazel build //flagz-scala/src/main/scala/org/flagz 134 | ``` 135 | 136 | The command line will output you the relevant `.jar` file containing the distributable. **TODO(mwitkow)** Add a script for Maven publishing. 137 | 138 | 139 | #### Sample Apps 140 | 141 | The `samples/` directory contains two sample JVM apps that show case what's going on. 142 | 143 | You can run the JMX example 144 | 145 | ```bash 146 | bazel run -- //samples/src/main/java/org/flagz/samples:jmx 147 | ``` 148 | 149 | or (with a running [etcd instance](https://github.com/coreos/etcd/releases)) the etcd demo: 150 | 151 | ```bash 152 | bazel run -- //samples/src/main/java/org/flagz/samples:etcd --flagz_etcd_directory=/foo 153 | ``` 154 | 155 | ## Status 156 | 157 | At Improbable we use `flagz-etcd` and `flagz-scala` to dynamically reconfigure our simulation runtime environment, 158 | allowing our engineers to quickly iterate on tuning parameters and launch new functionality hidden behind flags. 159 | 160 | As such, the code is considered **production quality**. 161 | 162 | 163 | ## License and Copyright 164 | 165 | `flagz` is released under the MIT License. See the [LICENSE](LICENSE) file for details. 166 | 167 | The project was completely written by [@mwitkow](https://github.com/mwitkow)'s as a thread-safe and dynamic 168 | version of [kennyyu/flags](https://github.com/kennyyu/flags), with only the public interfaces remaining from the old project. 169 | -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- 1 | workspace(name = "org_flagz") 2 | 3 | maven_jar( 4 | name = "org_reflections_artifact", 5 | artifact = "org.reflections:reflections::0.9.10", 6 | ) 7 | 8 | maven_jar( 9 | name = "org_javassist_artifact", 10 | artifact = "org.javassist:javassist::3.20.0-GA", 11 | ) 12 | 13 | maven_jar( 14 | name = "org_slf4j_api_artifact", 15 | artifact= "org.slf4j:slf4j-api:1.7.13", 16 | ) 17 | 18 | maven_jar( 19 | name = "org_slf4j_simple_artifact", 20 | artifact = "org.slf4j:slf4j-simple:1.7.13", 21 | ) 22 | 23 | maven_jar( 24 | name = "com_google_guava_artifact", 25 | artifact = "com.google.guava:guava:19.0", 26 | ) 27 | 28 | maven_jar( 29 | name = "com_google_code_findbugs_jsr305_artifact", 30 | artifact = "com.google.code.findbugs:jsr305:1.3.9", 31 | ) 32 | 33 | maven_jar( 34 | name = "junit_artifact", 35 | artifact = "junit:junit:4.12", 36 | ) 37 | 38 | maven_jar( 39 | name = "hamcrest_artifact", 40 | artifact = "org.hamcrest:hamcrest-all:1.3", 41 | ) 42 | 43 | maven_jar( 44 | name = "org_mockito_artifact", 45 | artifact = "org.mockito:mockito-all:1.10.19", 46 | ) 47 | 48 | maven_jar( 49 | name = "org_mousio_etcd4j_artifact", 50 | artifact = "org.mousio:etcd4j:2.12.0", 51 | ) 52 | 53 | maven_jar( 54 | name = "io_netty_artifact", 55 | artifact = "io.netty:netty-all:4.1.3.Final", 56 | ) 57 | 58 | 59 | maven_jar( 60 | name = "com_fasterxml_jackson_annotations_artifact", 61 | artifact = "com.fasterxml.jackson.core:jackson-annotations:2.8.0", 62 | ) 63 | 64 | maven_jar( 65 | name = "com_fasterxml_jackson_core_artifact", 66 | artifact = "com.fasterxml.jackson.core:jackson-core:2.8.0", 67 | ) 68 | 69 | maven_jar( 70 | name = "com_fasterxml_jackson_databind_artifact", 71 | artifact = "com.fasterxml.jackson.core:jackson-databind:2.8.0", 72 | ) 73 | 74 | maven_jar( 75 | name = "com_fasterxml_jackson_afterburner_artifact", 76 | artifact = "com.fasterxml.jackson.module:jackson-module-afterburner:2.8.0", 77 | ) 78 | 79 | 80 | # Scala stuff. 81 | git_repository( 82 | name = "io_bazel", 83 | remote = "git://github.com/bazelbuild/bazel.git", 84 | tag = "0.3.2", 85 | ) 86 | 87 | git_repository( 88 | name = "io_bazel_rules_scala", 89 | remote = "https://github.com/bazelbuild/rules_scala.git", 90 | commit = "48977a511ab0f15af694a8bf5e0f85357f1d8ea6", # update this as needed 91 | ) 92 | 93 | load("@io_bazel_rules_scala//scala:scala.bzl", "scala_repositories") 94 | scala_repositories() -------------------------------------------------------------------------------- /checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 69 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 94 | 95 | 96 | 98 | 99 | 100 | 101 | 103 | 104 | 105 | 106 | 108 | 109 | 110 | 111 | 113 | 114 | 115 | 116 | 117 | 118 | 120 | 121 | 122 | 123 | 125 | 126 | 127 | 128 | 130 | 131 | 132 | 133 | 135 | 136 | 137 | 138 | 140 | 142 | 144 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /flagz-etcd/src/main/java/org/flagz/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_library( 4 | name = "flagz", 5 | srcs = glob(["*.java"]), 6 | deps = [ 7 | "//flagz-java/src/main/java/org/flagz", 8 | "//third_party/etcd4j", 9 | "//third_party/guava", 10 | "//third_party/slf4j:api", 11 | ], 12 | ) -------------------------------------------------------------------------------- /flagz-etcd/src/main/java/org/flagz/EtcdFlagFieldUpdater.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | import com.google.common.base.MoreObjects; 4 | import com.google.common.base.Preconditions; 5 | import com.google.common.base.Strings; 6 | import com.google.common.collect.ImmutableList; 7 | import mousio.client.retry.RetryPolicy; 8 | import mousio.etcd4j.EtcdClient; 9 | import mousio.etcd4j.promises.EtcdResponsePromise; 10 | import mousio.etcd4j.responses.EtcdAuthenticationException; 11 | import mousio.etcd4j.responses.EtcdException; 12 | import mousio.etcd4j.responses.EtcdKeysResponse; 13 | import mousio.etcd4j.responses.EtcdKeysResponse.EtcdNode; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | import java.io.IOException; 18 | import java.net.URI; 19 | import java.util.List; 20 | import java.util.concurrent.ExecutorService; 21 | import java.util.concurrent.Executors; 22 | import java.util.concurrent.TimeoutException; 23 | import java.util.stream.Collectors; 24 | 25 | /** 26 | * Populates and updates {@link Flag}s from a given path in Etc.d. 27 | * 28 | * The class assumes that all flagz exist in the same Etc.d directory, and their keys are the flagz 29 | * full names (not alt names). The values of each separate key is treated as the value of the flag. 30 | * 31 | * For example: 32 | * 33 | * ``` 34 | * /v2/keys/some/path/my_int_flag_1 -> "700" 35 | * /v2/keys/some/path/my_string_flag_2 -> "some random string" 36 | * ``` 37 | */ 38 | public class EtcdFlagFieldUpdater { 39 | 40 | private static final Logger LOG = LoggerFactory.getLogger(EtcdFlagFieldUpdater.class); 41 | 42 | @FlagInfo(name = "flagz_etcd_enabled", help = "Whether to use EtcdFlagFieldUpdater at all.") 43 | private static final Flag enabledFlag = Flagz.valueOf(false); 44 | 45 | @FlagInfo(name = "flagz_etcd_directory", 46 | help = "Directory containing flags. E.g. for /v2/keys/foo/bar/flag the " 47 | + "value should be /foo/bar/.") 48 | private static final Flag directoryFlag = Flagz.valueOf(""); 49 | 50 | @FlagInfo(name = "flagz_etcd_server_uris", 51 | help = "List of comma-delimited URIs for etc.d servers.") 52 | private static final Flag> urisFlag = Flagz 53 | .valueOf(ImmutableList.of("https://127.0.0.1:2379")); 54 | 55 | @FlagInfo(name = "flagz_etcd_retry_policy", 56 | help = "Name of EtcdRetryPolicy to use for all accesses") 57 | private static final Flag retryPolicyFlag = 58 | Flagz.valueOf(EtcdRetryPolicy.EXPONENTIAL_BACKOFF_5MS_MAX_10_TIMES); 59 | 60 | @FlagInfo(name = "flagz_etcd_reelection_backoff", 61 | help = "Time to backoff for during Etcd reelection.") 62 | private static final Flag reelectionBackoffMs = Flagz.valueOf(200L); 63 | 64 | /** 65 | * Public method to check if the flag flagz_etcd_enabled is true. 66 | */ 67 | public static boolean isEnabled() { 68 | return enabledFlag.get(); 69 | } 70 | 71 | private static final int ETCD_PRECONDITION_FAILED_CODE = 101; 72 | private static final int ETCD_LEADER_ELECT_CODE = 301; 73 | private static final int ETCD_WATCHER_CLEARED_CODE = 400; 74 | private static final int ETCD_EVENT_INDEX_CLEARED_CODE = 401; 75 | 76 | 77 | private final List uris; 78 | private final RetryPolicy retryPolicy; 79 | private final FlagFieldRegistry registry; 80 | private final ExecutorService executorService; 81 | private String directoryPrefix; 82 | private EtcdClient client; 83 | private volatile boolean running = false; 84 | private long lastKnownFlagzModificationIndex = 0; 85 | 86 | public EtcdFlagFieldUpdater(FlagFieldRegistry registry) { 87 | this( 88 | registry, urisFlag.get(), retryPolicyFlag.get().get(), Executors.newSingleThreadExecutor()); 89 | } 90 | 91 | EtcdFlagFieldUpdater(FlagFieldRegistry registry, List uris, RetryPolicy retryPolicy, 92 | ExecutorService executorService) { 93 | this.registry = Preconditions.checkNotNull(registry); 94 | this.uris = Preconditions.checkNotNull(uris).stream().map(URI::create) 95 | .collect(Collectors.toList()); 96 | this.retryPolicy = Preconditions.checkNotNull(retryPolicy); 97 | this.executorService = Preconditions.checkNotNull(executorService); 98 | } 99 | 100 | public void init() { 101 | init(Preconditions.checkNotNull(Strings.emptyToNull(directoryFlag.get()))); 102 | } 103 | 104 | /** Init performs the initial read of values from etcd. */ 105 | public void init(String flagzDirectory) throws FlagException, EtcdFlagFieldUpdaterException { 106 | this.directoryPrefix = MoreObjects.firstNonNull(flagzDirectory, directoryFlag.get()); 107 | client = new EtcdClient(uris.toArray(new URI[uris.size()])); 108 | client.setRetryHandler(retryPolicy); 109 | initialSetAllFlagz(); 110 | } 111 | 112 | /** Kicks off a separate thread that will keep flag values in sync with etcd. */ 113 | public void watchForUpdates() { 114 | Preconditions.checkState(client != null, "You need to call init() before watchForUpdates()."); 115 | running = true; 116 | executorService.submit( 117 | () -> { 118 | while (running) { 119 | try { 120 | watchAndUpdateSingleFlagz(); 121 | } catch (Exception exception) { 122 | LOG.warn("Unexpected exception. Continuing with Flagz watch thread.", exception); 123 | } 124 | } 125 | }); 126 | } 127 | 128 | /** Stops the watching of etcd values. */ 129 | public void stop() { 130 | running = false; 131 | // TODO(michal): Add handling of pending requests, as EtcdClient doesn't do that =( 132 | try { 133 | client.close(); 134 | } catch (IOException exception) { 135 | LOG.error("Exception while closing EtcdFlagFieldUpdater.", exception); 136 | } 137 | } 138 | 139 | private void initialSetAllFlagz() { 140 | for (EtcdNode n : fetchAllFlagzNodes()) { 141 | setFlagFromFlagzNode(n); 142 | } 143 | } 144 | 145 | private void watchAndUpdateSingleFlagz() { 146 | try { 147 | EtcdKeysResponse response = watchForUpdatedFlagzNode(); 148 | if (response.node.value == null) { 149 | // A 'delete' or some other non-continuous action. 150 | return; 151 | } 152 | String flagName = nodeKeyToFlagName(response.node.key); 153 | try { 154 | setFlagFromFlagzNode(response.node); 155 | } catch (FlagException.UnknownFlag exception) { 156 | LOG.warn( 157 | "Flag({}) is not known, but set at EtcdIndex({}). Consider manual deletion. Ignoring.", 158 | flagName, response.node.modifiedIndex); 159 | } catch (FlagException exception) { 160 | LOG.warn( 161 | "Flag({}) update to value='{}' at EtcdIndex({}) failed due to error='{}'. " 162 | + "Will try to roll back Etc.d value.", 163 | flagName, response.node.modifiedIndex, response.node.value, exception.getMessage()); 164 | rollbackFlagzNode(response); 165 | } 166 | } catch (EtcdFlagFieldUpdaterException.EtcdFetchingFailed exception) { 167 | LOG.error("Couldn't fetch updates for Flagz due to connectivity issues..", exception); 168 | } catch (EtcdFlagFieldUpdaterException.EtcdRollbackFailed exception) { 169 | LOG.warn( 170 | String 171 | .format("Flag({}) rollback failed due to connectivity issues.", exception.flagName()), 172 | exception); 173 | } 174 | } 175 | 176 | private List fetchAllFlagzNodes() { 177 | try { 178 | EtcdResponsePromise promise = client 179 | .get(this.directoryPrefix) 180 | .dir() 181 | .send(); 182 | EtcdKeysResponse response = promise.get(); 183 | // NOTE: We use etcdIndex here, because we know we got latest data up to this point. 184 | lastKnownFlagzModificationIndex = response.etcdIndex; 185 | return MoreObjects.firstNonNull(response.node.nodes, ImmutableList.of()); 186 | } catch (IOException | EtcdException 187 | | TimeoutException | EtcdAuthenticationException exception) { 188 | throw new EtcdFlagFieldUpdaterException.EtcdFetchingFailed(exception); 189 | } 190 | } 191 | 192 | private EtcdKeysResponse watchForUpdatedFlagzNode() { 193 | try { 194 | EtcdResponsePromise promise = client 195 | .get(this.directoryPrefix) 196 | .dir() 197 | .recursive() 198 | .waitForChange(lastKnownFlagzModificationIndex + 1) 199 | .send(); 200 | EtcdKeysResponse response = promise.get(); 201 | // NOTE: We are not using, because there might be more than one write that had happened. 202 | lastKnownFlagzModificationIndex = response.node.modifiedIndex; 203 | return response; 204 | } catch (EtcdException exception) { 205 | return handleEtcdWatchErrors(exception); 206 | } catch (IOException | TimeoutException | EtcdAuthenticationException exception) { 207 | throw new EtcdFlagFieldUpdaterException.EtcdFetchingFailed(exception); 208 | } 209 | } 210 | 211 | /** 212 | * Handles watch issues with watching. See Etcd docs: 213 | * 214 | * @see Docs 215 | */ 216 | private EtcdKeysResponse handleEtcdWatchErrors(EtcdException exception) { 217 | // 218 | if (exception.errorCode == ETCD_EVENT_INDEX_CLEARED_CODE) { 219 | // If our watching failed due to index, re-read everything because we might have missed 220 | // something. The lastKnownFlagzModificationIndex will be reset in fetchAllFlagzNodes. 221 | initialSetAllFlagz(); 222 | return null; 223 | } else if (exception.errorCode == ETCD_WATCHER_CLEARED_CODE) { 224 | // This means that etcd is recovering from a problem. 225 | try { 226 | Thread.sleep(reelectionBackoffMs.get()); 227 | } catch (InterruptedException e1) { 228 | // ignore 229 | } 230 | return null; 231 | } else { 232 | throw new EtcdFlagFieldUpdaterException.EtcdFetchingFailed(exception); 233 | } 234 | } 235 | 236 | private void setFlagFromFlagzNode(EtcdNode node) throws FlagException { 237 | String flagName = nodeKeyToFlagName(node.key); 238 | registry.setField(flagName, node.value); 239 | LOG.info( 240 | "Flag({}) updated to value='{}' from EtcdIndex({}).", flagName, node.value, 241 | node.modifiedIndex); 242 | } 243 | 244 | private void rollbackFlagzNode(EtcdKeysResponse response) { 245 | // NOTE: the prevIndex here is crucial, we only want to rollback once (from one server) and 246 | // don't want to overwrite by mistake something that was written since we read it. 247 | String flagName = nodeKeyToFlagName(response.node.key); 248 | try { 249 | if (response.prevNode == null) { // It didn't exist before. 250 | // TODO(michal): Remove cast once upstream correctly returns EtcdKeyDeleteRequest. 251 | (client 252 | .delete(response.node.key)) 253 | .prevIndex(response.node.modifiedIndex) 254 | .send() 255 | .get(); 256 | LOG.warn( 257 | "Flag({}) successfully removed due to rollback with EtcdIndex({}).", 258 | flagName, response.node.modifiedIndex); 259 | 260 | } else { 261 | client 262 | .put(response.node.key, response.prevNode.value) 263 | .prevIndex(response.node.modifiedIndex) 264 | .send() 265 | .get(); 266 | LOG.warn( 267 | "Flag({}) successfully rolled back to value='{}' with EtcdIndex({}).", 268 | flagName, response.prevNode.value, response.node.modifiedIndex); 269 | } 270 | 271 | } catch (EtcdException exception) { 272 | if (exception.errorCode == ETCD_PRECONDITION_FAILED_CODE) { 273 | LOG.info( 274 | "Flag({}) rollback wouldn't be atomic. Probably done by another server.", flagName); 275 | } else { 276 | throw new EtcdFlagFieldUpdaterException.EtcdRollbackFailed(exception, flagName); 277 | } 278 | } catch (TimeoutException | IOException | EtcdAuthenticationException exception) { 279 | throw new EtcdFlagFieldUpdaterException.EtcdRollbackFailed(exception, flagName); 280 | } 281 | } 282 | 283 | private String nodeKeyToFlagName(String key) { 284 | Preconditions.checkArgument( 285 | key.startsWith(directoryPrefix), 286 | String.format("The key {} doesn't start with {}", key, directoryPrefix)); 287 | int offset = directoryPrefix.length(); 288 | if (!directoryPrefix.endsWith("/")) { 289 | offset += 1; 290 | } 291 | return key.substring(offset); 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /flagz-etcd/src/main/java/org/flagz/EtcdFlagFieldUpdaterException.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | /** 4 | * Exception thrown when Etcd cannot be contacted to parse Flagz. 5 | */ 6 | public class EtcdFlagFieldUpdaterException extends RuntimeException { 7 | EtcdFlagFieldUpdaterException(Throwable cause) { 8 | super(cause); 9 | } 10 | 11 | public static class EtcdFetchingFailed extends EtcdFlagFieldUpdaterException { 12 | EtcdFetchingFailed(Throwable cause) { 13 | super(cause); 14 | } 15 | } 16 | 17 | static class EtcdRollbackFailed extends EtcdFlagFieldUpdaterException { 18 | private String flagName; 19 | 20 | EtcdRollbackFailed(Throwable cause, String flagName) { 21 | super(cause); 22 | this.flagName = flagName; 23 | } 24 | 25 | public String flagName() { 26 | return flagName; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /flagz-etcd/src/main/java/org/flagz/EtcdRetryPolicy.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | import mousio.client.retry.RetryNTimes; 4 | import mousio.client.retry.RetryPolicy; 5 | import mousio.client.retry.RetryWithExponentialBackOff; 6 | 7 | import java.util.function.Supplier; 8 | 9 | /** 10 | * Enum-factory for the {@link RetryPolicy} to use for {@link EtcdFlagFieldUpdater} flags. 11 | */ 12 | public enum EtcdRetryPolicy implements Supplier { 13 | EXPONENTIAL_BACKOFF_5MS_MAX_10_TIMES { 14 | @Override 15 | public RetryPolicy get() { 16 | return new RetryWithExponentialBackOff(5, 10, 5 * 60 * 1000); // max 5ms * 2**10 = 5sec. 17 | } 18 | }, 19 | EXPONENTIAL_BACKOFF_10MS_MAX_20_TIMES { 20 | @Override 21 | public RetryPolicy get() { 22 | return new RetryWithExponentialBackOff(10, 13, 160 * 1000); // max 10ms * 2*20 = 160 sec 23 | } 24 | }, 25 | LINEAR_5MS_MAX_20_TIMES { 26 | @Override 27 | public RetryPolicy get() { 28 | return new RetryNTimes(5, 20); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /flagz-etcd/src/test/java/org/flagz/BUILD: -------------------------------------------------------------------------------- 1 | java_test( 2 | name = "tests", 3 | srcs = glob(['*.java']), 4 | size = "large", 5 | deps = [ 6 | "//flagz-java/src/main/java/org/flagz", 7 | "//flagz-etcd/src/main/java/org/flagz", 8 | "//third_party/etcd4j", 9 | "//third_party/guava", 10 | "//third_party/slf4j:api", 11 | "//third_party/slf4j:simple-stdout", 12 | "//third_party/testing", 13 | ], 14 | tags=["exclusive"], 15 | ) 16 | -------------------------------------------------------------------------------- /flagz-etcd/src/test/java/org/flagz/EtcdFlagFieldUpdaterTest.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.ImmutableMap; 5 | import com.google.common.collect.ImmutableSet; 6 | import mousio.client.retry.RetryOnce; 7 | import mousio.client.retry.RetryPolicy; 8 | import mousio.etcd4j.EtcdClient; 9 | import mousio.etcd4j.promises.EtcdResponsePromise; 10 | import mousio.etcd4j.requests.EtcdKeyGetRequest; 11 | import mousio.etcd4j.responses.EtcdKeyAction; 12 | import mousio.etcd4j.responses.EtcdKeysResponse; 13 | import org.junit.*; 14 | import org.junit.experimental.categories.Category; 15 | import org.junit.runners.MethodSorters; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | 19 | import java.io.IOException; 20 | import java.net.URI; 21 | import java.util.Map; 22 | import java.util.concurrent.Executors; 23 | import java.util.concurrent.locks.ReentrantLock; 24 | import java.util.function.Consumer; 25 | 26 | import static org.hamcrest.CoreMatchers.is; 27 | import static org.hamcrest.CoreMatchers.nullValue; 28 | import static org.junit.Assert.assertThat; 29 | import static org.mockito.Mockito.*; 30 | 31 | @FixMethodOrder(MethodSorters.NAME_ASCENDING) 32 | public class EtcdFlagFieldUpdaterTest { 33 | private static final Logger LOG = LoggerFactory.getLogger(EtcdFlagFieldUpdater.class); 34 | 35 | private static final String ETCD_SERVER = "http://localhost:2379"; 36 | private static final String FLAGZ_PATH = "/FLAGZ_TESTING_DIR/"; 37 | private static final RetryPolicy ETCD_RETRY = new RetryOnce(50); 38 | private static final ReentrantLock singleTestLock = new ReentrantLock(); 39 | 40 | private static EtcdClient client; 41 | 42 | @SuppressWarnings("unchecked") 43 | private final Consumer intListener = mock(Consumer.class); 44 | @SuppressWarnings("unchecked") 45 | private final Consumer stringListener = mock(Consumer.class); 46 | 47 | @FlagInfo(name = "etcd_test_int", help = "some int") 48 | public final Flag flagInt = Flagz.valueOf(400) 49 | .withValidator(Validators.greaterThan(100)) 50 | .withListener(intListener); 51 | 52 | @FlagInfo(name = "etcd_test_string", help = "some string") 53 | public final Flag flagString = Flagz.valueOf("unoverwritten") 54 | .withListener(stringListener); 55 | 56 | @FlagInfo(name = "etcd_test_map_int", help = "some map") 57 | public final Flag> flagMap = Flagz.valueOf(ImmutableMap.of("foo", 100, "boo", 200)); 58 | 59 | private EtcdFlagFieldUpdater etcdUpdater; 60 | 61 | @BeforeClass 62 | public static void connectEtcd() { 63 | try { 64 | client = new EtcdClient(URI.create(ETCD_SERVER)); 65 | client.setRetryHandler(ETCD_RETRY); 66 | Long etcdIndex = client.getAll().send().get().etcdIndex; 67 | LOG.info("Connected to Etcd version={} at EtcdIndex({}).", client.version(), etcdIndex); 68 | } catch (Exception e) { 69 | throw new RuntimeException("Etc.d must be running on localhost for these tests.", e); 70 | } 71 | } 72 | 73 | @AfterClass 74 | public static void disconnectEtcd() throws IOException { 75 | client.close(); 76 | } 77 | 78 | @Before 79 | public void setUp() throws Exception { 80 | // singleTestLock.lock(); 81 | client.putDir(FLAGZ_PATH).send().get(); 82 | String[] argz = { 83 | "--etcd_test_string=cmdline_overwrite", 84 | String.format("--flagz_etcd_directory=%s", FLAGZ_PATH) 85 | }; 86 | FlagFieldRegistry fieldRegistry = Flagz.parse(argz, ImmutableList.of(), ImmutableSet.of(this)); 87 | etcdUpdater = new EtcdFlagFieldUpdater(fieldRegistry, 88 | ImmutableList.of(ETCD_SERVER), 89 | ETCD_RETRY, Executors.newSingleThreadExecutor()); 90 | 91 | } 92 | 93 | @After 94 | public void tearDown() throws Exception { 95 | etcdUpdater.stop(); 96 | client.deleteDir(FLAGZ_PATH).recursive().send().get(); 97 | // singleTestLock.unlock(); 98 | } 99 | 100 | @Test 101 | public void testNothingInEtcd() throws Exception { 102 | etcdUpdater.init(); 103 | assertThat(flagInt.get(), is(flagInt.defaultValue())); 104 | assertThat(flagString.get(), is("cmdline_overwrite")); 105 | assertThat(flagMap.get(), is(flagMap.defaultValue())); 106 | } 107 | 108 | @Test 109 | public void testInitFromEtcd() throws Exception { 110 | client.put(FLAGZ_PATH + "etcd_test_int", "101").send().get(); 111 | client.put(FLAGZ_PATH + "etcd_test_string", "etcdinit_overwritten").send().get(); 112 | etcdUpdater.init(); 113 | assertThat(flagInt.get(), is(101)); 114 | assertThat(flagString.get(), is("etcdinit_overwritten")); 115 | assertThat(flagMap.get(), is(flagMap.defaultValue())); 116 | } 117 | 118 | @Test 119 | public void testInitFromEtcd_CallsListener() throws Exception { 120 | client.put(FLAGZ_PATH + "etcd_test_int", "333").send().get(); 121 | etcdUpdater.init(); 122 | assertThat(flagInt.get(), is(333)); 123 | verify(intListener).accept(eq(333)); 124 | } 125 | 126 | @Test(expected = FlagException.UnknownFlag.class) 127 | public void testInitFromEtcd_UnknownFlag_ThrowsException() throws Exception { 128 | client.put(FLAGZ_PATH + "etcd_test_some_flag", "123").send().get(); 129 | etcdUpdater.init(); 130 | } 131 | 132 | @Test(expected = FlagException.IllegalFormat.class) 133 | public void testInitFromEtcd_IllegalFormat_ThrowsException() throws Exception { 134 | client.put(FLAGZ_PATH + "etcd_test_map_int", "random_stuff").send().get(); 135 | etcdUpdater.init(); 136 | } 137 | 138 | @Test(expected = FlagException.BadValue.class) 139 | public void testInitFromEtcd_BadValue_ThrowsException() throws Exception { 140 | // 99 is below the >100 validator. 141 | client.put(FLAGZ_PATH + "etcd_test_int", "99").send().get(); 142 | etcdUpdater.init(); 143 | } 144 | 145 | @Test 146 | public void testUpdateWatch_CallsListener() throws Exception { 147 | etcdUpdater.init(); 148 | etcdUpdater.watchForUpdates(); 149 | client.put(FLAGZ_PATH + "etcd_test_int", "333").send().get(); 150 | verify(intListener, timeout(100)).accept(eq(333)); 151 | assertThat(flagInt.get(), is(333)); 152 | } 153 | 154 | @Test 155 | public void testUpdateWatch_MultipleValues() throws Exception { 156 | etcdUpdater.init(); 157 | etcdUpdater.watchForUpdates(); 158 | client.put(FLAGZ_PATH + "etcd_test_int", "999").send().get(); 159 | client.put(FLAGZ_PATH + "etcd_test_string", "new_dynamic").send().get(); 160 | 161 | verify(intListener, timeout(100)).accept(eq(999)); 162 | verify(stringListener, timeout(100)).accept(eq("new_dynamic")); 163 | assertThat(flagInt.get(), is(999)); 164 | assertThat(flagString.get(), is("new_dynamic")); 165 | } 166 | 167 | @Test 168 | public void testUpdateWatch_BadNewValue_IsRolledBack() throws Exception { 169 | etcdUpdater.init(); 170 | etcdUpdater.watchForUpdates(); 171 | EtcdKeysResponse put = client.put(FLAGZ_PATH + "etcd_test_int", "99").send().get(); 172 | EtcdKeysResponse watch = client.get(FLAGZ_PATH + "etcd_test_int") 173 | .waitForChange(put.node.modifiedIndex + 1).send().get(); 174 | // NOTE: This is crucial, because it means we're using an atomic delete, deleting only what we've read. 175 | assertThat(watch.action, is(EtcdKeyAction.compareAndDelete)); 176 | assertThat(watch.node.value, nullValue()); 177 | verify(intListener, never()).accept(eq(99)); 178 | } 179 | 180 | @Test 181 | public void testUpdateWatch_BadExistingValue_IsRolledBack() throws Exception { 182 | etcdUpdater.init(); 183 | etcdUpdater.watchForUpdates(); 184 | EtcdKeysResponse firstPut = client.put(FLAGZ_PATH + "etcd_test_int", "1337").send().get(); 185 | EtcdKeysResponse secondPut = client.put(FLAGZ_PATH + "etcd_test_int", "99").send().get(); 186 | EtcdKeysResponse watch = client.get(FLAGZ_PATH + "etcd_test_int") 187 | .waitForChange(secondPut.node.modifiedIndex + 1).send().get(); 188 | // NOTE: This is crucial, because it means we're using an atomic set, overwriting only what we've read. 189 | assertThat(watch.action, is(EtcdKeyAction.compareAndSwap)); 190 | assertThat(watch.node.value, is("1337")); 191 | // We will have this value updates twice, because of the rollback. 192 | verify(intListener, atLeast(1)).accept(eq(1337)); 193 | verify(intListener, never()).accept(eq(99)); 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /flagz-java/src/main/java/org/flagz/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_library( 4 | name = "flagz", 5 | srcs = glob(["*.java"]), 6 | deps = [ 7 | "//third_party/guava", 8 | "//third_party/jsr305", 9 | "//third_party/reflections", 10 | "//third_party/slf4j:api", 11 | ], 12 | ) 13 | -------------------------------------------------------------------------------- /flagz-java/src/main/java/org/flagz/BaseFlag.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | import com.google.common.base.Preconditions; 4 | import com.google.common.base.Strings; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import javax.annotation.Nullable; 9 | import java.util.LinkedList; 10 | import java.util.List; 11 | import java.util.function.Consumer; 12 | import java.util.function.Predicate; 13 | 14 | /** 15 | * Base implementation of a {@link Flag}. 16 | * 17 | * This class provides most of the _pure_ functionality of a the public interface of {@link Flag}. 18 | * Flags are not discoverable without the {@link FlagField}, so please subclass that instead. 19 | */ 20 | class BaseFlag implements Flag { 21 | 22 | private static final Logger LOG = LoggerFactory.getLogger(FlagFieldRegistry.class); 23 | 24 | private volatile T value; 25 | private final T defaultValue; 26 | 27 | protected String name; 28 | protected String altName; 29 | protected String help; 30 | protected boolean unusedMarker; 31 | 32 | private final List> validators = new LinkedList<>(); 33 | private final List> listeners = new LinkedList<>(); 34 | 35 | BaseFlag(T defaultValue) { 36 | this.value = defaultValue; 37 | this.defaultValue = defaultValue; 38 | } 39 | 40 | @Override 41 | public T get() { 42 | if (unusedMarker) { 43 | String errMsg = String.format("Trying to get a value from flag: %s, which is marked as unused.", name); 44 | LOG.error(errMsg); 45 | } 46 | return value; 47 | } 48 | 49 | @Override 50 | public T defaultValue() { 51 | return defaultValue; 52 | } 53 | 54 | @Override 55 | public void accept(T value) { 56 | checkValidators(value); 57 | this.value = value; 58 | notifyListeners(value); 59 | } 60 | 61 | @Override 62 | public String name() { 63 | Preconditions 64 | .checkState(name != null, "Name and other Flag metadata must be set before proceeding."); 65 | return name; 66 | } 67 | 68 | @Nullable 69 | @Override 70 | public String altName() { 71 | return Strings.emptyToNull(altName); 72 | } 73 | 74 | @Override 75 | public String help() { 76 | return Strings.emptyToNull(help); 77 | } 78 | 79 | /** 80 | * Add a validator predicate. 81 | * 82 | * If the predicate returns false for any value (parsed from command line, or set dynamically) it 83 | * will be rejected and the Flag's current value will remain unchained. 84 | * 85 | * In order to make predicate failures easier to understand, consider throwing a 86 | * {@link IllegalArgumentException} with a human-readable message. 87 | * 88 | * Validators are evaluated in order they are registered. 89 | */ 90 | public BaseFlag withValidator(Predicate predicate) { 91 | validators.add(predicate); 92 | return this; 93 | } 94 | 95 | /** 96 | * Add a listener for changes to this flag. 97 | * 98 | * Whenever the flag is changed (parsed from command line, or set dynamically), the given 99 | * listener will be called. 100 | */ 101 | public BaseFlag withListener(Consumer predicate) { 102 | listeners.add(predicate); 103 | return this; 104 | } 105 | 106 | 107 | private void checkValidators(T value) throws FlagException.BadValue { 108 | for (Predicate predicate : validators) { 109 | try { 110 | if (!predicate.test(value)) { 111 | throw new FlagException.BadValue( 112 | this, value, new IllegalArgumentException("Predicate failed.")); 113 | } 114 | } catch (IllegalArgumentException exception) { 115 | throw new FlagException.BadValue(this, value, exception); 116 | } 117 | } 118 | } 119 | 120 | private void notifyListeners(T value) { 121 | for (Consumer listener : listeners) { 122 | listener.accept(value); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /flagz-java/src/main/java/org/flagz/ContainerFlagField.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | 4 | import com.google.common.base.Strings; 5 | import com.google.common.primitives.Primitives; 6 | 7 | import java.lang.reflect.Field; 8 | import java.lang.reflect.ParameterizedType; 9 | import java.util.Collection; 10 | import java.util.Map; 11 | import java.util.function.Supplier; 12 | import java.util.stream.Collectors; 13 | 14 | /** 15 | * A generic for a Flag that contains other types. 16 | * 17 | * An equivalent of a {@link Collection} for {@link FlagField}. Has two available implementations: 18 | * 19 | * - {@link CollectionFlagField} - allows building Flagz for {@link Collection} 20 | * types (e.g. List, Set), where values are primitives that are comma-separated, e.g. 21 | * `--my_set=foo,bar,car`, `--my_list=2,3,1,4`. 22 | * - {@link MapFlagField} - allows building Flagz for {@link Map} types, where both keys and 23 | * values are primitives that are comma-separated entries with a colon dividing key and 24 | * value, e.g. `--my_map=foo:123,bar:456`. 25 | */ 26 | abstract class ContainerFlagField extends FlagField { 27 | 28 | private final Supplier constructor; 29 | 30 | public ContainerFlagField(T defaultValue, Supplier constructor) { 31 | super(defaultValue); 32 | this.constructor = constructor; 33 | } 34 | 35 | protected abstract T addItem(T existing, String value) throws FlagException; 36 | 37 | @Override 38 | protected void parseString(String value) throws FlagException { 39 | T newValue = constructor.get(); 40 | String stripped = value.replaceAll("^\"|\"$", ""); 41 | for (String token : stripped.split(",")) { 42 | if (!Strings.isNullOrEmpty(token)) { 43 | newValue = addItem(newValue, token); 44 | } 45 | } 46 | accept(newValue); 47 | } 48 | 49 | @SuppressWarnings("unchecked") 50 | protected static X itemFromString(String value, Class clazz, Flag flag) 51 | throws FlagException { 52 | // In case we get a primitive type (e.g. from Scala), get the boxed version to know 53 | // how to serialize. 54 | clazz = Primitives.wrap(clazz); 55 | if (Number.class.isAssignableFrom(clazz)) { 56 | return clazz.cast(PrimitiveFlagField.NumberFlagField.fromString(value, clazz, flag)); 57 | } else if (Boolean.class.isAssignableFrom(clazz)) { 58 | return clazz.cast(Boolean.valueOf(value)); 59 | } else if (String.class.isAssignableFrom(clazz)) { 60 | return clazz.cast(value); 61 | } else if (clazz.isEnum()) { 62 | return (X) PrimitiveFlagField.EnumFlagField.fromString(value, (Class) clazz, flag); 63 | } 64 | throw new FlagException.UnsupportedType(flag, clazz); 65 | } 66 | 67 | /** 68 | * A {@link FlagField} that supports {@link Collection} construction of primitive values. 69 | */ 70 | static class CollectionFlagField> extends ContainerFlagField { 71 | 72 | private Class elementClazz; 73 | 74 | public CollectionFlagField(T defaultValue, Supplier constructor) { 75 | super(defaultValue, constructor); 76 | } 77 | 78 | @Override 79 | protected T addItem(T existing, String value) throws FlagException { 80 | E item = itemFromString(value, elementClazz, this); 81 | existing.add(item); 82 | return existing; 83 | } 84 | 85 | @Override 86 | @SuppressWarnings("unchecked") 87 | protected void bind(Field containingField) { 88 | super.bind(containingField); 89 | try { 90 | elementClazz = (Class) ((ParameterizedType) fieldType()).getActualTypeArguments()[0]; 91 | } catch (ClassCastException exception) { 92 | throw new FlagException.UnsupportedType(this, fieldType()); 93 | } 94 | } 95 | 96 | @Override 97 | public String valueString(T value) { 98 | return value.stream() 99 | .map(Object::toString) 100 | .collect(Collectors.joining(",")); 101 | } 102 | } 103 | 104 | /** 105 | * A {@link FlagField} that supports {@link Map} construction of primitive keys and values. 106 | */ 107 | static class MapFlagField> extends ContainerFlagField { 108 | 109 | private Class keyClazz; 110 | private Class valueClazz; 111 | 112 | public MapFlagField(T defaultValue, Supplier constructor) { 113 | super(defaultValue, constructor); 114 | } 115 | 116 | @Override 117 | protected T addItem(T existing, String itemString) throws FlagException { 118 | String[] components = itemString.split(":"); 119 | if (components.length != 2) { 120 | throw new FlagException.IllegalFormat(this, itemString, null); 121 | } 122 | K key = itemFromString(components[0], keyClazz, this); 123 | V value = itemFromString(components[1], valueClazz, this); 124 | existing.put(key, value); 125 | return existing; 126 | } 127 | 128 | @Override 129 | @SuppressWarnings("unchecked") 130 | protected void bind(Field containingField) { 131 | super.bind(containingField); 132 | try { 133 | keyClazz = (Class) ((ParameterizedType) fieldType()).getActualTypeArguments()[0]; 134 | valueClazz = (Class) ((ParameterizedType) fieldType()).getActualTypeArguments()[1]; 135 | } catch (ClassCastException exception) { 136 | throw new FlagException.UnsupportedType(this, fieldType()); 137 | } 138 | } 139 | 140 | @Override 141 | public String valueString(T value) { 142 | return value.entrySet().stream() 143 | .map(e -> e.getKey().toString() + ":" + e.getValue().toString()) 144 | .collect(Collectors.joining(",")); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /flagz-java/src/main/java/org/flagz/Flag.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | import javax.annotation.Nullable; 4 | import java.util.function.Consumer; 5 | import java.util.function.Predicate; 6 | import java.util.function.Supplier; 7 | 8 | /** 9 | * Public interface visible to users of {@link FlagField}. 10 | * 11 | * Flags are Java field variables that are exposed as CLI or dynamic properties. Each such field 12 | * must be annotated using {@link FlagInfo} and processed through {@link Flagz#parse} which 13 | * discovers all fields throughout the classpath. 14 | * 15 | * Flag objects must be tied to fields that are final. Static final fields 16 | * (private/protected/public) will be automatically picked up if they exist on the Java classpath. 17 | * Final fields in instantiated objects can only be discovered if said objects are passed 18 | * to {@link Flagz#parse}. 19 | * 20 | * @param the type this flag holds. 21 | */ 22 | public interface Flag extends Supplier, Consumer { 23 | 24 | /** Returns the value of this flag. */ 25 | T get(); 26 | 27 | /** Returns the default value of this flag. */ 28 | T defaultValue(); 29 | 30 | /** Sets the value of this flag. */ 31 | void accept(T flagValue); 32 | 33 | /** 34 | * Returns the user-visible flag name. 35 | */ 36 | String name(); 37 | 38 | /** 39 | * Returns a user-visible alternative name for the flag, if any. 40 | */ 41 | @Nullable 42 | String altName(); 43 | 44 | /** 45 | * Returns the user-visible help string of the flag. 46 | */ 47 | String help(); 48 | 49 | 50 | Flag withValidator(Predicate predicate); 51 | 52 | /** 53 | * Add a listener for changes to this flag. 54 | * 55 | * Whenever the flag is changed (parsed from command line, or set dynamically), the given 56 | * listener will be called. 57 | */ 58 | Flag withListener(Consumer predicate); 59 | } 60 | -------------------------------------------------------------------------------- /flagz-java/src/main/java/org/flagz/FlagException.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | import java.lang.reflect.Field; 4 | import java.lang.reflect.Type; 5 | 6 | /** 7 | * Super-type and all exceptions used within the Flagz library. 8 | */ 9 | public class FlagException extends RuntimeException { 10 | 11 | protected Flag flag; 12 | protected String message; 13 | 14 | 15 | FlagException() { 16 | } 17 | 18 | FlagException(String message) { 19 | this.message = message; 20 | } 21 | 22 | @Override 23 | public String getMessage() { 24 | if (flag != null) { 25 | return String.format("%s %s", flag, message); 26 | } else { 27 | return message; 28 | } 29 | } 30 | 31 | /** 32 | * Thrown when the given flag is not found in the system. 33 | */ 34 | public static class UnknownFlag extends FlagException { 35 | 36 | UnknownFlag(String flagName) { 37 | this.message = String.format("Could not find Flag for name/alt-name '%s'", flagName); 38 | } 39 | 40 | } 41 | 42 | /** 43 | * Thrown when the value passed to {@link Flag} does not parse for the given type. 44 | */ 45 | public static class IllegalFormat extends FlagException { 46 | 47 | IllegalFormat(Flag flag, String value, Throwable exception) { 48 | this.flag = flag; 49 | this.message = String.format("Value '%.30s' failed parsing due to: %s", value, exception); 50 | } 51 | 52 | } 53 | 54 | /** 55 | * Thrown when the value of the {@link Flag} was correctly parsed but is invalid. 56 | */ 57 | public static class BadValue extends FlagException { 58 | 59 | BadValue(Flag flag, Object value, Throwable exception) { 60 | this.flag = flag; 61 | this.message = String.format("Value '%.30s' failed validation due to: %s", value, exception); 62 | } 63 | } 64 | 65 | /** 66 | * Thrown when multiple {@link Flag} fields have same names/alt-names. 67 | */ 68 | public static class NameConflict extends FlagException { 69 | 70 | NameConflict(Flag... conflicting) { 71 | this.message = "Conflict on Flag names/alt-names, with the following:\n"; 72 | for (Flag flag : conflicting) { 73 | Field field = ((FlagField) flag).containingField(); 74 | this.message = String.format( 75 | "\t%s declared in Field(%s#%s)\n", 76 | flag, 77 | field.getDeclaringClass().getCanonicalName(), 78 | field.getName()); 79 | } 80 | } 81 | } 82 | 83 | /** 84 | * Thrown when {@link Flag} is expected to handle a type it doesn't support. 85 | */ 86 | public static class UnsupportedType extends FlagException { 87 | 88 | public UnsupportedType(Flag flag, Type type) { 89 | this.flag = flag; 90 | this.message = String.format("Does not support Type(%s)", type.getTypeName()); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /flagz-java/src/main/java/org/flagz/FlagField.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | import com.google.common.base.Preconditions; 4 | import com.google.common.base.Strings; 5 | import com.google.common.collect.ImmutableList; 6 | 7 | import java.lang.annotation.Annotation; 8 | import java.lang.reflect.Field; 9 | import java.lang.reflect.ParameterizedType; 10 | import java.lang.reflect.Type; 11 | 12 | /** 13 | * {@link Flag} that is tied to a concrete Java field. 14 | * 15 | * Due to Java's type erasure, we need to reflectively find the type of the {@link Flag} that 16 | * is being processed. This class abstracts that problem away, by filling out the missing fields 17 | * of {@link BaseFlag}, and serving as the base class for all concrete implementations of flags. 18 | * 19 | * You may subclass this, making all your custom {@link Flag} classes discoverable through 20 | * reflection. 21 | */ 22 | public abstract class FlagField extends BaseFlag { 23 | 24 | // Data from reflection o the field, to avoid type erasure. 25 | private Field containingField; 26 | private Type containingFieldType; 27 | 28 | FlagField(T defaultValue) { 29 | super(defaultValue); 30 | } 31 | 32 | protected Type fieldType() { 33 | Preconditions.checkNotNull( 34 | containingFieldType, 35 | "The field reference must be set to work around type erasure."); 36 | return containingFieldType; 37 | } 38 | 39 | protected abstract void parseString(String value) throws FlagException; 40 | 41 | public String valueString(T value) { 42 | return value.toString(); 43 | } 44 | 45 | /** Returns the annotations present on the flag field. */ 46 | public ImmutableList getAnnotations() { 47 | return ImmutableList.copyOf(containingField.getAnnotations()); 48 | } 49 | 50 | /** 51 | * Updates the reflected reference to the {@link Field} this object is defined in. 52 | *

53 | * This is needed to work around type erasure. 54 | */ 55 | void bind(Field containingField) { 56 | Preconditions.checkArgument( 57 | Flag.class.isAssignableFrom(containingField.getType()), 58 | "Passed Field is not a Flag."); 59 | FlagInfo[] annotations = containingField.getAnnotationsByType(FlagInfo.class); 60 | Preconditions.checkArgument( 61 | annotations.length == 1, 62 | "FlagField containing field must contain exactly one @FlagInfo annotation."); 63 | FlagInfo annotation = annotations[0]; 64 | this.name = Strings.isNullOrEmpty(annotation.name()) 65 | ? containingField.getName() 66 | : annotation.name(); 67 | this.altName = annotation.altName(); 68 | this.help = annotation.help(); 69 | this.containingField = containingField; 70 | // Extract the Class or ParametrizedType from Flag 71 | this.containingFieldType = ((ParameterizedType) containingField.getGenericType()) 72 | .getActualTypeArguments()[0]; 73 | this.unusedMarker = containingField.isAnnotationPresent(FlagzUnused.class); 74 | } 75 | 76 | Field containingField() { 77 | return containingField; 78 | } 79 | 80 | @Override 81 | public String toString() { 82 | return String.format("FlagField<%s>(%s)", containingFieldType, name); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /flagz-java/src/main/java/org/flagz/FlagFieldRegistry.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | import com.google.common.base.Strings; 4 | import com.google.common.collect.ImmutableSet; 5 | import com.google.common.collect.Maps; 6 | import com.google.common.collect.Sets; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.lang.annotation.Annotation; 11 | import java.util.Arrays; 12 | import java.util.Map; 13 | import java.util.Set; 14 | import java.util.function.Function; 15 | import java.util.stream.Collectors; 16 | 17 | /** 18 | * Holds a name to {@link Flag} resolution and allows for updating of flags from strings. 19 | */ 20 | public class FlagFieldRegistry { 21 | 22 | private static final Logger LOG = LoggerFactory.getLogger(FlagFieldRegistry.class); 23 | private final Set scanners; 24 | private Map> nameToField = Maps.newHashMap(); 25 | private Map> allNamesToField = Maps.newHashMap(); 26 | 27 | FlagFieldRegistry(Set scanners) { 28 | this.scanners = scanners; 29 | } 30 | 31 | /** Transforms unused {@link FlagField} objects for pretty printing */ 32 | static String[] unusedFlagsMessages(Set> unusedFlags) { 33 | String header = "The following flag is unused - it will have no effect on the system: "; 34 | return unusedFlags.stream() 35 | .map(flag -> header + Utils.flagDescriptorString(flag)) 36 | .toArray(String[]::new); 37 | } 38 | 39 | /** Retrieves the Flag by its default name. */ 40 | public Flag getField(String name) throws FlagException { 41 | BaseFlag field = nameToField.get(name); 42 | if (field == null) { 43 | throw new FlagException.UnknownFlag(name); 44 | } 45 | return field; 46 | } 47 | 48 | /** 49 | * Returns a reference to all the flags contained in this registry. Note that value changes which 50 | * happen after calling this method will be reflected in the returned list. 51 | */ 52 | public Set> getAllFields() { 53 | return ImmutableSet.copyOf(nameToField.values()); 54 | } 55 | 56 | /** 57 | * Returns a reference to all the flags contained in this registry which are annotated with an 58 | * annotation of the supplied type. Note that value changes which happen after calling this 59 | * method will be reflected in the returned list. 60 | */ 61 | public Set> getFieldsAnnotatedWith(Class annotationType) { 62 | ImmutableSet.Builder> result = ImmutableSet.builder(); 63 | nameToField.values().forEach(flag -> { 64 | if (flag.containingField().isAnnotationPresent(annotationType)) { 65 | result.add(flag); 66 | } 67 | }); 68 | return result.build(); 69 | } 70 | 71 | /** Sets the value of the Flag, parsing it from string. */ 72 | public void setField(String name, String value) throws FlagException { 73 | FlagField field = (FlagField) getField(name); 74 | field.parseString(value); 75 | } 76 | 77 | void init() throws FlagException { 78 | Set> fields = scanners.stream() 79 | .flatMap(s -> s.scanAndBind().stream()) 80 | .collect(Collectors.toSet()); 81 | // Add all maps, and throw exceptions if there are conflicts. 82 | addFieldNamesToMap(nameToField, fields, FlagField::name); 83 | allNamesToField.putAll(nameToField); 84 | addFieldNamesToMap(allNamesToField, fields, FlagField::altName); 85 | } 86 | 87 | void parseAll(Map nameToValue) throws FlagException { 88 | // Check for all missing values to ease the pain of having to start up the binary many 89 | // times to resolve them. 90 | Set unknownNames = Sets.difference(nameToValue.keySet(), allNamesToField.keySet()); 91 | if (unknownNames.size() > 0) { 92 | throw new FlagException.UnknownFlag(unknownNames.stream().collect(Collectors.joining(","))); 93 | } 94 | Set> unusedFlags = unusedFlags(nameToValue); 95 | if (!unusedFlags.isEmpty()) { 96 | Arrays.stream(unusedFlagsMessages(unusedFlags)).forEach(LOG::warn); 97 | } 98 | for (String name : nameToValue.keySet()) { 99 | FlagField field = allNamesToField.get(name); 100 | field.parseString(nameToValue.get(name)); 101 | } 102 | } 103 | 104 | /** Returns a set of all user-passed flags which are marked as unused. */ 105 | Set> unusedFlags(Map nameToValue) { 106 | return nameToValue.keySet().stream() 107 | .map(key -> allNamesToField.get(key)) 108 | .filter(flag -> flag != null && flag.unusedMarker) 109 | .collect(Collectors.toSet()); 110 | } 111 | 112 | Set> allFields() { 113 | return ImmutableSet.copyOf(nameToField.values()); 114 | } 115 | 116 | /** Adds fields to map for a given name retrieval function. Throws exceptions on conflicts. */ 117 | private static void addFieldNamesToMap(Map> map, 118 | Set> fields, 119 | Function, String> fieldNameGetter) 120 | throws FlagException.NameConflict { 121 | for (FlagField field : fields) { 122 | String name = fieldNameGetter.apply(field); 123 | if (Strings.isNullOrEmpty(name)) { 124 | continue; 125 | } 126 | FlagField previousExisted = map.put(name, field); 127 | if (previousExisted != null) { 128 | throw new FlagException.NameConflict(previousExisted, field); 129 | } 130 | } 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /flagz-java/src/main/java/org/flagz/FlagFieldScanner.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | import com.google.common.base.Preconditions; 4 | import org.reflections.ReflectionUtils; 5 | import org.reflections.Reflections; 6 | 7 | import java.lang.reflect.Field; 8 | import java.lang.reflect.Modifier; 9 | import java.util.HashSet; 10 | import java.util.List; 11 | import java.util.Set; 12 | 13 | import static com.google.common.base.Predicates.not; 14 | 15 | 16 | /** 17 | * Scanners yielding {@link FlagInfo} annotated {@link FlagField}s objects. 18 | * 19 | * These objects are guaranteed to be bound to their respective {@link Field} Java objects 20 | * to maintain typing. 21 | */ 22 | abstract class FlagFieldScanner { 23 | 24 | /** 25 | * Scans for {@link FlagField} objects and binds them to the {@link Field} to preserve type. 26 | */ 27 | public abstract Set> scanAndBind(); 28 | 29 | private final FlagPropertySyncer propertySyncer = new FlagPropertySyncer(); 30 | 31 | 32 | protected FlagField boundFlagField(Field field, Object declaredIn) { 33 | FlagField flagField = null; 34 | if (!Flag.class.isAssignableFrom(field.getType())) { 35 | throw new RuntimeException( 36 | String.format( 37 | "Field(%s#%s) annotated with @FlagInfo but not a Flag.", 38 | field.getDeclaringClass().getCanonicalName(), 39 | field.getName())); 40 | } 41 | try { 42 | final boolean wasAccessible = field.isAccessible(); 43 | if (!field.isAccessible()) { 44 | field.setAccessible(true); 45 | } 46 | flagField = (FlagField) field.get(declaredIn); 47 | flagField.bind(field); 48 | FlagProperty property = propertySyncer.fieldPropertyAnnotation(field); 49 | if (property != null) { 50 | propertySyncer.handlePropertyAnnotaton(flagField, property); 51 | } 52 | if (!wasAccessible) { 53 | field.setAccessible(false); 54 | } 55 | } catch (IllegalAccessException exception) { 56 | // Should never happen because access is granted, but let's get a nice stack trace. 57 | throw new RuntimeException("Unexpected problem parsing Field " + field.toString(), exception); 58 | } 59 | return flagField; 60 | } 61 | 62 | /** 63 | * Scans the entire Java classpath to find static final fields of {@link Flag}. 64 | */ 65 | static class StaticFinalScanner extends FlagFieldScanner { 66 | 67 | private final List prefixes; 68 | 69 | public StaticFinalScanner(final List prefixes) { 70 | this.prefixes = Preconditions.checkNotNull(prefixes); 71 | } 72 | 73 | @Override 74 | public Set> scanAndBind() { 75 | Reflections reflections = ReflectionsCache.reflectionsForPrefixes(prefixes); 76 | Set> fields = new HashSet<>(); 77 | for (Field field : reflections.getFieldsAnnotatedWith(FlagInfo.class)) { 78 | if (!Modifier.isStatic(field.getModifiers()) 79 | || !Modifier.isFinal(field.getModifiers())) { 80 | continue; 81 | } 82 | fields.add(boundFlagField(field, null)); // null is ok, these are static. 83 | } 84 | 85 | return fields; 86 | } 87 | } 88 | 89 | /** Scans a set of objects, finding bound final fields. Useful for Scala 'objects'. */ 90 | static class ObjectBoundFinalScanner extends FlagFieldScanner { 91 | 92 | private final Set objectsToScan; 93 | 94 | public ObjectBoundFinalScanner(Set objectsToScan) { 95 | this.objectsToScan = Preconditions.checkNotNull(objectsToScan); 96 | } 97 | 98 | @SuppressWarnings("unchecked") 99 | @Override 100 | public Set> scanAndBind() { 101 | Set> fields = new HashSet<>(); 102 | for (Object obj : objectsToScan) { 103 | ReflectionUtils.getAllFields( 104 | obj.getClass(), 105 | ReflectionUtils.withTypeAssignableTo(Flag.class), 106 | ReflectionUtils.withAnnotation(FlagInfo.class), 107 | ReflectionUtils.withModifier(Modifier.FINAL), 108 | not(ReflectionUtils.withModifier(Modifier.STATIC))) 109 | .stream() 110 | .map(f -> boundFlagField(f, obj)) 111 | .forEach(fields::add); 112 | } 113 | return fields; 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /flagz-java/src/main/java/org/flagz/FlagInfo.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Annotation for discovering {@link Flag} annotated fields. 10 | * 11 | * All fields annotated with {@link FlagInfo} will be dynamically discovered through reflection on 12 | * {@link Flagz#parse}, and exposed as command line arguments arccording to information 13 | * in this annotation. 14 | */ 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Target(ElementType.FIELD) 17 | public @interface FlagInfo { 18 | /** User-visible information on how to use this flag. */ 19 | String help(); 20 | 21 | 22 | /** 23 | * Override of default flag name (inferred from variable name). 24 | * 25 | * Flag will be available on the commandline as "--flag_name=" 26 | */ 27 | String name() default ""; 28 | 29 | /** 30 | * Alternate name for flag, useful for shorthands. 31 | * 32 | * Flag will be available on the command line as "-f=" 33 | */ 34 | String altName() default ""; 35 | } -------------------------------------------------------------------------------- /flagz-java/src/main/java/org/flagz/FlagProperty.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Annotation for marking {@link Flag} objects to be synchronized with the given System property. 10 | * 11 | * The purpose of this functionality is to provide backwards compatibility with libraries that 12 | * expect to be configured through {@link System#getProperty}. It allows for both one way, and two 13 | * way synchronization of properties and Flags, including synchronizing changes made through 14 | * dynamic mechanisms (JMX, Etc.d). 15 | * The behaviour for {@link FlagProperty} annotated {@link Flag}s is as follows: 16 | * 17 | * - if a System property exists upon {@link Flagz#parse} call, it is parsed in exactly the same 18 | * way as command line arguments and *overrides* the defaultValue set using {@link Flagz#valueOf} 19 | * - if the {@link Flag} later is found on the command line or a {@link Flag#accept} is called, 20 | * the value overrides both {@link Flag} value, as well as the System property string is updated 21 | * according to the command line format. This behaviour may be turned off 22 | * using {@link FlagProperty#sync} = false 23 | */ 24 | @Retention(RetentionPolicy.RUNTIME) 25 | @Target(ElementType.FIELD) 26 | public @interface FlagProperty { 27 | 28 | /** System property, similar to {@link System#getProperty}. E.g. "path.separator". */ 29 | String value(); 30 | 31 | /** Whether the System property should be updated upon modifications to {@link Flag}. */ 32 | boolean sync() default true; 33 | } -------------------------------------------------------------------------------- /flagz-java/src/main/java/org/flagz/FlagPropertySyncer.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | import com.google.common.base.Preconditions; 4 | import com.google.common.base.Strings; 5 | 6 | import javax.annotation.Nullable; 7 | import java.lang.reflect.Field; 8 | 9 | /** 10 | * Handles {@link FlagProperty} annotations on {@link FlagField}. 11 | * 12 | * Sets default values from properties, adds listeners for changes. 13 | */ 14 | class FlagPropertySyncer { 15 | 16 | @Nullable 17 | FlagProperty fieldPropertyAnnotation(Field javaField) { 18 | FlagProperty property = null; 19 | FlagProperty[] properties = javaField.getAnnotationsByType(FlagProperty.class); 20 | if (properties.length > 0) { 21 | property = properties[0]; 22 | Preconditions.checkNotNull( 23 | Strings.emptyToNull(property.value()), 24 | "FlagProperty can't be empty or null."); 25 | } 26 | return property; 27 | } 28 | 29 | void handlePropertyAnnotaton(FlagField flagField, 30 | @Nullable FlagProperty propertyAnnotation) { 31 | if (propertyAnnotation == null) { 32 | return; 33 | } 34 | String propertyName = propertyAnnotation.value(); 35 | String propertyValue = System.getProperty(propertyName); 36 | if (propertyValue != null) { 37 | flagField.parseString(propertyValue); 38 | } 39 | if (propertyAnnotation.sync()) { 40 | flagField.withListener( 41 | (flag) -> System.setProperty(propertyAnnotation.value(), flagField.valueString(flag))); 42 | // Trigger an override. 43 | flagField.accept(flagField.get()); 44 | } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /flagz-java/src/main/java/org/flagz/Flagz.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.ImmutableSet; 5 | 6 | import java.util.ArrayList; 7 | import java.util.HashMap; 8 | import java.util.HashSet; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Set; 12 | 13 | /** 14 | * Wrapper class containing utility methods for working with {@link Flag} 15 | * objects. Flag objects must be annotated with {@link FlagInfo} in order to 16 | * be recognized as a flag. 17 | * 18 | * To create a new flag, create a new static final {@link Flag} field. The parameter 19 | * type of the field will be the type of the flag. Then annotate the field 20 | * with {@link FlagInfo} and provide the necessary fields. Example: 21 | * 22 | * ``` 23 | * {@literal @}FlagInfo(help = "max number of threads to use", altName = "n") 24 | * private static final Flag{@literal <}Integer{@literal >} maxNumThreads = 25 | * Flag<>.valueOf(4); 26 | * } 27 | * ``` 28 | * 29 | * This example declares a new flag indicating the maximum number of threads 30 | * to use. On the right hand side, you may provide a default value for the flag. 31 | * To pass in the value via command line, run the class with flags passed in 32 | * the format: 33 | * 34 | * ``` 35 | * java MyApp --maxNumThreads=5 -shortName=foo --booleanFlag ... 36 | * ``` 37 | * 38 | * To parse the flags from the command line, use {@link #parse} and try to catch 39 | * {@link FlagException} when parsing. 40 | */ 41 | public class Flagz { 42 | 43 | @FlagInfo(name = "help", altName = "h", help = "display this help menu") 44 | private static final Flag showHelp = Flagz.valueOf(false); 45 | 46 | protected Flagz() { 47 | } 48 | 49 | public static Flag valueOf(N defaultValue) { 50 | return new PrimitiveFlagField.NumberFlagField<>(defaultValue); 51 | } 52 | 53 | public static Flag valueOf(Boolean defaultValue) { 54 | return new PrimitiveFlagField.BooleanFlagField(defaultValue); 55 | } 56 | 57 | public static > Flag valueOf(E defaultValue) { 58 | return new PrimitiveFlagField.EnumFlagField<>(defaultValue); 59 | } 60 | 61 | public static Flag valueOf(String defaultValue) { 62 | return new PrimitiveFlagField.StringFlagField(defaultValue); 63 | } 64 | 65 | public static Flag> valueOf(Map defaultMap) { 66 | return new ContainerFlagField.MapFlagField<>(defaultMap, HashMap::new); 67 | } 68 | 69 | public static Flag> valueOf(List defaultList) { 70 | return new ContainerFlagField.CollectionFlagField<>(defaultList, ArrayList::new); 71 | } 72 | 73 | public static Flag> valueOf(Set defaultSet) { 74 | return new ContainerFlagField.CollectionFlagField<>(defaultSet, HashSet::new); 75 | } 76 | 77 | 78 | /** 79 | * Parses the command line arguments and updates as necessary all {@link Flag} 80 | * objects annotated with {@link FlagInfo}. 81 | * 82 | * If "--help" of "-h" is passed in at the command line, then the help menu 83 | * will be printed and the JVM will exit with a 0 exit status. 84 | * 85 | * @param args command line arguments in the form 86 | * "--defaultFlagName=value --booleanFlag -c=foo ..." 87 | * @param packagePrefixes list of Java packages to be scanned for Flag objects, keeping the scope 88 | * narrow makes it start fast. By default an empty list is used, meaning 89 | * all classes will be reflected on. 90 | * @param objects Set of objects that should be reflected to discover non-static final 91 | * {@link Flag} fields. 92 | * @return A registry that can be used for accessing all flags. 93 | */ 94 | public static FlagFieldRegistry parse(String[] args, List packagePrefixes, 95 | Set objects) { 96 | Set scanners = ImmutableSet.of( 97 | new FlagFieldScanner.ObjectBoundFinalScanner(objects), 98 | new FlagFieldScanner.StaticFinalScanner(packagePrefixes)); 99 | FlagFieldRegistry registry = new FlagFieldRegistry(scanners); 100 | registry.init(); 101 | registry.parseAll(Utils.parseArgsToFieldMap(args)); 102 | 103 | if (showHelp.get()) { 104 | Utils.printHelpPage(registry.allFields()); 105 | System.exit(0); 106 | } 107 | return registry; 108 | } 109 | 110 | 111 | public static FlagFieldRegistry parse(String[] args) { 112 | return parse(args, ImmutableList.of(), ImmutableSet.of()); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /flagz-java/src/main/java/org/flagz/FlagzUnused.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * An annotation used on fields of type Flag to indicate that their value is never read (and, as 10 | * such, it is useless to specify them). Note that this is subtly stronger than {@link Deprecated} 11 | * in that deprecated flags might still be read whereas {@link FlagzUnused} may not. 12 | * 13 | * Any attempt to get a value from an unused flag will raise a {@link RuntimeException}. 14 | */ 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Target(ElementType.FIELD) 17 | public @interface FlagzUnused {} 18 | -------------------------------------------------------------------------------- /flagz-java/src/main/java/org/flagz/JmxFlagFieldRegistrar.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | import com.google.common.base.Preconditions; 4 | import com.google.common.base.Throwables; 5 | 6 | import javax.management.InstanceAlreadyExistsException; 7 | import javax.management.MBeanRegistrationException; 8 | import javax.management.MBeanServer; 9 | import javax.management.MalformedObjectNameException; 10 | import javax.management.NotCompliantMBeanException; 11 | import javax.management.ObjectName; 12 | import javax.management.StandardMBean; 13 | 14 | /** 15 | * Registers the flags seen by {@link FlagFieldRegistry} as JMX MBeans. 16 | */ 17 | public class JmxFlagFieldRegistrar { 18 | 19 | @FlagInfo(name = "flagz_jmx_domain", help = "Domain (prefix) used for all Flag JMX objects.") 20 | private static final Flag jmxDomain = Flagz.valueOf("org.flagz"); 21 | 22 | private final FlagFieldRegistry registry; 23 | 24 | public JmxFlagFieldRegistrar(FlagFieldRegistry registry) { 25 | this.registry = Preconditions.checkNotNull(registry); 26 | } 27 | 28 | /** Registers FlagField beans with the given MBeanServer server. */ 29 | public void register(MBeanServer server) { 30 | for (FlagField field : registry.allFields()) { 31 | FlagFieldMBean delegator = new FlagFieldMBean<>(field); 32 | delegator.register(server); 33 | } 34 | } 35 | 36 | private static class FlagFieldMBean implements FlagMBean { 37 | 38 | private final FlagField delegateFlag; 39 | 40 | FlagFieldMBean(FlagField delegateFlag) { 41 | this.delegateFlag = Preconditions.checkNotNull(delegateFlag); 42 | } 43 | 44 | private void register(MBeanServer server) { 45 | try { 46 | StandardMBean bean = new StandardMBean(this, FlagMBean.class); 47 | server.registerMBean(bean, objectName()); 48 | } catch (NotCompliantMBeanException | InstanceAlreadyExistsException 49 | | MBeanRegistrationException exception) { 50 | Throwables.propagate(exception); 51 | } 52 | } 53 | 54 | private ObjectName objectName() { 55 | try { 56 | return new ObjectName(jmxDomain.get(), "name", getName()); 57 | } catch (MalformedObjectNameException exception) { 58 | Throwables.propagate(exception); 59 | return null; 60 | } 61 | } 62 | 63 | @Override 64 | public String getName() { 65 | return delegateFlag.name(); 66 | } 67 | 68 | @Override 69 | public String getHelp() { 70 | return delegateFlag.help(); 71 | } 72 | 73 | @Override 74 | public String getDefaultValue() { 75 | return delegateFlag.valueString(delegateFlag.defaultValue()); 76 | } 77 | 78 | @Override 79 | public String getValue() { 80 | return delegateFlag.valueString(delegateFlag.get()); 81 | } 82 | 83 | @Override 84 | public String getType() { 85 | return delegateFlag.fieldType().getTypeName(); 86 | } 87 | 88 | @Override 89 | public void setValue(String value) { 90 | try { 91 | delegateFlag.parseString(value); 92 | } catch (FlagException exception) { 93 | throw new IllegalArgumentException("Failed parsing flag " + getName(), exception); 94 | } 95 | } 96 | } 97 | 98 | /** The public interface describing that what will be visible. */ 99 | public interface FlagMBean { 100 | 101 | public String getName(); 102 | 103 | public String getHelp(); 104 | 105 | public String getDefaultValue(); 106 | 107 | public String getValue(); 108 | 109 | public String getType(); 110 | 111 | public void setValue(String value) throws IllegalArgumentException; 112 | 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /flagz-java/src/main/java/org/flagz/PrimitiveFlagField.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | import com.google.common.base.Strings; 4 | import com.google.common.collect.ImmutableSet; 5 | 6 | import java.util.Set; 7 | import java.util.function.BiFunction; 8 | 9 | /** 10 | * Flag implementations for all primitive Java types. 11 | */ 12 | class PrimitiveFlagField { 13 | 14 | 15 | static class EnumFlagField> extends FlagField { 16 | public EnumFlagField(E defaultValue) { 17 | super(defaultValue); 18 | } 19 | 20 | @Override 21 | @SuppressWarnings("unchecked") 22 | protected void parseString(String value) throws FlagException { 23 | accept(fromString(value, (Class) fieldType(), this)); 24 | } 25 | 26 | public static > T fromString(String value, Class clazz, Flag flag) 27 | throws FlagException { 28 | try { 29 | return Enum.valueOf(clazz, value); 30 | } catch (IllegalArgumentException exception) { 31 | throw new FlagException.IllegalFormat(flag, value, exception); 32 | } 33 | } 34 | } 35 | 36 | static class StringFlagField extends FlagField { 37 | 38 | public StringFlagField(String defaultValue) { 39 | super(defaultValue); 40 | } 41 | 42 | @Override 43 | protected void parseString(String value) { 44 | accept(value); 45 | } 46 | } 47 | 48 | static class NumberFlagField extends FlagField { 49 | 50 | NumberFlagField(T defaultValue) { 51 | super(defaultValue); 52 | } 53 | 54 | @Override 55 | @SuppressWarnings("unchecked") 56 | protected void parseString(String value) throws FlagException { 57 | accept((T) fromString(value, (Class) fieldType(), this)); 58 | } 59 | 60 | public static Number fromString(String value, Class clazz, Flag flag) throws FlagException { 61 | try { 62 | if (clazz.equals(Byte.class)) { 63 | // Special parsing of Bytes, so we can work around Java's crappy singed bytes. 64 | int intVal = withRadixPrefix(value, Integer::parseInt); 65 | if (intVal > 255 || intVal < 0) { 66 | throw new NumberFormatException(String.format("Input out of range input: %d", intVal)); 67 | } 68 | return (byte) intVal; 69 | } else if (clazz.equals(Short.class)) { 70 | return withRadixPrefix(value, Short::parseShort); 71 | } else if (clazz.equals(Integer.class)) { 72 | return withRadixPrefix(value, Integer::parseInt); 73 | } else if (clazz.equals(Long.class)) { 74 | return withRadixPrefix(value, Long::parseLong); 75 | } else if (clazz.equals(Float.class)) { 76 | return Float.parseFloat(value); 77 | } else if (clazz.equals(Double.class)) { 78 | return Double.parseDouble(value); 79 | } 80 | throw new FlagException.UnsupportedType(flag, clazz); 81 | } catch (NumberFormatException exception) { 82 | throw new FlagException.IllegalFormat(flag, value, exception); 83 | } 84 | } 85 | 86 | private static N withRadixPrefix(String value, BiFunction parseFunc) { 87 | if (value.startsWith("0x")) { 88 | return parseFunc.apply(value.substring(2), 16); 89 | } else { 90 | return parseFunc.apply(value, 10); 91 | } 92 | } 93 | } 94 | 95 | static class BooleanFlagField extends FlagField { 96 | 97 | private static final Set VALID_VALUES = ImmutableSet.of("true", "false"); 98 | 99 | BooleanFlagField(Boolean defaultValue) { 100 | super(defaultValue); 101 | } 102 | 103 | @Override 104 | protected void parseString(String value) { 105 | // Handle shorthand of Boolean flags. E.g. --my_flag is equal to --my_flag=true 106 | if (Strings.isNullOrEmpty(value)) { 107 | accept(true); 108 | } else { 109 | String lowerValue = value.toLowerCase(); 110 | if (!VALID_VALUES.contains(lowerValue)) { 111 | throw new FlagException.IllegalFormat( 112 | this, value, 113 | new IllegalArgumentException("Accepted values ['true', 'false'].")); 114 | } 115 | accept(lowerValue.equals("true")); 116 | } 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /flagz-java/src/main/java/org/flagz/ReflectionsCache.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import org.reflections.Reflections; 5 | import org.reflections.scanners.FieldAnnotationsScanner; 6 | import org.reflections.scanners.SubTypesScanner; 7 | import org.reflections.util.ClasspathHelper; 8 | import org.reflections.util.ConfigurationBuilder; 9 | 10 | import java.net.URL; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.Set; 15 | import java.util.stream.Collectors; 16 | 17 | /** Contains a cache of Reflection results to speed up multiple reflection operations. */ 18 | class ReflectionsCache { 19 | 20 | static final Map, Reflections> packagePrefixesToReflections = new HashMap<>(); 21 | 22 | static synchronized Reflections reflectionsForPrefixes(List prefixes) { 23 | if (packagePrefixesToReflections.containsKey(prefixes)) { 24 | return packagePrefixesToReflections.get(prefixes); 25 | } 26 | ConfigurationBuilder builder = new ConfigurationBuilder() 27 | .setUrls(urlsToReflect(prefixes)) 28 | .setScanners( 29 | new FieldAnnotationsScanner(), 30 | new SubTypesScanner()); 31 | Reflections reflections = new Reflections(builder); 32 | packagePrefixesToReflections.put(prefixes, reflections); 33 | return reflections; 34 | } 35 | 36 | /** 37 | * Get the appropriate set of URLs. 38 | * 39 | * This returns a set of URLs for a given set of package prefixes. You can use an empty string 40 | * to get everything but it can be slow for big apps. 41 | */ 42 | private static Set urlsToReflect(List packagePrefixes) { 43 | List prefixesWithFlags = ImmutableList.builder().addAll(packagePrefixes) 44 | .add("org.flagz").build(); 45 | return prefixesWithFlags 46 | .stream() 47 | .flatMap(prefix -> ClasspathHelper.forPackage(prefix).stream()) 48 | .collect(Collectors.toSet()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /flagz-java/src/main/java/org/flagz/Utils.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | import com.google.common.base.Strings; 4 | 5 | import java.util.Collection; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.stream.Collectors; 10 | 11 | /** 12 | * General static methods. 13 | */ 14 | class Utils { 15 | 16 | /** 17 | * Generate the list of Flagz and print to StdOut. 18 | */ 19 | static void printHelpPage(Collection> fields) { 20 | StringBuilder builder = new StringBuilder(); 21 | List> sorted = fields 22 | .stream() 23 | .sorted((f1, f2) -> f1.name().compareTo((f2.name()))) 24 | .collect(Collectors.toList()); 25 | builder.append("List of Flagz available in this program:\n"); 26 | builder.append("\n"); 27 | for (FlagField field : sorted) { 28 | String flag = flagDescriptorString(field); 29 | builder.append(String.format("%-35s\t%s\t[default='%s']\n", flag, field.help(), 30 | flagDefaultValue(field))); 31 | } 32 | System.out.println(builder.toString()); 33 | } 34 | 35 | static String flagDescriptorString(FlagField field) { 36 | return Strings.isNullOrEmpty(field.altName()) 37 | ? String.format("--%s", field.name()) 38 | : String.format("--%s [-%s]", field.name(), field.altName()); 39 | } 40 | 41 | /** 42 | * String of a default value of a field. For type safety. 43 | */ 44 | private static String flagDefaultValue(FlagField field) { 45 | return field.valueString(field.defaultValue()); 46 | } 47 | 48 | /** 49 | * Return a map that takes each string of the form 50 | * "--flagName=stringValue" 51 | * and creates a map (flagName) -> (stringValue). 52 | * 53 | * @param args strings of the form "--flagName=stringValue" 54 | */ 55 | static Map parseArgsToFieldMap(String[] args) { 56 | Map argsToFieldMap = new HashMap<>(); 57 | for (String arg : args) { 58 | String flagName = ""; 59 | String value = ""; 60 | 61 | if (!arg.startsWith("-")) { 62 | continue; // skip this string 63 | } else if (arg.startsWith("--")) { 64 | // parse out --flag=value 65 | int equalsIndex = arg.indexOf("="); 66 | flagName = arg.substring(2); 67 | if (equalsIndex >= 2) { 68 | flagName = arg.substring(2, equalsIndex); 69 | value = arg.substring(equalsIndex + 1); 70 | } 71 | } else if (arg.startsWith("-")) { 72 | // parse out -f=value 73 | int equalsIndex = arg.indexOf("="); 74 | flagName = arg.substring(1); 75 | if (equalsIndex >= 1) { 76 | flagName = arg.substring(1, equalsIndex); 77 | value = arg.substring(equalsIndex + 1); 78 | } 79 | } 80 | argsToFieldMap.put(flagName, value); 81 | } 82 | return argsToFieldMap; 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /flagz-java/src/main/java/org/flagz/Validators.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | import com.google.common.base.Strings; 4 | 5 | import java.util.function.Predicate; 6 | 7 | /** 8 | * Readily-available validators for {@link Flag#withValidator}. 9 | */ 10 | public class Validators { 11 | 12 | /** Validates that the given integer is greater than a value. */ 13 | public static Predicate greaterThan(Integer than) { 14 | return integer -> { 15 | if (integer > than) { 16 | return true; 17 | } else { 18 | throw new IllegalArgumentException(String.format("%d not greater than %d", integer, than)); 19 | } 20 | }; 21 | } 22 | 23 | /** Validates whether the given integer is within [lower, upper] range. */ 24 | public static Predicate inRange(Integer lower, Integer upper) { 25 | return integer -> { 26 | if (lower <= integer && integer <= upper) { 27 | return true; 28 | } else { 29 | throw new IllegalArgumentException( 30 | String.format("%d not in range [%d, %d]", integer, lower, upper)); 31 | } 32 | }; 33 | } 34 | 35 | /** Validates that the string is not empty. */ 36 | public static Predicate isNotEmpty() { 37 | return string -> { 38 | if (!Strings.isNullOrEmpty(string)) { 39 | return true; 40 | } else { 41 | throw new IllegalArgumentException("passed string is empty"); 42 | } 43 | }; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /flagz-java/src/test/java/org/flagz/BUILD: -------------------------------------------------------------------------------- 1 | java_test( 2 | name = "tests", 3 | srcs = glob(['*.java']), 4 | size = "small", 5 | deps = [ 6 | "//flagz-java/src/main/java/org/flagz", 7 | "//flagz-java/src/test/java/org/flagz/testclasses", 8 | "//third_party/guava", 9 | "//third_party/testing", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /flagz-java/src/test/java/org/flagz/BaseFlagTest.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.ImmutableSet; 5 | import org.junit.Test; 6 | 7 | import java.util.List; 8 | import java.util.Set; 9 | import java.util.function.Consumer; 10 | 11 | import static org.mockito.Mockito.*; 12 | 13 | /** 14 | * Tests for validator and listener functionality of {@link BaseFlag}. 15 | */ 16 | public class BaseFlagTest { 17 | 18 | public static final String[] EMPTY_ARGS = {}; 19 | public static final List EMPTY_PACKAGE_PREFIXES = ImmutableList.of(); 20 | 21 | @SuppressWarnings("unchecked") 22 | Consumer mockConsumer = mock(Consumer.class); 23 | 24 | @FlagInfo(name = "test_flag_int", help = "some int") 25 | public final Flag flagInt = Flagz.valueOf(200).withValidator(Validators.inRange(0, 100)); 26 | 27 | @FlagInfo(name = "test_flag_string", help = "some string") 28 | public final Flag flagMap = Flagz.valueOf("bar") 29 | .withValidator(Validators.isNotEmpty()) 30 | .withListener(mockConsumer); 31 | final Set SET_OF_THIS_TEST = ImmutableSet.of(this); 32 | 33 | @Test 34 | public void testNoActionOnDefultValues() { 35 | String[] args = {}; 36 | // This should not throw an exception, as default values are not subject to validators. 37 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 38 | verify(mockConsumer, never()).accept(anyString()); 39 | } 40 | 41 | @Test 42 | public void testRangeAcceptsCorrect() { 43 | String[] args = {}; 44 | FlagFieldRegistry registry = Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 45 | registry.setField("test_flag_int", "90"); 46 | } 47 | 48 | @Test(expected = FlagException.BadValue.class) 49 | public void testRangeRejectsBad() { 50 | String[] args = {}; 51 | FlagFieldRegistry registry = Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 52 | registry.setField("test_flag_int", "-1"); 53 | } 54 | 55 | @Test(expected = FlagException.BadValue.class) 56 | public void testNonEmptyRejectsBad() { 57 | String[] args = {}; 58 | FlagFieldRegistry registry = Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 59 | registry.setField("test_flag_string", ""); 60 | } 61 | 62 | @Test 63 | public void testListenerFiredOnCommandLine() { 64 | String[] args = {"--test_flag_string=moo"}; 65 | FlagFieldRegistry registry = Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 66 | verify(mockConsumer).accept("moo"); 67 | } 68 | 69 | @Test 70 | public void testListenerFiredOnDynamicChange() { 71 | String[] args = {}; 72 | FlagFieldRegistry registry = Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 73 | registry.setField("test_flag_string", "foo"); 74 | verify(mockConsumer).accept("foo"); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /flagz-java/src/test/java/org/flagz/ContainerFlagFieldTest.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.ImmutableMap; 5 | import com.google.common.collect.ImmutableSet; 6 | import org.flagz.testclasses.SomeEnum; 7 | import org.junit.Test; 8 | 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Set; 12 | 13 | import static org.junit.Assert.*; 14 | import static org.hamcrest.CoreMatchers.*; 15 | 16 | /** 17 | * Tests of flags that contain other values (Sets, Lists, Maps). 18 | * 19 | * **Note:** All flags are declared in-object, and the `this` object is passed to Flagz to find these fields. We are not 20 | * testing the ability of Flagz to resolve fields, just value parsing. 21 | */ 22 | public class ContainerFlagFieldTest { 23 | 24 | public static final String[] EMPTY_ARGS = {}; 25 | public static final List EMPTY_PACKAGE_PREFIXES = ImmutableList.of(); 26 | 27 | @FlagInfo(name = "test_list_int_flag", help = "") 28 | final Flag> integerListFlag = Flagz.valueOf(ImmutableList.of(1337, 9999)); 29 | 30 | @FlagInfo(name = "test_set_string_flag", help = "") 31 | final Flag> stringSetFlag = Flagz.valueOf(ImmutableSet.of("elite", "awesome")); 32 | 33 | @FlagInfo(name = "test_list_enum_flag", help = "") 34 | final Flag> enumListFlag = Flagz.valueOf(ImmutableList.of(SomeEnum.NORMAL, SomeEnum.CHILLED)); 35 | 36 | @FlagInfo(name = "test_map_enum_flag", help = "") 37 | final Flag> enumMapFlag = Flagz.valueOf(ImmutableMap.of(SomeEnum.NORMAL, 1337)); 38 | 39 | @FlagInfo(name = "test_map_string_flag", help = "") 40 | final Flag> stringMapFlag = Flagz.valueOf(ImmutableMap.of("key1", "value1", "key2", "value2")); 41 | 42 | 43 | final Set SET_OF_THIS_TEST = ImmutableSet.of(this); 44 | 45 | 46 | @Test 47 | public void testSmokeNoCrosstalk() { 48 | String[] args = {"--test_list_int_flag=123,987", "--test_map_string_flag=key4:value4"}; 49 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 50 | assertThat(integerListFlag.get(), equalTo(ImmutableList.of(123, 987))); 51 | assertThat(stringMapFlag.get(), equalTo(ImmutableMap.of("key4", "value4"))); 52 | } 53 | 54 | @Test 55 | public void testCollections_Default() { 56 | Flagz.parse(EMPTY_ARGS, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 57 | assertThat(integerListFlag.get(), equalTo(ImmutableList.of(1337, 9999))); 58 | assertThat(enumListFlag.get(), equalTo(ImmutableList.of(SomeEnum.NORMAL, SomeEnum.CHILLED))); 59 | assertThat(stringSetFlag.get(), equalTo(ImmutableSet.of("elite", "awesome"))); 60 | } 61 | 62 | @Test 63 | public void testCollections_Set() { 64 | String[] args = {"--test_list_int_flag=123,987", "--test_list_enum_flag=WACKY,CRAZY", 65 | "--test_set_string_flag=foo,bar"}; 66 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 67 | assertThat(integerListFlag.get(), equalTo(ImmutableList.of(123, 987))); 68 | assertThat(enumListFlag.get(), equalTo(ImmutableList.of(SomeEnum.WACKY, SomeEnum.CRAZY))); 69 | assertThat(stringSetFlag.get(), equalTo(ImmutableSet.of("bar", "foo"))); 70 | } 71 | 72 | @Test(expected = FlagException.IllegalFormat.class) 73 | public void testCollections_Bad_Separator() { 74 | String[] args = {"--test_list_int_flag=123:987", "--test_list_enum_flag=WACKY:CRAZY",}; 75 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 76 | } 77 | 78 | @Test(expected = FlagException.IllegalFormat.class) 79 | public void testCollections_Bad_Integer() { 80 | String[] args = {"--test_list_int_flag=1e1,231"}; 81 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 82 | } 83 | 84 | @Test(expected = FlagException.IllegalFormat.class) 85 | public void testCollections_Bad_Enum() { 86 | String[] args = {"--test_list_enum_flag=RUBBISH,BIN"}; 87 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 88 | } 89 | 90 | @Test 91 | public void testCollections_StringValue() { 92 | // This is not a public interface, but is important that this representation is solid. 93 | Flagz.parse(EMPTY_ARGS, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 94 | FlagField> integerListReal = (FlagField>) integerListFlag; 95 | FlagField> enumListReal = (FlagField>) enumListFlag; 96 | FlagField> stringSetReal = (FlagField>) stringSetFlag; 97 | 98 | assertThat(integerListReal.valueString(integerListReal.get()), equalTo("1337,9999")); 99 | assertThat(enumListReal.valueString(enumListReal.get()), equalTo("NORMAL,CHILLED")); 100 | assertThat(stringSetReal.valueString(stringSetReal.get()), equalTo("elite,awesome")); 101 | 102 | } 103 | 104 | @Test 105 | public void testMaps_Default() { 106 | Flagz.parse(EMPTY_ARGS, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 107 | assertThat(integerListFlag.get(), equalTo(ImmutableList.of(1337, 9999))); 108 | assertThat(enumMapFlag.get(), equalTo(ImmutableMap.of(SomeEnum.NORMAL, 1337))); 109 | assertThat(stringMapFlag.get(), equalTo(ImmutableMap.of("key1", "value1", "key2", "value2"))); 110 | } 111 | 112 | @Test 113 | public void testMaps_Set() { 114 | String[] args = {"--test_map_enum_flag=WACKY:123,CRAZY:1000", "--test_map_string_flag=key2:value2,key3:value3"}; 115 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 116 | assertThat(enumMapFlag.get(), equalTo(ImmutableMap.of(SomeEnum.WACKY, 123, SomeEnum.CRAZY, 1000))); 117 | assertThat(stringMapFlag.get(), equalTo(ImmutableMap.of("key3", "value3", "key2", "value2"))); 118 | } 119 | 120 | @Test(expected = FlagException.IllegalFormat.class) 121 | public void testMaps_Bad_Separator() { 122 | String[] args = {"--test_map_enum_flag=WACKY=123,CRAZY=1000"}; 123 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 124 | } 125 | 126 | @Test(expected = FlagException.IllegalFormat.class) 127 | public void testMaps_Bad_Item() { 128 | String[] args = {"--test_map_enum_flag=RUBBISH:123,CRAZY:1000"}; 129 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 130 | } 131 | 132 | @Test 133 | public void testMaps_Set_Empty() { 134 | String[] args = {"--test_map_enum_flag=", "--test_map_string_flag="}; 135 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 136 | assertThat(enumMapFlag.get(), equalTo(ImmutableMap.of())); 137 | assertThat(stringMapFlag.get(), equalTo(ImmutableMap.of())); 138 | } 139 | 140 | @Test 141 | public void testMaps_StringValue() { 142 | // This is not a public interface, but is important that this representation is solid. 143 | Flagz.parse(EMPTY_ARGS, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 144 | FlagField> enumMapReal = (FlagField>) enumMapFlag; 145 | FlagField> stringMapReal = (FlagField>) stringMapFlag; 146 | 147 | assertThat(enumMapReal.valueString(enumMapReal.get()), equalTo("NORMAL:1337")); 148 | assertThat(stringMapReal.valueString(stringMapReal.get()), equalTo("key1:value1,key2:value2")); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /flagz-java/src/test/java/org/flagz/FlagFieldScannerTest.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.ImmutableSet; 5 | import org.flagz.testclasses.StaticFlags; 6 | import org.junit.Test; 7 | 8 | import java.util.List; 9 | import java.util.Set; 10 | 11 | import static org.hamcrest.CoreMatchers.is; 12 | import static org.junit.Assert.assertThat; 13 | 14 | /** 15 | * Tests for scanning of {@link Flag} annotated {@link FlagField} through reflection. 16 | * 17 | * This uses {@link StaticFlags} class that defines `static` fields. These should be found through normal reflection. 18 | * The `Container*` objects in this class are used to find non-static fields in `Singleton` objects. 19 | */ 20 | public class FlagFieldScannerTest { 21 | 22 | public static final String[] EMPTY_ARGS = {}; 23 | public static final List EMPTY_PACKAGE_PREFIXES = ImmutableList.of(); 24 | 25 | static class ContainerOne { 26 | @FlagInfo(name = "test_flag_obj_public", help = "") 27 | public final Flag testPublicFlag = Flagz.valueOf(1337L); 28 | 29 | @FlagInfo(name = "test_flag_obj_private", help = "") 30 | private final Flag testPrivateFlag = Flagz.valueOf(4000L); 31 | 32 | public Flag testPrivateFlagAccessor() { 33 | return testPrivateFlag; 34 | } 35 | } 36 | 37 | ; 38 | 39 | static class ContainerTwo { 40 | @FlagInfo(name = "test_flag_obj_public_two", help = "") 41 | public final Flag testPublicFlag = Flagz.valueOf(999L); 42 | } 43 | 44 | ; 45 | 46 | static class ContainerClashingWithStatic { 47 | @FlagInfo(name = "test_flag_public_static", help = "") 48 | public final Flag testStaticClash = Flagz.valueOf(999L); 49 | } 50 | 51 | ; 52 | 53 | private ContainerOne one = new ContainerOne(); 54 | private ContainerTwo two = new ContainerTwo(); 55 | private ContainerClashingWithStatic clashing = new ContainerClashingWithStatic(); 56 | private Set goodContainers = ImmutableSet.of(one, two); 57 | private Set clashingContainers = ImmutableSet.of(one, two, clashing); 58 | 59 | 60 | @Test 61 | public void testGeneric_FieldNameResolution() { 62 | String[] args = {"--testFieldNameFlag=101010"}; 63 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, goodContainers); 64 | assertThat(StaticFlags.testFieldNameFlag.get(), is(101010)); 65 | } 66 | 67 | @Test 68 | public void testGeneric_AltNameResolution() { 69 | String[] args = {"-t_alt=scoobydoo"}; 70 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, goodContainers); 71 | assertThat(StaticFlags.testAltNameFlag.get(), is("scoobydoo")); 72 | } 73 | 74 | @Test 75 | public void testGeneric_AltNameResolution_WithFullName() { 76 | String[] args = {"-test_alt_name=scoobydoo"}; 77 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, goodContainers); 78 | assertThat(StaticFlags.testAltNameFlag.get(), is("scoobydoo")); 79 | } 80 | 81 | @Test 82 | public void testGeneric_FullNameResolution() { 83 | String[] args = {"--test_flag_full_name=1337"}; 84 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, goodContainers); 85 | assertThat(StaticFlags.testFullNameFlag.get(), is(1337L)); 86 | } 87 | 88 | @Test 89 | public void testStatic_PublicVisible() { 90 | String[] args = {"--test_flag_public_static=7777"}; 91 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, goodContainers); 92 | assertThat(StaticFlags.testPublicStaticFlag.get(), is(7777L)); 93 | } 94 | 95 | @Test 96 | public void testStatic_PrivateVisible() { 97 | String[] args = {"--test_flag_private_static=5555"}; 98 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, goodContainers); 99 | assertThat(StaticFlags.testPrivateStaticFlagAccessor().get(), is(5555L)); 100 | } 101 | 102 | @Test(expected = FlagException.UnknownFlag.class) 103 | public void testStatic_NonStaticInvisible() { 104 | String[] args = {"--test_flag_non_static=5555"}; 105 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, goodContainers); 106 | } 107 | 108 | @Test 109 | public void testSingleton_PublicVisible() { 110 | String[] args = {"--test_flag_obj_public=7777"}; 111 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, goodContainers); 112 | assertThat(one.testPublicFlag.get(), is(7777L)); 113 | } 114 | 115 | @Test 116 | public void testSingleton_PrivateVisible() { 117 | String[] args = {"--test_flag_obj_private=44444"}; 118 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, goodContainers); 119 | assertThat(one.testPrivateFlagAccessor().get(), is(44444L)); 120 | } 121 | 122 | @Test 123 | public void testSingleton_ManySingletons() { 124 | String[] args = {"--test_flag_obj_public=7777", "--test_flag_obj_public_two=9999"}; 125 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, goodContainers); 126 | assertThat(one.testPublicFlag.get(), is(7777L)); 127 | assertThat(two.testPublicFlag.get(), is(9999L)); 128 | } 129 | 130 | @Test(expected = FlagException.NameConflict.class) 131 | public void testNameClashThrowsException() { 132 | String[] args = {"--test_flag_obj_public=7777", "--test_flag_obj_public_two=9999"}; 133 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, clashingContainers); 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /flagz-java/src/test/java/org/flagz/FlagPropertySyncerTest.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.ImmutableMap; 5 | import com.google.common.collect.ImmutableSet; 6 | import org.junit.After; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.Properties; 13 | import java.util.Set; 14 | 15 | import static org.hamcrest.CoreMatchers.*; 16 | import static org.junit.Assert.assertThat; 17 | 18 | /** 19 | * Tests for {@link Flag}s annotated with {@link FlagProperty}. 20 | * 21 | * **Note:** All flags are declared in-object, and the `this` object is passed to Flagz to find these fields. We are not 22 | * testing the ability of Flagz to resolve fields, just value parsing. 23 | */ 24 | public class FlagPropertySyncerTest { 25 | 26 | public static final String[] EMPTY_ARGS = {}; 27 | public static final List EMPTY_PACKAGE_PREFIXES = ImmutableList.of(); 28 | static final float PRECISION = 0.001f; 29 | static Properties systemProps; 30 | 31 | @FlagInfo(name = "paramflag_int", help = "some int") 32 | @FlagProperty(value = "org.flagz.testing.some_int", sync = false) 33 | final Flag flagInt = Flagz.valueOf(400); 34 | 35 | @FlagInfo(name = "paramflag_map_int", help = "some map") 36 | @FlagProperty(value = "org.flagz.testing.some_map", sync = true) 37 | final Flag> flagMap = Flagz.valueOf(ImmutableMap.of("foo", 100, "boo", 200)); 38 | 39 | final Set SET_OF_THIS_TEST = ImmutableSet.of(this); 40 | 41 | 42 | @Before 43 | public void setProperties() { 44 | systemProps = System.getProperties(); 45 | System.setProperties(new Properties(systemProps)); 46 | } 47 | 48 | @After 49 | public void resetProperties() { 50 | System.setProperties(systemProps); 51 | } 52 | 53 | @Test 54 | public void testOneWay_NoPropertySetWithoutExisting() { 55 | String[] args = {}; 56 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 57 | assertThat(flagMap.get(), is(flagMap.defaultValue())); 58 | assertThat(System.getProperty("org.flagz.testing.some_int"), nullValue()); 59 | } 60 | 61 | @Test 62 | public void testOneWay_ValueFromProperty() { 63 | System.setProperty("org.flagz.testing.some_int", "777"); 64 | String[] args = {}; 65 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 66 | assertThat(flagInt.get(), is(777)); 67 | } 68 | 69 | @Test 70 | public void testOneWay_CmdLnLeavesProperty() { 71 | System.setProperty("org.flagz.testing.some_int", "777"); 72 | String[] args = {"--paramflag_int=1337"}; 73 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 74 | assertThat(flagInt.get(), is(1337)); 75 | assertThat(System.getProperty("org.flagz.testing.some_int"), is("777")); 76 | } 77 | 78 | @Test 79 | public void testOneWay_DynamicLeavesProperty() { 80 | System.setProperty("org.flagz.testing.some_int", "777"); 81 | String[] args = {}; 82 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 83 | assertThat(flagInt.get(), is(777)); 84 | flagInt.accept(1337); 85 | assertThat(System.getProperty("org.flagz.testing.some_int"), is("777")); 86 | assertThat(flagInt.get(), is(1337)); 87 | } 88 | 89 | @Test 90 | public void testBidirectional_DefaultUpdatesProperty() { 91 | String[] args = {}; 92 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 93 | assertThat(flagMap.get(), is(flagMap.defaultValue())); 94 | assertThat(System.getProperty("org.flagz.testing.some_map"), is("foo:100,boo:200")); 95 | } 96 | 97 | @Test 98 | public void testBidirectional_ValueFromProperty() { 99 | System.setProperty("org.flagz.testing.some_map", "car:234,tar:789"); 100 | String[] args = {}; 101 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 102 | assertThat(flagMap.get(), equalTo(ImmutableMap.of("car", 234, "tar", 789))); 103 | } 104 | 105 | @Test 106 | public void testBidirectional_CmdlnUpdatesProperty() { 107 | System.setProperty("org.flagz.testing.some_map", "car:234,tar:789"); 108 | String[] args = {"--paramflag_map_int=zoo:700,moo:800"}; 109 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 110 | assertThat(flagMap.get(), equalTo(ImmutableMap.of("zoo", 700, "moo", 800))); 111 | assertThat(System.getProperty("org.flagz.testing.some_map"), is("zoo:700,moo:800")); 112 | } 113 | 114 | @Test 115 | public void testBidirectional_DynamicUpdatesProperty() { 116 | System.setProperty("org.flagz.testing.some_map", "car:234,tar:789"); 117 | String[] args = {}; 118 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 119 | flagMap.accept(ImmutableMap.of("zoo", 700, "moo", 800)); 120 | assertThat(System.getProperty("org.flagz.testing.some_map"), is("zoo:700,moo:800")); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /flagz-java/src/test/java/org/flagz/PrimitiveFlagFieldTest.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.ImmutableSet; 5 | import org.flagz.testclasses.SomeEnum; 6 | import org.junit.Test; 7 | 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | import java.util.Set; 13 | 14 | import static org.hamcrest.CoreMatchers.is; 15 | import static org.junit.Assert.assertThat; 16 | 17 | /** 18 | * Tests of flags of primitive types. 19 | * 20 | * **Note:** All flags are declared in-object, and the `this` object is passed to Flagz to find these fields. We are not 21 | * testing the ability of Flagz to resolve fields, just value parsing. 22 | */ 23 | public class PrimitiveFlagFieldTest { 24 | 25 | public static final String[] EMPTY_ARGS = {}; 26 | public static final List EMPTY_PACKAGE_PREFIXES = ImmutableList.of(); 27 | 28 | @Retention(RetentionPolicy.RUNTIME) 29 | public @interface CustomAnnotation {} 30 | 31 | @Retention(RetentionPolicy.RUNTIME) 32 | public @interface OtherCustomAnnotation {} 33 | 34 | @FlagInfo(name = "test_int_flag", help = "") 35 | final Flag integerFlag = Flagz.valueOf(1337); 36 | 37 | @FlagInfo(name = "test_long_flag", help = "") 38 | final Flag longFlag = Flagz.valueOf(13371337L); 39 | 40 | @FlagInfo(name = "test_double_flag", help = "") 41 | final Flag doubleFlag = Flagz.valueOf(13.37); 42 | 43 | @FlagInfo(name = "test_float_flag", help = "") 44 | final Flag floatFlag = Flagz.valueOf((float) 13.37); 45 | 46 | @FlagInfo(name = "test_bool_flag", help = "") 47 | final Flag booleanFlag = Flagz.valueOf(false); 48 | 49 | @FlagInfo(name = "test_string_flag", help = "") 50 | final Flag stringFlag = Flagz.valueOf("elite"); 51 | 52 | @FlagInfo(name = "test_short_flag", help = "") 53 | final Flag shortFlag = Flagz.valueOf((short) 13); 54 | 55 | @FlagInfo(name = "test_byte_flag", help = "") 56 | final Flag byteFlag = Flagz.valueOf((byte) 0xDD); 57 | 58 | @FlagInfo(name = "test_enum_flag", help = "") 59 | final Flag enumFlag = Flagz.valueOf(SomeEnum.NORMAL); 60 | 61 | @CustomAnnotation 62 | @FlagInfo(name = "test_annotated_flag", help = "") 63 | final Flag annotatedFlag = Flagz.valueOf(""); 64 | 65 | final Set SET_OF_THIS_TEST = ImmutableSet.of(this); 66 | 67 | @Test 68 | public void testAnnotatedFlag() { 69 | String[] args = {}; 70 | FlagFieldRegistry registry = Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 71 | assertThat(registry.getFieldsAnnotatedWith(CustomAnnotation.class).size(), is(1)); 72 | assertThat(registry.getFieldsAnnotatedWith(OtherCustomAnnotation.class).isEmpty(), is(true)); 73 | } 74 | 75 | @Test 76 | public void testSmokeNoCrosstalk() { 77 | String[] args = {"--test_int_flag=100", "--test_double_flag=0.33", "--test_long_flag=13337"}; 78 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 79 | assertThat(integerFlag.get(), is(100)); 80 | assertThat(doubleFlag.get(), is(0.33d)); 81 | assertThat(longFlag.get(), is(13337L)); 82 | } 83 | 84 | @Test 85 | public void testInteger_Default() { 86 | Flagz.parse(EMPTY_ARGS, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 87 | assertThat(integerFlag.get(), is(1337)); 88 | } 89 | 90 | @Test 91 | public void testInteger_Set() { 92 | String[] args = {"--test_int_flag=100"}; 93 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 94 | assertThat(integerFlag.get(), is(100)); 95 | } 96 | 97 | @Test 98 | public void testInteger_Hex() { 99 | String[] args = {"--test_int_flag=0x1000"}; 100 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 101 | assertThat(integerFlag.get(), is(0x1000)); 102 | } 103 | 104 | @Test 105 | public void testLong_Default() { 106 | Flagz.parse(EMPTY_ARGS, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 107 | assertThat(longFlag.get(), is(13371337L)); 108 | } 109 | 110 | @Test(expected = FlagException.IllegalFormat.class) 111 | public void testInteger_Bad() { 112 | String[] args = {"--test_int_flag=99.9"}; 113 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 114 | } 115 | 116 | @Test 117 | public void testLong_Set() { 118 | String[] args = {"--test_long_flag=99999999999"}; 119 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 120 | assertThat(longFlag.get(), is(99999999999L)); 121 | } 122 | 123 | @Test 124 | public void testDouble_Default() { 125 | Flagz.parse(EMPTY_ARGS, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 126 | assertThat(doubleFlag.get(), is(13.37)); 127 | } 128 | 129 | @Test(expected = FlagException.IllegalFormat.class) 130 | public void testDouble_Bad() { 131 | String[] args = {"--test_double_flag=99,9"}; 132 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 133 | } 134 | 135 | @Test 136 | public void testDouble_Set() { 137 | String[] args = {"--test_double_flag=9.999"}; 138 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 139 | assertThat(doubleFlag.get(), is(9.999d)); 140 | } 141 | 142 | @Test 143 | public void testFloat_Default() { 144 | Flagz.parse(EMPTY_ARGS, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 145 | assertThat(floatFlag.get(), is(13.37f)); 146 | } 147 | 148 | @Test 149 | public void testFloat_Set() { 150 | String[] args = {"--test_float_flag=9.999"}; 151 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 152 | assertThat(floatFlag.get(), is(9.999f)); 153 | } 154 | 155 | @Test 156 | public void testBoolean_Default() { 157 | Flagz.parse(EMPTY_ARGS, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 158 | assertThat(booleanFlag.get(), is(false)); 159 | } 160 | 161 | @Test 162 | public void testBoolean_Set() { 163 | String[] args = {"--test_bool_flag=true"}; 164 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 165 | assertThat(booleanFlag.get(), is(true)); 166 | } 167 | 168 | @Test 169 | public void testBoolean_Set_Short() { 170 | String[] args = {"--test_bool_flag"}; 171 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 172 | assertThat(booleanFlag.get(), is(true)); 173 | } 174 | 175 | @Test() 176 | public void testBoolean_Set_Insensitive() { 177 | String[] args = {"--test_bool_flag=False"}; 178 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 179 | assertThat(booleanFlag.get(), is(false)); 180 | } 181 | 182 | @Test(expected = FlagException.IllegalFormat.class) 183 | public void testBoolean_Bad() { 184 | String[] args = {"--test_bool_flag=rubbish"}; 185 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 186 | } 187 | 188 | @Test 189 | public void testString_Default() { 190 | Flagz.parse(EMPTY_ARGS, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 191 | assertThat(stringFlag.get(), is("elite")); 192 | } 193 | 194 | @Test 195 | public void testString_Set() { 196 | String[] args = {"--test_string_flag=my_value"}; 197 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 198 | assertThat(stringFlag.get(), is("my_value")); 199 | } 200 | 201 | @Test 202 | public void testString_Set_Empty() { 203 | String[] args = {"--test_string_flag="}; 204 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 205 | assertThat(stringFlag.get(), is("")); 206 | } 207 | 208 | @Test 209 | public void testString_Set_WithEquals() { 210 | String[] args = {"--test_string_flag=something=good"}; 211 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 212 | assertThat(stringFlag.get(), is("something=good")); 213 | } 214 | 215 | 216 | @Test 217 | public void testShort_Default() { 218 | Flagz.parse(EMPTY_ARGS, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 219 | assertThat(shortFlag.get(), is((short) 13)); 220 | } 221 | 222 | @Test 223 | public void testShort_Set() { 224 | String[] args = {"--test_short_flag=1337"}; 225 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 226 | assertThat(shortFlag.get(), is((short) 1337)); 227 | } 228 | 229 | @Test(expected = FlagException.IllegalFormat.class) 230 | public void testShort_Bad() { 231 | String[] args = {"--test_short_flag=13371337"}; 232 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 233 | } 234 | 235 | @Test 236 | public void testByte_Default() { 237 | Flagz.parse(EMPTY_ARGS, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 238 | assertThat(byteFlag.get(), is((byte) 0xDD)); 239 | } 240 | 241 | @Test 242 | public void testByte_Set() { 243 | String[] args = {"--test_byte_flag=0xAF"}; 244 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 245 | assertThat(byteFlag.get(), is((byte) 0xAF)); 246 | } 247 | 248 | @Test(expected = FlagException.IllegalFormat.class) 249 | public void testByte_Bad() { 250 | String[] args = {"--test_byte_flag=DEAD"}; // Bigger than range. 251 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 252 | } 253 | 254 | @Test 255 | public void testEnum_Default() { 256 | Flagz.parse(EMPTY_ARGS, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 257 | assertThat(enumFlag.get(), is(SomeEnum.NORMAL)); 258 | } 259 | 260 | @Test 261 | public void testEnum_Set() { 262 | String[] args = {"--test_enum_flag=WACKY"}; 263 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 264 | assertThat(enumFlag.get(), is(SomeEnum.WACKY)); 265 | } 266 | 267 | @Test(expected = FlagException.IllegalFormat.class) 268 | public void testEnum_Bad() { 269 | String[] args = {"--test_enum_flag=RUBBISH"}; 270 | Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, SET_OF_THIS_TEST); 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /flagz-java/src/test/java/org/flagz/UnusedFlagTest.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.ImmutableSet; 5 | import org.junit.Test; 6 | 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.Set; 10 | 11 | import static org.hamcrest.CoreMatchers.is; 12 | import static org.junit.Assert.assertThat; 13 | 14 | /** 15 | * Tests for validator and listener functionality of {@link BaseFlag}. 16 | */ 17 | public class UnusedFlagTest { 18 | 19 | public static final List EMPTY_PACKAGE_PREFIXES = ImmutableList.of(); 20 | private UsedFlags usedFlags = new UsedFlags(); 21 | private UnusedFlags unusedFlags = new UnusedFlags(); 22 | private Set onlyUsed = ImmutableSet.of(usedFlags); 23 | private Set onlyUnused = ImmutableSet.of(unusedFlags); 24 | private Set mixed = ImmutableSet.of(usedFlags, unusedFlags); 25 | 26 | @Test 27 | public void testNoParsingAllFlagsAvailable() { 28 | String[] args = {}; 29 | FlagFieldRegistry registry = Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, mixed); 30 | Set> unused = registry.unusedFlags(Utils.parseArgsToFieldMap(args)); 31 | assertThat(unused.isEmpty(), is(true)); 32 | } 33 | 34 | @Test 35 | public void testNoParsingUnusedFlagsAvailable() { 36 | String[] args = {}; 37 | FlagFieldRegistry registry = Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, onlyUnused); 38 | Set> unused = registry.unusedFlags(Utils.parseArgsToFieldMap(args)); 39 | assertThat(unused.isEmpty(), is(true)); 40 | } 41 | 42 | @Test 43 | public void testNoParsingUsedFlagsAvailable() { 44 | String[] args = {}; 45 | FlagFieldRegistry registry = Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, onlyUsed); 46 | Set> unused = registry.unusedFlags(Utils.parseArgsToFieldMap(args)); 47 | assertThat(unused.isEmpty(), is(true)); 48 | } 49 | 50 | @Test 51 | public void testParsingOnlyUsedFlags() { 52 | String[] args = {"--test_flag_int=101010", "--test_flag_string=hi", "--test_flag_float=200.0"}; 53 | FlagFieldRegistry registry = Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, onlyUsed); 54 | Set> unused = registry.unusedFlags(Utils.parseArgsToFieldMap(args)); 55 | assertThat(unused.isEmpty(), is(true)); 56 | } 57 | 58 | @Test 59 | public void testParsingOnlyUnusedFlags() { 60 | String[] args = {"--test_flag_other_int=101010", "--test_flag_other_string=hi", "--test_flag_other_float=200.0"}; 61 | FlagFieldRegistry registry = Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, onlyUnused); 62 | Set> unused = registry.unusedFlags(Utils.parseArgsToFieldMap(args)); 63 | assertThat(unused.size(), is(3)); 64 | } 65 | 66 | @Test 67 | public void testParsingMixedFlags() { 68 | String[] args = {"--test_flag_int=101010", "--test_flag_string=hi", "--test_flag_float=200.0", 69 | "--test_flag_other_int=101010", "--test_flag_other_string=hi", "--test_flag_other_float=200.0"}; 70 | FlagFieldRegistry registry = Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, mixed); 71 | Set> unused = registry.unusedFlags(Utils.parseArgsToFieldMap(args)); 72 | assertThat(unused.size(), is(3)); 73 | } 74 | 75 | @Test 76 | public void testCorrectStringProduced() { 77 | String[] args = {"--test_flag_int=101010", "--test_flag_string=hi", "--test_flag_float=200.0", 78 | "--test_flag_other_int=101010", "--test_flag_other_string=hi", "--test_flag_other_float=200.0"}; 79 | String[] expectedMessage = 80 | {"The following flag is unused - it will have no effect on the system: --test_flag_other_int [-tfoi]", 81 | "The following flag is unused - it will have no effect on the system: --test_flag_other_string", 82 | "The following flag is unused - it will have no effect on the system: --test_flag_other_float [-tfof]"}; 83 | FlagFieldRegistry registry = Flagz.parse(args, EMPTY_PACKAGE_PREFIXES, mixed); 84 | Set> unused = registry.unusedFlags(Utils.parseArgsToFieldMap(args)); 85 | String[] generatedMessage = FlagFieldRegistry.unusedFlagsMessages(unused); 86 | Arrays.sort(expectedMessage); 87 | Arrays.sort(generatedMessage); 88 | assertThat(generatedMessage, is(expectedMessage)); 89 | } 90 | 91 | static class UsedFlags { 92 | @FlagInfo(name = "test_flag_int", help = "some int") 93 | public final Flag flagInt = Flagz.valueOf(200); 94 | 95 | @FlagInfo(name = "test_flag_string", help = "some string") 96 | public final Flag flagStr = Flagz.valueOf("test"); 97 | 98 | @FlagInfo(name = "test_flag_float", help = "some float") 99 | public final Flag flagFloat = Flagz.valueOf(200.0f); 100 | } 101 | 102 | static class UnusedFlags { 103 | @FlagzUnused 104 | @FlagInfo(name = "test_flag_other_int", altName = "tfoi", help = "some other int") 105 | public final Flag flagOtherInt = Flagz.valueOf(100); 106 | 107 | @FlagzUnused 108 | @FlagInfo(name = "test_flag_other_string", help = "some other string") 109 | public final Flag flagOtherStr = Flagz.valueOf("other test"); 110 | 111 | @FlagzUnused 112 | @FlagInfo(name = "test_flag_other_float", altName = "tfof", help = "some other float") 113 | public final Flag flagOtherFloat = Flagz.valueOf(100.0f); 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /flagz-java/src/test/java/org/flagz/testclasses/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_library( 4 | name = "testclasses", 5 | srcs = glob(['*.java']), 6 | deps = [ 7 | "//flagz-java/src/main/java/org/flagz", 8 | "//third_party/guava", 9 | "//third_party/testing", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /flagz-java/src/test/java/org/flagz/testclasses/SomeEnum.java: -------------------------------------------------------------------------------- 1 | package org.flagz.testclasses; 2 | 3 | /** 4 | * Test enum. Used in testing of Flagz. 5 | */ 6 | public enum SomeEnum { 7 | CRAZY, 8 | WACKY, 9 | NORMAL, 10 | CHILLED, 11 | } 12 | -------------------------------------------------------------------------------- /flagz-java/src/test/java/org/flagz/testclasses/StaticFlags.java: -------------------------------------------------------------------------------- 1 | package org.flagz.testclasses; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import org.flagz.Flag; 5 | import org.flagz.FlagInfo; 6 | import org.flagz.Flagz; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * Set of flags that are useful for testing of {@link Flagz}. 12 | */ 13 | public class StaticFlags { 14 | 15 | @FlagInfo(help = "for testing of resolution with no name argument") 16 | public static final Flag testFieldNameFlag = Flagz.valueOf(0); 17 | 18 | @FlagInfo(name = "test_alt_name", help = "for testing of resolution with alt name", altName = "t_alt") 19 | public static final Flag testAltNameFlag = Flagz.valueOf("my_alt"); 20 | 21 | @FlagInfo(name = "test_flag_full_name", help = "for testing of resolution with full name") 22 | public static final Flag testFullNameFlag = Flagz.valueOf(0L); 23 | 24 | @FlagInfo(name = "test_flag_private_static", help = "for testing of resolution of private static fields") 25 | private static final Flag testPrivateStaticFlag = Flagz.valueOf(0L); 26 | 27 | @FlagInfo(name = "test_flag_public_static", help = "for testing of resolution of public static fields") 28 | public static final Flag testPublicStaticFlag = Flagz.valueOf(0L); 29 | 30 | @FlagInfo(name = "test_flag_non_static", help = "for validating that we won't see non static fields") 31 | private final Flag testInvisibleNonStaticFlag = Flagz.valueOf(0L); 32 | 33 | 34 | public static Flag testPrivateStaticFlagAccessor() { 35 | return testPrivateStaticFlag; 36 | } 37 | 38 | @SuppressWarnings("unchecked") 39 | public static void resetToDefaults() { 40 | List> list = ImmutableList.of( 41 | testFieldNameFlag, testAltNameFlag, testFullNameFlag, testPrivateStaticFlag, testPublicStaticFlag); 42 | for (Flag f : list) { 43 | f.accept(f.defaultValue()); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /flagz-scala/src/main/scala/org/flagz/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | load("@io_bazel_rules_scala//scala:scala.bzl", "scala_library", "scala_binary", "scala_test") 4 | 5 | 6 | scala_library( 7 | name = "flagz", 8 | srcs = glob(["*.scala"]) + glob(["*.java"]), 9 | deps = [ 10 | "//flagz-java/src/main/java/org/flagz", 11 | "//third_party/etcd4j", 12 | "//third_party/guava", 13 | "//third_party/slf4j:api", 14 | ], 15 | ) -------------------------------------------------------------------------------- /flagz-scala/src/main/scala/org/flagz/FlagContainer.scala: -------------------------------------------------------------------------------- 1 | package org.flagz 2 | 3 | /** Trait to be used by all Scala objects that are to contain {@link Flag} fields. */ 4 | trait FlagContainer { 5 | } 6 | -------------------------------------------------------------------------------- /flagz-scala/src/main/scala/org/flagz/ScalaCollectionsFlagField.scala: -------------------------------------------------------------------------------- 1 | package org.flagz 2 | 3 | import scala.reflect.ClassTag 4 | 5 | /** FlagField that supports Scala {@link Set}. */ 6 | class SetFlagField[E](defaultValue: Set[E])(implicit tag: ClassTag[E]) 7 | extends ContainerFlagField[Set[E]](defaultValue, () => Set.empty[E]) { 8 | val elementClazz = tag.runtimeClass.asInstanceOf[Class[E]] 9 | 10 | override protected def addItem(existing: Set[E], value: String): Set[E] = { 11 | existing + ContainerFlagField.itemFromString(value, elementClazz, this) 12 | } 13 | 14 | override def valueString(value: Set[E]): String = { 15 | value.mkString(",") 16 | } 17 | } 18 | 19 | /** FlagField that supports Scala {@link List}. */ 20 | class ListFlagField[E](defaultValue: List[E])(implicit tag: ClassTag[E]) 21 | extends ContainerFlagField[List[E]](defaultValue, () => Nil) { 22 | val elementClazz = tag.runtimeClass.asInstanceOf[Class[E]] 23 | 24 | override protected def addItem(existing: List[E], value: String): List[E] = { 25 | existing ::: List(ContainerFlagField.itemFromString(value, elementClazz, this)) 26 | } 27 | 28 | override def valueString(value: List[E]): String = { 29 | value.mkString(",") 30 | } 31 | } 32 | 33 | /** FlagField that supports Scala {@link Map}. */ 34 | class MapFlagField[K, V](defaultValue: Map[K, V])(implicit keyTag: ClassTag[K], valueTag: ClassTag[V]) 35 | extends ContainerFlagField[Map[K, V]](defaultValue, () => Map.empty[K, V]) { 36 | val keyClazz = keyTag.runtimeClass.asInstanceOf[Class[K]] 37 | val valueClazz = valueTag.runtimeClass.asInstanceOf[Class[V]] 38 | 39 | override protected def addItem(existing: Map[K, V], itemString: String): Map[K, V] = { 40 | val components: Array[String] = itemString.split(":") 41 | if (components.length != 2) { 42 | throw new FlagException.IllegalFormat(this, itemString, null) 43 | } 44 | val key: K = ContainerFlagField.itemFromString(components(0), keyClazz, this) 45 | val value: V = ContainerFlagField.itemFromString(components(1), valueClazz, this) 46 | existing + (key -> value) 47 | } 48 | 49 | override def valueString(value: Map[K, V]): String = { 50 | value.map { case (k, v) => (k.toString + ":" + v.toString) }.mkString(",") 51 | } 52 | } -------------------------------------------------------------------------------- /flagz-scala/src/main/scala/org/flagz/ScalaFlagz.scala: -------------------------------------------------------------------------------- 1 | package org.flagz 2 | 3 | import scala.collection.JavaConversions._ 4 | import scala.reflect.ClassTag 5 | 6 | /** 7 | * Wrapper class containing utility methods for working with {@link Flag} 8 | * objects in Scala. Flag objects must be annotated with {@link FlagInfo} in order to 9 | * be recognized as a flag. 10 | * 11 | * The recommended way for definining flags in Scala is through objects. To make {@link Flag} objects discoverable 12 | * inside object definitions, the object must implement the {@link FlagContainer} trait. For example: 13 | * 14 | * ``` 15 | * object MyObject extends FlagContainer { 16 | * {@literal @}FlagInfo(name = "num_threads", help = "Number of threads for request processing.") 17 | * final val threadCount = ScalaFlagz.valueOf(10) 18 | * 19 | * {@literal @}FlagInfo(name = "admin_users", help = "List of admin users.") 20 | * final val admins = ScalaFlagz.valueOf(List.empty[String]) 21 | * } 22 | * ``` 23 | * 24 | * There is built-in support for Scala immutable collections {@link Set}, {@link Map}, and {@link List}. Scala 25 | * primitives (Int, Long, Double, Float, Short, Byte, String) are handled through built-in implicit conversions. 26 | **/ 27 | object ScalaFlagz extends Flagz { 28 | 29 | def valueOf(defaultValue: Int): Flag[java.lang.Integer] = { 30 | Flagz.valueOf(new java.lang.Integer(defaultValue)) 31 | } 32 | 33 | def valueOf(defaultValue: Long): Flag[java.lang.Long] = { 34 | Flagz.valueOf(new java.lang.Long(defaultValue)) 35 | } 36 | 37 | def valueOf(defaultValue: Float): Flag[java.lang.Float] = { 38 | Flagz.valueOf(new java.lang.Float(defaultValue)) 39 | } 40 | 41 | def valueOf(defaultValue: Double): Flag[java.lang.Double] = { 42 | Flagz.valueOf(new java.lang.Double(defaultValue)) 43 | } 44 | 45 | def valueOf(defaultValue: Short): Flag[java.lang.Short] = { 46 | Flagz.valueOf(new java.lang.Short(defaultValue)) 47 | } 48 | 49 | def valueOf(defaultValue: Byte): Flag[java.lang.Byte] = { 50 | Flagz.valueOf(new java.lang.Byte(defaultValue)) 51 | } 52 | 53 | def valueOf(defaultValue: Boolean): Flag[java.lang.Boolean] = { 54 | Flagz.valueOf(new java.lang.Boolean(defaultValue)) 55 | } 56 | 57 | def valueOf(defaultValue: String): Flag[java.lang.String] = { 58 | Flagz.valueOf(new java.lang.String(defaultValue)) 59 | } 60 | 61 | def valueOf[K, V](defaultValue: Map[K, V])(implicit keyTag: ClassTag[K], valueTag: ClassTag[V]): Flag[Map[K, V]] = { 62 | new MapFlagField[K, V](defaultValue) 63 | } 64 | 65 | def valueOf[E](defaultValue: List[E])(implicit tag: ClassTag[E]): Flag[List[E]] = { 66 | new ListFlagField[E](defaultValue) 67 | } 68 | 69 | def valueOf[E](defaultValue: Set[E])(implicit tag: ClassTag[E]): Flag[Set[E]] = { 70 | new SetFlagField[E](defaultValue) 71 | } 72 | 73 | /** 74 | * Parses the command line arguments and updates as necessary all {@link Flag} 75 | * objects annotated with {@link FlagInfo}. 76 | *

77 | * If "--help" of "-h" is passed in at the command line, then the help menu 78 | * will be printed and the JVM will exit with a 0 exit status. 79 | * 80 | * @param args command line arguments in the form 81 | * "--defaultFlagName=value --booleanFlag -c=foo ..." 82 | * @param packagePrefixes list of Java packages to be scanned for Flag objects, keeping the scope narrow makes it 83 | * start fast. By default an empty list is used, meaning all classes will be reflected on. 84 | * @return registry useful for setting flags at runtime 85 | */ 86 | def parse(args: Array[String], packagePrefixes: List[String]): FlagFieldRegistry = { 87 | Flagz.parse(args, packagePrefixes, ScalaObjectScanner.scanFlagObjects(packagePrefixes)) 88 | } 89 | 90 | def parse(args: Array[String]): FlagFieldRegistry = { 91 | val emptyPrefixes = List.empty[String] 92 | Flagz.parse(args, emptyPrefixes, ScalaObjectScanner.scanFlagObjects(emptyPrefixes)) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /flagz-scala/src/main/scala/org/flagz/ScalaObjectScanner.java: -------------------------------------------------------------------------------- 1 | package org.flagz; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import org.reflections.Reflections; 5 | import scala.DelayedInit; 6 | 7 | import java.lang.reflect.Field; 8 | import java.util.HashSet; 9 | import java.util.List; 10 | import java.util.Optional; 11 | import java.util.Set; 12 | 13 | 14 | /** 15 | * Scanner for Scala objects annotated with {@link FlagContainer}. 16 | * 17 | * Scala objects are really classes with a "$" suffix that initialize a singleton static field. 18 | * However, Scala doesn't execute the initialization until the object is referenced. This class 19 | * reflects on the whole classpath, finds the classes for objects, and initializes them, returning 20 | * the singleton instances of the Scala objects. 21 | */ 22 | class ScalaObjectScanner { 23 | 24 | static synchronized Set scanFlagObjects(List packagePrefixes) { 25 | 26 | Reflections reflections = ReflectionsCache.reflectionsForPrefixes(packagePrefixes); 27 | Set objects = new HashSet<>(); 28 | for (Class clazz : reflections.getSubTypesOf(FlagContainer.class)) { 29 | checkDelayedInit(clazz); 30 | // Scala objects have a MODULE$ static field. 31 | // http://grahamhackingscala.blogspot.co.uk/2009/11/scala-under-hood-of-hello-world.html 32 | try { 33 | Field staticModuleField = clazz.getDeclaredField("MODULE$"); 34 | boolean wasAccessible = staticModuleField.isAccessible(); 35 | if (!wasAccessible) { 36 | staticModuleField.setAccessible(true); 37 | } 38 | objects.add(staticModuleField.get(null)); // null is fine, this is a static field. 39 | staticModuleField.setAccessible(wasAccessible); 40 | } catch (NoSuchFieldException | IllegalAccessException exception) { 41 | throw new IllegalArgumentException( 42 | "Error reflecting on Scala object. " + clazz.getCanonicalName(), exception); 43 | } 44 | } 45 | return objects; 46 | } 47 | 48 | /** Checks if the flags are defined in a DelayedInit class, that isn't on the call trace. */ 49 | private static void checkDelayedInit(Class clazz) { 50 | if (DelayedInit.class.isAssignableFrom(clazz)) { 51 | Optional onStack = ImmutableList 52 | .copyOf(Thread.currentThread().getStackTrace()) 53 | .stream() 54 | .filter(x -> x.getClassName().startsWith(clazz.getCanonicalName())) 55 | .findFirst(); 56 | if (!onStack.isPresent()) { 57 | throw new FlagException( 58 | "A DelayedInit (e.g. App) object defines a Flag but is not initialized. Consider " 59 | + "moving to a non DelayedInit object. Class: " + clazz.getCanonicalName()); 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /flagz-scala/src/main/scala/org/flagz/package.scala: -------------------------------------------------------------------------------- 1 | package org 2 | 3 | import java.util.function.{Predicate, Supplier} 4 | import language.implicitConversions 5 | 6 | package object flagz { 7 | 8 | /** Scala function to Java 8 {@link Supplier} implicit conversion. */ 9 | implicit def FuncToJavaSupplier[T](func: () => T): Supplier[T] = { 10 | new Supplier[T] { 11 | override def get(): T = { 12 | func() 13 | } 14 | } 15 | } 16 | 17 | /** Java 8 {@link Supplier} to Scala function implicit conversion. Useful for getting {@link Flag} values. */ 18 | implicit def JavaSupplierToFunc[T](supplier: Supplier[T]): (() => T) = { 19 | supplier.get 20 | } 21 | 22 | /** Scala function to Java 8 {@link Predicate} implicit conversion. */ 23 | implicit def FuncToJavaPredicate[T](func: T => Boolean): Predicate[T] = { 24 | new Predicate[T] { 25 | override def test(value: T): Boolean = { 26 | func(value) 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /flagz-scala/src/test/scala/org/flagz/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | load("@io_bazel_rules_scala//scala:scala.bzl", "scala_library", "scala_binary", "scala_test") 4 | 5 | 6 | scala_test( 7 | name = "flagz", 8 | srcs = glob(["*.scala"]) + glob(["*.java"]), 9 | deps = [ 10 | "//flagz-scala/src/main/scala/org/flagz", 11 | "//flagz-scala/src/test/scala/org/flagz/testclasses", 12 | "//flagz-java/src/main/java/org/flagz", 13 | "//third_party/etcd4j", 14 | "//third_party/guava", 15 | "//third_party/slf4j:api", 16 | ], 17 | ) -------------------------------------------------------------------------------- /flagz-scala/src/test/scala/org/flagz/FlagObjectRegistryTest.scala: -------------------------------------------------------------------------------- 1 | package org.flagz 2 | 3 | import org.flagz.testclasses.{TestObjectOne, TestObjectTwo} 4 | import org.scalatest.WordSpec 5 | 6 | class FlagObjectRegistryTest extends WordSpec { 7 | 8 | "FlagObject registry" must { 9 | 10 | "parse objects annotated with FlagContainer" in { 11 | val args = Array[String]("--test_one_int=4", "--test_two_int=5") 12 | val flagRegistry = ScalaFlagz.parse(args) 13 | 14 | assert(TestObjectOne.flagInt.get() == 4) 15 | assert(TestObjectTwo.flagInt.get() == 5) 16 | } 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /flagz-scala/src/test/scala/org/flagz/ScalaCollectionsFlagFieldTest.scala: -------------------------------------------------------------------------------- 1 | package org.flagz 2 | 3 | import org.flagz.testclasses.TestCollectionObject 4 | import org.scalatest.WordSpec 5 | 6 | class ScalaCollectionsFlagFieldTest extends WordSpec { 7 | 8 | "ListFlagField" must { 9 | 10 | "preserve defaults" in { 11 | val flagRegistry = ScalaFlagz.parse(Array[String]()) 12 | assert(TestCollectionObject.flagList.defaultValue() == TestCollectionObject.flagList.get()) 13 | } 14 | 15 | "parse objects annotated with FlagContainer" in { 16 | val flagRegistry = ScalaFlagz.parse(Array[String]("--test_coll_list=0,1,2,3,4")) 17 | assert(List(0, 1, 2, 3, 4) == TestCollectionObject.flagList.get()) 18 | } 19 | 20 | "unwrap to the original String implementation" in { 21 | val flagRegistry = ScalaFlagz.parse(Array[String]("--test_coll_list=0,1,2,3,4")) 22 | val flagField = TestCollectionObject.flagList.asInstanceOf[FlagField[List[Int]]] 23 | assert("0,1,2,3,4" == flagField.valueString(flagField.get())) 24 | } 25 | } 26 | 27 | "SetFlagField" must { 28 | 29 | "preserve defaults" in { 30 | val flagRegistry = ScalaFlagz.parse(Array[String]()) 31 | assert(TestCollectionObject.flagSet.defaultValue() == TestCollectionObject.flagSet.get()) 32 | } 33 | 34 | "parse objects annotated with FlagContainer" in { 35 | val flagRegistry = ScalaFlagz.parse(Array[String]("--test_coll_set=foo,boo,zoo")) 36 | assert(Set("foo", "boo", "zoo") == TestCollectionObject.flagSet.get()) 37 | } 38 | 39 | "unwrap to the original String implementation" in { 40 | val flagRegistry = ScalaFlagz.parse(Array[String]("--test_coll_set=foo,boo,zoo")) 41 | val flagField = TestCollectionObject.flagSet.asInstanceOf[FlagField[Set[Int]]] 42 | assert("foo,boo,zoo" == flagField.valueString(flagField.get())) 43 | } 44 | } 45 | 46 | "MapFlagField" must { 47 | 48 | "preserve defaults" in { 49 | val flagRegistry = ScalaFlagz.parse(Array[String]()) 50 | assert(TestCollectionObject.flagMap.defaultValue() == TestCollectionObject.flagMap.get()) 51 | } 52 | 53 | "parse objects annotated with FlagContainer" in { 54 | val flagRegistry = ScalaFlagz.parse(Array[String]("--test_coll_map=foo:2,boo:3")) 55 | assert(Map("foo" -> 2, "boo" -> 3) == TestCollectionObject.flagMap.get()) 56 | } 57 | 58 | "unwrap to the original String implementation" in { 59 | val flagRegistry = ScalaFlagz.parse(Array[String]("--test_coll_map=foo:2,boo:3")) 60 | val flagField = TestCollectionObject.flagMap.asInstanceOf[FlagField[Map[String, Int]]] 61 | assert("foo:2,boo:3" == flagField.valueString(flagField.get())) 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /flagz-scala/src/test/scala/org/flagz/testclasses/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | load("@io_bazel_rules_scala//scala:scala.bzl", "scala_library", "scala_binary", "scala_test") 4 | 5 | 6 | scala_library( 7 | name = "testclasses", 8 | srcs = glob(["*.scala"]), 9 | deps = [ 10 | "//flagz-java/src/main/java/org/flagz", 11 | "//flagz-scala/src/main/scala/org/flagz", 12 | ], 13 | ) -------------------------------------------------------------------------------- /flagz-scala/src/test/scala/org/flagz/testclasses/TestCollectionObject.scala: -------------------------------------------------------------------------------- 1 | package org.flagz.testclasses 2 | 3 | import org.flagz.{FlagContainer, ScalaFlagz, FlagInfo} 4 | 5 | object TestCollectionObject extends FlagContainer { 6 | 7 | @FlagInfo(name = "test_coll_list", help = "For testing") 8 | final val flagList = ScalaFlagz.valueOf(List(300, 400, 500)) 9 | 10 | @FlagInfo(name = "test_coll_map", help = "For testing") 11 | final val flagMap = ScalaFlagz.valueOf(Map("alpha" -> 0, "bravo" -> 1, "charlie" -> 2)) 12 | 13 | @FlagInfo(name = "test_coll_set", help = "For testing") 14 | final val flagSet = ScalaFlagz.valueOf(Set("alpha", "bravo", "charlie")) 15 | } 16 | -------------------------------------------------------------------------------- /flagz-scala/src/test/scala/org/flagz/testclasses/TestObjectOne.scala: -------------------------------------------------------------------------------- 1 | package org.flagz.testclasses 2 | 3 | import org.flagz._ 4 | 5 | object TestObjectOne extends FlagContainer { 6 | @FlagInfo(name = "test_one_int", help = "One's int") 7 | final val flagInt = ScalaFlagz.valueOf(300) 8 | 9 | } 10 | 11 | -------------------------------------------------------------------------------- /flagz-scala/src/test/scala/org/flagz/testclasses/TestObjectTwo.scala: -------------------------------------------------------------------------------- 1 | package org.flagz.testclasses 2 | 3 | import org.flagz._ 4 | 5 | object TestObjectTwo extends FlagContainer { 6 | @FlagInfo(name = "test_two_int", help = "One's int") 7 | final val flagInt = ScalaFlagz.valueOf(300) 8 | 9 | } 10 | 11 | -------------------------------------------------------------------------------- /samples/src/main/java/org/flagz/samples/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_binary( 4 | name = "jmx", 5 | srcs = ["JmxSampleApp.java"], 6 | main_class = "org.flagz.samples.JmxSampleApp", 7 | deps = [ 8 | "//flagz-java/src/main/java/org/flagz", 9 | "//third_party/guava", 10 | "//third_party/slf4j:api", 11 | "//third_party/slf4j:simple-stdout", 12 | ], 13 | ) 14 | 15 | java_binary( 16 | name = "etcd", 17 | srcs = ["EtcdSampleApp.java"], 18 | main_class = "org.flagz.samples.EtcdSampleApp", 19 | deps = [ 20 | "//flagz-java/src/main/java/org/flagz", 21 | "//flagz-etcd/src/main/java/org/flagz", 22 | "//third_party/etcd4j", 23 | "//third_party/guava", 24 | "//third_party/slf4j:api", 25 | "//third_party/slf4j:simple-stdout", 26 | ], 27 | ) -------------------------------------------------------------------------------- /samples/src/main/java/org/flagz/samples/EtcdSampleApp.java: -------------------------------------------------------------------------------- 1 | package org.flagz.samples; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import org.flagz.EtcdFlagFieldUpdater; 5 | import org.flagz.Flag; 6 | import org.flagz.FlagFieldRegistry; 7 | import org.flagz.FlagInfo; 8 | import org.flagz.Flagz; 9 | 10 | import java.util.Map; 11 | 12 | /** 13 | * Sample App that demonstrates use of {@link EtcdFlagFieldUpdater}. 14 | * 15 | * To use, start: 16 | * {@code 17 | * java -classpath some.jar org.flagz.samples.EtcdSampleApp \ 18 | * --flagz_etcd_enabled=true \ 19 | * --flagz_etcd_directory=/myapp/flagz 20 | * } 21 | */ 22 | public class EtcdSampleApp { 23 | 24 | @FlagInfo(name = "test_etcd_int", help = "Some Etcd int.") 25 | private static final Flag someInt = Flagz.valueOf(1337); 26 | 27 | @FlagInfo(name = "test_etcd_map", help = "Some Etcd Map.") 28 | private static final Flag> someMap = Flagz 29 | .valueOf(ImmutableMap.of("car", 200, "bar", 300)); 30 | 31 | /** Sample app that listens to etcd at port 4001. */ 32 | public static void main(String[] args) throws Exception { 33 | FlagFieldRegistry registry = Flagz.parse(args); 34 | EtcdFlagFieldUpdater updater = new EtcdFlagFieldUpdater(registry); 35 | updater.init(); 36 | updater.watchForUpdates(); 37 | try { 38 | while (true) { 39 | System.out.println("test_etcd_int: " + someInt.get().toString()); 40 | System.out.println("test_etcd_map: " + someMap.get()); 41 | System.out.println(); 42 | Thread.sleep(3000); 43 | } 44 | } finally { 45 | updater.stop(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /samples/src/main/java/org/flagz/samples/JmxSampleApp.java: -------------------------------------------------------------------------------- 1 | package org.flagz.samples; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.ImmutableMap; 5 | import org.flagz.Flag; 6 | import org.flagz.FlagFieldRegistry; 7 | import org.flagz.FlagInfo; 8 | import org.flagz.Flagz; 9 | import org.flagz.JmxFlagFieldRegistrar; 10 | 11 | import java.lang.management.ManagementFactory; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | public class JmxSampleApp { 16 | 17 | @FlagInfo(name = "test_some_list", help = "Some list") 18 | private static final Flag> someStrings = Flagz 19 | .valueOf(ImmutableList.of("foo", "boo", "zoo")); 20 | 21 | @FlagInfo(name = "test_some_int", help = "Some int") 22 | private static final Flag someInt = Flagz.valueOf(1337); 23 | 24 | @FlagInfo(name = "test_some_map", help = "Some int") 25 | private static final Flag> someMap = Flagz 26 | .valueOf(ImmutableMap.of("car", 200, "bar", 300)); 27 | 28 | @FlagInfo(name = "test_running", help = "Controls start and stop.") 29 | private static final Flag running = Flagz.valueOf(true); 30 | 31 | 32 | /** Example app that listens for JMX params. */ 33 | public static void main(String[] args) throws Exception { 34 | FlagFieldRegistry registry = Flagz.parse(args); 35 | JmxFlagFieldRegistrar jmx = new JmxFlagFieldRegistrar(registry); 36 | jmx.register(ManagementFactory.getPlatformMBeanServer()); 37 | while (running.get()) { 38 | System.out.println("test_some_int: " + someInt.get().toString()); 39 | System.out.println("test_some_list: " + someStrings.get()); 40 | System.out.println("test_some_map: " + someMap.get()); 41 | System.out.println(); 42 | Thread.sleep(1000); 43 | } 44 | System.out.println("Bye!"); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /third_party/etcd4j/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_library( 4 | name = "etcd4j", 5 | exports = [ 6 | "@org_mousio_etcd4j_artifact//jar", 7 | "@io_netty_artifact//jar", 8 | "@com_fasterxml_jackson_core_artifact//jar", 9 | "@com_fasterxml_jackson_annotations_artifact//jar", 10 | "@com_fasterxml_jackson_afterburner_artifact//jar", 11 | "@com_fasterxml_jackson_databind_artifact//jar", 12 | ], 13 | licenses = ["permissive"], 14 | ) 15 | -------------------------------------------------------------------------------- /third_party/etcd4j/etcd4j.LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /third_party/guava/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_library( 4 | name = "guava", 5 | exports = [ 6 | "@com_google_guava_artifact//jar", 7 | ], 8 | runtime_deps = [ 9 | "//third_party/jsr305", 10 | ], 11 | licenses = ["permissive"], 12 | ) 13 | -------------------------------------------------------------------------------- /third_party/guava/guava.LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /third_party/javassist/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_library( 4 | name = "javassist", 5 | exports = [ 6 | "@org_javassist_artifact//jar", 7 | ], 8 | licenses = ["permissive"], 9 | ) 10 | -------------------------------------------------------------------------------- /third_party/jsr305/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | # This package contains the necessary jsr305 extension that provides javax.annotations.Nullable. 3 | 4 | java_library( 5 | name = "jsr305", 6 | exports = [ 7 | "@com_google_code_findbugs_jsr305_artifact//jar", 8 | ], 9 | licenses = ["permissive"], 10 | ) 11 | -------------------------------------------------------------------------------- /third_party/jsr305/guava.LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /third_party/netty/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_library( 4 | name = "netty", 5 | exports = [ 6 | "@io_netty_artifact//jar", 7 | ], 8 | licenses = ["permissive"], 9 | ) 10 | -------------------------------------------------------------------------------- /third_party/netty/netty.LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /third_party/reflections/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_library( 4 | name = "reflections", 5 | exports = [ 6 | "@org_reflections_artifact//jar", 7 | ], 8 | runtime_deps = [ 9 | "//third_party/javassist", 10 | ], 11 | licenses = ["permissive"], 12 | ) 13 | -------------------------------------------------------------------------------- /third_party/reflections/reflections.LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /third_party/slf4j/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_library( 4 | name = "api", 5 | exports = [ 6 | "@org_slf4j_api_artifact//jar", 7 | ], 8 | licenses = ["permissive"], 9 | ) 10 | 11 | java_library( 12 | name = "simple-stdout", 13 | exports = [ 14 | "@org_slf4j_simple_artifact//jar", 15 | ], 16 | licenses = ["permissive"], 17 | ) 18 | 19 | -------------------------------------------------------------------------------- /third_party/slf4j/slf4j.LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2004-2013 QOS.ch 2 | All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /third_party/testing/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_library( 4 | name = "testing", 5 | exports = [ 6 | "@junit_artifact//jar", 7 | "@hamcrest_artifact//jar", 8 | "@org_mockito_artifact//jar", 9 | ], 10 | licenses = ["permissive"], 11 | ) 12 | -------------------------------------------------------------------------------- /third_party/testing/junit.LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and 10 | b) in the case of each subsequent Contributor: 11 | i) changes to the Program, and 12 | ii) additions to the Program; 13 | where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. 14 | "Contributor" means any person or entity that distributes the Program. 15 | 16 | "Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. 17 | 18 | "Program" means the Contributions distributed in accordance with this Agreement. 19 | 20 | "Recipient" means anyone who receives the Program under this Agreement, including all Contributors. 21 | 22 | 2. GRANT OF RIGHTS 23 | 24 | a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. 25 | b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. 26 | c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. 27 | d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. 28 | 3. REQUIREMENTS 29 | 30 | A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: 31 | 32 | a) it complies with the terms and conditions of this Agreement; and 33 | b) its license agreement: 34 | i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; 35 | ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; 36 | iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and 37 | iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. 38 | When the Program is made available in source code form: 39 | 40 | a) it must be made available under this Agreement; and 41 | b) a copy of this Agreement must be included with each copy of the Program. 42 | Contributors may not remove or alter any copyright notices contained within the Program. 43 | 44 | Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. 45 | 46 | 4. COMMERCIAL DISTRIBUTION 47 | 48 | Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. 49 | 50 | For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. 51 | 52 | 5. NO WARRANTY 53 | 54 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. 55 | 56 | 6. DISCLAIMER OF LIABILITY 57 | 58 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 59 | 60 | 7. GENERAL 61 | 62 | If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. 63 | 64 | If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. 65 | 66 | All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. 67 | 68 | Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. 69 | 70 | This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. 71 | -------------------------------------------------------------------------------- /third_party/testing/mockito.LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2007 Mockito contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | --------------------------------------------------------------------------------