Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Builder methods for adding individual items to Collection and Map properties (WIP) #212

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@
<name>Martynas Sateika</name>
<url>https://github.com/martynassateika</url>
</contributor>
<contributor>
<name>Ashley Heath</name>
<url>https://github.com/ashleyheath</url>
</contributor>
</contributors>

<!-- ==================================================================== -->
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/org/joda/beans/gen/BeanGen.java
Original file line number Diff line number Diff line change
Expand Up @@ -1267,6 +1267,8 @@ private void generateBuilderClass() {
}
generateIndentedSeparator();
generateBuilderPropertySetMethods();
generateBuilderPropertyAddMethods();
generateBuilderPropertyPutMethods();
generateIndentedSeparator();
generateBuilderToString();
addLine(1, "}");
Expand Down Expand Up @@ -1423,6 +1425,22 @@ private void generateBuilderPropertySetMethods() {
}
}

private void generateBuilderPropertyAddMethods() {
if (data.isEffectiveBuilderScopeVisible()) {
for (PropertyGen prop : nonDerivedProperties()) {
addLines(prop.generateBuilderAddMethod());
}
}
}

private void generateBuilderPropertyPutMethods() {
if (data.isEffectiveBuilderScopeVisible()) {
for (PropertyGen prop : nonDerivedProperties()) {
addLines(prop.generateBuilderPutMethod());
}
}
}

private void generateBuilderToString() {
List<PropertyGen> nonDerived = toStringProperties();
if (data.isImmutable() && data.isTypeFinal()) {
Expand Down
44 changes: 41 additions & 3 deletions src/main/java/org/joda/beans/gen/BeanGenConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ public final class BeanGenConfig {
* The builder generators.
*/
private final Map<String, BuilderGen> builderGenerators;
/**
* The collection generators.
*/
private final Map<String, CollectionGen> collectionGenerators;
/**
* The invalid immutable types.
*/
Expand Down Expand Up @@ -100,7 +104,7 @@ public static BeanGenConfig parse(String resourceLocator) {
if (loader == null) {
throw new IllegalArgumentException("ClassLoader was null: " + fullFile);
}
URL url = loader.getResource(fullFile);
URL url = BeanGenConfig.class.getResource("guava.ini");
if (url == null) {
throw new IllegalArgumentException("Configuration file not found: " + fullFile);
}
Expand Down Expand Up @@ -135,6 +139,7 @@ private static BeanGenConfig parse(List<String> lines) {
Map<String, String> immutableGetClones = new HashMap<>();
Map<String, String> immutableVarArgs = new HashMap<>();
Map<String, String> builderInits = new HashMap<>();
Map<String, String> builderAdds = new HashMap<>();
Map<String, String> builderTypes = new HashMap<>();
Set<String> invalidImmutableTypes = new HashSet<>();
for (ListIterator<String> iterator = lines.listIterator(); iterator.hasNext(); ) {
Expand Down Expand Up @@ -242,6 +247,21 @@ private static BeanGenConfig parse(List<String> lines) {
String value = line.substring(pos + 1).trim();
builderInits.put(key, value);
}
} else if (line.equals("[immutable.builder.add]")){
while (iterator.hasNext()) {
line = iterator.next().trim();
if (line.startsWith("[")) {
iterator.previous();
break;
}
int pos = line.indexOf('=');
if (pos <= 0) {
throw new IllegalArgumentException("Invalid ini file line: " + line);
}
String key = line.substring(0, pos).trim();
String value = line.substring(pos + 1).trim();
builderAdds.put(key, value);
}
} else {
throw new IllegalArgumentException("Invalid ini file section: " + line);
}
Expand All @@ -265,7 +285,13 @@ private static BeanGenConfig parse(List<String> lines) {
}
copyGenerators.put(fieldType, new CopyGen.PatternCopyGen(immutableCopier, mutableCopier));
}
return new BeanGenConfig(copyGenerators, builderGenerators, builderTypes, invalidImmutableTypes, immutableVarArgs, immutableGetClones);
Map<String, CollectionGen> collectionGenerators = new HashMap<>();
for (Entry<String, String> entry : builderAdds.entrySet()) {
String fieldType = entry.getKey();
String adder = entry.getValue();
collectionGenerators.put(fieldType, new CollectionGen.PatternCollectionGen(adder));
}
return new BeanGenConfig(copyGenerators, builderGenerators, collectionGenerators, builderTypes, invalidImmutableTypes, immutableVarArgs, immutableGetClones);
}

//-----------------------------------------------------------------------
Expand All @@ -274,6 +300,7 @@ private static BeanGenConfig parse(List<String> lines) {
*
* @param copyGenerators the copy generators, not null
* @param builderGenerators the builder generators, not null
* @param collectionGenerators the collection generators, not null
* @param builderTypes the builder types, not null
* @param invalidImmutableTypes the invalid immutable types, not null
* @param immutableVarArgs the varargs code
Expand All @@ -282,12 +309,14 @@ private static BeanGenConfig parse(List<String> lines) {
private BeanGenConfig(
Map<String, CopyGen> copyGenerators,
Map<String, BuilderGen> builderGenerators,
Map<String, CollectionGen> collectionGenerators,
Map<String, String> builderTypes,
Set<String> invalidImmutableTypes,
Map<String, String> immutableVarArgs,
Map<String, String> immutableGetClones) {
this.copyGenerators = copyGenerators;
this.builderGenerators = builderGenerators;
this.collectionGenerators = collectionGenerators;
this.builderTypes = builderTypes;
this.invalidImmutableTypes = invalidImmutableTypes;
this.immutableVarArgs = immutableVarArgs;
Expand All @@ -306,13 +335,22 @@ public Map<String, CopyGen> getCopyGenerators() {

/**
* The builder generators.
*
*
* @return the generators, not null
*/
public Map<String, BuilderGen> getBuilderGenerators() {
return builderGenerators;
}

/**
* The collection generators.
*
* @return the generators, not null
*/
public Map<String, CollectionGen> getCollectionGenerators() {
return collectionGenerators;
}

/**
* The builder types.
*
Expand Down
61 changes: 61 additions & 0 deletions src/main/java/org/joda/beans/gen/CollectionGen.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2019-present Stephen Colebourne
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.joda.beans.gen;

/**
* A generator of collection code.
*/
abstract class CollectionGen {

/**
* Generates code to add an element to a collection.
*
* @param existing the name of an existing collection or an expression evaluating to an instance of a collection, not null
* @param collectionTypeParams the type params of the collection, may be empty
* @param value the name of the value to add to the collection or an expression evaluating to an instance of a collection, not null
* @return the generated code, not null
*/
abstract String generateAddToCollection(String existing, String collectionTypeParams, String value);

/**
* Generates code to add a new key/value pair to a map.
*
* @param existing the name of an existing map or an expression evaluating to an instance of a map, not null
* @param mapTypeParams the type params of the map, may be empty
* @param key the name of the key to add to the map or an expression evaluating to an instance of the key type, not null
* @param value the name of the value to add to the map or an expression evaluating to an instance of the value type, not null
* @return the generated code, not null
*/
abstract String generateAddToMap(String existing, String mapTypeParams, String key, String value);

static class PatternCollectionGen extends CollectionGen {
private final String pattern;

PatternCollectionGen(String pattern) {
this.pattern = pattern;
}

@Override
String generateAddToCollection(String existing, String collectionTypeParams, String value) {
return generateAddToMap(existing, collectionTypeParams, "", value);
}

@Override
String generateAddToMap(String existing, String mapTypeParams, String key, String value) {
return pattern.replaceAll("\\$existing", existing).replaceAll("\\$params", mapTypeParams).replaceAll("\\$value", value).replaceAll("\\$key", key);
}
}
}
72 changes: 71 additions & 1 deletion src/main/java/org/joda/beans/gen/PropertyData.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
Expand Down Expand Up @@ -119,6 +120,8 @@ class PropertyData {
private CopyGen copyGen;
/** The builder generator. */
private BuilderGen builderGen;
/** The collection generator. */
private CollectionGen collectionGen;
/** The config. */
private BeanGenConfig config;

Expand Down Expand Up @@ -658,6 +661,19 @@ public String getGenericParamType() {
return type.substring(pos + 1, type.length() - 1);
}

/**
* Gets the parameterisations of the property.
* {@code Map<String, Object>} will return a list of {@code String} and {@code Object}.
* @return the generic types, or an empty list if not generic, not null
*/
public List<String> getGenericParamTypes() {
String params = getGenericParamType();
if (params.isEmpty()) {
return Collections.emptyList();
}
return separateGenericParamTypes(params);
}

/**
* Checks if the type is the generic type of the bean.
* For example, if the property is of type T or T[] in a bean of Foo[T].
Expand Down Expand Up @@ -984,7 +1000,7 @@ public CopyGen getCopyGen() {

//-----------------------------------------------------------------------
/**
* Resolves the copy generator.
* Resolves the builder generator.
*/
public void resolveBuilderGen() {
if (getBean().isMutable()) {
Expand Down Expand Up @@ -1012,6 +1028,29 @@ public BuilderGen getBuilderGen() {
return builderGen;
}

//-----------------------------------------------------------------------
/**
* Resolves the collection generator.
*/
public void resolveCollectionGen() {
if (getBean().isMutable()) {
if (!getBean().isBuilderScopeVisible() && !getBean().isBeanStyleLightOrMinimal()) {
return; // no builder
}
}
if (!isDerived()) {
collectionGen = config.getCollectionGenerators().get(getFieldTypeRaw());
}
}

/**
* Gets the collection generator.
* @return the collection generator
*/
public CollectionGen getCollectionGen() {
return collectionGen;
}

//-----------------------------------------------------------------------
/**
* Checks if this property is an array type.
Expand Down Expand Up @@ -1157,4 +1196,35 @@ public String getVarArgsCode() {
return config.getImmutableVarArgs().get(getTypeRaw());
}

/**
* Take a string of type parameters (such as (@code String, List<String>} and separate it out
* into the top-level types it contains.
*
* @param typesString the param type string
* @return a list of the top-level types contained within the string (including their own parameter types)
*/
private static List<String> separateGenericParamTypes(String typesString) {
List<String> types = new ArrayList<>();

StringBuilder typeBuilder = new StringBuilder();
int nestingDepth = 0;
for (Character character : typesString.toCharArray()) {
if (character.equals('<')) {
nestingDepth += 1;
typeBuilder.append(character);
} else if (character.equals('>')) {
nestingDepth -= 1;
typeBuilder.append(character);
} else if (character.equals(',') && nestingDepth == 0) {
types.add(typeBuilder.toString().trim());
typeBuilder = new StringBuilder();
} else {
typeBuilder.append(character);
}
}

types.add(typeBuilder.toString().trim());

return types;
}
}