├── .gitignore ├── .travis.yml ├── README.md ├── build.gradle ├── dependencies.yml ├── example ├── build.gradle └── src │ ├── jmh │ └── java │ │ └── com │ │ └── github │ │ └── imasahiro │ │ └── stringformatter │ │ ├── processor │ │ └── benchmark │ │ │ ├── AllTypesBench.java │ │ │ ├── CapacityBench.java │ │ │ ├── IntegerStringifyBench.java │ │ │ └── StringBench.java │ │ └── runtime │ │ └── benchmark │ │ └── IntegerUtilsBench.java │ └── main │ └── java │ └── com │ └── github │ └── imasahiro │ └── stringformatter │ ├── example │ ├── Example.java │ ├── Example2.java │ ├── FormatHexId.java │ └── package-info.java │ └── processor │ └── benchmark │ ├── AllTypesBenchFormatter.java │ ├── CapacityBenchFormatter.java │ ├── IntegerStringifyBenchFormatter.java │ ├── StringBenchFormatter.java │ └── package-info.java ├── gradle.properties ├── gradle ├── scripts │ ├── .github │ │ └── CODEOWNERS │ ├── .gitrepo │ ├── README.md │ ├── build-flags.gradle │ ├── lib │ │ ├── bom.gradle │ │ ├── common-dependencies.gradle │ │ ├── common-git.gradle │ │ ├── common-info.gradle │ │ ├── common-misc.gradle │ │ ├── common-publish.gradle │ │ ├── common-release.gradle │ │ ├── common-wrapper.gradle │ │ ├── java-alpn.gradle │ │ ├── java-coverage.gradle │ │ ├── java-javadoc.css │ │ ├── java-javadoc.gradle │ │ ├── java-javadoc.pre-jdk9.css │ │ ├── java-publish.gradle │ │ ├── java-rpc-proto.gradle │ │ ├── java-rpc-thrift.gradle │ │ ├── java-shade.gradle │ │ ├── java-versionprops.gradle │ │ ├── java.gradle │ │ ├── prerequisite.gradle │ │ └── thrift │ │ │ ├── 0.10 │ │ │ ├── thrift.linux-x86_64 │ │ │ ├── thrift.osx-x86_64 │ │ │ ├── thrift.windows-x86_32.exe │ │ │ └── thrift.windows-x86_64.exe │ │ │ ├── 0.11 │ │ │ ├── thrift.linux-x86_64 │ │ │ ├── thrift.osx-x86_64 │ │ │ ├── thrift.windows-x86_32.exe │ │ │ └── thrift.windows-x86_64.exe │ │ │ ├── 0.12 │ │ │ ├── thrift.linux-x86_64 │ │ │ ├── thrift.osx-x86_64 │ │ │ ├── thrift.windows-x86_32 │ │ │ └── thrift.windows-x86_64 │ │ │ └── 0.9 │ │ │ ├── thrift.linux-x86_64 │ │ │ ├── thrift.osx-x86_64 │ │ │ ├── thrift.windows-x86_32.exe │ │ │ └── thrift.windows-x86_64.exe │ └── settings-flags.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── processor ├── build.gradle └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── imasahiro │ │ │ └── stringformatter │ │ │ └── processor │ │ │ ├── FixedString.java │ │ │ ├── FormatFlag.java │ │ │ ├── FormatParser.java │ │ │ ├── FormatSpecifier.java │ │ │ ├── FormatString.java │ │ │ ├── FormatStringBuilder.java │ │ │ ├── FormatterMethod.java │ │ │ ├── StringFormatterProcessor.java │ │ │ ├── package-info.java │ │ │ ├── specifier │ │ │ ├── BooleanFormatConversionType.java │ │ │ ├── CharacterFormatConversionType.java │ │ │ ├── FloatFormatConversionType.java │ │ │ ├── FormatConversionType.java │ │ │ ├── HexIntegerFormatConversionType.java │ │ │ ├── IntegerFormatConversionType.java │ │ │ ├── StringFormatConversionType.java │ │ │ └── package-info.java │ │ │ └── util │ │ │ ├── AbortProcessingException.java │ │ │ ├── ErrorReporter.java │ │ │ ├── TypeUtils.java │ │ │ └── package-info.java │ └── resources │ │ ├── META-INF │ │ └── services │ │ │ └── javax.annotation.processing.Processor │ │ └── template │ │ ├── boolean.mustache │ │ ├── boolean_with_width.mustache │ │ ├── formattable.mustache │ │ ├── int.mustache │ │ ├── int_with_width.mustache │ │ └── string.mustache │ └── test │ └── java │ └── com │ └── github │ └── imasahiro │ └── stringformatter │ └── processor │ └── StringFormatterTest.java ├── release.mk ├── runtime ├── build.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── github │ │ └── imasahiro │ │ └── stringformatter │ │ ├── annotation │ │ ├── AutoStringFormatter.java │ │ ├── Format.java │ │ └── package-info.java │ │ └── runtime │ │ └── integers │ │ ├── HexIntegerFormatter.java │ │ ├── IntegerFormatter.java │ │ ├── IntegerUtils.java │ │ └── package-info.java │ └── test │ └── java │ └── com │ └── github │ └── imasahiro │ └── stringformatter │ └── runtime │ └── integers │ ├── HexIntegerFormatterTest.java │ ├── IntegerFormatterTest.java │ └── IntegerUtilsTest.java ├── settings.gradle └── settings └── checkstyle ├── checkstyle-suppressions.xml └── checkstyle.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # General 2 | *.swp 3 | *~ 4 | .DS_Store 5 | target/ 6 | settings.xml 7 | 8 | ### Java template 9 | *.class 10 | 11 | # Package Files # 12 | *.jar 13 | 14 | !**/libs/*.jar 15 | 16 | # checkstyle 17 | /.checkstyle 18 | 19 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 20 | hs_err_pid* 21 | 22 | ### Gradle template 23 | .gradle 24 | build/ 25 | 26 | # Ignore Gradle GUI config 27 | gradle-app.setting 28 | 29 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 30 | !gradle-wrapper.jar 31 | 32 | ### JetBrains template 33 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion 34 | 35 | *.iml 36 | 37 | ## Directory-based project format: 38 | .idea/ 39 | 40 | ## File-based project format: 41 | *.ipr 42 | *.iws 43 | 44 | ## Plugin-specific files: 45 | 46 | # IntelliJ 47 | /out/ 48 | 49 | # Java annotation processor (APT) 50 | .factorypath 51 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | sudo: false 3 | os: 4 | - linux 5 | branches: 6 | only: 7 | - master 8 | jdk: 9 | - openjdk11 10 | 11 | cache: 12 | directories: 13 | - "$HOME/.gradle/wrapper/dists" 14 | - "$HOME/.gradle/caches/jars-3" 15 | - "$HOME/.gradle/caches/modules-2" 16 | - "$HOME/.gradle/caches/package-lists" 17 | - "$HOME/.jdk" 18 | env: 19 | global: 20 | - _JAVA_OPTIONS= 21 | - GRADLE_OPTS=-Xmx1280m 22 | before_install: 23 | - "./gradlew --version" 24 | install: 25 | - true 26 | script: 27 | - "./gradlew --no-daemon --stacktrace -Pcoverage checkstyle test build" 28 | before_cache: 29 | - find $HOME/.gradle/caches -name '*.lock' -delete 30 | after_success: 31 | - bash <(curl -s https://codecov.io/bash) 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # auto-string-formatter 2 | 3 | `auto-string-formatter` is a string formatting library for Java, generating optimized formatter methods at compile time with annotation processing. 4 | 5 | `auto-string-formatter` has the following features: 6 | * Fast as hand-written code with `StringBuilder`. 7 | * (Mostly) compatible for `String.format`. 8 | 9 | ## Getting Started 10 | `auto-string-formatter` requires JDK 8 (1.8.0_65 or later) to run annotation processors. 11 | 12 | ## Synopsis 13 | Just define a format with `@AutoStringFormatter` and `@Format`, and use it. 14 | 15 | ```java 16 | package com.github.imasahiro.stringformatter.example; 17 | 18 | import com.github.imasahiro.stringformatter.annotation.AutoStringFormatter; 19 | import com.github.imasahiro.stringformatter.annotation.Format; 20 | 21 | public class Example { 22 | @AutoStringFormatter 23 | interface Formatter { 24 | @Format("Hi %s, my name is %s.") 25 | String formatTo(String friendName, String myName); 26 | } 27 | 28 | public static void main(String... args) { 29 | System.out.println(new Example_Formatter().formatTo("Alice", "Bob")); 30 | } 31 | } 32 | ``` 33 | 34 | Then the annotation processor generate following java code based on AutoStringFormatter annotation. 35 | 36 | ```java 37 | package com.github.imasahiro.stringformatter.example; 38 | 39 | import java.lang.String; 40 | import javax.annotation.Generated; 41 | import javax.inject.Inject; 42 | import javax.inject.Named; 43 | 44 | @Generated({"com.github.imasahiro.stringformatter.processor.StringFormatterProcessor"}) 45 | @Named 46 | public final class Example_Formatter implements Example.Formatter { 47 | @Inject 48 | Example_Formatter() { 49 | } 50 | 51 | public final String formatTo(final String arg0, final String arg1) { 52 | final StringBuilder sb = new StringBuilder(16); 53 | sb.append("Hi "); 54 | sb.append(String.valueOf(arg0)); 55 | sb.append(", my name is "); 56 | sb.append(String.valueOf(arg1)); 57 | sb.append("."); 58 | return sb.toString(); 59 | } 60 | } 61 | ``` 62 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | maven { url "https://plugins.gradle.org/m2/" } 4 | mavenCentral() 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.google.gradle:osdetector-gradle-plugin:1.6.2' 9 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' 10 | classpath 'io.spring.gradle:dependency-management-plugin:1.0.7.RELEASE' 11 | classpath 'me.champeau.gradle:jmh-gradle-plugin:0.4.8' 12 | classpath 'net.ltgt.gradle:gradle-apt-plugin:0.21' 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | mavenCentral() 19 | jcenter() 20 | } 21 | } 22 | 23 | apply from: "${rootDir}/gradle/scripts/build-flags.gradle" 24 | 25 | configure(projectsWithFlag('java', 'publish')) { 26 | apply plugin: 'com.jfrog.bintray' 27 | 28 | bintray { 29 | user = System.getenv("bintrayUser") 30 | key = System.getenv("bintrayKey") 31 | 32 | publications = ['jar'] 33 | publish = true 34 | // dryRun = true 35 | pkg { 36 | repo = 'maven' 37 | userOrg = 'imasahiro' 38 | name = project.archivesBaseName 39 | desc = 'An annotation processor for auto-string-formatter to generate optimized String.format() methods' 40 | websiteUrl = 'https://github.com/imasahiro/auto-string-formatter' 41 | issueTrackerUrl = 'https://github.com/imasahiro/auto-string-formatter/issues' 42 | vcsUrl = 'git@github.com:imasahiro/auto-string-formatter.git' 43 | licenses = ['Apache-2.0'] 44 | publicDownloadNumbers = true 45 | //Optional version descriptor 46 | version { 47 | name = project.version 48 | } 49 | } 50 | } 51 | } 52 | 53 | // Configure all Java projects 54 | configure(projectsWithFlags('java')) { 55 | // Common dependencies 56 | dependencies { 57 | // Test-time dependencies 58 | testCompile 'com.google.guava:guava-testlib' 59 | testCompile 'junit:junit' 60 | testCompile 'org.assertj:assertj-core' 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /dependencies.yml: -------------------------------------------------------------------------------- 1 | com.google.guava: 2 | guava: 3 | version: &GUAVA_VERSION '27.1-jre' 4 | guava-testlib: 5 | version: *GUAVA_VERSION 6 | 7 | com.google.auto: 8 | auto-common: { version: '0.10' } 9 | 10 | com.google.testing.compile: 11 | compile-testing: { version: '0.17' } 12 | 13 | com.github.spullara.mustache.java: 14 | compiler: { version: '0.9.6' } 15 | 16 | com.puppycrawl.tools: 17 | checkstyle: { version: '8.21' } 18 | 19 | com.squareup: 20 | javapoet: { version: '1.11.1' } 21 | 22 | gradle.plugin.net.davidecavestro: 23 | gradle-jxr-plugin: { version: '0.2.1' } 24 | 25 | it.unimi.dsi: 26 | fastutil: 27 | version: '8.2.2' 28 | relocations: 29 | - from: it.unimi.dsi.fastutil 30 | to: com.linecorp.armeria.internal.shaded.fastutil 31 | 32 | javax.annotation: 33 | javax.annotation-api: { version: '1.3.2' } 34 | 35 | javax.inject: 36 | javax.inject: { version: '1' } 37 | 38 | junit: 39 | junit: { version: '4.12' } 40 | 41 | me.champeau.gradle: 42 | jmh-gradle-plugin: { version: '0.4.8' } 43 | 44 | org.assertj: 45 | assertj-core: { version: '3.12.2' } 46 | 47 | org.openjdk.jmh: 48 | jmh-core: { version: &JMH_VERSION '1.21' } 49 | jmh-generator-annprocess: { version: *JMH_VERSION } 50 | -------------------------------------------------------------------------------- /example/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'net.ltgt.apt-idea' 2 | apply plugin: 'net.ltgt.apt-eclipse' 3 | apply plugin: 'me.champeau.gradle.jmh' 4 | 5 | dependencies { 6 | annotationProcessor project(':processor') 7 | compile project(':runtime') 8 | compileOnly 'javax.annotation:javax.annotation-api' 9 | compileOnly 'javax.inject:javax.inject' 10 | } 11 | 12 | jmh { 13 | timeUnit = 'ms' 14 | fork = 2 15 | iterations = 10 16 | warmupIterations = 10 17 | timeOnIteration = '1s' 18 | warmup = '1s' 19 | zip64 = true 20 | } 21 | -------------------------------------------------------------------------------- /example/src/jmh/java/com/github/imasahiro/stringformatter/processor/benchmark/AllTypesBench.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.processor.benchmark; 18 | 19 | import org.openjdk.jmh.annotations.Benchmark; 20 | import org.openjdk.jmh.infra.Blackhole; 21 | 22 | public class AllTypesBench { 23 | private static final AllTypesBenchFormatter.Formatter formatter = 24 | new AllTypesBenchFormatter_Formatter(); 25 | 26 | private static String javaStringFormat( 27 | String formatString, boolean b, char c, double d, float f, int i, long lng, 28 | Object obj, String str) { 29 | return String.format(formatString, b, c, d, f, i, lng, obj, str); 30 | } 31 | 32 | private static final boolean[] BOOLEAN = new boolean[] { false }; 33 | private static final char[] CHAR = new char[] { 'f' }; 34 | private static final double[] DOUBLE = new double[] { 1.3424 }; 35 | private static final float[] FLOAT = new float[] { 1424.1424f }; 36 | private static final int[] INT = new int[] { 34234234 }; 37 | private static final long[] LONG = new long[] { 324249243L }; 38 | private static final Object[] OBJ = new Object[] { new Object() }; 39 | private static final String[] STR = new String[] { "foobar" }; 40 | 41 | @Benchmark 42 | public void javaStringFormat(Blackhole blackhole) { 43 | blackhole.consume(javaStringFormat( 44 | AllTypesBenchFormatter.FORMAT, 45 | BOOLEAN[0], CHAR[0], DOUBLE[0], FLOAT[0], INT[0], LONG[0], OBJ[0], STR[0])); 46 | } 47 | 48 | private static String javaStringConcat(boolean b, char c, double d, float f, int i, long lng, 49 | Object obj, String str) { 50 | return "Benchmark - " + b + ' ' + c + ' ' + d + ' ' + f + ' ' + i + ' ' + lng + ' ' + obj + ' ' + str; 51 | } 52 | 53 | @Benchmark 54 | public void javaStringConcat(Blackhole blackhole) { 55 | blackhole.consume(javaStringConcat( 56 | BOOLEAN[0], CHAR[0], DOUBLE[0], FLOAT[0], INT[0], LONG[0], OBJ[0], STR[0])); 57 | } 58 | 59 | private static String stringBuilder(boolean b, char c, double d, float f, int i, long lng, 60 | Object obj, String str) { 61 | return new StringBuilder() 62 | .append(b) 63 | .append(' ') 64 | .append(c) 65 | .append(' ') 66 | .append(d) 67 | .append(' ') 68 | .append(f) 69 | .append(' ') 70 | .append(i) 71 | .append(' ') 72 | .append(lng) 73 | .append(' ') 74 | .append(obj) 75 | .append(' ') 76 | .append(str) 77 | .toString(); 78 | } 79 | 80 | @Benchmark 81 | public void stringBuilder(Blackhole blackhole) { 82 | blackhole.consume(stringBuilder( 83 | BOOLEAN[0], CHAR[0], DOUBLE[0], FLOAT[0], INT[0], LONG[0], OBJ[0], STR[0])); 84 | } 85 | 86 | @Benchmark 87 | public void autoStringFormatter(Blackhole blackhole) { 88 | blackhole.consume(formatter.format( 89 | BOOLEAN[0], CHAR[0], DOUBLE[0], FLOAT[0], INT[0], LONG[0], OBJ[0], STR[0])); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /example/src/jmh/java/com/github/imasahiro/stringformatter/processor/benchmark/CapacityBench.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.processor.benchmark; 18 | 19 | import org.openjdk.jmh.annotations.Benchmark; 20 | import org.openjdk.jmh.annotations.Scope; 21 | import org.openjdk.jmh.annotations.State; 22 | import org.openjdk.jmh.infra.Blackhole; 23 | 24 | @State(Scope.Benchmark) 25 | public class CapacityBench { 26 | private static final CapacityBenchFormatter.Formatter formatter = new CapacityBenchFormatter_Formatter(); 27 | private static final CapacityBenchFormatter.FormatterWithCapacity formatterWithCapacity 28 | = new CapacityBenchFormatter_FormatterWithCapacity(); 29 | 30 | private static final int[] VALUES = new int[] { 1, 10000000, -10000000, 555, 19032313, 14142, 0 }; 31 | 32 | @Benchmark 33 | public void javaStringFormat(Blackhole blackhole) { 34 | blackhole.consume(String.format(CapacityBenchFormatter.FORMAT, 35 | VALUES[0], 36 | VALUES[1], 37 | VALUES[2], 38 | VALUES[3], 39 | VALUES[4], 40 | VALUES[5])); 41 | } 42 | 43 | @Benchmark 44 | public void autoStringFormatter(Blackhole blackhole) { 45 | blackhole.consume(formatter.format(VALUES[0], 46 | VALUES[1], 47 | VALUES[2], 48 | VALUES[3], 49 | VALUES[4], 50 | VALUES[5])); 51 | } 52 | 53 | @Benchmark 54 | public void autoStringFormatterCapacity(Blackhole blackhole) { 55 | blackhole.consume(formatterWithCapacity.format(VALUES[0], 56 | VALUES[1], 57 | VALUES[2], 58 | VALUES[3], 59 | VALUES[4], 60 | VALUES[5])); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /example/src/jmh/java/com/github/imasahiro/stringformatter/processor/benchmark/IntegerStringifyBench.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.processor.benchmark; 18 | 19 | import org.openjdk.jmh.annotations.Benchmark; 20 | import org.openjdk.jmh.infra.Blackhole; 21 | 22 | public class IntegerStringifyBench { 23 | private static final IntegerStringifyBenchFormatter.Formatter formatter = 24 | new IntegerStringifyBenchFormatter_Formatter(); 25 | 26 | private static String javaStringFormat(String formatString, int a, int b, int c, int d) { 27 | return String.format(formatString, a, b, c, d); 28 | } 29 | 30 | private static final int[] VALUES = new int[] { 1, 10000000, -10000000, 5555 }; 31 | 32 | @Benchmark 33 | public void javaStringFormat(Blackhole blackhole) { 34 | blackhole.consume(javaStringFormat(IntegerStringifyBenchFormatter.FORMAT, 35 | VALUES[0], 36 | VALUES[1], 37 | VALUES[2], 38 | VALUES[3])); 39 | } 40 | 41 | private static String javaStringConcat(int a, int b, int c, int d) { 42 | return a + " + " + b + " * " + c + " = " + d; 43 | } 44 | 45 | @Benchmark 46 | public void javaStringConcat(Blackhole blackhole) { 47 | blackhole.consume(javaStringConcat(VALUES[0], 48 | VALUES[1], 49 | VALUES[2], 50 | VALUES[3])); 51 | } 52 | 53 | private static String stringBuilder(int a, int b, int c, int d) { 54 | return new StringBuilder() 55 | .append(a) 56 | .append(" + ") 57 | .append(b) 58 | .append(" * ") 59 | .append(c) 60 | .append(" = ") 61 | .append(d) 62 | .toString(); 63 | } 64 | 65 | @Benchmark 66 | public void stringBuilder(Blackhole blackhole) { 67 | blackhole.consume(stringBuilder(VALUES[0], 68 | VALUES[1], 69 | VALUES[2], 70 | VALUES[3])); 71 | } 72 | 73 | @Benchmark 74 | public void autoStringFormatter(Blackhole blackhole) { 75 | blackhole.consume(formatter.format(VALUES[0], 76 | VALUES[1], 77 | VALUES[2], 78 | VALUES[3])); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /example/src/jmh/java/com/github/imasahiro/stringformatter/processor/benchmark/StringBench.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.processor.benchmark; 18 | 19 | import org.openjdk.jmh.annotations.Benchmark; 20 | import org.openjdk.jmh.infra.Blackhole; 21 | 22 | public class StringBench { 23 | 24 | private static final StringBenchFormatter.Formatter formatter = new StringBenchFormatter_Formatter(); 25 | 26 | private static final int[] VALUES = new int[] { 1, 100000000 }; 27 | 28 | private static String javaStringFormat(String formatString, int i, int j) { 29 | return String.format(formatString, i, j); 30 | } 31 | 32 | @Benchmark 33 | public void javaStringFormat(Blackhole blackhole) { 34 | blackhole.consume(javaStringFormat(StringBenchFormatter.FORMAT, 35 | VALUES[0], 36 | VALUES[1])); 37 | } 38 | 39 | private static String javaStringConcat(int i, int j) { 40 | return "Hi " + i + "; Hi to you " + j; 41 | } 42 | 43 | @Benchmark 44 | public void javaStringConcat(Blackhole blackhole) { 45 | blackhole.consume(javaStringConcat(VALUES[0], 46 | VALUES[1])); 47 | } 48 | 49 | private static String stringBuilder(int i, int j) { 50 | StringBuilder bldString = new StringBuilder("Hi "); 51 | bldString.append(i); 52 | bldString.append("; Hi to you "); 53 | bldString.append(j); 54 | return bldString.toString(); 55 | } 56 | 57 | @Benchmark 58 | public void stringBuilder(Blackhole blackhole) { 59 | blackhole.consume(stringBuilder(VALUES[0], 60 | VALUES[1])); 61 | } 62 | 63 | @Benchmark 64 | public void autoStringFormatter(Blackhole blackhole) { 65 | blackhole.consume(formatter.format(VALUES[0], 66 | VALUES[1])); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /example/src/jmh/java/com/github/imasahiro/stringformatter/runtime/benchmark/IntegerUtilsBench.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.runtime.benchmark; 18 | 19 | import static com.github.imasahiro.stringformatter.runtime.integers.IntegerUtils.log10; 20 | 21 | import org.openjdk.jmh.annotations.Benchmark; 22 | import org.openjdk.jmh.infra.Blackhole; 23 | 24 | public class IntegerUtilsBench { 25 | private static final long[] values = { 26 | 1L, 27 | 9L, 28 | 10L, 29 | 11L, 30 | 99L, 31 | 100L, 32 | 500L, 33 | 999L, 34 | 1000L, 35 | 5000L, 36 | 9999L, 37 | 10000L, 38 | 50000L, 39 | 99999L, 40 | 1000000000000000000L, 41 | 5555555555555555555L 42 | }; 43 | 44 | @Benchmark 45 | public void loop(Blackhole bh) { 46 | for (long v : values) { 47 | bh.consume(stringSize(v)); 48 | } 49 | } 50 | 51 | @Benchmark 52 | public void array(Blackhole bh) { 53 | for (long v : values) { 54 | bh.consume(log10(v)); 55 | } 56 | } 57 | 58 | private static int stringSize(long x) { 59 | long p = 10; 60 | 61 | for (int i = 1; i < 19; i++) { 62 | if (x < p) { 63 | return i; 64 | } 65 | p = 10 * p; 66 | } 67 | 68 | return 19; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /example/src/main/java/com/github/imasahiro/stringformatter/example/Example.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.imasahiro.stringformatter.example; 17 | 18 | import com.github.imasahiro.stringformatter.annotation.AutoStringFormatter; 19 | import com.github.imasahiro.stringformatter.annotation.Format; 20 | 21 | /** 22 | * An example usage of {@link AutoStringFormatter}. 23 | */ 24 | @SuppressWarnings({ "checkstyle:UncommentedMain", "checkstyle:HideUtilityClassConstructor" }) 25 | public class Example { 26 | /** 27 | * Entry point. 28 | */ 29 | public static void main(String... args) { 30 | System.out.println(new Example_Formatter().formatTo("Alice", "Bob")); 31 | } 32 | 33 | @AutoStringFormatter 34 | interface Formatter { 35 | @Format("Hi %s, my name is %s.") 36 | String formatTo(String myName, String frientName); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /example/src/main/java/com/github/imasahiro/stringformatter/example/Example2.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.imasahiro.stringformatter.example; 17 | 18 | import com.github.imasahiro.stringformatter.annotation.AutoStringFormatter; 19 | import com.github.imasahiro.stringformatter.annotation.Format; 20 | 21 | /** 22 | * An example usage of {@link AutoStringFormatter}. 23 | */ 24 | @SuppressWarnings({ "checkstyle:UncommentedMain", "checkstyle:HideUtilityClassConstructor" }) 25 | public class Example2 { 26 | 27 | /** 28 | * Entry point. 29 | */ 30 | public static void main(String... args) { 31 | long version = 10; 32 | long mainVersion = (version - 1) / 100 + 1; 33 | long minorVersion = (version - 1) % 100; 34 | System.out.println(new Example2_Formatter().format(mainVersion, minorVersion)); 35 | } 36 | 37 | @AutoStringFormatter 38 | interface Formatter { 39 | @Format("V%d.%02d") 40 | String format(long major, long minor); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /example/src/main/java/com/github/imasahiro/stringformatter/example/FormatHexId.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.example; 18 | 19 | import com.github.imasahiro.stringformatter.annotation.AutoStringFormatter; 20 | import com.github.imasahiro.stringformatter.annotation.Format; 21 | 22 | /** 23 | * An example usage of {@link AutoStringFormatter}. 24 | */ 25 | @SuppressWarnings({ "checkstyle:UncommentedMain", "checkstyle:HideUtilityClassConstructor" }) 26 | public class FormatHexId { 27 | /** 28 | * Entry point. 29 | */ 30 | public static void main(String... args) { 31 | long upperId = 0x0123456789abcdefL; 32 | long lowerId = 0x0123456789abcdefL; 33 | System.out.println(new FormatHexId_Formatter().formatTo(upperId, lowerId)); 34 | } 35 | 36 | @AutoStringFormatter 37 | interface Formatter { 38 | @Format(value = "%016x%016x", capacity = 32) 39 | String formatTo(long upper, long lower); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /example/src/main/java/com/github/imasahiro/stringformatter/example/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.example; 18 | -------------------------------------------------------------------------------- /example/src/main/java/com/github/imasahiro/stringformatter/processor/benchmark/AllTypesBenchFormatter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.processor.benchmark; 18 | 19 | import com.github.imasahiro.stringformatter.annotation.AutoStringFormatter; 20 | import com.github.imasahiro.stringformatter.annotation.Format; 21 | 22 | /** 23 | * Formatter definition for integer to string formatting. 24 | */ 25 | public final class AllTypesBenchFormatter { 26 | public static final String FORMAT = "Benchmark - %s %s %s %s %s %s %s %s"; 27 | 28 | private AllTypesBenchFormatter() { 29 | } 30 | 31 | @AutoStringFormatter 32 | interface Formatter { 33 | @Format(FORMAT) 34 | String format( 35 | boolean b, char c, double d, float f, int i, long lng, Object obj, String str); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /example/src/main/java/com/github/imasahiro/stringformatter/processor/benchmark/CapacityBenchFormatter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.processor.benchmark; 18 | 19 | import com.github.imasahiro.stringformatter.annotation.AutoStringFormatter; 20 | import com.github.imasahiro.stringformatter.annotation.Format; 21 | 22 | /** 23 | * Definition formatters to benchmark with/without capacity. 24 | */ 25 | public final class CapacityBenchFormatter { 26 | public static final String FORMAT = "%32d%32d%32d%32d%32d%32d"; 27 | 28 | private CapacityBenchFormatter() { 29 | } 30 | 31 | @AutoStringFormatter 32 | interface Formatter { 33 | @Format(value = FORMAT) 34 | String format(int a, int b, int c, int d, int e, int g); 35 | } 36 | 37 | @AutoStringFormatter 38 | interface FormatterWithCapacity { 39 | @Format(value = FORMAT, capacity = 32 * 6) 40 | String format(int a, int b, int c, int d, int e, int g); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /example/src/main/java/com/github/imasahiro/stringformatter/processor/benchmark/IntegerStringifyBenchFormatter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.processor.benchmark; 18 | 19 | import com.github.imasahiro.stringformatter.annotation.AutoStringFormatter; 20 | import com.github.imasahiro.stringformatter.annotation.Format; 21 | 22 | /** 23 | * Definition of formatter for benchmarking integer to string. 24 | */ 25 | public final class IntegerStringifyBenchFormatter { 26 | public static final String FORMAT = "%d + %d * %d = %d"; 27 | 28 | private IntegerStringifyBenchFormatter() { 29 | } 30 | 31 | @AutoStringFormatter 32 | interface Formatter { 33 | @Format(FORMAT) 34 | String format(int a, int b, int c, int d); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example/src/main/java/com/github/imasahiro/stringformatter/processor/benchmark/StringBenchFormatter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.processor.benchmark; 18 | 19 | import com.github.imasahiro.stringformatter.annotation.AutoStringFormatter; 20 | import com.github.imasahiro.stringformatter.annotation.Format; 21 | 22 | /** 23 | * Formatter definition for integer to string formatting. 24 | */ 25 | public final class StringBenchFormatter { 26 | public static final String FORMAT = "Hi %s; Hi to you %s"; 27 | 28 | private StringBenchFormatter() { 29 | } 30 | 31 | @AutoStringFormatter 32 | interface Formatter { 33 | @Format(FORMAT) 34 | String format(int thisName, int otherName); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example/src/main/java/com/github/imasahiro/stringformatter/processor/benchmark/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.processor.benchmark; 18 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | group=com.github.imasahiro 2 | version=0.6.2 3 | projectName=auto-string-formatter 4 | projectUrl=https://github.com/imasahiro/auto-string-formatter/ 5 | projectDescription=auto-string-formatter 6 | authorName=Masahiro Ide 7 | authorEmail=imasahiro9@gmail.com 8 | authorUrl=https://github.com/imasahiro/ 9 | inceptionYear=2018 10 | licenseName=The Apache License, Version 2.0 11 | licenseUrl=https://www.apache.org/license/LICENSE-2.0.txt 12 | scmUrl=https://github.com/imasahiro/auto-string-formatter/ 13 | scmConnection=scm:git:https://github.com/imasahiro/auto-string-formatter.git 14 | scmDeveloperConnection=scm:git:ssh://git@github.com/auto-string-formatter.git 15 | publishUrlForRelease=https://oss.sonatype.org/service/local/staging/deploy/maven2/ 16 | publishUrlForSnapshot=https://oss.sonatype.org/content/repositories/snapshots/ 17 | publishUsernameProperty=ossrhUsername 18 | publishPasswordProperty=ossrhPassword 19 | versionPattern=^[0-9]+\\.[0-9]+\\.[0-9]+$ 20 | -------------------------------------------------------------------------------- /gradle/scripts/.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # https://help.github.com/articles/about-codeowners/ 2 | 3 | * @hyangtack @minwoox @trustin 4 | 5 | -------------------------------------------------------------------------------- /gradle/scripts/.gitrepo: -------------------------------------------------------------------------------- 1 | ; DO NOT EDIT (unless you know what you are doing) 2 | ; 3 | ; This subdirectory is a git "subrepo", and this file is maintained by the 4 | ; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme 5 | ; 6 | [subrepo] 7 | remote = git@github.com:line/gradle-scripts.git 8 | branch = master 9 | commit = ac1bd4b2b7686b82956b1a2c0cce154dc926c9ba 10 | parent = 87be3554d8b2d8bef5e10ceff1a1a13a4f27c971 11 | cmdver = 0.3.1 12 | -------------------------------------------------------------------------------- /gradle/scripts/build-flags.gradle: -------------------------------------------------------------------------------- 1 | def libDir = new File(buildscript.sourceFile.parentFile, 'lib') 2 | 3 | apply from: "${libDir}/prerequisite.gradle" 4 | apply from: "${libDir}/common-dependencies.gradle" 5 | apply from: "${libDir}/common-git.gradle" 6 | apply from: "${libDir}/common-info.gradle" 7 | apply from: "${libDir}/common-publish.gradle" 8 | apply from: "${libDir}/common-release.gradle" 9 | apply from: "${libDir}/common-wrapper.gradle" 10 | apply from: "${libDir}/common-misc.gradle" 11 | 12 | if (!projectsWithFlags('bom').isEmpty()) { 13 | apply from: "${libDir}/bom.gradle" 14 | } 15 | 16 | if (!projectsWithFlags('java').isEmpty()) { 17 | apply from: "${libDir}/java.gradle" 18 | apply from: "${libDir}/java-javadoc.gradle" 19 | apply from: "${libDir}/java-coverage.gradle" 20 | apply from: "${libDir}/java-alpn.gradle" 21 | apply from: "${libDir}/java-rpc-thrift.gradle" 22 | 23 | if (projectsWithFlags('java').find { it.ext.hasSourceDirectory('proto') }) { 24 | if (!managedVersions.containsKey('com.google.protobuf:protobuf-gradle-plugin')) { 25 | throw new IllegalStateException( 26 | "com.google.protobuf:protobuf-gradle-plugin must be specified in dependencies.yml " + 27 | "to compile .proto files.") 28 | } 29 | apply from: "${libDir}/java-rpc-proto.gradle" 30 | } 31 | 32 | if (!projectsWithFlags('java', 'relocate').isEmpty()) { 33 | ['com.github.jengelman.gradle.plugins:shadow', 'net.sf.proguard:proguard-gradle'].each { 34 | if (!managedVersions.containsKey(it)) { 35 | throw new IllegalStateException( 36 | "${it} must be specified in dependencies.yml to use the 'relocate' flag.") 37 | } 38 | } 39 | apply from: "${libDir}/java-shade.gradle" 40 | } 41 | 42 | apply from: "${libDir}/java-versionprops.gradle" 43 | apply from: "${libDir}/java-publish.gradle" 44 | } 45 | -------------------------------------------------------------------------------- /gradle/scripts/lib/bom.gradle: -------------------------------------------------------------------------------- 1 | import groovy.xml.QName 2 | 3 | configure(projectsWithFlags('bom')) { 4 | apply plugin: 'base' 5 | 6 | dependencyManagement { 7 | generatedPomCustomization { 8 | enabled = true 9 | } 10 | } 11 | 12 | publishing { 13 | publications { 14 | bom(MavenPublication) { 15 | artifactId project.ext.artifactId 16 | pom.packaging = 'pom' 17 | // Generate the POM. 18 | pom.withXml { 19 | findChildNode(asNode(), 'packaging') + { 20 | resolveStrategy = DELEGATE_FIRST 21 | 22 | // Write the elements required by OSSRH. 23 | name "${project.ext.projectName} (${project.ext.artifactId})" 24 | description "${project.ext.projectDescription} (${project.ext.artifactId})" 25 | url "${project.ext.projectUrl}" 26 | inceptionYear "${project.ext.inceptionYear}" 27 | 28 | licenses { 29 | license { 30 | name "${project.ext.licenseName}" 31 | url "${project.ext.licenseUrl}" 32 | distribution 'repo' 33 | } 34 | } 35 | 36 | developers { 37 | developer { 38 | name "${project.ext.authorName}" 39 | email "${project.ext.authorEmail}" 40 | url "${project.ext.authorUrl}" 41 | } 42 | } 43 | 44 | scm { 45 | url "${project.ext.scmUrl}" 46 | connection "${project.ext.scmConnection}" 47 | developerConnection "${project.ext.scmDeveloperConnection}" 48 | } 49 | } 50 | 51 | // Find (or create) the 'dependencyManagement' section. 52 | Node dependencyMgmt = findChildNode(asNode(), 'dependencyManagement') 53 | if (dependencyMgmt == null) { 54 | findChildNode(asNode(), 'scm') + { 55 | resolveStrategy = DELEGATE_FIRST 56 | dependencyManagement {} 57 | } 58 | dependencyMgmt = findChildNode(asNode(), 'dependencyManagement') 59 | } 60 | 61 | // Replace the `dependencyManagement` section populated by dependency-management-plugin 62 | // or created by ourselves above. 63 | dependencyMgmt.replaceNode { 64 | resolveStrategy = DELEGATE_FIRST 65 | dependencyManagement { 66 | dependencies { 67 | projectsWithFlags('java', 'publish').toList().sort { a, b -> 68 | def groupComparison = "${a.group}".compareTo("${b.group}") 69 | if (groupComparison != 0) { 70 | return groupComparison 71 | } 72 | return "${a.ext.artifactId}".compareTo("${b.ext.artifactId}") 73 | }.each { p -> 74 | dependency { 75 | groupId "${p.group}" 76 | artifactId "${p.ext.artifactId}" 77 | version "${p.version}" 78 | } 79 | } 80 | } 81 | } 82 | } 83 | } 84 | } 85 | } 86 | } 87 | 88 | if (project.ext.isSigning()) { 89 | signing { 90 | sign publishing.publications 91 | } 92 | } 93 | 94 | tasks.assemble.dependsOn { 95 | tasks.generatePomFileForBomPublication 96 | } 97 | } 98 | 99 | private static Node findChildNode(Node parent, String childName) { 100 | return parent.children().find { 101 | def name = it.name() 102 | if (name instanceof QName) { 103 | name = name.localPart 104 | } else { 105 | name = name.toString() 106 | } 107 | 108 | return name == childName 109 | } as Node 110 | } 111 | -------------------------------------------------------------------------------- /gradle/scripts/lib/common-dependencies.gradle: -------------------------------------------------------------------------------- 1 | import org.yaml.snakeyaml.Yaml 2 | 3 | buildscript { 4 | repositories { 5 | mavenCentral() 6 | jcenter() 7 | } 8 | 9 | dependencies { 10 | // These should be the only dependencies that need hard-coded versions. 11 | classpath 'com.github.ben-manes:gradle-versions-plugin:0.21.0' 12 | classpath 'io.spring.gradle:dependency-management-plugin:1.0.7.RELEASE' 13 | classpath 'org.yaml:snakeyaml:1.24' 14 | } 15 | } 16 | 17 | apply plugin: com.github.benmanes.gradle.versions.VersionsPlugin 18 | 19 | rootProject.ext { 20 | def dependenciesYamlFile = new File("${project.projectDir}/dependencies.yml") 21 | if (dependenciesYamlFile.exists()) { 22 | dependenciesYaml = Collections.unmodifiableMap(new Yaml().loadAs(dependenciesYamlFile.text, Map)) 23 | } else { 24 | dependenciesYaml = Collections.emptyMap() 25 | } 26 | } 27 | 28 | allprojects { 29 | apply plugin: 'io.spring.dependency-management' 30 | 31 | dependencyManagement { 32 | generatedPomCustomization { 33 | enabled = false 34 | } 35 | 36 | imports { 37 | if (rootProject.ext.dependenciesYaml.containsKey('boms')) { 38 | dependenciesYaml['boms'].each { 39 | mavenBom "${it}" 40 | } 41 | } 42 | } 43 | 44 | dependencies { 45 | rootProject.ext.dependenciesYaml.forEach { String key, value -> 46 | if (key != 'boms') { 47 | def groupId = key 48 | def artifact = value as Map 49 | artifact.forEach { String artifactId, Map props -> 50 | if (props.containsKey('version')) { 51 | dependency("${groupId}:${artifactId}:${props['version']}") { 52 | if (props.containsKey('exclusions')) { 53 | props['exclusions'].each { String spec -> 54 | exclude spec 55 | } 56 | } 57 | } 58 | } 59 | } 60 | } 61 | } 62 | } 63 | } 64 | 65 | ext { 66 | managedVersions = dependencyManagement.managedVersions 67 | } 68 | } 69 | 70 | // Create a new configuration called 'allDependencies'. 71 | rootProject.configurations { 72 | allDependencies { 73 | visible = false 74 | transitive = false 75 | } 76 | } 77 | 78 | // Add all dependencies declared in 'dependencies.yml' to 'allDependencies' because otherwise 79 | // 'dependencyUpdates' task will not check all dependencies. 80 | rootProject.dependencies { 81 | rootProject.ext.dependenciesYaml.forEach { String key, value -> 82 | if (key != 'boms') { 83 | def groupId = key 84 | def artifact = value as Map 85 | artifact.forEach { String artifactId, Map props -> 86 | if (props.containsKey('version')) { 87 | allDependencies("${groupId}:${artifactId}") 88 | } 89 | } 90 | } 91 | } 92 | } 93 | 94 | rootProject.ext { 95 | // Build the relocation table from dependencies.yml. 96 | relocations = dependenciesYaml.entrySet().inject([]) { list, Map.Entry entry -> 97 | if (entry.key != 'boms') { 98 | def groupId = entry.key 99 | entry.value.forEach { String artifactId, Map props -> 100 | if (props.containsKey('relocations')) { 101 | props.get('relocations').each { 102 | list.add([ 103 | name: "${groupId}:${artifactId}", 104 | from: it['from'], 105 | to: it['to']]) 106 | } 107 | } 108 | } 109 | } 110 | return list 111 | } 112 | 113 | // Build the Javadoc link table from dependencies.yml. 114 | javadocLinks = dependenciesYaml.entrySet().inject([]) { list, Map.Entry entry -> 115 | if (entry.key != 'boms') { 116 | def groupId = entry.key 117 | entry.value.forEach { String artifactId, Map props -> 118 | if (props.containsKey('javadocs')) { 119 | def version = String.valueOf(managedVersions["${groupId}:${artifactId}"]) 120 | props['javadocs'].each { url -> 121 | list.add([ 122 | groupId: groupId, 123 | artifactId: artifactId, 124 | version: version, 125 | url: url 126 | ]) 127 | } 128 | } 129 | } 130 | } 131 | return list 132 | } 133 | } 134 | 135 | subprojects { 136 | ext { 137 | dependenciesYaml = rootProject.ext.dependenciesYaml 138 | relocations = rootProject.ext.relocations 139 | javadocLinks = rootProject.ext.javadocLinks 140 | } 141 | } 142 | 143 | configure(rootProject) { 144 | task managedVersions( 145 | group: 'Build', 146 | description: 'Generates the file that contains dependency versions.') { 147 | inputs.properties dependencyManagement.managedVersions 148 | doLast { 149 | def f = file("${project.buildDir}/managed_versions.yml") 150 | f.parentFile.mkdir() 151 | f.withWriter('UTF-8') { 152 | new Yaml().dump(dependencyManagement.managedVersions, it) 153 | } 154 | } 155 | } 156 | } 157 | 158 | // Ignore release candidates when checking dependency updates. 159 | rootProject.tasks.dependencyUpdates.resolutionStrategy { 160 | componentSelection { rules -> 161 | rules.all { ComponentSelection selection -> 162 | boolean rejected = ['alpha', 'beta', 'rc', 'cr', 'm', 'preview'].any { qualifier -> 163 | selection.candidate.version ==~ /(?i).*[.-]${qualifier}[.\d-]*/ 164 | } 165 | if (rejected) { 166 | selection.reject('Release candidate') 167 | } 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /gradle/scripts/lib/common-git.gradle: -------------------------------------------------------------------------------- 1 | import static org.gradle.internal.os.OperatingSystem.* 2 | 3 | rootProject.ext { 4 | // The path to the 'git' command 5 | gitPath = getGitPath() 6 | executeGit = this.&executeGit.curry(rootProject) 7 | 8 | // The Git repository status 9 | repoStatus = getRepoStatus().asImmutable() 10 | } 11 | 12 | // Copy the properties to all projects for easier access. 13 | subprojects { 14 | ext { 15 | gitPath = rootProject.ext.gitPath 16 | executeGit = this.&executeGit.curry(project) 17 | repoStatus = rootProject.ext.repoStatus 18 | } 19 | } 20 | 21 | private def getGitPath() { 22 | if (rootProject.hasProperty('gitPath')) { 23 | return rootProject.property('gitPath').toString() 24 | } 25 | 26 | // Find the location of the 'git' command. 27 | try { 28 | if (current() == WINDOWS) { 29 | return executeCommand('where.exe', 'git.exe') 30 | } else { 31 | return executeCommand('which', 'git') 32 | } 33 | } catch (e) { 34 | logger.warn("Git not available: ${e}", e) 35 | return null 36 | } 37 | } 38 | 39 | private def getRepoStatus() { 40 | // Make sure this method is executed only once during the build. 41 | assert !rootProject.hasProperty('repoStatus') 42 | 43 | // The default values taken from Netty. 44 | def result = [ 45 | version : project.version, 46 | longCommitHash : '0000000000000000000000000000000000000000', 47 | shortCommitHash : '0000000', 48 | commitDate : '1970-01-01 00:00:00 +0000', 49 | repoStatus : 'unknown' 50 | ] 51 | 52 | // Make sure 'git' is available. 53 | if (gitPath == null) { 54 | return result 55 | } 56 | 57 | // Do not run 'git' if the project is not from a Git repository. 58 | if (!rootProject.file("${rootProject.projectDir}/.git").isDirectory()) { 59 | return result 60 | } 61 | 62 | // Retrieve the repository status from the Git repository. 63 | try { 64 | def gitLogOut = project.ext.executeGit('log', '-1', '--format=format:%h%x20%H%x20%cd', '--date=iso') 65 | if (gitLogOut) { 66 | logger.info("Latest commit: ${gitLogOut}") 67 | def tokens = gitLogOut.tokenize(' ') 68 | result.shortCommitHash = tokens[0] 69 | result.longCommitHash = tokens[1] 70 | result.commitDate = tokens[2..4].join(' ') 71 | } 72 | 73 | def gitStatusOut = project.ext.executeGit('status', '--porcelain') 74 | if (!gitStatusOut.empty) { 75 | result.repoStatus = 'dirty' 76 | logger.info("Repository is dirty:${System.lineSeparator()}${gitStatusOut}") 77 | } else { 78 | result.repoStatus = 'clean' 79 | } 80 | } catch (e) { 81 | logger.warn('Failed to retrieve the repository status:', e) 82 | } 83 | 84 | return result 85 | } 86 | 87 | static String executeGit(Project project, ...args) { 88 | def command = [project.ext.gitPath] 89 | args.each { command += it.toString() } 90 | return executeCommand(command.toArray(new String[command.size()])) 91 | } 92 | 93 | private static String executeCommand(...command) { 94 | def cmd = [] 95 | command.each { cmd += it.toString() } 96 | 97 | def proc = cmd.execute() 98 | proc.waitFor() 99 | if (proc.exitValue() != 0) { 100 | throw new IOException( 101 | "'${command}' exited with a non-zero exit code: ${proc.exitValue()}:" + 102 | "${System.lineSeparator()}${proc.err.text}") 103 | } 104 | 105 | return proc.in.text.trim() 106 | } 107 | -------------------------------------------------------------------------------- /gradle/scripts/lib/common-info.gradle: -------------------------------------------------------------------------------- 1 | import java.time.LocalDateTime 2 | 3 | rootProject.ext { 4 | projectName = project.findProperty('projectName') 5 | projectUrl = project.findProperty('projectUrl') 6 | projectDescription = project.findProperty('projectDescription')?: rootProject.name 7 | authorName = project.findProperty('authorName')?: 'LINE Corporation' 8 | authorEmail = project.findProperty('authorEmail')?: 'dl_oss_dev@linecorp.com' 9 | authorUrl = project.findProperty('authorUrl')?: 'https://engineering.linecorp.com/en/' 10 | inceptionYear = project.findProperty('inceptionYear') 11 | licenseName = project.findProperty('licenseName') 12 | licenseUrl = project.findProperty('licenseUrl') 13 | scmUrl = project.findProperty('scmUrl') 14 | scmConnection = project.findProperty('scmConnection') 15 | scmDeveloperConnection = project.findProperty('scmDeveloperConnection') 16 | copyrightFooter = 17 | '© Copyright ' + "${rootProject.ext.inceptionYear}–${LocalDateTime.now().year} " + 18 | '' + rootProject.ext.authorName + '. ' + 19 | 'All rights reserved.' 20 | } 21 | 22 | subprojects { 23 | ext { 24 | projectName = rootProject.ext.projectName 25 | projectUrl = rootProject.ext.projectUrl 26 | projectDescription = rootProject.ext.projectDescription 27 | authorName = rootProject.ext.authorName 28 | authorEmail = rootProject.ext.authorEmail 29 | authorUrl = rootProject.ext.authorUrl 30 | inceptionYear = rootProject.ext.inceptionYear 31 | licenseName = rootProject.ext.licenseName 32 | licenseUrl = rootProject.ext.licenseUrl 33 | scmUrl = rootProject.ext.scmUrl 34 | scmConnection = rootProject.ext.scmConnection 35 | scmDeveloperConnection = rootProject.ext.scmDeveloperConnection 36 | copyrightFooter = rootProject.ext.copyrightFooter 37 | } 38 | } 39 | 40 | allprojects { 41 | ext { 42 | artifactId = { 43 | // Use the overridden one if available. 44 | if (rootProject.ext.has('artifactIdOverrides')) { 45 | def overrides = rootProject.ext.artifactIdOverrides 46 | if (!(overrides instanceof Map)) { 47 | throw new IllegalStateException("artifactIdOverrides must be a Map: ${overrides}") 48 | } 49 | 50 | for (Map.Entry e : overrides.entrySet()) { 51 | if (rootProject.project(e.key) == project) { 52 | return e.value 53 | } 54 | } 55 | } 56 | 57 | // Generate from the project names otherwise. 58 | List chain = [] 59 | Project p = project 60 | for (;;) { 61 | chain += p 62 | p = p.parent 63 | if (p == null) { 64 | break 65 | } 66 | } 67 | 68 | return String.join('-', chain.reverse().collect { it.name }) 69 | }.call() 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /gradle/scripts/lib/common-misc.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | ext { 3 | hasSourceDirectory = this.&hasSourceDirectory.curry(project) 4 | } 5 | } 6 | 7 | static boolean hasSourceDirectory(Project project, String name) { 8 | return new File(project.projectDir, 'src').listFiles(new FileFilter() { 9 | @Override 10 | boolean accept(File pathname) { 11 | return pathname.isDirectory() 12 | } 13 | }).find { 14 | dir -> new File(dir, name).isDirectory() 15 | } != null 16 | } 17 | -------------------------------------------------------------------------------- /gradle/scripts/lib/common-publish.gradle: -------------------------------------------------------------------------------- 1 | def isReleaseVersion = !(rootProject.version.endsWith('-SNAPSHOT')) 2 | def publishSignatureRequired = 'true' == rootProject.findProperty('publishSignatureRequired') && 3 | 'true' != System.getenv('JITPACK') && 4 | !rootProject.hasProperty('nosign') 5 | def publishUrlForRelease = rootProject.findProperty('publishUrlForRelease') 6 | def publishUrlForSnapshot = rootProject.findProperty('publishUrlForSnapshot') 7 | def publishUsernameProperty = rootProject.findProperty('publishUsernameProperty') 8 | def publishPasswordProperty = rootProject.findProperty('publishPasswordProperty') 9 | 10 | if (publishSignatureRequired) { 11 | apply plugin: 'signing' 12 | } 13 | 14 | rootProject.ext { 15 | isPublishing = { gradle.taskGraph.allTasks.find { it.name =~ /(?:^|:)publish[^:]*ToMaven[^:]*$/ } != null } 16 | if (publishSignatureRequired) { 17 | isSigning = { 18 | rootProject.signing.signatory != null && 19 | (isReleaseVersion || rootProject.hasProperty('sign')) 20 | } 21 | } else { 22 | isSigning = { false } 23 | } 24 | 25 | if (publishSignatureRequired) { 26 | gradle.taskGraph.whenReady { 27 | // Do *NOT* proceed if a user is publishing a release version and signatory is missing. 28 | if (rootProject.ext.isPublishing() && 29 | isReleaseVersion && 30 | rootProject.signing.signatory == null) { 31 | 32 | throw new IllegalStateException( 33 | 'Cannot publish a release version without a GPG key; missing signatory. ' + 34 | 'Use "-Pnosign" option to disable PGP signing.') 35 | } 36 | } 37 | } 38 | } 39 | 40 | subprojects { 41 | ext { 42 | isPublishing = rootProject.ext.isPublishing 43 | isSigning = rootProject.ext.isSigning 44 | } 45 | } 46 | 47 | 48 | configure(projectsWithFlags('publish')) { 49 | apply plugin: 'maven-publish' 50 | 51 | if (project.ext.isSigning()) { 52 | apply plugin: 'signing' 53 | 54 | signing { 55 | required { true } 56 | } 57 | } 58 | 59 | publishing { 60 | repositories { 61 | maven { 62 | if (project.hasProperty('publishUrl')) { 63 | url project.findProperty('publishUrl') 64 | } else if (isReleaseVersion) { 65 | url publishUrlForRelease 66 | } else { 67 | url publishUrlForSnapshot 68 | } 69 | 70 | credentials { 71 | username = project.findProperty(publishUsernameProperty) 72 | password = project.findProperty(publishPasswordProperty) 73 | } 74 | } 75 | } 76 | } 77 | 78 | task install( 79 | group: 'Publishing', 80 | description: 'An alias of publishToMavenLocal', 81 | dependsOn: tasks.publishToMavenLocal) 82 | } 83 | -------------------------------------------------------------------------------- /gradle/scripts/lib/common-release.gradle: -------------------------------------------------------------------------------- 1 | import groovy.text.SimpleTemplateEngine 2 | 3 | import java.util.regex.Pattern 4 | 5 | rootProject.ext { 6 | versionPattern = rootProject.findProperty('versionPattern')?: /^[0-9]+\.[0-9]+\.[0-9]+$/ 7 | } 8 | 9 | task release( 10 | group: 'Publishing', 11 | description: 'Releases a new version.') { 12 | 13 | doLast { 14 | if (gitPath == null) { 15 | throw new IllegalStateException('Git is not available.') 16 | } 17 | 18 | if (!project.hasProperty('releaseVersion') || !project.hasProperty('nextVersion')) { 19 | throw new InvalidUserDataException( 20 | "Specify 'releaseVersion' and 'nextVersion' properties.${System.lineSeparator()}" + 21 | 'e.g. ./gradlew release -PreleaseVersion=0.1.2 -PnextVersion=0.1.3') 22 | } 23 | 24 | def releaseVersion = project.property('releaseVersion') 25 | def nextVersion = project.property('nextVersion') 26 | 27 | // Validate the specified version numbers. 28 | if (!(releaseVersion =~ rootProject.ext.versionPattern)) { 29 | throw new InvalidUserDataException( 30 | "invalid release version: ${releaseVersion} (expected: ${rootProject.ext.versionPattern})") 31 | } 32 | if (!(nextVersion =~ rootProject.ext.versionPattern)) { 33 | throw new InvalidUserDataException( 34 | "invalid next version: ${nextVersion} (expected: ${rootProject.ext.versionPattern})") 35 | } 36 | 37 | // Ensure the repository is upstream. 38 | def repoUri = "${project.ext.executeGit('config', '--get', 'remote.origin.url').trim()}" 39 | def upstreamRepoUri = getUpstreamRepoUri() 40 | def upstreamRepoHost = upstreamRepoUri.host?: '' 41 | def upstreamRepoPath = upstreamRepoUri.path?: '/' 42 | def originIsUpstream 43 | if (repoUri.contains('://')) { 44 | try { 45 | def parsedRepoUri = URI.create(repoUri) 46 | originIsUpstream = 47 | upstreamRepoHost == parsedRepoUri.host && 48 | upstreamRepoPath == parsedRepoUri.path 49 | } catch (ignored) { 50 | originIsUpstream = false 51 | } 52 | } else { 53 | def matcher = Pattern.compile('^(?:[^@]*@)?([^:/]+)[:/]?(.*)$').matcher(repoUri) 54 | originIsUpstream = matcher.matches() && 55 | matcher.group(1) == upstreamRepoHost && 56 | "/${matcher.group(2)}" == upstreamRepoPath 57 | } 58 | 59 | if (!originIsUpstream) { 60 | throw new InvalidUserDataException( 61 | "Release must be performed at the upstream repository: ${upstreamRepoHost}${upstreamRepoPath}") 62 | } 63 | 64 | // Ensure the repository is clean. 65 | def gitStatusOut = project.ext.executeGit('status', '--porcelain') 66 | if (!gitStatusOut.empty) { 67 | throw new IllegalStateException( 68 | "Git repository is not clean:${System.lineSeparator()}${gitStatusOut}") 69 | } 70 | 71 | def tag = project.findProperty("tag") 72 | if (tag == null) { 73 | tag = "$rootProject.name-$releaseVersion" 74 | } 75 | 76 | def gradlePropsFile = project.file("${project.projectDir}/gradle.properties") 77 | def gradlePropsContent = gradlePropsFile.getText('ISO-8859-1') 78 | def versionPattern = /\nversion=[^\r\n]*(\r?\n)/ 79 | if (!(gradlePropsContent =~ versionPattern)) { 80 | throw new IllegalStateException( 81 | "Cannot find a valid 'version' property from gradle.properties"); 82 | } 83 | 84 | // Update the version to the release version, commit and tag. 85 | gradlePropsFile.write(gradlePropsContent.replaceFirst(versionPattern, "\nversion=${releaseVersion}\$1"), 86 | 'ISO-8859-1') 87 | project.ext.executeGit('add', gradlePropsFile.toString()) 88 | project.ext.executeGit('commit', '-m', "Release $tag") 89 | project.ext.executeGit('tag', tag) 90 | 91 | // Update the version to the next version and commit. 92 | def actualNextVersion = "${nextVersion}-SNAPSHOT" 93 | project.ext.executeGit('reset', '--hard', 'HEAD^') 94 | gradlePropsFile.write(gradlePropsContent.replaceFirst(versionPattern, "\nversion=${actualNextVersion}\$1"), 95 | 'ISO-8859-1') 96 | project.ext.executeGit('add', gradlePropsFile.toString()) 97 | project.ext.executeGit('commit', '-m', "Update the project version to ${actualNextVersion}") 98 | 99 | // Push the commits and tags. 100 | project.ext.executeGit('push', 'origin') 101 | project.ext.executeGit('push', 'origin', tag) 102 | 103 | println() 104 | println "Tagged: ${tag}" 105 | 106 | def postReleaseMessageFile = new File("${rootDir}", '.post-release-msg') 107 | if (postReleaseMessageFile.exists()) { 108 | println '-' * (tag.length() + 8) 109 | print new SimpleTemplateEngine().createTemplate(postReleaseMessageFile).make([tag: tag, releaseVersion: releaseVersion]) 110 | } 111 | } 112 | } 113 | 114 | def getUpstreamRepoUri() { 115 | int schemeEndIndex = project.ext.scmDeveloperConnection.indexOf('://') 116 | if (schemeEndIndex <= 0) { 117 | rejectScmDeveloperConnection() 118 | } 119 | int schemeStartIndex = project.ext.scmDeveloperConnection.lastIndexOf(':', schemeEndIndex - 1) 120 | try { 121 | if (schemeStartIndex >= 0) { 122 | return URI.create(project.ext.scmDeveloperConnection.substring(schemeStartIndex + 1)) 123 | } else { 124 | return URI.create(project.ext.scmDeveloperConnection) 125 | } 126 | } catch (ignored) { 127 | rejectScmDeveloperConnection() 128 | } 129 | } 130 | 131 | def rejectScmDeveloperConnection() { 132 | throw new IllegalStateException( 133 | "scmDeveloperConnection must be a URI with scheme and authority: ${project.ext.scmDeveloperConnection}") 134 | } 135 | -------------------------------------------------------------------------------- /gradle/scripts/lib/common-wrapper.gradle: -------------------------------------------------------------------------------- 1 | tasks.withType(Wrapper) { 2 | distributionType = Wrapper.DistributionType.ALL 3 | } 4 | -------------------------------------------------------------------------------- /gradle/scripts/lib/java-alpn.gradle: -------------------------------------------------------------------------------- 1 | // JDK 9 does not require Jetty ALPN agent at all. 2 | if (JavaVersion.current() >= JavaVersion.VERSION_1_9) { 3 | return 4 | } 5 | 6 | configure(projectsWithFlags('java')) { 7 | // Use Jetty ALPN agent if dependencyManagement mentions it. 8 | if (managedVersions.containsKey('org.mortbay.jetty.alpn:jetty-alpn-agent')) { 9 | configurations { 10 | alpnAgent { 11 | visible = false 12 | } 13 | } 14 | 15 | dependencies { 16 | alpnAgent 'org.mortbay.jetty.alpn:jetty-alpn-agent' 17 | } 18 | 19 | task copyAlpnAgent(type: Copy) { 20 | from configurations.alpnAgent 21 | into "${rootProject.buildDir}" 22 | rename { String fileName -> 23 | fileName.replaceFirst("-[0-9]+\\.[0-9]+\\.[0-9]+(?:\\.[^\\.]+)?\\.jar", ".jar") 24 | } 25 | } 26 | 27 | tasks.withType(JavaForkOptions) { 28 | dependsOn tasks.copyAlpnAgent 29 | jvmArgs "-javaagent:${rootProject.buildDir}/jetty-alpn-agent.jar" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /gradle/scripts/lib/java-coverage.gradle: -------------------------------------------------------------------------------- 1 | // Enable JaCoCo test coverage when '-Pcoverage' option is specified. 2 | def jacocoEnabled = project.hasProperty('coverage') 3 | if (!jacocoEnabled) { 4 | return 5 | } 6 | 7 | // Collect JaCoCo execution data for all modules. 8 | configure(projectsWithFlags('java')) { 9 | if (project.hasFlags('no_aggregation')) { 10 | return; 11 | } 12 | 13 | apply plugin: 'jacoco' 14 | 15 | project.addFlags('coverage') 16 | 17 | tasks.withType(Test) { 18 | jacoco { 19 | enabled = true 20 | } 21 | } 22 | 23 | // Do not generate per-module report; generate aggregated report only. 24 | tasks.jacocoTestReport.configure { 25 | onlyIf { false } 26 | } 27 | } 28 | 29 | // Generate JaCoCo report from the collected execution data. 30 | configure(rootProject) { 31 | configurations { 32 | jacocoAnt { 33 | visible = false 34 | } 35 | } 36 | 37 | dependencies { 38 | jacocoAnt "org.jacoco:org.jacoco.ant:${JacocoPlugin.DEFAULT_JACOCO_VERSION}" 39 | } 40 | 41 | task jacocoTestReport(type: JacocoReport) { 42 | def reportTask = delegate 43 | reports { 44 | csv.enabled = false 45 | xml.enabled = true 46 | xml.destination = file("${rootProject.buildDir}/report/jacoco/jacocoTestReport.xml") 47 | html.enabled = true 48 | html.destination = file("${rootProject.buildDir}/report/jacoco/html") 49 | } 50 | 51 | jacocoClasspath = configurations.jacocoAnt 52 | 53 | afterProjectsWithFlags(['java', 'coverage']) { projects -> 54 | // Set dependencies related with report generation and feed execution data. 55 | projects.each { Project p -> 56 | if (p.hasFlags('relocate')) { 57 | reportTask.dependsOn(p.tasks.shadedClasses) 58 | } 59 | 60 | p.tasks.withType(Test).each { testTask -> 61 | reportTask.dependsOn(testTask) 62 | reportTask.mustRunAfter(testTask) 63 | testTask.finalizedBy(reportTask) 64 | 65 | def dataFile = testTask.extensions.findByType(JacocoTaskExtension.class).destinationFile 66 | reportTask.doFirst { 67 | // Create an empty .exec file just in case the test task did not run. 68 | if (!dataFile.exists()) { 69 | dataFile.parentFile.mkdirs() 70 | try { 71 | dataFile.createNewFile() 72 | } catch (ignored) {} 73 | } 74 | } 75 | 76 | reportTask.executionData(testTask) 77 | } 78 | } 79 | 80 | // Include all sources and classes directories so that the report includes other modules. 81 | sourceDirectories.from = files(projectsWithFlags('java').inject([], { a, b -> 82 | a + b.sourceSets.main.java.srcDirs.findAll { File srcDir -> 83 | // Exclude generated sources. 84 | return !srcDir.path.contains('/gen-src/') && !srcDir.path.contains('\\gen-src\\') 85 | } 86 | })) 87 | classDirectories.from = files(projectsWithFlags('java').inject([], { a, b -> 88 | if (b.hasFlags('no_aggregation')) { 89 | return a 90 | } 91 | if (b.hasFlags('relocate')) { 92 | return a + [b.tasks.shadedClasses.destinationDir] 93 | } 94 | return a + b.sourceSets.main.output.classesDirs 95 | })) 96 | 97 | // Exclude shaded classes from the report. 98 | def exclusions = rootProject.ext.relocations.collect { "/${it['to'].replace('.', '/')}/**" } 99 | def additionalExclusions = rootProject.findProperty('jacocoExclusions'); 100 | if (additionalExclusions) { 101 | additionalExclusions.each { exclusions.add("${it}") } 102 | } 103 | classDirectories.from = files(classDirectories.files.collect { 104 | fileTree(dir: it, exclude: exclusions) 105 | }) 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /gradle/scripts/lib/java-publish.gradle: -------------------------------------------------------------------------------- 1 | configure(projectsWithFlags('publish', 'java')) { 2 | publishing { 3 | publications { 4 | jar(MavenPublication) { 5 | def currentArtifactId = project.ext.artifactId 6 | artifactId currentArtifactId 7 | 8 | // Generate the POM. 9 | pom.withXml { 10 | asNode().children().last() + { 11 | resolveStrategy = Closure.DELEGATE_FIRST 12 | 13 | // Write the elements required by OSSRH. 14 | name "${project.ext.projectName} (${currentArtifactId})" 15 | description "${project.ext.projectDescription} (${currentArtifactId})" 16 | url "${project.ext.projectUrl}" 17 | inceptionYear "${project.ext.inceptionYear}" 18 | 19 | licenses { 20 | license { 21 | name "${project.ext.licenseName}" 22 | url "${project.ext.licenseUrl}" 23 | distribution 'repo' 24 | } 25 | } 26 | 27 | developers { 28 | developer { 29 | name "${project.ext.authorName}" 30 | email "${project.ext.authorEmail}" 31 | url "${project.ext.authorUrl}" 32 | } 33 | } 34 | 35 | scm { 36 | url "${project.ext.scmUrl}" 37 | connection "${project.ext.scmConnection}" 38 | developerConnection "${project.ext.scmDeveloperConnection}" 39 | } 40 | 41 | dependencies { 42 | def writeExclusionRules = { Set excludeRules -> 43 | if (!excludeRules.empty) { 44 | exclusions { 45 | excludeRules.each { rule -> 46 | exclusion { 47 | groupId rule.group 48 | artifactId rule.module 49 | } 50 | } 51 | } 52 | } 53 | } 54 | 55 | // Write compile-time project dependencies. 56 | configurations.compile.dependencies.findAll { 57 | it instanceof ProjectDependency 58 | }.collect { 59 | (ProjectDependency) it 60 | }.toSorted({ 61 | "${it.group}:${it.dependencyProject.ext.artifactId}" 62 | }).each { dep -> 63 | dependency { 64 | groupId dep.group 65 | artifactId dep.dependencyProject.ext.artifactId 66 | version dep.version ?: dep.dependencyProject.version 67 | if (dep.hasProperty('optional') && dep.optional) { 68 | optional true 69 | } 70 | writeExclusionRules(dep.excludeRules) 71 | } 72 | } 73 | 74 | // Write runtime project dependencies. 75 | configurations.runtime.dependencies.findAll { 76 | it instanceof ProjectDependency && 77 | !configurations.compile.dependencies.contains(it) 78 | }.collect { 79 | (ProjectDependency) it 80 | }.toSorted({ 81 | "${it.group}:${it.dependencyProject.ext.artifactId}" 82 | }).each { dep -> 83 | dependency { 84 | groupId dep.group 85 | artifactId dep.dependencyProject.ext.artifactId 86 | version dep.version ?: dep.dependencyProject.version 87 | scope 'runtime' 88 | if (dep.hasProperty('optional') && dep.optional) { 89 | optional true 90 | } 91 | writeExclusionRules(dep.excludeRules) 92 | } 93 | } 94 | 95 | // Write module dependencies. 96 | Set compileDeps = 97 | configurations.compile.resolvedConfiguration.firstLevelModuleDependencies 98 | Set runtimeDeps = 99 | configurations.runtime.resolvedConfiguration.firstLevelModuleDependencies 100 | Set unresolvedDeps = 101 | configurations.runtime.dependencies + configurations.compile.dependencies 102 | 103 | runtimeDeps.toSorted({ "${it.moduleGroup}:${it.moduleName}" }).each { dep -> 104 | def unresolvedDep = unresolvedDeps.find { 105 | it.group == dep.moduleGroup && it.name == dep.moduleName 106 | } 107 | 108 | if (unresolvedDep instanceof ExternalModuleDependency) { 109 | if (project.hasFlags('relocate') && project.ext.relocations.find({ 110 | it.name == "${dep.moduleGroup}:${dep.moduleName}" 111 | })) { 112 | // Shaded dependency 113 | return 114 | } 115 | 116 | dependency { 117 | groupId dep.moduleGroup 118 | artifactId dep.moduleName 119 | version dep.moduleVersion 120 | 121 | // Write classifier if exists. 122 | if (dep.moduleArtifacts.find()?.classifier) { 123 | classifier dep.moduleArtifacts.find().classifier 124 | } 125 | 126 | if (!compileDeps.contains(dep)) { 127 | scope 'runtime' 128 | } 129 | 130 | if (unresolvedDep.hasProperty('optional') && unresolvedDep.optional) { 131 | optional true 132 | } 133 | 134 | writeExclusionRules(unresolvedDep.excludeRules) 135 | } 136 | } 137 | } 138 | } 139 | } 140 | } 141 | 142 | // Find the main JAR and the task that generates it. 143 | File mainJarFile 144 | Task mainJarTask 145 | if (tasks.findByName('trimShadedJar')) { 146 | mainJarFile = tasks.trimShadedJar.outJarFiles.find() as File 147 | mainJarTask = tasks.trimShadedJar 148 | } else if (tasks.findByName('shadedJar')) { 149 | mainJarFile = tasks.shadedJar.archivePath 150 | mainJarTask = tasks.shadedJar 151 | } else { 152 | mainJarFile = tasks.jar.archivePath 153 | mainJarTask = tasks.jar 154 | } 155 | 156 | // Add the main JAR. 157 | artifact(mainJarFile).builtBy(mainJarTask) 158 | 159 | // Add the sources JAR. 160 | artifact(tasks.sourceJar) { 161 | classifier = 'sources' 162 | } 163 | 164 | // Add the Javadoc JAR. 165 | artifact(tasks.javadocJar) { 166 | classifier = 'javadoc' 167 | } 168 | } 169 | } 170 | } 171 | 172 | if (project.ext.isSigning()) { 173 | signing { 174 | sign publishing.publications 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /gradle/scripts/lib/java-rpc-proto.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | dependencies { 6 | classpath "com.google.protobuf:protobuf-gradle-plugin:${managedVersions['com.google.protobuf:protobuf-gradle-plugin']}" 7 | } 8 | } 9 | 10 | configure(projectsWithFlags('java')) { 11 | // Add protobuf/gRPC support if there is src/*/proto 12 | if (project.ext.hasSourceDirectory('proto')) { 13 | apply plugin: com.google.protobuf.gradle.ProtobufPlugin 14 | 15 | protobuf { 16 | generatedFilesBaseDir = project.ext.genSrcDir 17 | if (managedVersions.containsKey('com.google.protobuf:protoc')) { 18 | protoc { 19 | artifact = "com.google.protobuf:protoc:${managedVersions['com.google.protobuf:protoc']}" 20 | } 21 | } 22 | 23 | if (managedVersions.containsKey('io.grpc:grpc-core')) { 24 | plugins { 25 | grpc { 26 | artifact = "io.grpc:protoc-gen-grpc-java:${managedVersions['io.grpc:grpc-core']}" 27 | } 28 | } 29 | generateProtoTasks { 30 | all()*.plugins { 31 | grpc { 32 | option 'enable_deprecated=false' 33 | } 34 | } 35 | all().each { task -> 36 | task.generateDescriptorSet = true 37 | task.descriptorSetOptions.includeSourceInfo = true 38 | task.descriptorSetOptions.includeImports = true 39 | task.descriptorSetOptions.path = 40 | "${project.ext.genSrcDir}/${task.sourceSet.name}/resources/" + 41 | "META-INF/armeria/grpc/armeria-${task.sourceSet.name}.dsc" 42 | } 43 | } 44 | } 45 | } 46 | 47 | // Add the generated 'grpc' directories to the source sets. 48 | project.sourceSets.all { sourceSet -> 49 | sourceSet.java.srcDir file("${project.ext.genSrcDir}/${sourceSet.name}/grpc") 50 | } 51 | 52 | // Make sure protoc runs before the resources are copied so that .dsc files are included. 53 | project.afterEvaluate { 54 | project.sourceSets.each { sourceSet -> 55 | def processResourcesTask = tasks.findByName(sourceSet.getTaskName('process', 'resources')) 56 | if (processResourcesTask != null) { 57 | processResourcesTask.dependsOn(tasks.findByName(sourceSet.getTaskName('generate', 'proto'))) 58 | } 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /gradle/scripts/lib/java-rpc-thrift.gradle: -------------------------------------------------------------------------------- 1 | def libDir = "${buildscript.sourceFile.parentFile}" 2 | 3 | configure(projectsWithFlags('java')) { 4 | def thriftJsonEnabled = true 5 | ext { 6 | thriftVersion = null 7 | thriftPath = null 8 | disableThriftJson = { thriftJsonEnabled = false } 9 | } 10 | 11 | // TODO(anuraaga): Consider replacing with https://github.com/yodle/griddle 12 | project.sourceSets.all { sourceSet -> 13 | def scope = sourceSet.name 14 | // Create the task assuming the source directory exists 15 | // so that subprojects can refer to the task. 16 | def task = project.tasks.create([ 17 | name: sourceSet.getTaskName('compile', 'thrift'), 18 | group: 'Build', 19 | description: "Compiles the ${sourceSet.name} .thrift files." 20 | ]) 21 | 22 | // Use afterEvaluate to give subprojects a chance to override the properties. 23 | project.afterEvaluate { 24 | def srcDirs = project.findProperty(sourceSet.getTaskName('', 'thriftSrcDirs')) 25 | if (srcDirs == null) { 26 | def defaultSrcDir = "${projectDir}/src/${scope}/thrift" 27 | if (!project.file(defaultSrcDir).isDirectory()) { 28 | // Disable the compile*Thrift task which turned out to be unnecessary. 29 | task.enabled = false 30 | return 31 | } 32 | srcDirs = [defaultSrcDir] 33 | } 34 | if (!(srcDirs instanceof Iterable) || srcDirs instanceof CharSequence) { 35 | srcDirs = [srcDirs] 36 | } 37 | 38 | def includeDirs = project.findProperty(sourceSet.getTaskName('', 'thriftIncludeDirs'))?: [] 39 | if (!(includeDirs instanceof Iterable) || includeDirs instanceof CharSequence) { 40 | includeDirs = [includeDirs] 41 | } 42 | def javaOutputDir = "${project.ext.genSrcDir}/${scope}/java" 43 | def jsonOutputDir = "${project.ext.genSrcDir}/${scope}/resources" 44 | task.configure { 45 | srcDirs.each { inputs.dir it } 46 | includeDirs.each { inputs.dir it } 47 | outputs.dir javaOutputDir 48 | outputs.dir jsonOutputDir 49 | 50 | doFirst { 51 | def actualThriftPath 52 | if (project.findProperty('thriftPath') != null) { 53 | actualThriftPath = project.findProperty('thriftPath') 54 | } else { 55 | actualThriftPath = 56 | "${libDir}/thrift" + 57 | "/${project.findProperty('thriftVersion')?: '0.12'}" + 58 | "/thrift.${rootProject.osdetector.classifier}" 59 | } 60 | 61 | srcDirs.each { srcDir -> 62 | project.fileTree(srcDir) { 63 | include '**/*.thrift' 64 | }.each { sourceFile -> 65 | logger.info("Using ${actualThriftPath} to generate Java sources from ${sourceFile}") 66 | project.mkdir(javaOutputDir) 67 | project.exec { 68 | commandLine actualThriftPath 69 | args '-gen', 'java', '-out', javaOutputDir 70 | includeDirs.each { 71 | args '-I', it 72 | } 73 | args sourceFile.absolutePath 74 | } 75 | if (thriftJsonEnabled) { 76 | logger.info("Using ${actualThriftPath} to generate JSON from ${sourceFile}") 77 | def outDir = "${jsonOutputDir}/META-INF/armeria/thrift" 78 | project.mkdir(outDir) 79 | project.exec { 80 | commandLine actualThriftPath 81 | args '-gen', 'json', '-out', outDir 82 | includeDirs.each { 83 | args '-I', it 84 | } 85 | args sourceFile.absolutePath 86 | } 87 | } 88 | } 89 | } 90 | } 91 | } 92 | 93 | def processResourcesTask = tasks.findByName(sourceSet.getTaskName('process', 'resources')) 94 | if (processResourcesTask != null) { 95 | processResourcesTask.dependsOn(task) 96 | } 97 | 98 | def compileTask = tasks.findByName(sourceSet.getCompileTaskName('java')) 99 | if (compileTask != null) { 100 | compileTask.dependsOn(task) 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /gradle/scripts/lib/java-shade.gradle: -------------------------------------------------------------------------------- 1 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 2 | import proguard.gradle.ProGuardTask 3 | 4 | import java.util.concurrent.atomic.AtomicInteger 5 | 6 | buildscript { 7 | repositories { 8 | gradlePluginPortal() 9 | } 10 | dependencies { 11 | classpath "com.github.jengelman.gradle.plugins:shadow:${managedVersions['com.github.jengelman.gradle.plugins:shadow']}" 12 | classpath "net.sf.proguard:proguard-gradle:${managedVersions['net.sf.proguard:proguard-gradle']}" 13 | } 14 | } 15 | 16 | def relocatedProjects = projectsWithFlags('java', 'relocate') 17 | def numConfiguredRelocatedProjects = new AtomicInteger() 18 | configure(relocatedProjects) { 19 | // Generate the shaded JARs. 20 | task shadedJar( 21 | type: ShadowJar, 22 | group: 'Build', 23 | description: 'Builds the shaded main JAR.', 24 | dependsOn: tasks.classes) { 25 | 26 | configureShadowTask(project, delegate, true) 27 | baseName = "${project.archivesBaseName}-shaded" 28 | 29 | // Exclude the class signature files. 30 | exclude '/META-INF/*.SF' 31 | exclude '/META-INF/*.DSA' 32 | exclude '/META-INF/*.RSA' 33 | // Exclude the files generated by Maven 34 | exclude '/META-INF/maven/**' 35 | } 36 | tasks.assemble.dependsOn tasks.shadedJar 37 | 38 | artifacts { 39 | archives shadedJar 40 | } 41 | 42 | task shadedClasses( 43 | type: Copy, 44 | group: 'Build', 45 | description: 'Extracts the shaded main JAR.', 46 | dependsOn: tasks.shadedJar) { 47 | 48 | from(zipTree(tasks.shadedJar.archivePath)) 49 | from(sourceSets.main.output.classesDirs) { 50 | // Add the JAR resources excluded in the 'shadedJar' task. 51 | include '**/*.jar' 52 | } 53 | into "${project.buildDir}/classes/java/shaded-main" 54 | } 55 | 56 | task shadedTestJar( 57 | type: ShadowJar, 58 | group: 'Build', 59 | description: 'Builds the shaded test JAR.', 60 | dependsOn: tasks.testClasses) { 61 | 62 | configureShadowTask(project, delegate, false) 63 | baseName = "test-${tasks.jar.baseName}-shaded" 64 | } 65 | 66 | task shadedTestClasses( 67 | type: Copy, 68 | group: 'Build', 69 | description: 'Extracts the shaded test JAR.', 70 | dependsOn: tasks.shadedTestJar) { 71 | 72 | from(zipTree(tasks.shadedTestJar.archivePath)) 73 | from(sourceSets.test.output.classesDirs) { 74 | // Add the JAR resources excluded in the 'shadedTestJar' task. 75 | include '**/*.jar' 76 | } 77 | into "${project.buildDir}/classes/java/shaded-test" 78 | } 79 | } 80 | 81 | // NB: Configure in a new closure so that all relocated projects have a 'shadedJar' task. 82 | configure(relocatedProjects) { 83 | if (project.hasFlags('trim')) { 84 | // Task 'shadedJar' may produce a very large JAR. Rename it to '*-untrimmed-*.jar' and 85 | // let the task 'trimShadedJar' produce the trimmed JAR from it. 86 | tasks.shadedJar.baseName = "${tasks.jar.baseName}-untrimmed" 87 | 88 | task trimShadedJar( 89 | type: ProGuardTask, 90 | group: 'Build', 91 | description: 'Shrinks the shaded JAR by removing unused classes.') { 92 | 93 | relocatedProjects.each { 94 | dependsOn it.tasks.shadedJar 95 | dependsOn it.tasks.shadedTestJar 96 | } 97 | 98 | def shadedFile = tasks.shadedJar.archivePath 99 | def shadedAndTrimmedFile = file(shadedFile.path.replaceFirst('-untrimmed-', '-shaded-')) 100 | 101 | injars shadedFile 102 | // NB: By specifying 'outjars' *before* other 'injars' below, ProGuard will put only the classes 103 | // from 'shadedFile' into 'shadedAndTrimmedFile'. See 'restructuring the output archives' 104 | // for more information: https://www.guardsquare.com/en/proguard/manual/examples#restructuring 105 | outjars shadedAndTrimmedFile 106 | 107 | // Include all other shaded JARs so that ProGuard does not trim the classes and methods 108 | // that are used actually. 109 | injars tasks.shadedTestJar.archivePath 110 | relocatedProjects.each { 111 | if (it != project && !it.hasFlags('no_aggregation')) { 112 | injars it.tasks.shadedJar.archivePath 113 | injars it.tasks.shadedTestJar.archivePath 114 | } 115 | } 116 | 117 | def dependencyJars = new LinkedHashSet() 118 | relocatedProjects.each { 119 | // NB: ProGuardTask picks the dependencies added *after* evaluation correctly 120 | // because libraryjar() intentionally keeps the specified dependencies as-is. 121 | // See ProGuardTask.libraryjar() for more information. 122 | if (!it.hasFlags('no_aggregation')) { 123 | it.afterEvaluate { 124 | it.configurations.testRuntime.collect().each { File file -> 125 | if (!file.path.startsWith("${rootProject.projectDir}")) { 126 | dependencyJars.add(file) 127 | } 128 | } 129 | } 130 | } 131 | } 132 | libraryjars files(dependencyJars) 133 | 134 | // Add JDK. Use modules instead of rt.jar if possible. 135 | File jmodDir = file("${System.getProperty('java.home')}/jmods") 136 | if (jmodDir.isDirectory()) { 137 | jmodDir.listFiles().findAll { File f -> 138 | f.isFile() && f.name.toLowerCase(Locale.ENGLISH).endsWith(".jmod") 139 | }.each { libraryjars it } 140 | } else { 141 | libraryjars file("${System.getProperty('java.home')}/lib/rt.jar") 142 | } 143 | 144 | dontoptimize 145 | dontobfuscate 146 | dontwarn // Ignore the harmless 'missing classes' warnings related with the optional dependencies. 147 | 148 | keepattributes '**' 149 | keepparameternames 150 | 151 | printconfiguration file("${project.buildDir}/proguard.cfg") 152 | } 153 | 154 | tasks.assemble.dependsOn tasks.trimShadedJar 155 | 156 | // Add the trimmed JAR to archives. 157 | artifacts { 158 | trimShadedJar.outJarFiles.each { 159 | archives it 160 | } 161 | } 162 | } 163 | } 164 | 165 | // NB: Configure in a new closure so that all relocated projects have a 'shadedJar' or 'trimShadedJar' task. 166 | configure(relocatedProjects) { 167 | // Add tests for the shaded JAR. 168 | task shadedTest( 169 | type: Test, 170 | group: 'Verification', 171 | description: 'Runs the unit tests with the shaded classes.') { 172 | 173 | relocatedProjects.each { 174 | if (it.tasks.findByName('trimShadedJar')) { 175 | dependsOn it.tasks.trimShadedJar 176 | } else { 177 | dependsOn it.tasks.shadedJar 178 | } 179 | } 180 | dependsOn tasks.shadedTestClasses 181 | 182 | // The tests against the shaded artifacts should run after the tests against the unshaded ones. 183 | shouldRunAfter tasks.test 184 | 185 | testClassesDirs = files(tasks.shadedTestClasses.destinationDir) 186 | classpath = testClassesDirs 187 | 188 | project.ext.relocations.each { 189 | exclude "${it['to'].replace('.', '/')}/**" 190 | } 191 | } 192 | tasks.check.dependsOn tasks.shadedTest 193 | 194 | // Update the classpath of the 'shadedTest' task after all shaded projects are evaluated 195 | // so that we get the complete dependency list. 196 | project.afterEvaluate { 197 | if (numConfiguredRelocatedProjects.incrementAndGet() == relocatedProjects.size()) { 198 | relocatedProjects.each { 199 | it.tasks.shadedTest.classpath += 200 | it.files(createShadedTestRuntimeConfiguration(it).resolve()) 201 | } 202 | } 203 | } 204 | 205 | gradle.taskGraph.whenReady { 206 | // Skip unshaded tests if shaded tests will run. 207 | if (gradle.taskGraph.hasTask(tasks.shadedTest)) { 208 | tasks.test.onlyIf { false } 209 | } 210 | } 211 | } 212 | 213 | private void configureShadowTask(Project project, ShadowJar task, boolean isMain) { 214 | List> relocations = project.ext.relocations 215 | 216 | task.configure { 217 | from(project.sourceSets[isMain ? 'main' : 'test'].output) { 218 | exclude 'META-INF/maven/**' 219 | // Prevent the shadow plugin from exploding the JARs in the resources directory. 220 | // e.g. WEB-INF/lib/hello.jar 221 | exclude '**/*.jar' 222 | } 223 | 224 | configurations = [project.configurations[isMain ? 'compile' : 'testCompile']] 225 | 226 | relocations.each { props -> 227 | task.relocate props['from'], props['to'] 228 | } 229 | 230 | dependencies { 231 | if (project.hasFlags('shade')) { 232 | // Shade the relocated dependencies only. 233 | exclude(dependency({ dep -> 234 | if (!relocations.find { dep.name.startsWith("${it['name']}:") }) { 235 | // Do not shade the dependencies not listed in 'relocations'. 236 | return true 237 | } 238 | 239 | if (isMain) { 240 | return false 241 | } else { 242 | // Do not shade the dependencies which is already shaded for 'main'. 243 | return project.configurations.compile.allDependencies.find { compileDep -> 244 | compileDep.group == dep.moduleGroup && compileDep.name == dep.moduleName 245 | } 246 | } 247 | })) 248 | } else { // hasFlags('relocate') 249 | // Only want to rewrite source references, not bundle dependencies. 250 | exclude(dependency({ true })) 251 | } 252 | } 253 | } 254 | } 255 | 256 | 257 | /** 258 | * Finds the dependencies of {@code project} recursively and adds the found dependencies to 259 | * the configuration named as {@code 'shadedTestRuntime'}. 260 | */ 261 | private Configuration createShadedTestRuntimeConfiguration( 262 | Project project, Project recursedProject = project, 263 | Set visitedProjects = new HashSet<>()) { 264 | 265 | if (visitedProjects.contains(recursedProject)) { 266 | return 267 | } else { 268 | visitedProjects.add(recursedProject); 269 | } 270 | 271 | def shadedTestRuntime = project.configurations.maybeCreate("shadedTestRuntime") 272 | 273 | if (recursedProject.tasks.findByName('trimShadedJar')) { 274 | project.dependencies.add(shadedTestRuntime.name, files(recursedProject.tasks.trimShadedJar.outJarFiles)) 275 | } else if (recursedProject.tasks.findByName('shadedJar')) { 276 | project.dependencies.add(shadedTestRuntime.name, files(recursedProject.tasks.shadedJar.archivePath)) 277 | } 278 | 279 | def shadedDependencyNames = project.ext.relocations.collect { it['name'] } 280 | recursedProject.configurations.findAll { cfg -> 281 | cfg.name in ['compile', 'runtime', 'testCompile', 'testRuntime'] 282 | }.each { cfg -> 283 | cfg.dependencies.each { dep -> 284 | if (dep instanceof ProjectDependency) { 285 | // Project dependency - recurse. 286 | createShadedTestRuntimeConfiguration(project, dep.dependencyProject, visitedProjects) 287 | } else { 288 | // Module dependency - add. 289 | if (shadedDependencyNames.contains("${dep.group}:${dep.name}")) { 290 | return 291 | } 292 | project.dependencies.add(shadedTestRuntime.name, dep) 293 | } 294 | } 295 | } 296 | 297 | return shadedTestRuntime 298 | } 299 | -------------------------------------------------------------------------------- /gradle/scripts/lib/java-versionprops.gradle: -------------------------------------------------------------------------------- 1 | configure(projectsWithFlags('java', 'publish')) { 2 | def generatedResourcesDir = project.file("${project.projectDir}/gen-src/main/resources") 3 | sourceSets.main.resources.srcDir generatedResourcesDir 4 | clean { 5 | delete generatedResourcesDir 6 | } 7 | 8 | // Generate version.properties, similar to Netty. 9 | task versionProperties { 10 | def propsFile = project.file("${generatedResourcesDir}/META-INF/${project.group}.versions.properties") 11 | outputs.file propsFile 12 | doLast { 13 | def artifactId = project.ext.artifactId 14 | 15 | logger.info("Generating versions.properties for ${artifactId} ..") 16 | def props = new Properties() 17 | project.ext.repoStatus.each { k, v -> 18 | props["${artifactId}.${k}"] = v 19 | } 20 | 21 | def upToDate = false 22 | if (propsFile.exists()) { 23 | def oldProps = new Properties() 24 | propsFile.withInputStream { oldProps.load(it) } 25 | if (oldProps == props) { 26 | upToDate = true 27 | } 28 | } else { 29 | logger.info("${propsFile} does not exist.") 30 | } 31 | 32 | if (!upToDate) { 33 | project.mkdir(propsFile.parentFile) 34 | logger.info("Writing ${propsFile} ..") 35 | propsFile.withOutputStream { props.store(it, null) } 36 | } else { 37 | logger.info("${propsFile} is up-to-date.") 38 | } 39 | } 40 | } 41 | 42 | // Ensure version.properties is available during the build. 43 | tasks.processResources.dependsOn(tasks.versionProperties) 44 | } 45 | -------------------------------------------------------------------------------- /gradle/scripts/lib/java.gradle: -------------------------------------------------------------------------------- 1 | // Enable checkstyle if the rule file exists. 2 | def checkstyleConfigDir = "${rootProject.projectDir}/settings/checkstyle" 3 | def checkstyleEnabled = new File(checkstyleConfigDir).isDirectory() && 4 | new File("${checkstyleConfigDir}/checkstyle.xml").isFile() 5 | 6 | configure(rootProject) { 7 | apply plugin: 'eclipse' 8 | apply plugin: 'idea' 9 | } 10 | 11 | configure(projectsWithFlags('java')) { 12 | 13 | apply plugin: 'java' 14 | apply plugin: 'eclipse' 15 | apply plugin: 'idea' 16 | 17 | archivesBaseName = project.ext.artifactId 18 | 19 | // Delete the generated source directory on clean. 20 | ext { 21 | genSrcDir = "${projectDir}/gen-src" 22 | } 23 | clean { 24 | delete project.ext.genSrcDir 25 | } 26 | 27 | // Add the generated source directories to the source sets. 28 | project.sourceSets.all { sourceSet -> 29 | sourceSet.java.srcDir file("${project.ext.genSrcDir}/${sourceSet.name}/java") 30 | sourceSet.resources.srcDir file("${project.ext.genSrcDir}/${sourceSet.name}/resources") 31 | } 32 | 33 | // Set the sensible compiler options. 34 | tasks.withType(JavaCompile) { 35 | def task = delegate 36 | sourceCompatibility = project.findProperty('javaSourceCompatibility') ?: '1.8' 37 | targetCompatibility = project.findProperty('javaTargetCompatibility') ?: '1.8' 38 | options.encoding = 'UTF-8' 39 | options.warnings = false 40 | options.debug = true 41 | options.compilerArgs += '-parameters' 42 | project.configurations.each { Configuration cfg -> 43 | if (cfg.name == 'annotationProcessor' || cfg.name.endsWith('AnnotationProcessor')) { 44 | cfg.withDependencies { deps -> 45 | // Use incremental compilation only when there are no annotation processors, 46 | // because it seems to cause compilation errors for some processors such as Lombok. 47 | def useIncrementalCompilation = deps.size() == 0 48 | options.incremental = useIncrementalCompilation 49 | logger.info("${useIncrementalCompilation ? 'Enabling' : 'Disabling'} " + 50 | "incremental compilation for '${task.path}'") 51 | } 52 | } 53 | } 54 | } 55 | 56 | // Generate a source JAR. 57 | task sourceJar(type: Jar) { 58 | classifier = 'sources' 59 | from sourceSets.main.allSource 60 | } 61 | 62 | // Generate a javadoc JAR. 63 | task javadocJar(type: Jar, dependsOn: tasks.javadoc) { 64 | classifier = 'javadoc' 65 | from javadoc.destinationDir 66 | } 67 | 68 | // Generate source/javadoc JARs only when publishing. 69 | def requestedTaskNames = gradle.startParameter.taskRequests.inject([]) { a, b -> a + b.args } 70 | tasks.sourceJar.onlyIf { 71 | project.ext.isSigning() || 72 | project.ext.isPublishing() || 73 | requestedTaskNames.find { it =~ /(?:^|:)sourceJar$/ } 74 | } 75 | [tasks.javadoc, tasks.javadocJar].each { 76 | it.onlyIf { 77 | project.ext.isSigning() || 78 | project.ext.isPublishing() || 79 | requestedTaskNames.find { it =~ /(?:^|:)javadoc(?:Jar)?$/ } 80 | } 81 | } 82 | 83 | artifacts { 84 | archives jar 85 | archives sourceJar 86 | archives javadocJar 87 | } 88 | 89 | // Enable full exception logging for test failures. 90 | tasks.withType(Test) { 91 | testLogging.exceptionFormat = 'full' 92 | } 93 | 94 | // Use JUnit 5 if it's defined in the dependencies. 95 | if (managedVersions.containsKey('org.junit.jupiter:junit-jupiter-engine')) { 96 | tasks.withType(Test) { 97 | useJUnitPlatform() 98 | } 99 | } 100 | 101 | // Enforce checkstyle rules. 102 | if (checkstyleEnabled) { 103 | apply plugin: 'checkstyle' 104 | 105 | checkstyle { 106 | configFile = new File("${checkstyleConfigDir}/checkstyle.xml") 107 | configProperties = ['checkstyleConfigDir': "$checkstyleConfigDir"] 108 | if (managedVersions.containsKey('com.puppycrawl.tools:checkstyle')) { 109 | toolVersion = managedVersions['com.puppycrawl.tools:checkstyle'] 110 | } 111 | } 112 | 113 | task checkstyle(group: 'Verification', description: 'Runs the checkstyle rules.') { 114 | project.sourceSets.all { SourceSet sourceSet -> 115 | def dependencyTask = project.tasks.findByName(sourceSet.getTaskName("checkstyle", "")) 116 | if (dependencyTask instanceof Checkstyle) { 117 | dependsOn dependencyTask 118 | } 119 | } 120 | } 121 | 122 | test { 123 | dependsOn tasks.checkstyle 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /gradle/scripts/lib/prerequisite.gradle: -------------------------------------------------------------------------------- 1 | try { 2 | rootProject.apply plugin: 'com.google.osdetector' 3 | rootProject.apply plugin: 'io.spring.dependency-management' 4 | } catch (UnknownPluginException e) { 5 | throw new IllegalStateException('''Add the following to the top-level build.gradle file: 6 | 7 | buildscript { 8 | repositories { 9 | mavenCentral() 10 | } 11 | dependencies { 12 | classpath 'com.google.gradle:osdetector-gradle-plugin:1.6.2' 13 | classpath 'io.spring.gradle:dependency-management-plugin:1.0.7.RELEASE' 14 | } 15 | } 16 | ''') 17 | } 18 | 19 | ['projectName', 'projectUrl', 'inceptionYear', 'licenseName', 'licenseUrl', 'scmUrl', 'scmConnection', 20 | 'scmDeveloperConnection', 'publishUrlForRelease', 'publishUrlForSnapshot', 'publishUsernameProperty', 21 | 'publishPasswordProperty'].each { 22 | if (rootProject.findProperty(it) == null) { 23 | throw new IllegalStateException('''Add project info properties to gradle.properties: 24 | 25 | projectName=My Example 26 | projectUrl=https://www.example.com/ 27 | projectDescription=My example project 28 | authorName=John Doe 29 | authorEmail=john@doe.com 30 | authorUrl=https://john.doe.com/ 31 | inceptionYear=2018 32 | licenseName=The Apache License, Version 2.0 33 | licenseUrl=https://www.apache.org/license/LICENSE-2.0.txt 34 | scmUrl=https://github.com/john.doe/example 35 | scmConnection=scm:git:https://github.com/john.doe/example.git 36 | scmDeveloperConnection=scm:git:ssh://git@github.com/john.doe/example.git 37 | publishUrlForRelease=https://oss.sonatype.org/service/local/staging/deploy/maven2/ 38 | publishUrlForSnapshot=https://oss.sonatype.org/content/repositories/snapshots/ 39 | publishUsernameProperty=ossrhUsername 40 | publishPasswordProperty=ossrhPassword 41 | versionPattern=^[0-9]+\\\\.[0-9]+\\\\.[0-9]+$ 42 | ''') 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /gradle/scripts/lib/thrift/0.10/thrift.linux-x86_64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imasahiro/auto-string-formatter/f200017b76de62ecf6d69cfc6eec44fa3de0a479/gradle/scripts/lib/thrift/0.10/thrift.linux-x86_64 -------------------------------------------------------------------------------- /gradle/scripts/lib/thrift/0.10/thrift.osx-x86_64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imasahiro/auto-string-formatter/f200017b76de62ecf6d69cfc6eec44fa3de0a479/gradle/scripts/lib/thrift/0.10/thrift.osx-x86_64 -------------------------------------------------------------------------------- /gradle/scripts/lib/thrift/0.10/thrift.windows-x86_32.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imasahiro/auto-string-formatter/f200017b76de62ecf6d69cfc6eec44fa3de0a479/gradle/scripts/lib/thrift/0.10/thrift.windows-x86_32.exe -------------------------------------------------------------------------------- /gradle/scripts/lib/thrift/0.10/thrift.windows-x86_64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imasahiro/auto-string-formatter/f200017b76de62ecf6d69cfc6eec44fa3de0a479/gradle/scripts/lib/thrift/0.10/thrift.windows-x86_64.exe -------------------------------------------------------------------------------- /gradle/scripts/lib/thrift/0.11/thrift.linux-x86_64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imasahiro/auto-string-formatter/f200017b76de62ecf6d69cfc6eec44fa3de0a479/gradle/scripts/lib/thrift/0.11/thrift.linux-x86_64 -------------------------------------------------------------------------------- /gradle/scripts/lib/thrift/0.11/thrift.osx-x86_64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imasahiro/auto-string-formatter/f200017b76de62ecf6d69cfc6eec44fa3de0a479/gradle/scripts/lib/thrift/0.11/thrift.osx-x86_64 -------------------------------------------------------------------------------- /gradle/scripts/lib/thrift/0.11/thrift.windows-x86_32.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imasahiro/auto-string-formatter/f200017b76de62ecf6d69cfc6eec44fa3de0a479/gradle/scripts/lib/thrift/0.11/thrift.windows-x86_32.exe -------------------------------------------------------------------------------- /gradle/scripts/lib/thrift/0.11/thrift.windows-x86_64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imasahiro/auto-string-formatter/f200017b76de62ecf6d69cfc6eec44fa3de0a479/gradle/scripts/lib/thrift/0.11/thrift.windows-x86_64.exe -------------------------------------------------------------------------------- /gradle/scripts/lib/thrift/0.12/thrift.linux-x86_64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imasahiro/auto-string-formatter/f200017b76de62ecf6d69cfc6eec44fa3de0a479/gradle/scripts/lib/thrift/0.12/thrift.linux-x86_64 -------------------------------------------------------------------------------- /gradle/scripts/lib/thrift/0.12/thrift.osx-x86_64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imasahiro/auto-string-formatter/f200017b76de62ecf6d69cfc6eec44fa3de0a479/gradle/scripts/lib/thrift/0.12/thrift.osx-x86_64 -------------------------------------------------------------------------------- /gradle/scripts/lib/thrift/0.12/thrift.windows-x86_32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imasahiro/auto-string-formatter/f200017b76de62ecf6d69cfc6eec44fa3de0a479/gradle/scripts/lib/thrift/0.12/thrift.windows-x86_32 -------------------------------------------------------------------------------- /gradle/scripts/lib/thrift/0.12/thrift.windows-x86_64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imasahiro/auto-string-formatter/f200017b76de62ecf6d69cfc6eec44fa3de0a479/gradle/scripts/lib/thrift/0.12/thrift.windows-x86_64 -------------------------------------------------------------------------------- /gradle/scripts/lib/thrift/0.9/thrift.linux-x86_64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imasahiro/auto-string-formatter/f200017b76de62ecf6d69cfc6eec44fa3de0a479/gradle/scripts/lib/thrift/0.9/thrift.linux-x86_64 -------------------------------------------------------------------------------- /gradle/scripts/lib/thrift/0.9/thrift.osx-x86_64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imasahiro/auto-string-formatter/f200017b76de62ecf6d69cfc6eec44fa3de0a479/gradle/scripts/lib/thrift/0.9/thrift.osx-x86_64 -------------------------------------------------------------------------------- /gradle/scripts/lib/thrift/0.9/thrift.windows-x86_32.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imasahiro/auto-string-formatter/f200017b76de62ecf6d69cfc6eec44fa3de0a479/gradle/scripts/lib/thrift/0.9/thrift.windows-x86_32.exe -------------------------------------------------------------------------------- /gradle/scripts/lib/thrift/0.9/thrift.windows-x86_64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imasahiro/auto-string-formatter/f200017b76de62ecf6d69cfc6eec44fa3de0a479/gradle/scripts/lib/thrift/0.9/thrift.windows-x86_64.exe -------------------------------------------------------------------------------- /gradle/scripts/settings-flags.gradle: -------------------------------------------------------------------------------- 1 | import java.util.concurrent.ConcurrentHashMap 2 | import org.gradle.util.GradleVersion; 3 | 4 | import static java.util.Objects.requireNonNull 5 | 6 | // Ensure the Gradle version first of all. 7 | GradleVersion minimumSupportedGradleVersion = GradleVersion.version('4.8') 8 | if (GradleVersion.current() < minimumSupportedGradleVersion) { 9 | throw new IllegalStateException("${minimumSupportedGradleVersion} or above is required to build this project.") 10 | } 11 | 12 | // Add the 'includeWithFlags' directive to 'settings'. 13 | ext.includeWithFlags = this.&includeWithFlags 14 | 15 | def includeWithFlags(CharSequence path, ...flags) { 16 | requireNonNull(path, "path is null.") 17 | if (path.length() == 0) { 18 | throw new IllegalArgumentException("path is empty.") 19 | } 20 | 21 | def pathStr = path.toString() 22 | def actualFlags = addFlags(new LinkedHashSet(), flags) 23 | 24 | settings.include pathStr 25 | 26 | // Ensure all projects has the extension methods and properties. 27 | if (!gradle.ext.has('installedFlagsExtension')) { 28 | gradle.ext.installedFlagsExtension = true 29 | gradle.projectsLoaded { 30 | gradle.allprojects { p -> 31 | p.ext.flags = Collections.emptySet() 32 | p.ext.hasFlag = p.ext.hasFlags = this.&hasFlags.curry(p) 33 | p.ext.assertFlag = p.ext.assertFlags = this.&assertFlags.curry(p) 34 | p.ext.projectsWithFlag = p.ext.projectsWithFlags = this.&projectsWithFlags.curry(p) 35 | p.ext.afterProjectsWithFlag = this.&afterProjectsWithFlag.curry(p) 36 | p.ext.afterProjectsWithFlags = this.&afterProjectsWithFlags.curry(p) 37 | p.ext.addFlag = p.ext.addFlags = { args -> p.ext.flags = addFlags(p.ext.flags, args) } 38 | } 39 | } 40 | } 41 | 42 | // Set the flags property. 43 | gradle.projectsLoaded { 44 | try { 45 | gradle.rootProject.project(pathStr).ext.flags = actualFlags 46 | } catch (UnknownProjectException ignored) { 47 | // Ignore 48 | } 49 | } 50 | } 51 | 52 | static def addFlags(Set actualFlags, ...flags) { 53 | if (actualFlags == null) { 54 | actualFlags = new LinkedHashSet<>() 55 | } else { 56 | actualFlags = new LinkedHashSet<>(actualFlags) 57 | } 58 | 59 | if (flags != null) { 60 | if (flags instanceof CharSequence) { 61 | actualFlags.add(flags.toString()) 62 | } else if (flags instanceof Iterable) { 63 | flags.each { it -> 64 | if (it instanceof CharSequence) { 65 | actualFlags.add(it.toString()) 66 | } else { 67 | throw new IllegalArgumentException("flags must contain only CharSequences: ${flags}") 68 | } 69 | } 70 | } else if (flags instanceof Object[]) { 71 | flags.each { it -> 72 | if (it instanceof CharSequence) { 73 | actualFlags.add(it.toString()) 74 | } else { 75 | throw new IllegalArgumentException("flags must contain only CharSequences: ${flags}") 76 | } 77 | } 78 | } else { 79 | throw new IllegalArgumentException( 80 | "flags must be a CharSequence or an Iterable of CharSequences: ${flags}") 81 | } 82 | } 83 | 84 | // 'trim' implies 'shade' 85 | if (actualFlags.contains('trim')) { 86 | actualFlags.add('shade') 87 | } 88 | 89 | // 'shade' implies 'relocate'. 90 | if (actualFlags.contains('shade')) { 91 | actualFlags.add('relocate') 92 | } 93 | 94 | // 'bom' implies 'publish'. 95 | if (actualFlags.contains('bom')) { 96 | actualFlags.add('publish') 97 | } 98 | 99 | return Collections.unmodifiableSet(actualFlags) 100 | } 101 | 102 | static def hasFlags(Project project, ...flags) { 103 | if (!project.ext.has('flags')) { 104 | return flags.length == 0 105 | } 106 | 107 | Set actualFlags = project.ext.flags 108 | for (def f in flags.flatten()) { 109 | if (!(f instanceof CharSequence)) { 110 | throw new IllegalArgumentException("not a CharSequence: ${f}") 111 | } 112 | if (!actualFlags.contains(f.toString())) { 113 | return false 114 | } 115 | } 116 | 117 | return true 118 | } 119 | 120 | static def assertFlags(Project project, ...flags) { 121 | assert hasFlags(project, flags) 122 | } 123 | 124 | static def projectsWithFlags(Project project, ...flags) { 125 | return new LinkedHashSet( 126 | project.rootProject.allprojects.findAll { 127 | it.ext.has('hasFlags') && it.ext.hasFlags(flags) 128 | }) 129 | } 130 | 131 | static void afterProjectsWithFlag(Project project, Object flag, Closure> closure) { 132 | afterProjectsWithFlags(project, [flag], closure) 133 | } 134 | 135 | static void afterProjectsWithFlags(Project project, Iterable flags, Closure> closure) { 136 | def projects = projectsWithFlags(project, flags) 137 | def remainingProjects = Collections.newSetFromMap(new ConcurrentHashMap()) 138 | remainingProjects.addAll(projects) 139 | 140 | projects.each { p -> 141 | p.afterEvaluate { 142 | remainingProjects.remove(p); 143 | if (remainingProjects.isEmpty()) { 144 | closure(projects) 145 | } 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imasahiro/auto-string-formatter/f200017b76de62ecf6d69cfc6eec44fa3de0a479/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS='"-Xmx64m"' 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS="-Xmx64m" 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /processor/build.gradle: -------------------------------------------------------------------------------- 1 | archivesBaseName = 'auto-string-formatter-processor' 2 | 3 | dependencies { 4 | compile project(':runtime') 5 | 6 | compile 'com.github.spullara.mustache.java:compiler' 7 | compile 'com.google.auto:auto-common' 8 | compile 'com.google.guava:guava' 9 | compile 'com.squareup:javapoet' 10 | compile 'javax.annotation:javax.annotation-api' 11 | compile 'javax.inject:javax.inject' 12 | 13 | if (JavaVersion.current() <= JavaVersion.VERSION_1_8) { 14 | testCompile files(org.gradle.internal.jvm.Jvm.current().getToolsJar()) 15 | } 16 | testCompile 'com.google.testing.compile:compile-testing' 17 | } 18 | -------------------------------------------------------------------------------- /processor/src/main/java/com/github/imasahiro/stringformatter/processor/FixedString.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.imasahiro.stringformatter.processor; 17 | 18 | import javax.lang.model.type.TypeMirror; 19 | 20 | import com.squareup.javapoet.CodeBlock.Builder; 21 | 22 | class FixedString implements FormatString { 23 | private final String text; 24 | 25 | FixedString(String text) { 26 | this.text = text; 27 | } 28 | 29 | @Override 30 | public int getIndex() { 31 | return -1; 32 | } 33 | 34 | @Override 35 | public void emit(Builder codeBlockBuilder, TypeMirror ignored) { 36 | codeBlockBuilder.add("sb.append(\"" + text + "\");\n"); 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return "FixedString(text:" + text + ')'; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /processor/src/main/java/com/github/imasahiro/stringformatter/processor/FormatFlag.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.imasahiro.stringformatter.processor; 17 | 18 | public enum FormatFlag { 19 | SHARP, 20 | SPACE, 21 | ZERO, 22 | PLUS, 23 | 24 | UPPER_CASE, 25 | TIME, 26 | PARENTHESIS, 27 | COMMA, 28 | MINUS, 29 | } 30 | -------------------------------------------------------------------------------- /processor/src/main/java/com/github/imasahiro/stringformatter/processor/FormatParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.imasahiro.stringformatter.processor; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | import java.util.regex.Matcher; 21 | import java.util.regex.Pattern; 22 | 23 | import javax.lang.model.element.Element; 24 | 25 | import com.github.imasahiro.stringformatter.processor.util.ErrorReporter; 26 | 27 | final class FormatParser { 28 | // %[index][flags][width][.precision][t]conversion 29 | private static final String FORMAT_SPECIFIER = 30 | "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])"; 31 | 32 | private static final Pattern FORMAT_SPECIFIER_PATTERN = Pattern.compile(FORMAT_SPECIFIER); 33 | 34 | private FormatParser() { 35 | } 36 | 37 | /** 38 | * Parse format specifiers in the format string. 39 | */ 40 | public static List parse(String fmt, Element element, ErrorReporter errorReporter) { 41 | ArrayList formatStrings = new ArrayList<>(); 42 | Matcher m = FORMAT_SPECIFIER_PATTERN.matcher(fmt); 43 | int index = 0; 44 | FormatStringBuilder formatStringBuilder = new FormatStringBuilder(element, errorReporter); 45 | for (int i = 0; i < fmt.length();) { 46 | if (m.find(i)) { 47 | if (m.start() != i) { 48 | formatStrings.add(formatStringBuilder.newFixedString(fmt, i, m.start())); 49 | } 50 | FormatString specifier = formatStringBuilder.format(fmt) 51 | .matcher(m) 52 | .index(index) 53 | .build(); 54 | if (specifier instanceof FormatSpecifier && index == specifier.getIndex()) { 55 | index++; 56 | } 57 | formatStrings.add(specifier); 58 | i = m.end(); 59 | } else { 60 | formatStrings.add(formatStringBuilder.newFixedString(fmt, i, fmt.length())); 61 | break; 62 | } 63 | } 64 | return formatStrings; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /processor/src/main/java/com/github/imasahiro/stringformatter/processor/FormatSpecifier.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.imasahiro.stringformatter.processor; 17 | 18 | import java.util.Set; 19 | 20 | import javax.lang.model.type.TypeMirror; 21 | 22 | import com.github.imasahiro.stringformatter.processor.specifier.FormatConversionType; 23 | import com.google.common.base.Joiner; 24 | import com.squareup.javapoet.CodeBlock; 25 | 26 | class FormatSpecifier implements FormatString { 27 | private final int index; 28 | private final int width; 29 | private final int precision; 30 | private final Set flags; 31 | private final FormatConversionType type; 32 | 33 | FormatSpecifier(int index, int width, int precision, Set flags, 34 | FormatConversionType type) { 35 | this.index = index; 36 | this.width = width; 37 | this.precision = precision; 38 | this.flags = flags; 39 | this.type = type; 40 | } 41 | 42 | @Override 43 | public int getIndex() { 44 | return index; 45 | } 46 | 47 | @Override 48 | public void emit(CodeBlock.Builder codeBlockBuilder, TypeMirror argumentType) { 49 | codeBlockBuilder.add(type.emit("arg" + index, width, precision, flags, argumentType)); 50 | } 51 | 52 | public FormatConversionType getConversionType() { 53 | return type; 54 | } 55 | 56 | @Override 57 | public String toString() { 58 | return "FormatSpecifier(" + 59 | "index:" + index + ',' + 60 | "width:" + width + ',' + 61 | "precision:" + precision + ',' + 62 | "flags:" + Joiner.on(',').join(flags) + 63 | ')'; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /processor/src/main/java/com/github/imasahiro/stringformatter/processor/FormatString.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.imasahiro.stringformatter.processor; 17 | 18 | import javax.lang.model.type.TypeMirror; 19 | 20 | import com.squareup.javapoet.CodeBlock.Builder; 21 | 22 | interface FormatString { 23 | int getIndex(); 24 | 25 | void emit(Builder codeBlockBuilder, TypeMirror argumentType); 26 | } 27 | -------------------------------------------------------------------------------- /processor/src/main/java/com/github/imasahiro/stringformatter/processor/FormatStringBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.processor; 18 | 19 | import java.util.EnumSet; 20 | import java.util.Set; 21 | import java.util.regex.Matcher; 22 | 23 | import javax.lang.model.element.Element; 24 | 25 | import com.github.imasahiro.stringformatter.processor.specifier.BooleanFormatConversionType; 26 | import com.github.imasahiro.stringformatter.processor.specifier.CharacterFormatConversionType; 27 | import com.github.imasahiro.stringformatter.processor.specifier.FloatFormatConversionType; 28 | import com.github.imasahiro.stringformatter.processor.specifier.FormatConversionType; 29 | import com.github.imasahiro.stringformatter.processor.specifier.HexIntegerFormatConversionType; 30 | import com.github.imasahiro.stringformatter.processor.specifier.IntegerFormatConversionType; 31 | import com.github.imasahiro.stringformatter.processor.specifier.StringFormatConversionType; 32 | import com.github.imasahiro.stringformatter.processor.util.ErrorReporter; 33 | import com.google.common.primitives.Ints; 34 | 35 | class FormatStringBuilder { 36 | private final Element element; 37 | private final ErrorReporter errorReporter; 38 | 39 | private String format; 40 | private Matcher matcher; 41 | private int index; 42 | 43 | FormatStringBuilder(Element element, ErrorReporter errorReporter) { 44 | this.element = element; 45 | this.errorReporter = errorReporter; 46 | } 47 | 48 | // "(\\d+\\$)" -> index 49 | private static int parseIndex(int index, String s, int start, int end) { 50 | if (start < 0) { 51 | return index; 52 | } 53 | return Ints.tryParse(s.substring(start, end - 1)); 54 | } 55 | 56 | // "([-#+ 0,(\\<]*)" -> [MINUS, SHARP, PLUS, ZERO, ...] 57 | private static Set parseFlags(String s, int start, int end) { 58 | Set flags = EnumSet.noneOf(FormatFlag.class); 59 | if (start < 0) { 60 | return flags; 61 | } 62 | for (int i = start; i < end; i++) { 63 | switch (s.charAt(i)) { 64 | case '-': 65 | flags.add(FormatFlag.MINUS); 66 | break; 67 | case '#': 68 | flags.add(FormatFlag.SHARP); 69 | break; 70 | case '+': 71 | flags.add(FormatFlag.PLUS); 72 | break; 73 | case ' ': 74 | flags.add(FormatFlag.SPACE); 75 | break; 76 | case '0': 77 | flags.add(FormatFlag.ZERO); 78 | break; 79 | case ',': 80 | flags.add(FormatFlag.COMMA); 81 | break; 82 | case '(': 83 | flags.add(FormatFlag.PARENTHESIS); 84 | break; 85 | case '<': 86 | break; 87 | } 88 | } 89 | return flags; 90 | } 91 | 92 | // (\\d+) 93 | private static int parseWidth(String s, int start, int end) { 94 | if (start < 0) { 95 | return -1; 96 | } 97 | return Ints.tryParse(s.substring(start, end)); 98 | } 99 | 100 | // "(\\.\\d+)" 101 | private static int parsePrecision(String s, int start, int end) { 102 | if (start < 0) { 103 | return -1; 104 | } 105 | return Ints.tryParse(s.substring(start + 1, end)); 106 | } 107 | 108 | // "([a-zA-Z%])" 109 | private static char parseConversion(String s, int index, Set flags) { 110 | char c = s.charAt(index); 111 | if (flags.contains(FormatFlag.TIME)) { 112 | throw new RuntimeException("Time conversion is not implemented"); 113 | } 114 | return c; 115 | } 116 | 117 | FormatStringBuilder format(String format) { 118 | this.format = format; 119 | return this; 120 | } 121 | 122 | FormatStringBuilder matcher(Matcher matcherResult) { 123 | this.matcher = matcherResult; 124 | return this; 125 | } 126 | 127 | FormatStringBuilder index(int index) { 128 | this.index = index; 129 | return this; 130 | } 131 | 132 | FormatString newFixedString(String s, int begin, int end) { 133 | for (int i = begin; i < end; i++) { 134 | if (s.charAt(i) == '%') { 135 | String conversion = i != end - 1 ? String.valueOf(s.charAt(i + 1)) : ""; 136 | errorReporter.fatal("Unrecognized conversion : " + conversion, element); 137 | } 138 | } 139 | return new FixedString(s.substring(begin, end)); 140 | } 141 | 142 | FormatString build() { 143 | index = parseIndex(index, format, matcher.start(1), matcher.end(1)); 144 | Set flags = parseFlags(format, matcher.start(2), matcher.end(2)); 145 | int width = parseWidth(format, matcher.start(3), matcher.end(3)); 146 | int precision = parsePrecision(format, matcher.start(4), matcher.end(4)); 147 | 148 | int time = matcher.start(5); 149 | if (time >= 0) { 150 | flags.add(FormatFlag.TIME); 151 | if (format.charAt(time) == 'T') { 152 | flags.add(FormatFlag.UPPER_CASE); 153 | } 154 | } 155 | char conversion = parseConversion(format, matcher.start(6), flags); 156 | if (Character.isUpperCase(conversion)) { 157 | flags.add(FormatFlag.UPPER_CASE); 158 | } 159 | 160 | FormatConversionType type = null; 161 | switch (conversion) { 162 | case 'n': 163 | checkArgument(width >= 0, "width is not applicable for line separator conversion."); 164 | checkArgument(precision >= 0, "precision is not applicable for line separator conversion."); 165 | return new FixedString(System.getProperty("line.separator", "\n")); 166 | case '%': 167 | checkArgument(width >= 0, "width is not applicable for percent conversion."); 168 | checkArgument(precision >= 0, "precision is not applicable for percent conversion."); 169 | return new FixedString("%"); 170 | case 'b': 171 | case 'B': 172 | checkArgument(precision >= 0, "precision is not applicable for boolean conversion."); 173 | type = new BooleanFormatConversionType(); 174 | break; 175 | case 'h': 176 | case 'H': 177 | checkArgument(precision >= 0, "precision is not applicable for integer conversion."); 178 | type = new IntegerFormatConversionType(); 179 | break; 180 | case 's': 181 | case 'S': 182 | checkArgument(precision >= 0, "precision is not applicable for string conversion."); 183 | type = new StringFormatConversionType(); 184 | break; 185 | case 'c': 186 | case 'C': 187 | checkArgument(precision >= 0, "precision is not applicable for character conversion."); 188 | type = new CharacterFormatConversionType(); 189 | break; 190 | case 'd': 191 | case 'o': 192 | checkArgument(precision >= 0, "precision is not applicable for integer conversion."); 193 | type = new IntegerFormatConversionType(); 194 | break; 195 | case 'x': 196 | case 'X': 197 | checkArgument(precision >= 0, "precision is not applicable for integer conversion."); 198 | type = new HexIntegerFormatConversionType(); 199 | break; 200 | case 'e': 201 | case 'E': 202 | type = new FloatFormatConversionType(); 203 | break; 204 | case 'f': 205 | type = new FloatFormatConversionType(); 206 | break; 207 | case 'g': 208 | case 'G': 209 | case 'a': 210 | case 'A': 211 | type = new FloatFormatConversionType(); 212 | break; 213 | } 214 | return new FormatSpecifier(index, width, precision, flags, type); 215 | } 216 | 217 | private void checkArgument(boolean condition, String message) { 218 | if (condition) { 219 | errorReporter.fatal(message, element); 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /processor/src/main/java/com/github/imasahiro/stringformatter/processor/FormatterMethod.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.imasahiro.stringformatter.processor; 17 | 18 | import static com.google.common.collect.ImmutableList.toImmutableList; 19 | 20 | import java.util.List; 21 | import java.util.Set; 22 | 23 | import javax.annotation.processing.ProcessingEnvironment; 24 | import javax.lang.model.element.Element; 25 | import javax.lang.model.element.Modifier; 26 | import javax.lang.model.type.TypeMirror; 27 | import javax.lang.model.util.Types; 28 | 29 | import com.github.imasahiro.stringformatter.processor.util.ErrorReporter; 30 | import com.google.common.collect.ImmutableList; 31 | import com.google.common.collect.Sets; 32 | import com.squareup.javapoet.CodeBlock; 33 | import com.squareup.javapoet.MethodSpec; 34 | import com.squareup.javapoet.ParameterSpec; 35 | import com.squareup.javapoet.TypeName; 36 | 37 | class FormatterMethod { 38 | private final String name; 39 | private final String format; 40 | private final int bufferCapacity; 41 | private final List argumentTypes; 42 | private final Element element; 43 | private final ErrorReporter errorReporter; 44 | 45 | FormatterMethod(String name, String format, int bufferCapacity, List argumentTypes, 46 | Element element, ErrorReporter errorReporter) { 47 | this.name = name; 48 | this.format = format; 49 | this.bufferCapacity = bufferCapacity; 50 | this.argumentTypes = argumentTypes; 51 | this.element = element; 52 | this.errorReporter = errorReporter; 53 | } 54 | 55 | private static List buildParamTypes(List argumentTypes) { 56 | ImmutableList.Builder builder = ImmutableList.builder(); 57 | for (int i = 0; i < argumentTypes.size(); i++) { 58 | builder.add(ParameterSpec.builder(TypeName.get(argumentTypes.get(i)), 59 | "arg" + i, Modifier.FINAL).build()); 60 | } 61 | return builder.build(); 62 | } 63 | 64 | public static Builder builder() { 65 | return new Builder(); 66 | } 67 | 68 | private CodeBlock buildBody(List formatStringList, List argumentTypes) { 69 | CodeBlock.Builder builder = CodeBlock.builder() 70 | .add("final StringBuilder sb = new StringBuilder(" + 71 | bufferCapacity + ");\n"); 72 | int idx = 0; 73 | for (FormatString formatString : formatStringList) { 74 | if (formatString instanceof FormatSpecifier) { 75 | formatString.emit(builder, argumentTypes.get(idx++)); 76 | } else { 77 | formatString.emit(builder, null); 78 | } 79 | } 80 | return builder.add("return sb.toString();\n") 81 | .build(); 82 | } 83 | 84 | private void checkArgumentTypes(ProcessingEnvironment processingEnv, List formatStringList, 85 | List expectedTypeList) { 86 | List formatSpecifiers = formatStringList.stream() 87 | .filter(FormatSpecifier.class::isInstance) 88 | .map(FormatSpecifier.class::cast) 89 | .collect(toImmutableList()); 90 | 91 | if (formatSpecifiers.size() != expectedTypeList.size()) { 92 | throw new RuntimeException(name + " cannot not acceptable to " + expectedTypeList); 93 | } 94 | 95 | Set> candidateArgumentTypes = Sets.cartesianProduct( 96 | formatSpecifiers.stream() 97 | .map(FormatSpecifier::getConversionType) 98 | .map(e -> e.getType(processingEnv.getTypeUtils(), 99 | processingEnv.getElementUtils())) 100 | .collect(toImmutableList())); 101 | 102 | if (candidateArgumentTypes.stream().noneMatch( 103 | candidate -> isAssignableArguments(processingEnv, candidate, expectedTypeList))) { 104 | errorReporter.fatal(name + " cannot not apply to " + expectedTypeList, element); 105 | } 106 | } 107 | 108 | private boolean isAssignableArguments(ProcessingEnvironment processingEnv, List candidate, 109 | List expectedTypeList) { 110 | Types typeUtils = processingEnv.getTypeUtils(); 111 | for (int i = 0; i < candidate.size(); i++) { 112 | if (!typeUtils.isAssignable(expectedTypeList.get(i), 113 | candidate.get(i))) { 114 | return false; 115 | } 116 | } 117 | return true; 118 | } 119 | 120 | public MethodSpec getMethod(ProcessingEnvironment processingEnv) { 121 | List formatStringList = FormatParser.parse(format, element, errorReporter); 122 | checkArgumentTypes(processingEnv, formatStringList, argumentTypes); 123 | return MethodSpec.methodBuilder(name) 124 | .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 125 | .addParameters(buildParamTypes(argumentTypes)) 126 | .addCode(buildBody(formatStringList, argumentTypes)) 127 | .returns(TypeName.get(String.class)) 128 | .build(); 129 | } 130 | 131 | @Override 132 | public String toString() { 133 | return "FormatterMethod(name:" + name + ", format:" + format + 134 | ", bufferCapacity:" + bufferCapacity + ')'; 135 | } 136 | 137 | static class Builder { 138 | private String name; 139 | private int bufferCapacity; 140 | private String format; 141 | private ImmutableList argumentTypes; 142 | private Element element; 143 | private ErrorReporter errorReporter; 144 | 145 | public Builder name(String name) { 146 | this.name = name; 147 | return this; 148 | } 149 | 150 | public Builder formatter(String format) { 151 | this.format = format; 152 | return this; 153 | } 154 | 155 | public Builder bufferCapacity(int bufferCapacity) { 156 | this.bufferCapacity = bufferCapacity; 157 | return this; 158 | } 159 | 160 | public Builder argumentTypeNames(ImmutableList argumentTypeNames) { 161 | this.argumentTypes = argumentTypeNames; 162 | return this; 163 | } 164 | 165 | public Builder element(Element element) { 166 | this.element = element; 167 | return this; 168 | } 169 | 170 | public Builder errorReporter(ErrorReporter errorReporter) { 171 | this.errorReporter = errorReporter; 172 | return this; 173 | } 174 | 175 | public FormatterMethod build() { 176 | return new FormatterMethod(name, format, bufferCapacity, argumentTypes, element, errorReporter); 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /processor/src/main/java/com/github/imasahiro/stringformatter/processor/StringFormatterProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.imasahiro.stringformatter.processor; 17 | 18 | import static com.google.common.collect.ImmutableList.toImmutableList; 19 | 20 | import java.io.IOException; 21 | import java.io.Writer; 22 | import java.util.List; 23 | import java.util.Set; 24 | 25 | import javax.annotation.Generated; 26 | import javax.annotation.processing.AbstractProcessor; 27 | import javax.annotation.processing.RoundEnvironment; 28 | import javax.annotation.processing.SupportedAnnotationTypes; 29 | import javax.annotation.processing.SupportedSourceVersion; 30 | import javax.inject.Named; 31 | import javax.lang.model.SourceVersion; 32 | import javax.lang.model.element.Element; 33 | import javax.lang.model.element.ExecutableElement; 34 | import javax.lang.model.element.Modifier; 35 | import javax.lang.model.element.PackageElement; 36 | import javax.lang.model.element.TypeElement; 37 | import javax.lang.model.util.ElementFilter; 38 | 39 | import com.github.imasahiro.stringformatter.annotation.AutoStringFormatter; 40 | import com.github.imasahiro.stringformatter.annotation.Format; 41 | import com.github.imasahiro.stringformatter.processor.util.AbortProcessingException; 42 | import com.github.imasahiro.stringformatter.processor.util.ErrorReporter; 43 | import com.github.imasahiro.stringformatter.processor.util.TypeUtils; 44 | import com.google.auto.common.MoreElements; 45 | import com.google.common.collect.ImmutableList; 46 | import com.squareup.javapoet.AnnotationSpec; 47 | import com.squareup.javapoet.JavaFile; 48 | import com.squareup.javapoet.TypeName; 49 | import com.squareup.javapoet.TypeSpec; 50 | 51 | @SupportedAnnotationTypes("com.github.imasahiro.stringformatter.*") 52 | @SupportedSourceVersion(SourceVersion.RELEASE_8) 53 | public class StringFormatterProcessor extends AbstractProcessor { 54 | 55 | private static final TypeName JAVA_LANG_STRING = TypeName.get(String.class); 56 | private ErrorReporter errorReporter; 57 | 58 | private static List filterFormatAnnotatedMethods(Set methods) { 59 | ImmutableList.Builder targetMethods = ImmutableList.builder(); 60 | methods.stream() 61 | .filter(method -> JAVA_LANG_STRING.equals(TypeName.get(method.getReturnType())) && 62 | method.getAnnotation(Format.class) != null) 63 | .forEach(targetMethods::add); 64 | return targetMethods.build(); 65 | } 66 | 67 | @Override 68 | public boolean process(final Set annotations, final RoundEnvironment roundEnv) { 69 | try { 70 | errorReporter = new ErrorReporter(processingEnv.getMessager()); 71 | ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(AutoStringFormatter.class)).forEach( 72 | typeElement -> { 73 | List formatterMethodList = buildFormatterMethods(typeElement); 74 | SourceData source = new SourceData(MoreElements.getPackage(typeElement), typeElement); 75 | 76 | try (Writer writer = processingEnv.getFiler() 77 | .createSourceFile(source.getSourceName()) 78 | .openWriter()) { 79 | JavaFile javaFile = JavaFile.builder(source.getPackageName(), 80 | buildClass(typeElement, source.getClassName(), 81 | formatterMethodList)) 82 | .build(); 83 | javaFile.writeTo(writer); 84 | } catch (IOException ignored) { 85 | errorReporter.fatal("Cannot write java file to " + source.getSourceName(), 86 | typeElement); 87 | } 88 | }); 89 | return true; 90 | } catch (AbortProcessingException ignored) { 91 | } 92 | return false; 93 | } 94 | 95 | private TypeSpec buildClass(TypeElement superInterface, String className, 96 | List formatterMethodList) { 97 | TypeSpec.Builder builder = 98 | TypeSpec.classBuilder(className) 99 | .addSuperinterface(TypeName.get(superInterface.asType())) 100 | .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 101 | .addAnnotation(AnnotationSpec.builder(Generated.class) 102 | .addMember("value", "{$S}", getClass().getCanonicalName()) 103 | .build()) 104 | .addAnnotation(AnnotationSpec.builder(Named.class).build()); 105 | formatterMethodList.forEach(formatter -> builder.addMethod(formatter.getMethod(processingEnv))); 106 | return builder.build(); 107 | } 108 | 109 | private List buildFormatterMethods(TypeElement element) { 110 | AutoStringFormatter type = element.getAnnotation(AutoStringFormatter.class); 111 | if (!TypeUtils.isInterface(element)) { 112 | errorReporter.warn("@" + AutoStringFormatter.class.getName() + 113 | "only applies to interfaces. " + type, element); 114 | return ImmutableList.of(); 115 | } 116 | return filterFormatAnnotatedMethods( 117 | MoreElements.getLocalAndInheritedMethods(element, 118 | processingEnv.getTypeUtils(), 119 | processingEnv.getElementUtils())) 120 | .stream() 121 | .map(this::buildFormatterMethod) 122 | .collect(toImmutableList()); 123 | } 124 | 125 | private FormatterMethod buildFormatterMethod(ExecutableElement method) { 126 | Format fmt = method.getAnnotation(Format.class); 127 | return FormatterMethod.builder() 128 | .name(method.getSimpleName().toString()) 129 | .formatter(fmt.value()) 130 | .bufferCapacity(fmt.capacity()) 131 | .argumentTypeNames(method.getParameters().stream() 132 | .map(Element::asType) 133 | .collect(toImmutableList())) 134 | .element(method) 135 | .errorReporter(errorReporter) 136 | .build(); 137 | } 138 | 139 | private static class SourceData { 140 | private final PackageElement packageElement; 141 | private final String packageName; 142 | private final String className; 143 | 144 | SourceData(PackageElement packageElement, TypeElement typeElement) { 145 | this.packageElement = packageElement; 146 | packageName = packageElement.getQualifiedName().toString(); 147 | className = TypeUtils.generateClassName(typeElement); 148 | } 149 | 150 | String getSourceName() { 151 | return packageElement + "." + className; 152 | } 153 | 154 | String getPackageName() { 155 | return packageName; 156 | } 157 | 158 | String getClassName() { 159 | return className; 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /processor/src/main/java/com/github/imasahiro/stringformatter/processor/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.processor; 18 | -------------------------------------------------------------------------------- /processor/src/main/java/com/github/imasahiro/stringformatter/processor/specifier/BooleanFormatConversionType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.processor.specifier; 18 | 19 | import java.util.Set; 20 | 21 | import javax.lang.model.type.TypeKind; 22 | import javax.lang.model.type.TypeMirror; 23 | import javax.lang.model.util.Elements; 24 | import javax.lang.model.util.Types; 25 | 26 | import com.github.imasahiro.stringformatter.processor.FormatFlag; 27 | import com.github.mustachejava.DefaultMustacheFactory; 28 | import com.github.mustachejava.Mustache; 29 | import com.google.common.collect.ImmutableMap; 30 | import com.google.common.collect.ImmutableSet; 31 | 32 | public class BooleanFormatConversionType extends FormatConversionType { 33 | private static final Mustache BOOLEAN_TEMPLATE = 34 | new DefaultMustacheFactory().compile("template/boolean.mustache"); 35 | private static final Mustache BOOLEAN_TEMPLATE_WITH_WIDTH = 36 | new DefaultMustacheFactory().compile("template/boolean_with_width.mustache"); 37 | 38 | @Override 39 | public Set getType(Types typeUtil, Elements elementUtil) { 40 | return ImmutableSet.of(typeUtil.getPrimitiveType(TypeKind.BOOLEAN)); 41 | } 42 | 43 | @Override 44 | public String emit(String arg, int width, int precision, Set flags, TypeMirror argumentType) { 45 | ImmutableMap scopeBuilder = ImmutableMap.of( 46 | "ARG", arg, 47 | "width", width, 48 | "TRUE", flags.contains(FormatFlag.UPPER_CASE) ? "TRUE" : "true", 49 | "FALSE", flags.contains(FormatFlag.UPPER_CASE) ? "FALSE" : "false"); 50 | if (width > Boolean.TRUE.toString().length()) { 51 | return getCode(BOOLEAN_TEMPLATE_WITH_WIDTH, scopeBuilder); 52 | } else { 53 | return getCode(BOOLEAN_TEMPLATE, scopeBuilder); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /processor/src/main/java/com/github/imasahiro/stringformatter/processor/specifier/CharacterFormatConversionType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.processor.specifier; 18 | 19 | import java.util.Set; 20 | 21 | import javax.lang.model.type.TypeKind; 22 | import javax.lang.model.type.TypeMirror; 23 | import javax.lang.model.util.Elements; 24 | import javax.lang.model.util.Types; 25 | 26 | import com.google.common.collect.ImmutableSet; 27 | 28 | public class CharacterFormatConversionType extends FormatConversionType { 29 | @Override 30 | public Set getType(Types typeUtil, Elements elementUtil) { 31 | return ImmutableSet.of(typeUtil.getPrimitiveType(TypeKind.CHAR)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /processor/src/main/java/com/github/imasahiro/stringformatter/processor/specifier/FloatFormatConversionType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.processor.specifier; 18 | 19 | import java.util.Set; 20 | 21 | import javax.lang.model.type.TypeKind; 22 | import javax.lang.model.type.TypeMirror; 23 | import javax.lang.model.util.Elements; 24 | import javax.lang.model.util.Types; 25 | 26 | import com.google.common.collect.ImmutableSet; 27 | 28 | public class FloatFormatConversionType extends FormatConversionType { 29 | @Override 30 | public Set getType(Types typeUtil, Elements elementUtil) { 31 | return ImmutableSet.of(typeUtil.getPrimitiveType(TypeKind.FLOAT), 32 | typeUtil.getPrimitiveType(TypeKind.DOUBLE)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /processor/src/main/java/com/github/imasahiro/stringformatter/processor/specifier/FormatConversionType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.imasahiro.stringformatter.processor.specifier; 17 | 18 | import java.io.StringWriter; 19 | import java.util.Map; 20 | import java.util.Set; 21 | 22 | import javax.lang.model.type.TypeMirror; 23 | import javax.lang.model.util.Elements; 24 | import javax.lang.model.util.Types; 25 | 26 | import com.github.imasahiro.stringformatter.processor.FormatFlag; 27 | import com.github.mustachejava.Mustache; 28 | 29 | public abstract class FormatConversionType { 30 | static String getCode(Mustache template, Map scope) { 31 | StringWriter sw = new StringWriter(); 32 | template.execute(sw, scope); 33 | return sw.toString(); 34 | } 35 | 36 | public abstract Set getType(Types typeUtil, Elements elementUtil); 37 | 38 | public String emit(String arg, int width, int precision, Set flags, TypeMirror argumentType) { 39 | return "sb.append(" + arg + ");\n"; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /processor/src/main/java/com/github/imasahiro/stringformatter/processor/specifier/HexIntegerFormatConversionType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.processor.specifier; 18 | 19 | import java.util.Set; 20 | 21 | import javax.lang.model.type.TypeMirror; 22 | 23 | import com.github.imasahiro.stringformatter.processor.FormatFlag; 24 | import com.github.imasahiro.stringformatter.runtime.integers.HexIntegerFormatter; 25 | import com.github.imasahiro.stringformatter.runtime.integers.IntegerFormatter; 26 | import com.github.mustachejava.DefaultMustacheFactory; 27 | import com.github.mustachejava.Mustache; 28 | import com.google.common.collect.ImmutableMap; 29 | 30 | public class HexIntegerFormatConversionType extends IntegerFormatConversionType { 31 | private static final String FORMATTER_NAME = HexIntegerFormatter.class.getCanonicalName(); 32 | 33 | private static final Mustache TEMPLATE = 34 | new DefaultMustacheFactory().compile("template/int_with_width.mustache"); 35 | 36 | private static String convertFlags(Set flags) { 37 | // TODO Support left-justified. 38 | if (flags.contains(FormatFlag.ZERO)) { 39 | return String.valueOf( 40 | IntegerFormatter.PADDED_WITH_ZEROS); 41 | } 42 | return "0"; 43 | } 44 | 45 | @Override 46 | public String emit(String arg, int width, int precision, Set flags, TypeMirror argumentType) { 47 | return getCode(TEMPLATE, ImmutableMap.of("FORMATTER_NAME", FORMATTER_NAME, 48 | "ARG", arg, 49 | "flags", convertFlags(flags), 50 | "width", String.valueOf(width))); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /processor/src/main/java/com/github/imasahiro/stringformatter/processor/specifier/IntegerFormatConversionType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.processor.specifier; 18 | 19 | import java.util.Set; 20 | 21 | import javax.lang.model.type.TypeKind; 22 | import javax.lang.model.type.TypeMirror; 23 | import javax.lang.model.util.Elements; 24 | import javax.lang.model.util.Types; 25 | 26 | import com.github.imasahiro.stringformatter.processor.FormatFlag; 27 | import com.github.imasahiro.stringformatter.runtime.integers.IntegerFormatter; 28 | import com.github.mustachejava.DefaultMustacheFactory; 29 | import com.github.mustachejava.Mustache; 30 | import com.google.common.collect.ImmutableMap; 31 | import com.google.common.collect.ImmutableSet; 32 | 33 | public class IntegerFormatConversionType extends FormatConversionType { 34 | private static final String FORMATTER_NAME = IntegerFormatter.class.getCanonicalName(); 35 | 36 | private static final Mustache TEMPLATE = 37 | new DefaultMustacheFactory().compile("template/int.mustache"); 38 | private static final Mustache TEMPLATE_WIDTH = 39 | new DefaultMustacheFactory().compile("template/int_with_width.mustache"); 40 | 41 | private static String convertFlags(Set flags) { 42 | // TODO Support left-justified. 43 | if (flags.contains(FormatFlag.ZERO)) { 44 | return String.valueOf(IntegerFormatter.PADDED_WITH_ZEROS); 45 | } 46 | return "0"; 47 | } 48 | 49 | @Override 50 | public Set getType(Types typeUtil, Elements elementUtil) { 51 | return ImmutableSet.of(typeUtil.getPrimitiveType(TypeKind.SHORT), 52 | typeUtil.getPrimitiveType(TypeKind.INT), 53 | typeUtil.getPrimitiveType(TypeKind.LONG)); 54 | } 55 | 56 | @Override 57 | public String emit(String arg, int width, int precision, Set flags, TypeMirror argumentType) { 58 | if (width >= 0) { 59 | return getCode(TEMPLATE_WIDTH, ImmutableMap.of("FORMATTER_NAME", FORMATTER_NAME, 60 | "ARG", arg, 61 | "flags", convertFlags(flags), 62 | "width", String.valueOf(width))); 63 | } else { 64 | return getCode(TEMPLATE, ImmutableMap.of("ARG", arg)); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /processor/src/main/java/com/github/imasahiro/stringformatter/processor/specifier/StringFormatConversionType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.processor.specifier; 18 | 19 | import java.util.Formattable; 20 | import java.util.FormattableFlags; 21 | import java.util.Formatter; 22 | import java.util.Map; 23 | import java.util.Set; 24 | 25 | import javax.lang.model.type.TypeMirror; 26 | import javax.lang.model.util.Elements; 27 | import javax.lang.model.util.Types; 28 | 29 | import com.github.imasahiro.stringformatter.processor.FormatFlag; 30 | import com.github.mustachejava.DefaultMustacheFactory; 31 | import com.github.mustachejava.Mustache; 32 | import com.google.common.base.Joiner; 33 | import com.google.common.collect.ImmutableList; 34 | import com.google.common.collect.ImmutableMap; 35 | import com.google.common.collect.ImmutableSet; 36 | import com.squareup.javapoet.TypeName; 37 | 38 | public class StringFormatConversionType extends FormatConversionType { 39 | private static final TypeName FORMATTABLE_TYPE = TypeName.get(Formattable.class); 40 | private static final TypeName FORMATTER_TYPE = TypeName.get(Formatter.class); 41 | 42 | private static final Mustache STRING_TEMPLATE = 43 | new DefaultMustacheFactory().compile("template/string.mustache"); 44 | private static final Mustache FORMATTABLE_TEMPLATE = 45 | new DefaultMustacheFactory().compile("template/formattable.mustache"); 46 | 47 | private static String convertToFormattableFlags(Set flags) { 48 | ImmutableList.Builder flagBuilder = ImmutableList.builder(); 49 | if (flags.contains(FormatFlag.UPPER_CASE)) { 50 | flagBuilder.add(FormattableFlags.UPPERCASE); 51 | } 52 | if (flags.contains(FormatFlag.SHARP)) { 53 | flagBuilder.add(FormattableFlags.ALTERNATE); 54 | } 55 | if (flags.contains(FormatFlag.MINUS)) { 56 | flagBuilder.add(FormattableFlags.LEFT_JUSTIFY); 57 | } 58 | ImmutableList formatterFlags = flagBuilder.build(); 59 | if (formatterFlags.isEmpty()) { 60 | formatterFlags = ImmutableList.of(0); 61 | } 62 | return Joiner.on("|").join(formatterFlags); 63 | } 64 | 65 | @Override 66 | public Set getType(Types typeUtil, Elements elementUtil) { 67 | return ImmutableSet.of(elementUtil.getTypeElement(Formattable.class.getCanonicalName()).asType(), 68 | elementUtil.getTypeElement(Object.class.getCanonicalName()).asType()); 69 | } 70 | 71 | @Override 72 | public String emit(String arg, int width, int precision, Set flags, TypeMirror argumentType) { 73 | Map scope = ImmutableMap.of("FORMATTER_NAME", FORMATTER_TYPE.toString(), 74 | "ARG", arg, 75 | "flags", convertToFormattableFlags(flags), 76 | "width", String.valueOf(width), 77 | "%precision%", String.valueOf(precision)); 78 | 79 | if (FORMATTABLE_TYPE.equals(TypeName.get(argumentType))) { 80 | return getCode(FORMATTABLE_TEMPLATE, scope); 81 | } else { 82 | return getCode(STRING_TEMPLATE, scope); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /processor/src/main/java/com/github/imasahiro/stringformatter/processor/specifier/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.processor.specifier; 18 | -------------------------------------------------------------------------------- /processor/src/main/java/com/github/imasahiro/stringformatter/processor/util/AbortProcessingException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.processor.util; 18 | 19 | public final class AbortProcessingException extends RuntimeException { 20 | private static final long serialVersionUID = 1478917429610333843L; 21 | } 22 | -------------------------------------------------------------------------------- /processor/src/main/java/com/github/imasahiro/stringformatter/processor/util/ErrorReporter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.processor.util; 18 | 19 | import javax.annotation.processing.Messager; 20 | import javax.lang.model.element.Element; 21 | import javax.tools.Diagnostic; 22 | 23 | /** 24 | * An class for logging/reporting compilation error/warning messages. 25 | */ 26 | public class ErrorReporter { 27 | private final Messager messager; 28 | 29 | public ErrorReporter(Messager messager) { 30 | this.messager = messager; 31 | } 32 | 33 | public void warn(String msg, Element e) { 34 | messager.printMessage(Diagnostic.Kind.WARNING, msg, e); 35 | } 36 | 37 | public void error(String msg, Element e) { 38 | messager.printMessage(Diagnostic.Kind.ERROR, msg, e); 39 | } 40 | 41 | /** 42 | * Reports error message and stop annotation processing. 43 | */ 44 | public void fatal(String msg, Element e) { 45 | error(msg, e); 46 | throw new AbortProcessingException(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /processor/src/main/java/com/github/imasahiro/stringformatter/processor/util/TypeUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.processor.util; 18 | 19 | import javax.lang.model.element.ElementKind; 20 | import javax.lang.model.element.TypeElement; 21 | 22 | /** 23 | * Utility methods for generating types. 24 | */ 25 | public final class TypeUtils { 26 | private TypeUtils() { 27 | } 28 | 29 | public static boolean isInterface(TypeElement type) { 30 | return type.getKind() == ElementKind.INTERFACE; 31 | } 32 | 33 | /** 34 | * Generates a type name from {@code type}. 35 | */ 36 | public static String generateClassName(TypeElement type) { 37 | String name = type.getSimpleName().toString(); 38 | while (type.getEnclosingElement() instanceof TypeElement) { 39 | type = (TypeElement) type.getEnclosingElement(); 40 | name = type.getSimpleName() + "_" + name; 41 | } 42 | return name; 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /processor/src/main/java/com/github/imasahiro/stringformatter/processor/util/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.processor.util; 18 | -------------------------------------------------------------------------------- /processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor: -------------------------------------------------------------------------------- 1 | com.github.imasahiro.stringformatter.processor.StringFormatterProcessor 2 | -------------------------------------------------------------------------------- /processor/src/main/resources/template/boolean.mustache: -------------------------------------------------------------------------------- 1 | sb.append({{ARG}} ? "{{TRUE}}" : "{{FALSE}}"); 2 | -------------------------------------------------------------------------------- /processor/src/main/resources/template/boolean_with_width.mustache: -------------------------------------------------------------------------------- 1 | int {{ARG}}_len = {{ARG}} ? 4/*true*/ : 5/*false*/; 2 | padding({{width}} - {{ARG}}_len); 3 | sb.append({{ARG}} ? "{{TRUE}}" : "{{FALSE}}"); 4 | -------------------------------------------------------------------------------- /processor/src/main/resources/template/formattable.mustache: -------------------------------------------------------------------------------- 1 | {{ARG}}.formatTo(new {{FORMATTER_TYPE}}(sb), {{flags}}, {{width}}, {{precision}}); 2 | -------------------------------------------------------------------------------- /processor/src/main/resources/template/int.mustache: -------------------------------------------------------------------------------- 1 | sb.append({{ARG}}); 2 | -------------------------------------------------------------------------------- /processor/src/main/resources/template/int_with_width.mustache: -------------------------------------------------------------------------------- 1 | {{FORMATTER_NAME}}.formatTo(sb, {{ARG}}, {{flags}}, {{width}}); 2 | -------------------------------------------------------------------------------- /processor/src/main/resources/template/string.mustache: -------------------------------------------------------------------------------- 1 | sb.append(String.valueOf({{ARG}})); 2 | -------------------------------------------------------------------------------- /release.mk: -------------------------------------------------------------------------------- 1 | 2 | check: 3 | echo ${bintrayUser} 4 | echo ${bintrayKey} 5 | ./gradlew -PdryRun=true -PbintrayUser=${bintrayUser} -PbintrayKey=${bintrayKey} runtime:bintrayUpload --info 6 | ./gradlew -PdryRun=true -PbintrayUser=${bintrayUser} -PbintrayKey=${bintrayKey} processor:bintrayUpload --info 7 | 8 | release: check 9 | ./gradlew -PdryRun=false -PbintrayUser=${bintrayUser} -PbintrayKey=${bintrayKey} runtime:bintrayUpload 10 | ./gradlew -PdryRun=false -PbintrayUser=${bintrayUser} -PbintrayKey=${bintrayKey} processor:bintrayUpload 11 | -------------------------------------------------------------------------------- /runtime/build.gradle: -------------------------------------------------------------------------------- 1 | archivesBaseName = 'auto-string-formatter-runtime' 2 | 3 | dependencies { 4 | } 5 | -------------------------------------------------------------------------------- /runtime/src/main/java/com/github/imasahiro/stringformatter/annotation/AutoStringFormatter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.imasahiro.stringformatter.annotation; 17 | 18 | import java.lang.annotation.ElementType; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.RetentionPolicy; 21 | import java.lang.annotation.Target; 22 | 23 | /** 24 | * An annotation to be applied to elements for which a string formatter should be automatically generated. 25 | * Specifies that {@link AutoStringFormatter} should generate an implementation class for the annotated 26 | * interface, implementing the string formatter specified by {@link Format} annotation. A simple example: 27 | *
{@code
28 |  * @AutoStringFormatter
29 |  * interface IdFormatter {
30 |  *     @Format("id%08d")
31 |  *     String formatTo(int id);
32 |  * }
33 |  * }
34 | */ 35 | @Target(ElementType.TYPE) 36 | @Retention(RetentionPolicy.SOURCE) 37 | public @interface AutoStringFormatter { 38 | } 39 | -------------------------------------------------------------------------------- /runtime/src/main/java/com/github/imasahiro/stringformatter/annotation/Format.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.imasahiro.stringformatter.annotation; 17 | 18 | import static java.lang.annotation.ElementType.METHOD; 19 | 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | /** 25 | * An annotation to specify the string format. A simple example: 26 | *
{@code
27 |  * @AutoStringFormatter
28 |  * interface IdFormatter {
29 |  *     @Format("id%08d")
30 |  *     String formatTo(int id);
31 |  * }
32 |  * }
33 | */ 34 | @Target(METHOD) 35 | @Retention(RetentionPolicy.SOURCE) 36 | public @interface Format { 37 | /** 38 | * Format string. See 39 | * format syntax for format string syntax. 40 | */ 41 | String value(); 42 | 43 | /** 44 | * The initial capacity of a buffer. Default is the default capacity of {@link StringBuilder} buffer 45 | * ({@code 16}). 46 | */ 47 | int capacity() default 16; 48 | } 49 | -------------------------------------------------------------------------------- /runtime/src/main/java/com/github/imasahiro/stringformatter/annotation/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.annotation; 18 | -------------------------------------------------------------------------------- /runtime/src/main/java/com/github/imasahiro/stringformatter/runtime/integers/HexIntegerFormatter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.imasahiro.stringformatter.runtime.integers; 17 | 18 | /** 19 | * HexInteger to string format helpers. 20 | */ 21 | public final class HexIntegerFormatter { 22 | public static final int PADDED_WITH_ZEROS = 1; 23 | 24 | private HexIntegerFormatter() { 25 | } 26 | 27 | /** 28 | * Formats {@code v} to {@link String}. 29 | */ 30 | public static StringBuilder formatTo(StringBuilder sb, short v, int flags, int width) { 31 | long unsigned = Math.abs(v); 32 | unsigned += v < 0 ? 1L << 16 : 0; 33 | return format0(sb, unsigned, flags, width); 34 | } 35 | 36 | /** 37 | * Formats {@code v} to {@link String}. 38 | */ 39 | public static StringBuilder formatTo(StringBuilder sb, int v, int flags, int width) { 40 | long unsigned = Math.abs(v); 41 | unsigned += v < 0 ? 1L << 32 : 0; 42 | return format0(sb, unsigned, flags, width); 43 | } 44 | 45 | /** 46 | * Formats {@code v} to {@link String}. 47 | */ 48 | public static StringBuilder formatTo(StringBuilder sb, long v, int flags, int width) { 49 | return format0(sb, v, flags, width); 50 | } 51 | 52 | private static StringBuilder format0(StringBuilder sb, long val, int flags, int width) { 53 | int len = (IntegerUtils.log2(val) + 3) / 4; 54 | if ((flags & PADDED_WITH_ZEROS) != PADDED_WITH_ZEROS) { 55 | for (int i = len; i < width; i++) { 56 | sb.append(' '); 57 | } 58 | } 59 | if ((flags & PADDED_WITH_ZEROS) == PADDED_WITH_ZEROS) { 60 | for (int i = len; i < width; i++) { 61 | sb.append('0'); 62 | } 63 | } 64 | len *= 4; 65 | do { 66 | len -= 4; 67 | sb.append(Character.forDigit((int) (val >> len) & 0xf, 16)); 68 | } while (len != 0); 69 | return sb; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /runtime/src/main/java/com/github/imasahiro/stringformatter/runtime/integers/IntegerFormatter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.imasahiro.stringformatter.runtime.integers; 17 | 18 | /** 19 | * Integer to string format helpers. 20 | */ 21 | public final class IntegerFormatter { 22 | public static final int PADDED_WITH_ZEROS = 1; 23 | private static final boolean ENSURE_CAPACITY = true; 24 | private static final boolean DISABLE_INT_TO_ASCII_UNROLLING = false; 25 | 26 | private static final char[] digits99 = { 27 | '0', '0', '0', '1', '0', '2', '0', '3', '0', '4', '0', '5', '0', '6', '0', '7', '0', '8', '0', '9', 28 | '1', '0', '1', '1', '1', '2', '1', '3', '1', '4', '1', '5', '1', '6', '1', '7', '1', '8', '1', '9', 29 | '2', '0', '2', '1', '2', '2', '2', '3', '2', '4', '2', '5', '2', '6', '2', '7', '2', '8', '2', '9', 30 | '3', '0', '3', '1', '3', '2', '3', '3', '3', '4', '3', '5', '3', '6', '3', '7', '3', '8', '3', '9', 31 | '4', '0', '4', '1', '4', '2', '4', '3', '4', '4', '4', '5', '4', '6', '4', '7', '4', '8', '4', '9', 32 | '5', '0', '5', '1', '5', '2', '5', '3', '5', '4', '5', '5', '5', '6', '5', '7', '5', '8', '5', '9', 33 | '6', '0', '6', '1', '6', '2', '6', '3', '6', '4', '6', '5', '6', '6', '6', '7', '6', '8', '6', '9', 34 | '7', '0', '7', '1', '7', '2', '7', '3', '7', '4', '7', '5', '7', '6', '7', '7', '7', '8', '7', '9', 35 | '8', '0', '8', '1', '8', '2', '8', '3', '8', '4', '8', '5', '8', '6', '8', '7', '8', '8', '8', '9', 36 | '9', '0', '9', '1', '9', '2', '9', '3', '9', '4', '9', '5', '9', '6', '9', '7', '9', '8', '9', '9' 37 | }; 38 | 39 | // Long.toString(-1 * Long.MIN_VALUE) 40 | private static final String LONG_MIN_ABS_VALUE = "9223372036854775808"; 41 | 42 | private IntegerFormatter() { 43 | } 44 | 45 | /** 46 | * Formats {@code v} to {@link String}. 47 | */ 48 | public static StringBuilder formatTo(StringBuilder sb, short v, int flags, int width) { 49 | long abs = writeLeftPadding(sb, v, flags, width); 50 | return formatTo0(sb, abs); 51 | } 52 | 53 | /** 54 | * Formats {@code v} to {@link String}. 55 | */ 56 | public static StringBuilder formatTo(StringBuilder sb, int v, int flags, int width) { 57 | long abs = writeLeftPadding(sb, v, flags, width); 58 | return formatTo0(sb, abs); 59 | } 60 | 61 | /** 62 | * Formats {@code v} to {@link String}. 63 | */ 64 | public static StringBuilder formatTo(StringBuilder sb, long v, int flags, int width) { 65 | long abs = writeLeftPadding(sb, v, flags, width); 66 | if (v == Long.MIN_VALUE) { 67 | return sb.append(LONG_MIN_ABS_VALUE); 68 | } 69 | return formatTo0(sb, abs); 70 | } 71 | 72 | private static StringBuilder formatTo0(StringBuilder sb, long abs) { 73 | if (DISABLE_INT_TO_ASCII_UNROLLING) { 74 | return formatWithByteArray(sb, abs); 75 | } else { 76 | return formatToStringUnrolled(sb, abs); 77 | } 78 | } 79 | 80 | private static long writeLeftPadding(StringBuilder sb, long val, int flags, int width) { 81 | long abs = Math.abs(val); 82 | boolean negative = val < 0; 83 | int len = IntegerUtils.log10(abs) + (negative ? 1 : 0); 84 | if (ENSURE_CAPACITY) { 85 | sb.ensureCapacity(sb.length() + len + width); 86 | } 87 | if ((flags & PADDED_WITH_ZEROS) != PADDED_WITH_ZEROS) { 88 | for (int i = len; i < width; i++) { 89 | sb.append(' '); 90 | } 91 | } 92 | if (negative) { 93 | sb.append('-'); 94 | } 95 | if ((flags & PADDED_WITH_ZEROS) == PADDED_WITH_ZEROS) { 96 | for (int i = len; i < width; i++) { 97 | sb.append('0'); 98 | } 99 | } 100 | return abs; 101 | } 102 | 103 | private static StringBuilder formatToStringUnrolled(StringBuilder sb, long val) { 104 | // val = aaaabbbbccccddddeeee 105 | if (val <= 99999999) { 106 | formatLessThan100Million(sb, val); 107 | } else if (val <= 9999999999999999L) { 108 | formatLessThan10Quadrillion(sb, val); 109 | } else { 110 | formatMoreThan10Quadrillion(sb, val); 111 | } 112 | return sb; 113 | } 114 | 115 | private static void formatMoreThan10Quadrillion(StringBuilder sb, long val) { 116 | // val = aaaabbbbccccddddeeee 117 | int a = (int) (val / 10000000000000000L); 118 | long bcde = val % 10000000000000000L; 119 | int a1 = a / 100 * 2; 120 | int a2 = a % 100 * 2; 121 | int bc = (int) (bcde / 100000000); 122 | int de = (int) (bcde % 100000000); 123 | int b = bc / 10000; 124 | int c = bc % 10000; 125 | int d = de / 10000; 126 | int e = de % 10000; 127 | 128 | int b1 = b / 100 * 2; 129 | int b2 = b % 100 * 2; 130 | int c1 = c / 100 * 2; 131 | int c2 = c % 100 * 2; 132 | int d1 = d / 100 * 2; 133 | int d2 = d % 100 * 2; 134 | int e1 = e / 100 * 2; 135 | int e2 = e % 100 * 2; 136 | 137 | if (a >= 1000) { 138 | sb.append(digits99[a1]); 139 | } 140 | if (a >= 100) { 141 | sb.append(digits99[a1 + 1]); 142 | } 143 | if (a >= 10) { 144 | sb.append(digits99[a2]); 145 | } 146 | sb.append(digits99[a2 + 1]); 147 | sb.append(digits99[b1]); 148 | sb.append(digits99[b1 + 1]); 149 | sb.append(digits99[b2]); 150 | sb.append(digits99[b2 + 1]); 151 | sb.append(digits99[c1]); 152 | sb.append(digits99[c1 + 1]); 153 | sb.append(digits99[c2]); 154 | sb.append(digits99[c2 + 1]); 155 | sb.append(digits99[d1]); 156 | sb.append(digits99[d1 + 1]); 157 | sb.append(digits99[d2]); 158 | sb.append(digits99[d2 + 1]); 159 | sb.append(digits99[e1]); 160 | sb.append(digits99[e1 + 1]); 161 | sb.append(digits99[e2]); 162 | sb.append(digits99[e2 + 1]); 163 | } 164 | 165 | private static void formatLessThan10Quadrillion(StringBuilder sb, long val) { 166 | // val = bbbbccccddddeeee 167 | long bc = val / 100000000; 168 | long de = val % 100000000; 169 | int b = (int) (bc / 10000); 170 | int c = (int) (bc % 10000); 171 | int d = (int) (de / 10000); 172 | int e = (int) (de % 10000); 173 | 174 | int b1 = b / 100 * 2; 175 | int b2 = b % 100 * 2; 176 | int c1 = c / 100 * 2; 177 | int c2 = c % 100 * 2; 178 | int d1 = d / 100 * 2; 179 | int d2 = d % 100 * 2; 180 | int e1 = e / 100 * 2; 181 | int e2 = e % 100 * 2; 182 | if (bc >= 10000000) { 183 | sb.append(digits99[b1]); 184 | } 185 | if (bc >= 1000000) { 186 | sb.append(digits99[b1 + 1]); 187 | } 188 | if (bc >= 100000) { 189 | sb.append(digits99[b2]); 190 | } 191 | if (bc >= 10000) { 192 | sb.append(digits99[b2 + 1]); 193 | } 194 | if (bc >= 1000) { 195 | sb.append(digits99[c1]); 196 | } 197 | if (bc >= 100) { 198 | sb.append(digits99[c1 + 1]); 199 | } 200 | if (bc >= 10) { 201 | sb.append(digits99[c2]); 202 | } 203 | if (bc >= 1) { 204 | sb.append(digits99[c2 + 1]); 205 | } 206 | sb.append(digits99[d1]); 207 | sb.append(digits99[d1 + 1]); 208 | sb.append(digits99[d2]); 209 | sb.append(digits99[d2 + 1]); 210 | sb.append(digits99[e1]); 211 | sb.append(digits99[e1 + 1]); 212 | sb.append(digits99[e2]); 213 | sb.append(digits99[e2 + 1]); 214 | } 215 | 216 | private static void formatLessThan100Million(StringBuilder sb, long val) { 217 | // val = ddddeeee 218 | if (val <= 9999) { 219 | // val = eeee 220 | int e1 = (int) (val / 100 * 2); 221 | int e2 = (int) (val % 100 * 2); 222 | if (val >= 1000) { 223 | sb.append(digits99[e1]); 224 | } 225 | if (val >= 100) { 226 | sb.append(digits99[e1 + 1]); 227 | } 228 | if (val >= 10) { 229 | sb.append(digits99[e2]); 230 | } 231 | sb.append(digits99[e2 + 1]); 232 | } else { 233 | // val = ddddeeee 234 | int d = (int) (val / 10000); 235 | int e = (int) (val % 10000); 236 | int d1 = d / 100 * 2; 237 | int d2 = d % 100 * 2; 238 | int e1 = e / 100 * 2; 239 | int e2 = e % 100 * 2; 240 | if (d >= 1000) { 241 | sb.append(digits99[d1]); 242 | } 243 | if (d >= 100) { 244 | sb.append(digits99[d1 + 1]); 245 | } 246 | if (d >= 10) { 247 | sb.append(digits99[d2]); 248 | } 249 | sb.append(digits99[d2 + 1]); 250 | sb.append(digits99[e1]); 251 | sb.append(digits99[e1 + 1]); 252 | sb.append(digits99[e2]); 253 | sb.append(digits99[e2 + 1]); 254 | } 255 | } 256 | 257 | private static StringBuilder formatWithByteArray(StringBuilder sb, long val) { 258 | int len = IntegerUtils.log10(val); 259 | char[] buf = new char[len]; 260 | int index = len; 261 | 262 | while (val >= 100) { 263 | int idx = (int) (val % 100 * 2); 264 | buf[--index] = digits99[idx + 1]; 265 | buf[--index] = digits99[idx]; 266 | val /= 100; 267 | } 268 | if (val < 10) { 269 | buf[--index] = (char) ('0' + val); 270 | } else { 271 | buf[--index] = digits99[(int) (val * 2 + 1)]; 272 | buf[--index] = digits99[(int) (val * 2)]; 273 | } 274 | return sb.append(buf); 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /runtime/src/main/java/com/github/imasahiro/stringformatter/runtime/integers/IntegerUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.runtime.integers; 18 | 19 | /** 20 | * Integer utilities. 21 | */ 22 | public final class IntegerUtils { 23 | private static final long[] powersOf10 = { 24 | 1L, 25 | 10L, 26 | 100L, 27 | 1000L, 28 | 10000L, 29 | 100000L, 30 | 1000000L, 31 | 10000000L, 32 | 100000000L, 33 | 1000000000L, 34 | 10000000000L, 35 | 100000000000L, 36 | 1000000000000L, 37 | 10000000000000L, 38 | 100000000000000L, 39 | 1000000000000000L, 40 | 10000000000000000L, 41 | 100000000000000000L, 42 | 1000000000000000000L, 43 | Long.MAX_VALUE 44 | }; 45 | // maxLog10ForLeadingZeros[i] == floor(log10(2^(Long.SIZE - i))) 46 | private static final int[] maxLog10ForLeadingZeros = { 47 | 19, 18, 18, 18, 18, 17, 17, 17, 16, 16, 16, 15, 15, 15, 15, 14, 14, 14, 13, 13, 13, 12, 12, 48 | 12, 12, 11, 11, 11, 10, 10, 10, 9, 9, 9, 9, 8, 8, 8, 7, 7, 7, 6, 6, 6, 6, 5, 5, 5, 4, 4, 4, 49 | 3, 3, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0 50 | }; 51 | 52 | private IntegerUtils() { 53 | } 54 | 55 | /** 56 | * Returns the base 2 logarithm of a {@code long} value. 57 | * @param v unsigned {@code long} value. 58 | */ 59 | public static int log2(long v) { 60 | if (v != 0) { 61 | return Long.SIZE - Long.numberOfLeadingZeros(v); 62 | } else { 63 | return 1; 64 | } 65 | } 66 | 67 | /** 68 | * Returns the base 10 logarithm of a {@code long} value. 69 | * @param unsigned unsigned {@code long} value. 70 | */ 71 | public static int log10(long unsigned) { 72 | if (unsigned != 0) { 73 | int digits = maxLog10ForLeadingZeros[Long.numberOfLeadingZeros(unsigned)]; 74 | return digits + (unsigned >= powersOf10[digits] ? 1 : 0); 75 | } else { 76 | return 1; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /runtime/src/main/java/com/github/imasahiro/stringformatter/runtime/integers/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.runtime.integers; 18 | -------------------------------------------------------------------------------- /runtime/src/test/java/com/github/imasahiro/stringformatter/runtime/integers/IntegerUtilsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Masahiro Ide 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.imasahiro.stringformatter.runtime.integers; 18 | 19 | import static org.junit.Assert.assertEquals; 20 | 21 | import org.junit.Test; 22 | 23 | public class IntegerUtilsTest { 24 | 25 | @Test 26 | public void testLog10() throws Exception { 27 | assertEquals(1, IntegerUtils.log10(1)); 28 | assertEquals(1, IntegerUtils.log10(9)); 29 | 30 | assertEquals(2, IntegerUtils.log10(10)); 31 | assertEquals(2, IntegerUtils.log10(11)); 32 | assertEquals(2, IntegerUtils.log10(99)); 33 | 34 | assertEquals(3, IntegerUtils.log10(100)); 35 | assertEquals(3, IntegerUtils.log10(500)); 36 | assertEquals(3, IntegerUtils.log10(999)); 37 | 38 | assertEquals(4, IntegerUtils.log10(1000)); 39 | assertEquals(4, IntegerUtils.log10(5000)); 40 | assertEquals(4, IntegerUtils.log10(9999)); 41 | 42 | assertEquals(5, IntegerUtils.log10(10000)); 43 | assertEquals(5, IntegerUtils.log10(50000)); 44 | assertEquals(5, IntegerUtils.log10(99999)); 45 | 46 | assertEquals(19, IntegerUtils.log10(1000000000000000000L)); 47 | assertEquals(19, IntegerUtils.log10(5555555555555555555L)); 48 | assertEquals(19, IntegerUtils.log10(Long.MAX_VALUE)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'auto-string-formatter' 2 | 3 | apply from: "${rootDir}/gradle/scripts/settings-flags.gradle" 4 | 5 | // Published Java projects 6 | includeWithFlags ':processor', 'java', 'publish' 7 | includeWithFlags ':runtime', 'java', 'publish' 8 | 9 | // Unpublished Java projects 10 | includeWithFlags ':example', 'java' 11 | -------------------------------------------------------------------------------- /settings/checkstyle/checkstyle-suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | --------------------------------------------------------------------------------