Skip to content

Commit 7d2f90e

Browse files
committed
Merge branch 'release/1.3'
2 parents 19896c1 + 64b0ef2 commit 7d2f90e

File tree

6 files changed

+225
-65
lines changed

6 files changed

+225
-65
lines changed

.version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.3.0
1+
1.3.1

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
#### Version 1.3.1 (TBD)
4+
* Fixed some bugs related to serializing and deserializing some built-in `Transformers`.
5+
* Added missing transformers to ensure values are handled as Strings.
6+
37
#### Version 1.3.0 (November 23, 2018)
48
* Added the `ScriptedTransformer` framework. `ScriptedTransformer` is abstract class that can be extended to provide transformation logic via a script that is compatible with the Java script engine platform. Right now, javascript is the supported language for writing transformer scripts.
59
* Added support for Transformer serialization. The rules of serialization are:

build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ artifacts {
5050
}
5151

5252
// Set the version for all Concourse dependencies
53-
ext.concourseVersion = '0.9.3'
53+
ext.concourseVersion = '0.9.4'
5454

5555
repositories {
5656
mavenCentral()
@@ -69,7 +69,7 @@ repositories {
6969
}
7070

7171
dependencies {
72-
compile group: 'com.cinchapi', name: 'accent4j', version: '1.4.0', changing:true
72+
compile group: 'com.cinchapi', name: 'accent4j', version: '1.5.1', changing:true
7373
compile group: 'com.cinchapi', name: 'concourse-driver-java', version: concourseVersion, changing:true // needed for util classes...
7474
compile group: 'com.google.code.findbugs', name: 'jsr305', version:'2.0.1'
7575
compile group: 'com.google.guava', name:'guava', version:'25.1-jre'

src/main/java/com/cinchapi/etl/TransformerSerializationFactory.java

Lines changed: 111 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,20 @@
3030
import java.nio.ByteBuffer;
3131
import java.nio.charset.StandardCharsets;
3232
import java.util.Arrays;
33+
import java.util.Collection;
34+
import java.util.HashMap;
35+
import java.util.Iterator;
3336
import java.util.List;
3437
import java.util.Map;
3538
import java.util.stream.Collectors;
3639

40+
import javax.annotation.Nullable;
41+
3742
import com.cinchapi.common.base.CheckedExceptions;
3843
import com.cinchapi.common.base.Verify;
3944
import com.cinchapi.common.io.ByteBuffers;
4045
import com.cinchapi.common.reflect.Reflection;
4146
import com.google.common.collect.Lists;
42-
import com.google.common.collect.Maps;
4347
import com.google.common.primitives.Ints;
4448

4549
/**
@@ -63,6 +67,19 @@ public static TransformerSerializationFactory instance() {
6367
return INSTANCE;
6468
}
6569

70+
/**
71+
* Return a list of potential factory methods that could produced a
72+
* {@link Transformer} from the provided {@code params}.
73+
*
74+
* @param params
75+
* @return the potential factory methods
76+
*/
77+
private static List<Method> getPotentialFactoryMethods(Object... params) {
78+
return Arrays.stream(Transformers.class.getDeclaredMethods())
79+
.filter(method -> Reflection.isCallableWith(method, params))
80+
.collect(Collectors.toList());
81+
}
82+
6683
/**
6784
* Serialize the {@code object} into a {@link ByteBuffer}.
6885
*
@@ -114,8 +131,27 @@ private static <T> T getSerializedObject(ByteBuffer bytes) {
114131
* intentionally kept local to this instance of the factory.
115132
* </p>
116133
*/
117-
private final Map<String, Class<? extends Transformer>> lambdas = Maps
118-
.newHashMap();
134+
private final Map<String, Class<? extends Transformer>> lambdas = new HashMap<String, Class<? extends Transformer>>() {
135+
136+
private static final long serialVersionUID = -407507799832599951L;
137+
138+
@Override
139+
public Class<? extends Transformer> put(String key,
140+
Class<? extends Transformer> value) {
141+
if(Arrays.stream(Transformers.class.getDeclaredMethods())
142+
.filter(method -> method.getName().equals(key))
143+
.count() == 1) {
144+
// Don't cache overloaded methods because the associations
145+
// between the called method name and the lambda class would get
146+
// mixed up with the method that actually produces the lambda
147+
return super.put(key, value);
148+
}
149+
else {
150+
return null;
151+
}
152+
}
153+
154+
};
119155

120156
private TransformerSerializationFactory() {}
121157

@@ -199,26 +235,41 @@ public ByteBuffer serialize(Transformer transformer) {
199235
}
200236
// Go through each of the Transformers factories and try to
201237
// guess which method produced the #transformer
202-
List<Method> candidates = Arrays
203-
.stream(Transformers.class.getDeclaredMethods())
204-
.filter(method -> Reflection.isCallableWith(method,
205-
params))
206-
.collect(Collectors.toList());
207-
Method method = null;
208-
for (Method candidate : candidates) {
209-
candidate.setAccessible(true);
210-
Class<? extends Transformer> clazz = lambdas
211-
.get(candidate.getName());
212-
if(clazz == null) {
213-
Transformer t = (Transformer) candidate.invoke(null,
214-
params);
215-
clazz = t.getClass();
216-
lambdas.put(candidate.getName(), clazz);
217-
}
218-
if(clazz.equals(transformer.getClass())) {
219-
method = candidate;
220-
break;
238+
List<Method> candidates = getPotentialFactoryMethods(params);
239+
Method method = getFactoryMethod(transformer, candidates,
240+
params);
241+
if(method == null) {
242+
// Handle corner case of the factories take arrays as
243+
// parameters and convert them to collections
244+
boolean retry = false;
245+
for (int i = 0; i < params.length; ++i) {
246+
Object param = params[i];
247+
if(param instanceof Collection) {
248+
Class<?> type = null;
249+
Collection<?> cparam = (Collection<?>) param;
250+
for (Object item : cparam) {
251+
// Get the actual type of the collection to
252+
// create the array correctly
253+
type = type == null || item.getClass() == type
254+
? item.getClass()
255+
: Reflection.getClosestCommonAncestor(
256+
type, item.getClass());
257+
}
258+
Object[] array = (Object[]) java.lang.reflect.Array
259+
.newInstance(type, cparam.size());
260+
Iterator<?> it = cparam.iterator();
261+
for (int j = 0; j < cparam.size(); ++j) {
262+
array[j] = it.next();
263+
}
264+
params[i] = array;
265+
retry = true;
266+
}
221267
}
268+
candidates = retry ? getPotentialFactoryMethods(params)
269+
: candidates;
270+
method = retry
271+
? getFactoryMethod(transformer, candidates, params)
272+
: method;
222273
}
223274
Verify.that(method != null,
224275
"Cannot serialize transformer using technique "
@@ -257,6 +308,44 @@ public ByteBuffer serialize(Transformer transformer) {
257308
return serialized;
258309
}
259310

311+
/**
312+
* Return the factory method from amongst the {@code candidates} that
313+
* produced the {@code transformer} using the provided {@code params}
314+
*
315+
* @param transformer
316+
* @param candidates
317+
* @param params
318+
* @return the factory method for the {@link Transformer}
319+
* @throws ReflectiveOperationException
320+
*/
321+
@Nullable
322+
private Method getFactoryMethod(Transformer transformer,
323+
List<Method> candidates, Object... params)
324+
throws ReflectiveOperationException {
325+
Method method = null;
326+
for (Method candidate : candidates) {
327+
candidate.setAccessible(true);
328+
Class<? extends Transformer> clazz = lambdas
329+
.get(candidate.getName());
330+
try {
331+
if(clazz == null) {
332+
Transformer t = (Transformer) candidate.invoke(null,
333+
params);
334+
clazz = t.getClass();
335+
lambdas.put(candidate.getName(), clazz);
336+
}
337+
if(clazz.equals(transformer.getClass())) {
338+
method = candidate;
339+
break;
340+
}
341+
}
342+
catch (IllegalArgumentException e) {
343+
continue;
344+
}
345+
}
346+
return method;
347+
}
348+
260349
/**
261350
* The possible serialization techniques.
262351
*

src/main/java/com/cinchapi/etl/Transformers.java

Lines changed: 74 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -202,26 +202,6 @@ public static Transformer keyMap(Map<String, String> map) {
202202
};
203203
}
204204

205-
/**
206-
* Return a {@link Transformer} that replaces whitespace characters with an
207-
* underscore in keys.
208-
*
209-
* @return the {@link Transformer}
210-
*/
211-
public static Transformer keyWhitespaceToUnderscore() {
212-
return keyReplaceChars(ImmutableMap.of(' ', '_'));
213-
}
214-
215-
/**
216-
* Return a {@link Transformer} that removes all whitespace characters from
217-
* a key.
218-
*
219-
* @return the {@link Transformer}
220-
*/
221-
public static Transformer keyRemoveWhitespace() {
222-
return keyRemoveInvalidChars(Character::isWhitespace);
223-
}
224-
225205
/**
226206
* Return a {@link Transformer} that removes all the character in the
227207
* {@code invalid} collection.
@@ -269,6 +249,16 @@ public static Transformer keyRemoveInvalidChars(
269249
};
270250
}
271251

252+
/**
253+
* Return a {@link Transformer} that removes all whitespace characters from
254+
* a key.
255+
*
256+
* @return the {@link Transformer}
257+
*/
258+
public static Transformer keyRemoveWhitespace() {
259+
return keyRemoveInvalidChars(Character::isWhitespace);
260+
}
261+
272262
/**
273263
* Return a {@link Transformer} that replaces all the character keys in the
274264
* {@code replacements} mapping with their associated character values in
@@ -366,6 +356,16 @@ public static Transformer keyValueStripQuotes() {
366356
return keyValueRemoveQuotes();
367357
}
368358

359+
/**
360+
* Return a {@link Transformer} that replaces whitespace characters with an
361+
* underscore in keys.
362+
*
363+
* @return the {@link Transformer}
364+
*/
365+
public static Transformer keyWhitespaceToUnderscore() {
366+
return keyReplaceChars(ImmutableMap.of(' ', '_'));
367+
}
368+
369369
/**
370370
* Return a {@link Transformer} that does not perform any key or value
371371
* transformations.
@@ -388,10 +388,6 @@ public static Transformer nullSafe(Transformer transformer) {
388388
: null;
389389
}
390390

391-
public static Transformer valueRemoveIfEmpty() {
392-
return valueRemoveIf(Empty.ness());
393-
}
394-
395391
/**
396392
* Return a {@link Transformer} that will cause a key/value pair to be
397393
* "removed" if the value is described by the provided {@code adjective}.
@@ -408,21 +404,6 @@ public static Transformer removeValuesThatAre(Adjective adjective) {
408404
return valueRemoveIf(adjective);
409405
}
410406

411-
/**
412-
* Return a {@link Transformer} that will cause a key/value pair to be
413-
* "removed" if the value is described by the provided {@code adjective}.
414-
* <p>
415-
* Removal is accomplished by returning an empty map for the transformation.
416-
* </p>
417-
*
418-
* @param adjective
419-
* @return the {@link Transformer}
420-
*/
421-
public static Transformer valueRemoveIf(Adjective adjective) {
422-
return (key, value) -> adjective.describes(value) ? ImmutableMap.of()
423-
: null;
424-
}
425-
426407
/**
427408
* Return a {@link Transformer} that, For EVERY key, transform values to a
428409
* {@link Boolean} if possible. If the value cannot be transformed, an
@@ -564,6 +545,41 @@ public static Transformer valueAsResolvableLinkInstruction(String... keys) {
564545
}
565546
}
566547

548+
/**
549+
* Return a {@link Transformer} that, for EVERY key, transforms values to a
550+
* {@link String}.
551+
*
552+
* @return the {@link Transformer}
553+
*/
554+
public static Transformer valueAsString() {
555+
return (key, value) -> value instanceof String ? null
556+
: Transformation.to(key, value.toString());
557+
}
558+
559+
/**
560+
* Return a {@link Transformer} that, for the specified {@code key}as,
561+
* transforms values to a {@link String}.
562+
*
563+
* @param keys
564+
* @return the {@link Transformer}
565+
*/
566+
public static Transformer valueAsString(String... keys) {
567+
if(keys.length == 0) {
568+
return valueAsString();
569+
}
570+
else {
571+
Set<String> _keys = Arrays.stream(keys).collect(Collectors.toSet());
572+
return (key, value) -> {
573+
if(_keys.contains(key)) {
574+
return valueAsString().transform(key, value);
575+
}
576+
else {
577+
return null;
578+
}
579+
};
580+
}
581+
}
582+
567583
/**
568584
* Return a {@link Transformer} that, For EVERY key, transform values to a
569585
* {@link Number} if possible. If the value cannot be transformed, an
@@ -687,6 +703,25 @@ public static Transformer valueNullifyIfEmpty(Empty empty) {
687703
};
688704
}
689705

706+
/**
707+
* Return a {@link Transformer} that will cause a key/value pair to be
708+
* "removed" if the value is described by the provided {@code adjective}.
709+
* <p>
710+
* Removal is accomplished by returning an empty map for the transformation.
711+
* </p>
712+
*
713+
* @param adjective
714+
* @return the {@link Transformer}
715+
*/
716+
public static Transformer valueRemoveIf(Adjective adjective) {
717+
return (key, value) -> adjective.describes(value) ? ImmutableMap.of()
718+
: null;
719+
}
720+
721+
public static Transformer valueRemoveIfEmpty() {
722+
return valueRemoveIf(Empty.ness());
723+
}
724+
690725
/**
691726
* Return a {@link Transformer} that splits a String value into multiple
692727
* strings that are all mapped from the original key.

0 commit comments

Comments
 (0)