diff --git a/README.adoc b/README.adoc index d1cf79e..811e14b 100644 --- a/README.adoc +++ b/README.adoc @@ -1,4 +1,4 @@ -== Stringify Object for Java += Stringify Object for Java https://travis-ci.org/wavesoftware/java-stringify-object[image:https://travis-ci.org/wavesoftware/java-stringify-object.svg?branch=master[Build Status]] @@ -22,20 +22,20 @@ inspected. This library has proper support for object graph cycles, and JPA (Hibernate) lazy loaded elements. -=== Usage +== Usage -==== In Promiscuous mode +=== In Promiscuous mode [source,java] ---- // In PROMISCUOUS mode define fields to exclude class Person { private int id; - @DisplayNull + @DisplayNull // <1> private Person parent; private List childs; private Account account; - @Inspect(conditionally = IsInDevelopment.class) + @Inspect(conditionally = IsInDevelopment.class) // <2> private String password; @DoNotInspect private String ignored; @@ -46,12 +46,15 @@ List people = query.getResultList(); Stringify stringify = Stringify.of(people); stringify.mode(Mode.PROMISCUOUS); // stringify.beanFactory(...); -assert ", childs=[], " - + "account=⁂Lazy>".equals(stringify.toString()); + + "account=⁂Lazy>]".equals(stringify.toString()); ---- -==== In Quiet mode +<1> Configures a field inspection to show null values +<2> Inspects a field, but only if given predicate returns `true` + +=== In Quiet mode [source,java] ---- @@ -68,12 +71,12 @@ class Person { List people = query.getResultList(); Stringify stringify = Stringify.of(people); stringify.mode(Mode.QUIET); -assert ", childs=[], " - + "account=⁂Lazy>".equals(stringify.toString()); + + "account=⁂Lazy>]".equals(stringify.toString()); ---- -=== Features +== Features * String representation of any Java class in two modes `+PROMISCUOUS+` and `+QUIET+` @@ -83,7 +86,7 @@ and `+QUIET+` instead [[vs-lombok-tostring]] -=== vs. Lombok @ToString +== vs. Lombok @ToString Stringify Object for Java is designed for *slightly different* use case then Lombok. @@ -95,13 +98,13 @@ Stringify Object for Java is designed to inspect complex objects that can have cycles and can be managed by JPA provider like Hibernate (introducing Lazy Loading problems). -==== Pros of Lombok vs Stringify Object +=== Pros of Lombok vs Stringify Object * Lombok is *fast* - it's statically generated code without using Reflection API. * Lombok is *easy* - it's zero configuration in most cases. -==== Cons of Lombok vs Stringify Object +=== Cons of Lombok vs Stringify Object * Lombok can't *detect cycles* is object graph, which implies `+StackOverflowException+` being thrown in that case @@ -110,12 +113,12 @@ loading it from JPA by invoking SQL statements. It's typical *n+1 problem*, but with nasty consequences - your `+toString()+` method is invoking SQL without your knowledge!! -=== Configuration +== Configuration Configuration is done in two ways: declarative - using Java's service loader mechanism, and programmatic. -==== Configuration using Service Loader +=== Configuration using Service Loader A `+Configurator+` interface is intended to be implemented in user code, and assigned to https://www.baeldung.com/java-spi[Service Loader] @@ -186,7 +189,7 @@ class SpringBeanFactory implements BeanFactory, BootingAware { } ---- -==== Programmatic configuration +=== Programmatic configuration You can also fine tune you configuration on instance level - using methods available at `+Stringify+` interface: @@ -205,13 +208,13 @@ stringifier .stringify(); ---- -=== Dependencies +== Dependencies * Java >= 8 * https://github.com/wavesoftware/java-eid-exceptions[EID Exceptions] library -==== Contributing +=== Contributing Contributions are welcome! diff --git a/src/main/java/pl/wavesoftware/utils/stringify/Stringify.java b/src/main/java/pl/wavesoftware/utils/stringify/Stringify.java index fb76b4e..0548f04 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/Stringify.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/Stringify.java @@ -19,8 +19,13 @@ import pl.wavesoftware.utils.stringify.api.Configuration; import pl.wavesoftware.utils.stringify.api.Inspect; import pl.wavesoftware.utils.stringify.api.Mode; +import pl.wavesoftware.utils.stringify.api.Namespace; +import pl.wavesoftware.utils.stringify.api.Store; import pl.wavesoftware.utils.stringify.impl.DefaultStringify; import pl.wavesoftware.utils.stringify.spi.BeanFactory; +import pl.wavesoftware.utils.stringify.spi.theme.Theme; + +import java.util.function.Consumer; /** *

Stringify Object for Java

@@ -129,4 +134,10 @@ static Stringify of(Object object) { @Override Stringify beanFactory(BeanFactory beanFactory); + + @Override + Stringify theme(Theme theme); + + @Override + Stringify store(Namespace namespace, Consumer storeConsumer); } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/api/Configuration.java b/src/main/java/pl/wavesoftware/utils/stringify/api/Configuration.java index 5c5e650..8a20b07 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/api/Configuration.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/api/Configuration.java @@ -20,6 +20,8 @@ import pl.wavesoftware.utils.stringify.spi.Configurator; import pl.wavesoftware.utils.stringify.spi.theme.Theme; +import java.util.function.Consumer; + /** * A interface that represents a configuration options for this library *

@@ -39,10 +41,10 @@ * {@link BeanFactory} interface can be used to create instances of classes, declared * in annotations used to define inspection rules. * + * @author Krzysztof Suszynski * @see Configurator * @see BeanFactory * @see pl.wavesoftware.utils.stringify.Stringify Stringify - * @author Krzysztof Suszynski * @since 2.0.0 */ public interface Configuration { @@ -69,4 +71,13 @@ public interface Configuration { * @return a self reference */ Configuration theme(Theme theme); + + /** + * A named general purpose configuration + * + * @param namespace a namespace oa a store to configure + * @param storeConsumer a consumer that can modify a named store + * @return a self reference + */ + Configuration store(Namespace namespace, Consumer storeConsumer); } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/api/IndentationControl.java b/src/main/java/pl/wavesoftware/utils/stringify/api/IndentationControl.java new file mode 100644 index 0000000..e47e307 --- /dev/null +++ b/src/main/java/pl/wavesoftware/utils/stringify/api/IndentationControl.java @@ -0,0 +1,58 @@ +/* + * Copyright 2018-2019 Wave Software + * + * 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 pl.wavesoftware.utils.stringify.api; + +/** + * An indentation of output that can be used by + * {@link pl.wavesoftware.utils.stringify.spi.theme.Theme Theme} elements to prepare + * pretty print. + * + * @author Krzysztof Suszynski + * @since 2.0.0 + */ +public interface IndentationControl { + /** + * Current level of indentation. + * + * @return an indentation level + */ + int current(); + + /** + * Increments current level of indentation. + */ + void increment(); + + /** + * Decrements current level of indentation. + */ + void decrement(); + + /** + * Calculates a indent text based on a indent value and current level + * + * @param indent a text representation of indent + * @return a complete indent + */ + default CharSequence indent(CharSequence indent) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < current(); i++) { + builder.append(indent); + } + return builder; + } +} diff --git a/src/main/java/pl/wavesoftware/utils/stringify/api/InspectionContext.java b/src/main/java/pl/wavesoftware/utils/stringify/api/InspectionContext.java new file mode 100644 index 0000000..e6eec56 --- /dev/null +++ b/src/main/java/pl/wavesoftware/utils/stringify/api/InspectionContext.java @@ -0,0 +1,40 @@ +/* + * Copyright 2018-2019 Wave Software + * + * 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 pl.wavesoftware.utils.stringify.api; + +/** + * An context of current inspection. + * + * @author Krzysztof Suszynski + * @since 2.0.0 + */ +public interface InspectionContext { + /** + * Control of indentation. + * + * @return a control of indentation + */ + IndentationControl indentationControl(); + + /** + * A named, general purpose store + * + * @param namespace a namespace of a store + * @return a store + */ + Store store(Namespace namespace); +} diff --git a/src/main/java/pl/wavesoftware/utils/stringify/api/InspectionPoint.java b/src/main/java/pl/wavesoftware/utils/stringify/api/InspectionPoint.java index 611c03f..00d31d3 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/api/InspectionPoint.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/api/InspectionPoint.java @@ -16,32 +16,32 @@ package pl.wavesoftware.utils.stringify.api; -import java.lang.reflect.Field; import java.util.function.Supplier; /** - * This interface represents a inspection point in some object. + * Represents a inspection point in some object, that is a field with value. * * @author Krzysztof Suszynski * @since 1.0.0 */ public interface InspectionPoint { /** - * Get field representation of inspection point - * @return a field + * Get a field value supplier + * + * @return a supplier of a field value */ - Field getField(); + Supplier getValue(); /** - * Get object that contains this inspection point - * @return an object + * Get a type of a field + * @return a type of a field */ - Object getContainingObject(); + Supplier> getType(); /** - * Get a field value supplier + * Gets a context object associated with this inspection point * - * @return a supplier of a field value + * @return a context object */ - Supplier getValueSupplier(); + InspectionContext getContext(); } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/api/Namespace.java b/src/main/java/pl/wavesoftware/utils/stringify/api/Namespace.java new file mode 100644 index 0000000..1e4383a --- /dev/null +++ b/src/main/java/pl/wavesoftware/utils/stringify/api/Namespace.java @@ -0,0 +1,79 @@ +/* + * Copyright 2018-2019 Wave Software + * + * 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 pl.wavesoftware.utils.stringify.api; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static pl.wavesoftware.eid.utils.EidPreconditions.checkArgument; +import static pl.wavesoftware.eid.utils.EidPreconditions.checkNotNull; + +/** + * A {@code Namespace} is used to provide a scope for data saved + * within a {@link Store}. + * + * @author Krzysztof Suszynski + * @since 2.0.0 + */ +public final class Namespace { + /** + * The default, global namespace which allows access to stored data. + */ + public static final Namespace GLOBAL = Namespace.create(new Object()); + + /** + * Create a namespace which restricts access to data to all extensions + * which use the same sequence of {@code parts} for creating a namespace. + * + *

The order of the {@code parts} is significant. + * + *

Internally the {@code parts} are compared using {@link Object#equals(Object)}. + * @param parts a list of parts that should build a namespace + * @return a created namespace + */ + public static Namespace create(Object... parts) { + checkArgument(parts.length > 0, "20190805:223659"); + for (Object part : parts) { + checkNotNull(part, "20190805:223729"); + } + return new Namespace(parts); + } + + private final List parts; + + private Namespace(Object... parts) { + this.parts = new ArrayList<>(Arrays.asList(parts)); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + Namespace that = (Namespace) obj; + return this.parts.equals(that.parts); + } + + @Override + public int hashCode() { + return this.parts.hashCode(); + } +} diff --git a/src/main/java/pl/wavesoftware/utils/stringify/api/Store.java b/src/main/java/pl/wavesoftware/utils/stringify/api/Store.java new file mode 100644 index 0000000..648d6aa --- /dev/null +++ b/src/main/java/pl/wavesoftware/utils/stringify/api/Store.java @@ -0,0 +1,44 @@ +/* + * Copyright 2018-2019 Wave Software + * + * 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 pl.wavesoftware.utils.stringify.api; + +import java.util.Map.Entry; +import java.util.Optional; + +/** + * @author Krzysztof Suszynski + * @since 2.0.0 + */ +public interface Store extends Iterable> { + /** + * Puts a value on a key + * + * @param key a key + * @param value a value + */ + void put(Object key, Object value); + + /** + * Gets a value set for a given key + * + * @param key a key + * @param a type of value + * @param requiredType a type of value + * @return a value, or absent + */ + Optional get(Object key, Class requiredType); +} diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/DefaultConfiguration.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/DefaultConfiguration.java index 060af33..cf41293 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/impl/DefaultConfiguration.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/DefaultConfiguration.java @@ -18,10 +18,16 @@ import pl.wavesoftware.utils.stringify.api.Configuration; import pl.wavesoftware.utils.stringify.api.Mode; +import pl.wavesoftware.utils.stringify.api.Namespace; +import pl.wavesoftware.utils.stringify.api.Store; import pl.wavesoftware.utils.stringify.impl.beans.BeansModule; import pl.wavesoftware.utils.stringify.spi.BeanFactory; import pl.wavesoftware.utils.stringify.spi.theme.Theme; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + /** * @author Krzysztof Suszynski * @since 2.0.0 @@ -30,10 +36,19 @@ final class DefaultConfiguration implements Configuration { private static final BeanFactory DEFAULT_BEAN_FACTORY = BeansModule.INSTANCE.defaultBeanFactory(); + private final Map stores; private Mode mode = Mode.DEFAULT_MODE; private BeanFactory beanFactory = DEFAULT_BEAN_FACTORY; private Theme theme = new Theme() {}; + DefaultConfiguration() { + this(new HashMap<>()); + } + + private DefaultConfiguration(Map stores) { + this.stores = stores; + } + @Override public Configuration mode(Mode mode) { this.mode = mode; @@ -52,8 +67,15 @@ public Configuration theme(Theme theme) { return this; } + @Override + public Configuration store(Namespace namespace, Consumer storeConsumer) { + Store store = storeResolver(namespace); + storeConsumer.accept(store); + return this; + } + DefaultConfiguration dup() { - DefaultConfiguration dup = new DefaultConfiguration(); + DefaultConfiguration dup = new DefaultConfiguration(copyNamespaces()); dup.beanFactory = beanFactory; dup.mode = mode; dup.theme = theme; @@ -71,4 +93,21 @@ BeanFactory getBeanFactory() { Theme getTheme() { return theme; } + + Store storeResolver(Namespace namespace) { + if (!stores.containsKey(namespace)) { + stores.put(namespace, new StoreImpl()); + } + return stores.get(namespace); + } + + private Map copyNamespaces() { + Map copy = new HashMap<>(stores.size()); + for (Map.Entry entry : stores.entrySet()) { + Namespace key = entry.getKey(); + Store store = entry.getValue(); + copy.put(key, new StoreImpl(store)); + } + return copy; + } } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/DefaultInspectionContext.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/DefaultStringifierContext.java similarity index 72% rename from src/main/java/pl/wavesoftware/utils/stringify/impl/DefaultInspectionContext.java rename to src/main/java/pl/wavesoftware/utils/stringify/impl/DefaultStringifierContext.java index 37b4cb2..0d8a3dc 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/impl/DefaultInspectionContext.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/DefaultStringifierContext.java @@ -16,8 +16,10 @@ package pl.wavesoftware.utils.stringify.impl; -import pl.wavesoftware.utils.stringify.impl.inspector.InspectionContext; +import pl.wavesoftware.utils.stringify.api.InspectionContext; +import pl.wavesoftware.utils.stringify.api.InspectionPoint; import pl.wavesoftware.utils.stringify.impl.inspector.RootInpector; +import pl.wavesoftware.utils.stringify.impl.inspector.StringifierContext; import pl.wavesoftware.utils.stringify.spi.theme.Theme; import java.util.IdentityHashMap; @@ -28,16 +30,18 @@ * @author Krzysztof Suszynski * @since 1.0.0 */ -final class DefaultInspectionContext implements InspectionContext { +final class DefaultStringifierContext implements StringifierContext { private static final Object CONTAIN = new Object(); private final Map resolved = new IdentityHashMap<>(); private final Supplier theme; + private final InspectionContext inspectionContext; private RootInpector rootInpector; - DefaultInspectionContext(Supplier theme) { + DefaultStringifierContext(Supplier theme, InspectionPoint inspectionPoint) { this.theme = theme; + this.inspectionContext = inspectionPoint.getContext(); } @Override @@ -60,7 +64,12 @@ public Theme theme() { return theme.get(); } - public void rootInpector(RootInpector rootInpector) { + @Override + public InspectionContext inspectionContext() { + return inspectionContext; + } + + void rootInpector(RootInpector rootInpector) { this.rootInpector = rootInpector; } } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/DefaultStringify.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/DefaultStringify.java index 51dd7ca..4e98907 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/impl/DefaultStringify.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/DefaultStringify.java @@ -17,12 +17,19 @@ package pl.wavesoftware.utils.stringify.impl; import pl.wavesoftware.utils.stringify.Stringify; -import pl.wavesoftware.utils.stringify.api.Configuration; import pl.wavesoftware.utils.stringify.api.Inspect; +import pl.wavesoftware.utils.stringify.api.InspectionContext; +import pl.wavesoftware.utils.stringify.api.InspectionPoint; import pl.wavesoftware.utils.stringify.api.Mode; +import pl.wavesoftware.utils.stringify.api.Namespace; +import pl.wavesoftware.utils.stringify.api.Store; +import pl.wavesoftware.utils.stringify.impl.inspector.InspectorModule; import pl.wavesoftware.utils.stringify.spi.BeanFactory; import pl.wavesoftware.utils.stringify.spi.theme.Theme; +import java.util.function.Consumer; +import java.util.function.Supplier; + /** * A default implementation of {@link Stringify}. * @@ -33,6 +40,7 @@ public final class DefaultStringify implements Stringify { private static final DefaultConfiguration CONFIGURATION = DefaultConfigurationLoader.load(); + private static final InspectorModule INSPECTOR = InspectorModule.INSTANCE; private final ToStringResolverImpl resolver; @@ -42,7 +50,13 @@ public final class DefaultStringify implements Stringify { * @param target a target object to inspect */ public DefaultStringify(Object target) { - resolver = new ToStringResolverImpl(target, CONFIGURATION.dup()); + DefaultConfiguration configuration = CONFIGURATION.dup(); + Supplier contextSupplier = + () -> INSPECTOR.inspectionContext(configuration::storeResolver); + InspectionPoint inspectionPoint = INSPECTOR.objectInspectionPoint( + target, contextSupplier + ); + resolver = new ToStringResolverImpl(inspectionPoint, configuration); } /** @@ -88,8 +102,14 @@ public Stringify beanFactory(BeanFactory beanFactory) { } @Override - public Configuration theme(Theme theme) { + public Stringify theme(Theme theme) { resolver.theme(theme); return this; } + + @Override + public Stringify store(Namespace namespace, Consumer storeConsumer) { + resolver.store(namespace, storeConsumer); + return this; + } } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/FieldInspectionPoint.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/FieldInspectionPoint.java new file mode 100644 index 0000000..2974d18 --- /dev/null +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/FieldInspectionPoint.java @@ -0,0 +1,27 @@ +/* + * Copyright 2018-2019 Wave Software + * + * 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 pl.wavesoftware.utils.stringify.impl; + +import pl.wavesoftware.utils.stringify.api.InspectionPoint; + +/** + * @author Krzysztof Suszynski + * @since 2.0.0 + */ +public interface FieldInspectionPoint extends InspectionPoint { + InspectingField getField(); +} diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/InspectionPointImpl.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/FieldInspectionPointImpl.java similarity index 54% rename from src/main/java/pl/wavesoftware/utils/stringify/impl/InspectionPointImpl.java rename to src/main/java/pl/wavesoftware/utils/stringify/impl/FieldInspectionPointImpl.java index a4f2a2a..9ffbc82 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/impl/InspectionPointImpl.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/FieldInspectionPointImpl.java @@ -17,9 +17,9 @@ package pl.wavesoftware.utils.stringify.impl; import lombok.Getter; -import lombok.RequiredArgsConstructor; import pl.wavesoftware.eid.utils.UnsafeSupplier; -import pl.wavesoftware.utils.stringify.api.InspectionPoint; +import pl.wavesoftware.utils.stringify.api.InspectionContext; +import pl.wavesoftware.utils.stringify.impl.lang.LangModule; import javax.annotation.Nonnull; import java.lang.reflect.Field; @@ -31,30 +31,53 @@ * @author Krzysztof Suszynski * @since 30.04.18 */ -@Getter -@RequiredArgsConstructor -final class InspectionPointImpl implements InspectionPoint { - private final Field field; - private final Object containingObject; +final class FieldInspectionPointImpl implements FieldInspectionPoint { + private final InspectingField field; + private final InspectionContext context; + private final Supplier value = LangModule.INSTANCE.lazy(this::value); + private final Supplier> type; + + FieldInspectionPointImpl(InspectingField field, InspectionContext context) { + this.field = field; + type = LangModule.INSTANCE.lazy(() -> field.getFieldReflection().getType()); + this.context = context; + } @Override - public Supplier getValueSupplier() { - return () -> { - try (final FieldAccessiblier accessiblier = new FieldAccessiblier(getField())) { - return tryToExecute(new UnsafeSupplier() { - @Override - @Nonnull - public Object get() throws IllegalAccessException { - return accessiblier - .getField() - .get(getContainingObject()); - } - }, "20180430:113514"); - } - }; + public InspectingField getField() { + return field; + } + + @Override + public Supplier getValue() { + return value; } + @Override + public Supplier> getType() { + return type; + } + + @Override + public InspectionContext getContext() { + return context; + } + + private Object value() { + try (final FieldAccessiblier accessiblier = new FieldAccessiblier(field.getFieldReflection())) { + return tryToExecute(new UnsafeSupplier() { + @Override + @Nonnull + public Object get() throws IllegalAccessException { + return accessiblier + .getField() + .get(field.getContainingObject()); + } + }, "20180430:113514"); + } + } private static final class FieldAccessiblier implements AutoCloseable { + @Getter private final Field field; private final boolean accessible; diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/InspectFieldPredicate.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/InspectFieldPredicate.java index a9f3ea1..cf4cc1a 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/impl/InspectFieldPredicate.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/InspectFieldPredicate.java @@ -16,12 +16,10 @@ package pl.wavesoftware.utils.stringify.impl; -import pl.wavesoftware.utils.stringify.api.InspectionPoint; - /** * @author Krzysztof Suszynski * @since 27.04.18 */ interface InspectFieldPredicate { - boolean shouldInspect(InspectionPoint inspectionPoint); + boolean shouldInspect(FieldInspectionPoint inspectionPoint); } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/InspectingField.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/InspectingField.java index 0681a03..fb0d353 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/impl/InspectingField.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/InspectingField.java @@ -18,6 +18,7 @@ import pl.wavesoftware.utils.stringify.spi.Masker; +import java.lang.reflect.Field; import java.util.Optional; /** @@ -25,12 +26,33 @@ * @since 1.0.0 */ interface InspectingField { + /** + * Gets a name of a field + * + * @return a name + */ + String getName(); + + /** + * Get field representation of inspecting field + * @return a field + */ + Field getFieldReflection(); + + /** + * Get object that contains this inspecting field + * + * @return an object + */ + Object getContainingObject(); + /** * True if should inspect given field * + * @param inspectionPoint a inspection point to test * @return true, if should inspect */ - boolean shouldInspect(); + boolean shouldInspect(FieldInspectionPoint inspectionPoint); /** * Show null if true @@ -47,4 +69,5 @@ interface InspectingField { * @return a masker */ Optional> masker(); + } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/InspectingFieldFactory.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/InspectingFieldFactory.java index 5262e34..ebefe16 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/impl/InspectingFieldFactory.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/InspectingFieldFactory.java @@ -18,9 +18,9 @@ import lombok.RequiredArgsConstructor; import pl.wavesoftware.utils.stringify.spi.BeanFactory; -import pl.wavesoftware.utils.stringify.api.InspectionPoint; import pl.wavesoftware.utils.stringify.api.Mode; +import java.lang.reflect.Field; import java.util.function.Supplier; /** @@ -31,10 +31,9 @@ final class InspectingFieldFactory { private final Supplier mode; - InspectingField create(InspectionPoint inspectionPoint, - BeanFactory beanFactory) { + InspectingField create(Field field, Object containingObject, BeanFactory beanFactory) { return new InspectingFieldImpl( - inspectionPoint, createPredicate(beanFactory), beanFactory + field, containingObject, createPredicate(beanFactory), beanFactory ); } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/InspectingFieldImpl.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/InspectingFieldImpl.java index 1c57851..d2ceb6a 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/impl/InspectingFieldImpl.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/InspectingFieldImpl.java @@ -16,9 +16,9 @@ package pl.wavesoftware.utils.stringify.impl; +import lombok.Getter; import lombok.RequiredArgsConstructor; import pl.wavesoftware.utils.stringify.api.DisplayNull; -import pl.wavesoftware.utils.stringify.api.InspectionPoint; import pl.wavesoftware.utils.stringify.api.Mask; import pl.wavesoftware.utils.stringify.spi.BeanFactory; import pl.wavesoftware.utils.stringify.spi.Masker; @@ -35,26 +35,33 @@ */ @RequiredArgsConstructor final class InspectingFieldImpl implements InspectingField { - private final InspectionPoint inspectionPoint; + @Getter + private final Field fieldReflection; + @Getter + private final Object containingObject; private final InspectFieldPredicate predicate; private final BeanFactory beanFactory; @Override - public boolean shouldInspect() { + public String getName() { + return fieldReflection.getName(); + } + + @Override + public boolean shouldInspect(FieldInspectionPoint inspectionPoint) { return technically() && predicate.shouldInspect(inspectionPoint); } private boolean technically() { - Field field = inspectionPoint.getField(); - int mods = field.getModifiers(); + int mods = fieldReflection.getModifiers(); return !Modifier.isStatic(mods) - && !field.isEnumConstant() - && !field.isSynthetic(); + && !fieldReflection.isEnumConstant() + && !fieldReflection.isSynthetic(); } @Override public Optional> masker() { - Mask annotation = inspectionPoint.getField().getAnnotation(Mask.class); + Mask annotation = fieldReflection.getAnnotation(Mask.class); return Optional.ofNullable(annotation) .map(Mask::value) .map(beanFactory::create) @@ -63,13 +70,12 @@ public Optional> masker() { private Masker validate(Masker masker) { Class validType = masker.type(); - Field field = inspectionPoint.getField(); checkState( - field.getType().isAssignableFrom(validType), + fieldReflection.getType().isAssignableFrom(validType), "20190724:230843", "Field {0} is annotated with masker of type {1}, but types " + "are not compatible.", - field, validType + fieldReflection, validType ); @SuppressWarnings("unchecked") Masker casted = (Masker) masker; @@ -78,8 +84,7 @@ private Masker validate(Masker masker) { @Override public boolean showNull() { - DisplayNull displayNull = inspectionPoint.getField() - .getAnnotation(DisplayNull.class); + DisplayNull displayNull = fieldReflection.getAnnotation(DisplayNull.class); if (displayNull != null) { return displayNull.value(); } else { diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/InspectorBasedToStringResolver.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/InspectorBasedToStringResolver.java index 6cbba07..666de37 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/impl/InspectorBasedToStringResolver.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/InspectorBasedToStringResolver.java @@ -18,7 +18,7 @@ import pl.wavesoftware.utils.stringify.api.InspectionPoint; import pl.wavesoftware.utils.stringify.impl.beans.BeanFactoryCache; -import pl.wavesoftware.utils.stringify.impl.inspector.InspectionContext; +import pl.wavesoftware.utils.stringify.impl.inspector.StringifierContext; import pl.wavesoftware.utils.stringify.impl.inspector.InspectorModule; import pl.wavesoftware.utils.stringify.impl.inspector.ObjectInspector; import pl.wavesoftware.utils.stringify.spi.theme.ComplexObjectStyle; @@ -41,52 +41,52 @@ final class InspectorBasedToStringResolver implements ToStringResolver { InspectorModule.INSTANCE.inspectors(); private final DefaultConfiguration configuration; - private final Object target; - private final InspectionContext inspectionContext; + private final InspectionPoint point; + private final StringifierContext stringifierContext; private final BeanFactoryCache beanFactoryCache; private final InspectingFieldFactory inspectingFieldFactory; InspectorBasedToStringResolver( DefaultConfiguration configuration, - Object target, - InspectionContext inspectionContext, + InspectionPoint point, + StringifierContext stringifierContext, BeanFactoryCache beanFactoryCache, InspectingFieldFactory inspectingFieldFactory ) { this.configuration = configuration; - this.target = target; - this.inspectionContext = inspectionContext; + this.point = point; + this.stringifierContext = stringifierContext; this.beanFactoryCache = beanFactoryCache; this.inspectingFieldFactory = inspectingFieldFactory; } @Override public CharSequence resolve() { - inspectionContext.markAsInspected(target); - ComplexObjectStyle style = inspectionContext.theme().complexObject(); + stringifierContext.markAsInspected(point.getValue().get()); + ComplexObjectStyle style = stringifierContext.theme().complexObject(); StringBuilder sb = new StringBuilder(); - sb.append(style.begin()); - sb.append(style.name(target)); + sb.append(style.begin(point)); + sb.append(style.name(point)); CharSequence props = propertiesForToString(); if (props.length() != 0) { - sb.append(style.nameSeparator()); + sb.append(style.nameSeparator(point)); sb.append(props); } - sb.append(style.end()); + sb.append(style.end(point)); return sb; } private CharSequence propertiesForToString() { Map props; - props = inspectTargetAsClass(target.getClass()); - ComplexObjectStyle style = inspectionContext.theme().complexObject(); + props = inspectTargetAsClass(point.getType().get()); + ComplexObjectStyle style = stringifierContext.theme().complexObject(); StringBuilder sb = new StringBuilder(); - CharSequence propertySeparator = style.propertySeparator(); + CharSequence propertySeparator = style.propertySeparator(point); for (Map.Entry entry : props.entrySet()) { String fieldName = entry.getKey(); CharSequence fieldStringValue = entry.getValue(); sb.append(fieldName); - sb.append(style.propertyEquals()); + sb.append(style.propertyEquals(point)); sb.append(fieldStringValue); sb.append(propertySeparator); } @@ -114,55 +114,50 @@ private void inspectFields( Field[] fields, Map properties ) { for (Field field : fields) { - InspectionPoint inspectionPoint = createInspectionPoint(field); InspectingField inspectingField = inspectingFieldFactory - .create(inspectionPoint, beanFactoryCache); - if (inspectingField.shouldInspect()) { - inspectAnnotatedField(properties, field, inspectingField); + .create(field, point.getValue().get(), beanFactoryCache); + FieldInspectionPoint inspectionPoint = createInspectionPoint(inspectingField); + if (inspectingField.shouldInspect(inspectionPoint)) { + inspectAnnotatedField(properties, inspectionPoint, inspectingField); } } } - private InspectionPoint createInspectionPoint(Field field) { - return new InspectionPointImpl(field, target); + private FieldInspectionPoint createInspectionPoint(InspectingField field) { + return new FieldInspectionPointImpl( + field, stringifierContext.inspectionContext() + ); } private void inspectAnnotatedField( final Map properties, - final Field field, + final InspectionPoint inspectionPoint, final InspectingField inspectingField ) { tryToExecute(() -> { - ensureAccessible(field); - @Nullable Object value = field.get(target); + @Nullable Object value = inspectionPoint.getValue().get(); Optional maybeMasked = inspectingField.masker() .map(masker -> masker.mask(value)) .map(masked -> checkNotNull(masked, "20190724:231722")); @Nullable CharSequence inspected = maybeMasked.orElseGet(() -> - value != null ? inspectObject(value) : null + value != null ? inspectObject(inspectionPoint) : null ); if (inspected != null || inspectingField.showNull()) { - properties.put(field.getName(), inspected); + properties.put(inspectingField.getName(), inspected); } }, "20130422:154938"); } - private static void ensureAccessible(Field field) { - if (!field.isAccessible()) { - field.setAccessible(true); - } - } - - CharSequence inspectObject(Object object) { + CharSequence inspectObject(InspectionPoint point) { for (ObjectInspector inspector : OBJECT_INSPECTORS) { - if (inspector.consentTo(object, inspectionContext)) { - return inspector.inspect(object, inspectionContext); + if (inspector.consentTo(point, stringifierContext)) { + return inspector.inspect(point, stringifierContext); } } ToStringResolverImpl sub = new ToStringResolverImpl( - object, + point, configuration, - inspectionContext, + stringifierContext, beanFactoryCache, inspectingFieldFactory ); diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/PromiscuousInspectFieldPredicate.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/PromiscuousInspectFieldPredicate.java index 2510382..53995e1 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/impl/PromiscuousInspectFieldPredicate.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/PromiscuousInspectFieldPredicate.java @@ -35,13 +35,15 @@ final class PromiscuousInspectFieldPredicate implements InspectFieldPredicate { private final BeanFactory beanFactory; @Override - public boolean shouldInspect(InspectionPoint inspectionPoint) { + public boolean shouldInspect(FieldInspectionPoint inspectionPoint) { DoNotInspect doNotInspect = inspectionPoint.getField() + .getFieldReflection() .getAnnotation(DoNotInspect.class); if (doNotInspect != null) { return shouldInspect(inspectionPoint, doNotInspect); } else { Inspect inspect = inspectionPoint.getField() + .getFieldReflection() .getAnnotation(Inspect.class); if (inspect != null && inspect.conditionally() != AlwaysTruePredicate.class) { Predicate predicate = beanFactory.create(inspect.conditionally()); diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/QuietInspectFieldPredicate.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/QuietInspectFieldPredicate.java index bf9681e..578d740 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/impl/QuietInspectFieldPredicate.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/QuietInspectFieldPredicate.java @@ -34,8 +34,9 @@ final class QuietInspectFieldPredicate implements InspectFieldPredicate { private final BeanFactory beanFactory; @Override - public boolean shouldInspect(InspectionPoint inspectionPoint) { + public boolean shouldInspect(FieldInspectionPoint inspectionPoint) { Inspect inspect = inspectionPoint.getField() + .getFieldReflection() .getAnnotation(Inspect.class); if (inspect != null) { return shouldInspect(inspectionPoint, inspect); diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/StoreImpl.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/StoreImpl.java new file mode 100644 index 0000000..7482d12 --- /dev/null +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/StoreImpl.java @@ -0,0 +1,59 @@ +/* + * Copyright 2018-2019 Wave Software + * + * 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 pl.wavesoftware.utils.stringify.impl; + +import pl.wavesoftware.utils.stringify.api.Store; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; + +/** + * @author Krzysztof Suszynski + * @since 2.0.0 + */ +final class StoreImpl implements Store { + private final Map map = new HashMap<>(); + + StoreImpl() { + // nothing + } + + StoreImpl(Store store) { + for (Entry entry : store) { + put(entry.getKey(), entry.getValue()); + } + } + + @Override + public void put(Object key, Object value) { + map.put(key, value); + } + + @Override + public Optional get(Object key, Class requiredType) { + T obj = requiredType.cast(map.get(key)); + return Optional.ofNullable(obj); + } + + @Override + public Iterator> iterator() { + return map.entrySet().iterator(); + } +} diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/ToStringResolverImpl.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/ToStringResolverImpl.java index fa2d8fa..509c108 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/impl/ToStringResolverImpl.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/ToStringResolverImpl.java @@ -17,13 +17,18 @@ package pl.wavesoftware.utils.stringify.impl; import pl.wavesoftware.utils.stringify.api.Configuration; +import pl.wavesoftware.utils.stringify.api.InspectionPoint; import pl.wavesoftware.utils.stringify.api.Mode; +import pl.wavesoftware.utils.stringify.api.Namespace; +import pl.wavesoftware.utils.stringify.api.Store; import pl.wavesoftware.utils.stringify.impl.beans.BeanFactoryCache; import pl.wavesoftware.utils.stringify.impl.beans.BeansModule; -import pl.wavesoftware.utils.stringify.impl.inspector.InspectionContext; +import pl.wavesoftware.utils.stringify.impl.inspector.StringifierContext; import pl.wavesoftware.utils.stringify.spi.BeanFactory; import pl.wavesoftware.utils.stringify.spi.theme.Theme; +import java.util.function.Consumer; + /** * @author Krzysztof Suszynski * @since 2018-04-18 @@ -36,44 +41,44 @@ final class ToStringResolverImpl implements ToStringResolver, Configuration { /** * A default constructor * - * @param target a target object to resolve - * @param configuration a configuration + * @param inspectionPoint a inspection point to resolve + * @param configuration a configuration */ - ToStringResolverImpl(Object target, DefaultConfiguration configuration) { + ToStringResolverImpl(InspectionPoint inspectionPoint, DefaultConfiguration configuration) { this( - target, + inspectionPoint, configuration, - new DefaultInspectionContext(configuration::getTheme), + new DefaultStringifierContext(configuration::getTheme, inspectionPoint), BeansModule.INSTANCE.cachedBeanFactory( - configuration::getBeanFactory, target + configuration::getBeanFactory, inspectionPoint ), new InspectingFieldFactory(configuration::getMode) ); } ToStringResolverImpl( - Object target, + InspectionPoint inspectionPoint, DefaultConfiguration configuration, - InspectionContext inspectionContext, + StringifierContext stringifierContext, BeanFactoryCache beanFactoryCache, InspectingFieldFactory inspectingFieldFactory ) { this.configuration = configuration; this.delegateResolver = new InspectorBasedToStringResolver( - configuration, target, inspectionContext, + configuration, inspectionPoint, stringifierContext, beanFactoryCache, inspectingFieldFactory ); } private ToStringResolverImpl( - Object target, + InspectionPoint inspectionPoint, DefaultConfiguration configuration, - DefaultInspectionContext inspectionContext, + DefaultStringifierContext inspectionContext, BeanFactoryCache beanFactoryCache, InspectingFieldFactory inspectingFieldFactory ) { this( - target, configuration, (InspectionContext) inspectionContext, + inspectionPoint, configuration, (StringifierContext) inspectionContext, beanFactoryCache, inspectingFieldFactory ); inspectionContext.rootInpector(delegateResolver::inspectObject); @@ -101,4 +106,10 @@ public Configuration theme(Theme theme) { delegateResolver.clear(); return configuration.theme(theme); } + + @Override + public Configuration store(Namespace namespace, Consumer storeConsumer) { + delegateResolver.clear(); + return configuration.store(namespace, storeConsumer); + } } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/beans/BeansModule.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/beans/BeansModule.java index 8cfb6b6..042fe84 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/impl/beans/BeansModule.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/beans/BeansModule.java @@ -16,6 +16,7 @@ package pl.wavesoftware.utils.stringify.impl.beans; +import pl.wavesoftware.utils.stringify.api.InspectionPoint; import pl.wavesoftware.utils.stringify.spi.BeanFactory; import java.util.function.Supplier; @@ -31,10 +32,13 @@ public BeanFactory defaultBeanFactory() { return new DefaultBeanFactory(); } - public BeanFactoryCache cachedBeanFactory(Supplier beanFactory, Object target) { + public BeanFactoryCache cachedBeanFactory( + Supplier beanFactory, + InspectionPoint inspectionPoint + ) { return new DefaultBeanFactoryCache(() -> new FallbackBootFactory( - new BootAwareBootFactory(beanFactory, target) + new BootAwareBootFactory(beanFactory, inspectionPoint) ) ); } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/beans/BootAwareBootFactory.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/beans/BootAwareBootFactory.java index 5fd8c7f..f6399d2 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/impl/beans/BootAwareBootFactory.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/beans/BootAwareBootFactory.java @@ -18,6 +18,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import pl.wavesoftware.utils.stringify.api.InspectionPoint; import pl.wavesoftware.utils.stringify.impl.lang.Inspectable; import pl.wavesoftware.utils.stringify.impl.lang.LangModule; import pl.wavesoftware.utils.stringify.spi.BeanFactory; @@ -33,11 +34,14 @@ final class BootAwareBootFactory implements BeanFactory, Inspectable { private static final Logger LOGGER = LoggerFactory.getLogger(BootAwareBootFactory.class); private final Supplier delegateSupplier; - private final Object target; + private final InspectionPoint inspectionPoint; - BootAwareBootFactory(Supplier delegateSupplier, Object target) { + BootAwareBootFactory( + Supplier delegateSupplier, + InspectionPoint inspectionPoint + ) { this.delegateSupplier = delegateSupplier; - this.target = target; + this.inspectionPoint = inspectionPoint; } @Override @@ -55,7 +59,9 @@ private BeanFactory getBeanFactory(Class contractClass) { " has been already invoked for {} while trying to resolve {}. This is " + "usually some race condition issues. Using default BeanFactory as a " + "fallback for this call.", - safeInspect(delegate), safeInspect(target), contractClass + safeInspect(delegate), + safeInspect(inspectionPoint.getValue().get()), + contractClass ); return BeansModule.INSTANCE.defaultBeanFactory(); } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/CharSequenceInspector.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/CharSequenceInspector.java index a755cde..4c0f109 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/CharSequenceInspector.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/CharSequenceInspector.java @@ -16,21 +16,26 @@ package pl.wavesoftware.utils.stringify.impl.inspector; +import pl.wavesoftware.utils.stringify.api.InspectionPoint; + /** * @author Krzysztof Suszynski * @since 1.0.0 */ final class CharSequenceInspector implements ObjectInspector { @Override - public boolean consentTo(Object candidate, InspectionContext context) { - return candidate instanceof CharSequence; + public boolean consentTo(InspectionPoint point, StringifierContext context) { + return CharSequence.class.isAssignableFrom(point.getType().get()); } @Override - public CharSequence inspect(Object object, InspectionContext context) { - CharSequence quote = context.theme().charSequence().quote(); - StringBuilder stringBuilder = new StringBuilder(quote); - return stringBuilder.append(object.toString()).append(quote); + public CharSequence inspect(InspectionPoint point, StringifierContext context) { + StringBuilder stringBuilder = new StringBuilder(quote(point, context)); + return stringBuilder.append(point.getValue().get().toString()).append(quote(point, context)); + } + + private CharSequence quote(InspectionPoint point, StringifierContext context) { + return context.theme().charSequence().quote(point); } } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/CharacterInspector.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/CharacterInspector.java index 8c84cee..e29bb02 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/CharacterInspector.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/CharacterInspector.java @@ -16,6 +16,8 @@ package pl.wavesoftware.utils.stringify.impl.inspector; +import pl.wavesoftware.utils.stringify.api.InspectionPoint; + /** * @author Krzysztof Suszynski * @since 1.0.0 @@ -23,14 +25,18 @@ final class CharacterInspector implements ObjectInspector { @Override - public boolean consentTo(Object candidate, InspectionContext context) { - return candidate instanceof Character; + public boolean consentTo(InspectionPoint point, StringifierContext context) { + Class type = point.getType().get(); + return Character.class.isAssignableFrom(type) || char.class.isAssignableFrom(type); } @Override - public CharSequence inspect(Object object, InspectionContext context) { - CharSequence quote = context.theme().character().quote(); - StringBuilder stringBuilder = new StringBuilder(quote); - return stringBuilder.append(object.toString()).append(quote); + public CharSequence inspect(InspectionPoint point, StringifierContext context) { + StringBuilder stringBuilder = new StringBuilder(quote(point, context)); + return stringBuilder.append(point.getValue().get().toString()).append(quote(point, context)); + } + + private CharSequence quote(InspectionPoint point, StringifierContext context) { + return context.theme().character().quote(point); } } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/IndentationControlImpl.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/IndentationControlImpl.java new file mode 100644 index 0000000..d44a0d4 --- /dev/null +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/IndentationControlImpl.java @@ -0,0 +1,44 @@ +/* + * Copyright 2018-2019 Wave Software + * + * 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 pl.wavesoftware.utils.stringify.impl.inspector; + +import pl.wavesoftware.utils.stringify.api.IndentationControl; + +import static pl.wavesoftware.eid.utils.EidPreconditions.checkState; + +/** + * @author Krzysztof Suszynski + * @since 2.0.0 + */ +final class IndentationControlImpl implements IndentationControl { + private int indent = 0; + @Override + public int current() { + return indent; + } + + @Override + public void increment() { + indent++; + } + + @Override + public void decrement() { + checkState(indent > 0, "20190805:230357"); + indent--; + } +} diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/InspectionContextImpl.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/InspectionContextImpl.java new file mode 100644 index 0000000..f4b58c0 --- /dev/null +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/InspectionContextImpl.java @@ -0,0 +1,47 @@ +/* + * Copyright 2018-2019 Wave Software + * + * 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 pl.wavesoftware.utils.stringify.impl.inspector; + +import pl.wavesoftware.utils.stringify.api.IndentationControl; +import pl.wavesoftware.utils.stringify.api.InspectionContext; +import pl.wavesoftware.utils.stringify.api.Namespace; +import pl.wavesoftware.utils.stringify.api.Store; + +import java.util.function.Function; + +/** + * @author Krzysztof Suszynski + * @since 2.0.0 + */ +final class InspectionContextImpl implements InspectionContext { + private final Function storeResolver; + private final IndentationControl indentationControl = new IndentationControlImpl(); + + InspectionContextImpl(Function storeResolver) { + this.storeResolver = storeResolver; + } + + @Override + public IndentationControl indentationControl() { + return indentationControl; + } + + @Override + public Store store(Namespace namespace) { + return storeResolver.apply(namespace); + } +} diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/InspectorModule.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/InspectorModule.java index 3c9d70c..cd3607c 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/InspectorModule.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/InspectorModule.java @@ -16,7 +16,14 @@ package pl.wavesoftware.utils.stringify.impl.inspector; +import pl.wavesoftware.utils.stringify.api.InspectionContext; +import pl.wavesoftware.utils.stringify.api.InspectionPoint; +import pl.wavesoftware.utils.stringify.api.Namespace; +import pl.wavesoftware.utils.stringify.api.Store; + import java.util.Arrays; +import java.util.function.Function; +import java.util.function.Supplier; /** * @author Krzysztof Suszynski @@ -36,4 +43,14 @@ public Iterable inspectors() { new RecursionInspector() ); } + + public InspectionPoint objectInspectionPoint( + Object target, Supplier contextSupplier + ) { + return new ObjectInspectionPoint(target, contextSupplier); + } + + public InspectionContext inspectionContext(Function storeResolver) { + return new InspectionContextImpl(storeResolver); + } } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/IterableInspector.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/IterableInspector.java index e59857b..41a95d7 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/IterableInspector.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/IterableInspector.java @@ -16,6 +16,7 @@ package pl.wavesoftware.utils.stringify.impl.inspector; +import pl.wavesoftware.utils.stringify.api.InspectionPoint; import pl.wavesoftware.utils.stringify.spi.theme.IterableStyle; /** @@ -24,29 +25,35 @@ */ final class IterableInspector implements ObjectInspector { @Override - public boolean consentTo(Object candidate, InspectionContext context) { - return candidate instanceof Iterable; + public boolean consentTo(InspectionPoint point, StringifierContext context) { + return Iterable.class.isAssignableFrom(point.getType().get()); } @Override - public CharSequence inspect(Object object, InspectionContext context) { - return inspectIterable((Iterable) object, context); + public CharSequence inspect(InspectionPoint point, StringifierContext context) { + return inspectIterable((Iterable) point.getValue().get(), point, context); } private static CharSequence inspectIterable( - Iterable iterable, InspectionContext context + Iterable iterable, + InspectionPoint point, + StringifierContext context ) { IterableStyle style = context.theme().iterable(); StringBuilder sb = new StringBuilder(); - sb.append(style.begin()); + sb.append(style.begin(point)); for (Object elem : iterable) { - sb.append(context.rootInspector().apply(elem)); - sb.append(style.separator()); + InspectionPoint subPoint = + InspectorModule.INSTANCE.objectInspectionPoint(elem, point::getContext); + sb.append(context.rootInspector().apply(subPoint)); + sb.append(style.separator(point)); } - if (sb.length() > style.begin().length()) { - sb.deleteCharAt(sb.length() - style.separator().length()); + if (sb.length() > style.begin(point).length()) { + for (int i=0; i < style.separator(point).length(); i++) { + sb.deleteCharAt(sb.length() - 1); + } } - sb.append(style.end()); + sb.append(style.end(point)); return sb.toString(); } } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/JpaLazyInspector.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/JpaLazyInspector.java index 3d2eba6..e27a049 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/JpaLazyInspector.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/JpaLazyInspector.java @@ -16,6 +16,7 @@ package pl.wavesoftware.utils.stringify.impl.inspector; +import pl.wavesoftware.utils.stringify.api.InspectionPoint; import pl.wavesoftware.utils.stringify.impl.jpa.JpaModule; /** @@ -24,16 +25,16 @@ */ final class JpaLazyInspector implements ObjectInspector { @Override - public boolean consentTo(Object candidate, InspectionContext context) { + public boolean consentTo(InspectionPoint point, StringifierContext context) { return JpaModule.INSTANCE .lazyChecker() - .isLazy(candidate); + .isLazy(point.getValue().get()); } @Override - public CharSequence inspect(Object object, InspectionContext context) { + public CharSequence inspect(InspectionPoint point, StringifierContext context) { return context.theme() .jpaLazy() - .representation(object); + .representation(point); } } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/MapInspector.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/MapInspector.java index fe0ff93..048a551 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/MapInspector.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/MapInspector.java @@ -16,6 +16,7 @@ package pl.wavesoftware.utils.stringify.impl.inspector; +import pl.wavesoftware.utils.stringify.api.InspectionPoint; import pl.wavesoftware.utils.stringify.spi.theme.MapStyle; import java.util.Map; @@ -27,30 +28,34 @@ final class MapInspector implements ObjectInspector { @Override - public boolean consentTo(Object candidate, InspectionContext context) { - return candidate instanceof Map; + public boolean consentTo(InspectionPoint point, StringifierContext context) { + return Map.class.isAssignableFrom(point.getType().get()); } @Override - public CharSequence inspect(Object object, InspectionContext context) { - Map map = (Map) object; + public CharSequence inspect(InspectionPoint point, StringifierContext context) { + Map map = (Map) point.getValue().get(); MapStyle style = context.theme().map(); StringBuilder sb = new StringBuilder(); - sb.append(style.begin()); + sb.append(style.begin(point)); for (Map.Entry entry : map.entrySet()) { Object key = entry.getKey(); + InspectionPoint keyPoint = + InspectorModule.INSTANCE.objectInspectionPoint(key, point::getContext); Object value = entry.getValue(); - sb.append(context.rootInspector().apply(key)); - sb.append(style.entryEquals()); - sb.append(context.rootInspector().apply(value)); - sb.append(style.separator()); + InspectionPoint valuePoint = + InspectorModule.INSTANCE.objectInspectionPoint(value, point::getContext); + sb.append(context.rootInspector().apply(keyPoint)); + sb.append(style.entryEquals(point)); + sb.append(context.rootInspector().apply(valuePoint)); + sb.append(style.separator(point)); } if (!map.isEmpty()) { - for (int i=0; i < style.separator().length(); i++) { + for (int i=0; i < style.separator(point).length(); i++) { sb.deleteCharAt(sb.length() - 1); } } - sb.append(style.end()); + sb.append(style.end(point)); return sb.toString(); } } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/ObjectInspectionPoint.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/ObjectInspectionPoint.java new file mode 100644 index 0000000..759c3ce --- /dev/null +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/ObjectInspectionPoint.java @@ -0,0 +1,52 @@ +/* + * Copyright 2018-2019 Wave Software + * + * 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 pl.wavesoftware.utils.stringify.impl.inspector; + +import pl.wavesoftware.utils.stringify.api.InspectionContext; +import pl.wavesoftware.utils.stringify.api.InspectionPoint; +import pl.wavesoftware.utils.stringify.impl.lang.LangModule; + +import java.util.function.Supplier; + +/** + * @author Krzysztof Suszynski + * @since 2.0.0 + */ +final class ObjectInspectionPoint implements InspectionPoint { + private final Object target; + private final Supplier contextSupplier; + + ObjectInspectionPoint(Object target, Supplier contextSupplier) { + this.target = target; + this.contextSupplier = LangModule.INSTANCE.lazy(contextSupplier); + } + + @Override + public Supplier getValue() { + return () -> target; + } + + @Override + public Supplier> getType() { + return target::getClass; + } + + @Override + public InspectionContext getContext() { + return contextSupplier.get(); + } +} diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/ObjectInspector.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/ObjectInspector.java index 69d6e37..2ba4651 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/ObjectInspector.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/ObjectInspector.java @@ -16,9 +16,11 @@ package pl.wavesoftware.utils.stringify.impl.inspector; +import pl.wavesoftware.utils.stringify.api.InspectionPoint; + /** * Represents a basic object inspector that can handle specific type of object, which - * is determined by call to {@link #consentTo(Object, InspectionContext)} + * is determined by call to {@link #consentTo(InspectionPoint, StringifierContext)} * method. * * @author Krzysztof Suszynski @@ -28,18 +30,18 @@ public interface ObjectInspector { /** * Determines if this inspector is suitable for a given object * - * @param candidate a candidate to inspect - * @param context a context of inspection + * @param point a point that is being inspected + * @param context a context of inspection * @return true, if this object inspector can inspect candidate object */ - boolean consentTo(Object candidate, InspectionContext context); + boolean consentTo(InspectionPoint point, StringifierContext context); /** * Will inspect a given object to character sequence * - * @param object a object to inspect + * @param point a point that is being inspected * @param context a context of inspection * @return a character sequence representation of given object */ - CharSequence inspect(Object object, InspectionContext context); + CharSequence inspect(InspectionPoint point, StringifierContext context); } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/PrimitiveInspector.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/PrimitiveInspector.java index cd948ea..713c7c6 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/PrimitiveInspector.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/PrimitiveInspector.java @@ -16,6 +16,8 @@ package pl.wavesoftware.utils.stringify.impl.inspector; +import pl.wavesoftware.utils.stringify.api.InspectionPoint; + import java.time.temporal.Temporal; import java.util.Date; @@ -26,13 +28,13 @@ final class PrimitiveInspector implements ObjectInspector { @Override - public boolean consentTo(Object candidate, InspectionContext context) { - return isPrimitive(candidate); + public boolean consentTo(InspectionPoint point, StringifierContext context) { + return isPrimitive(point.getValue().get()); } @Override - public CharSequence inspect(Object object, InspectionContext context) { - return object.toString(); + public CharSequence inspect(InspectionPoint point, StringifierContext context) { + return point.getValue().get().toString(); } private static boolean isPrimitive(Object object) { diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/RecursionInspector.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/RecursionInspector.java index 76afb1b..44cce86 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/RecursionInspector.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/RecursionInspector.java @@ -16,20 +16,22 @@ package pl.wavesoftware.utils.stringify.impl.inspector; +import pl.wavesoftware.utils.stringify.api.InspectionPoint; + /** * @author Krzysztof Suszynski * @since 1.0.0 */ final class RecursionInspector implements ObjectInspector { @Override - public boolean consentTo(Object candidate, InspectionContext context) { - return context.wasInspected(candidate); + public boolean consentTo(InspectionPoint point, StringifierContext context) { + return context.wasInspected(point.getValue().get()); } @Override - public CharSequence inspect(Object object, InspectionContext context) { + public CharSequence inspect(InspectionPoint point, StringifierContext context) { return context.theme() .recursion() - .representation(object); + .representation(point); } } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/RootInpector.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/RootInpector.java index 784ccb4..c9d69d8 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/RootInpector.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/RootInpector.java @@ -16,6 +16,8 @@ package pl.wavesoftware.utils.stringify.impl.inspector; +import pl.wavesoftware.utils.stringify.api.InspectionPoint; + import java.util.function.Function; /** @@ -25,5 +27,5 @@ * @author Krzysztof Suszynski * @since 2.0.0 */ -public interface RootInpector extends Function { +public interface RootInpector extends Function { } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/InspectionContext.java b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/StringifierContext.java similarity index 87% rename from src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/InspectionContext.java rename to src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/StringifierContext.java index a79d393..1eaaa32 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/InspectionContext.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/impl/inspector/StringifierContext.java @@ -16,6 +16,7 @@ package pl.wavesoftware.utils.stringify.impl.inspector; +import pl.wavesoftware.utils.stringify.api.InspectionContext; import pl.wavesoftware.utils.stringify.spi.theme.Theme; /** @@ -25,7 +26,7 @@ * @author Krzysztof Suszynski * @since 1.0.0 */ -public interface InspectionContext { +public interface StringifierContext { /** * Determine if given object was already inspected. * @@ -54,4 +55,11 @@ public interface InspectionContext { * @return a theme */ Theme theme(); + + /** + * Current inspection context + * + * @return a current inspection context + */ + InspectionContext inspectionContext(); } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/CharSequenceStyle.java b/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/CharSequenceStyle.java index 4cd6a44..d1b72b6 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/CharSequenceStyle.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/CharSequenceStyle.java @@ -16,12 +16,14 @@ package pl.wavesoftware.utils.stringify.spi.theme; +import pl.wavesoftware.utils.stringify.api.InspectionPoint; + /** * @author Krzysztof Suszynski * @since 2.0.0 */ public interface CharSequenceStyle { - default CharSequence quote() { + default CharSequence quote(InspectionPoint point) { return "\""; } } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/CharacterStyle.java b/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/CharacterStyle.java index 7ab4752..0b5d732 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/CharacterStyle.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/CharacterStyle.java @@ -16,12 +16,14 @@ package pl.wavesoftware.utils.stringify.spi.theme; +import pl.wavesoftware.utils.stringify.api.InspectionPoint; + /** * @author Krzysztof Suszynski * @since 2.0.0 */ public interface CharacterStyle { - default CharSequence quote() { + default CharSequence quote(InspectionPoint point) { return "'"; } } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/ComplexObjectStyle.java b/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/ComplexObjectStyle.java index 4e5def8..106baad 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/ComplexObjectStyle.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/ComplexObjectStyle.java @@ -16,32 +16,34 @@ package pl.wavesoftware.utils.stringify.spi.theme; +import pl.wavesoftware.utils.stringify.api.InspectionPoint; + /** * @author Krzysztof Suszynski * @since 2.0.0 */ public interface ComplexObjectStyle { - default CharSequence begin() { + default CharSequence begin(InspectionPoint point) { return "<"; } - default CharSequence name(Object target) { - return target.getClass().getSimpleName(); + default CharSequence name(InspectionPoint point) { + return point.getType().get().getSimpleName(); } - default CharSequence end() { + default CharSequence end(InspectionPoint point) { return ">"; } - default CharSequence nameSeparator() { + default CharSequence nameSeparator(InspectionPoint point) { return " "; } - default CharSequence propertyEquals() { + default CharSequence propertyEquals(InspectionPoint point) { return "="; } - default CharSequence propertySeparator() { + default CharSequence propertySeparator(InspectionPoint point) { return ", "; } } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/IterableStyle.java b/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/IterableStyle.java index 767995e..e15a03a 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/IterableStyle.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/IterableStyle.java @@ -16,20 +16,22 @@ package pl.wavesoftware.utils.stringify.spi.theme; +import pl.wavesoftware.utils.stringify.api.InspectionPoint; + /** * @author Krzysztof Suszynski * @since 2.0.0 */ public interface IterableStyle { - default CharSequence begin() { + default CharSequence begin(InspectionPoint point) { return "["; } - default CharSequence separator() { + default CharSequence separator(InspectionPoint point) { return ","; } - default CharSequence end() { + default CharSequence end(InspectionPoint point) { return "]"; } } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/JpaLazyStyle.java b/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/JpaLazyStyle.java index 8ea9223..a57e99c 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/JpaLazyStyle.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/JpaLazyStyle.java @@ -16,12 +16,14 @@ package pl.wavesoftware.utils.stringify.spi.theme; +import pl.wavesoftware.utils.stringify.api.InspectionPoint; + /** * @author Krzysztof Suszynski * @since 2.0.0 */ public interface JpaLazyStyle { - default CharSequence representation(Object target) { + default CharSequence representation(InspectionPoint point) { return "⁂Lazy"; } } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/MapStyle.java b/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/MapStyle.java index 7ff0a91..62bbff0 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/MapStyle.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/MapStyle.java @@ -16,24 +16,26 @@ package pl.wavesoftware.utils.stringify.spi.theme; +import pl.wavesoftware.utils.stringify.api.InspectionPoint; + /** * @author Krzysztof Suszynski * @since 0.1.0 */ public interface MapStyle { - default CharSequence begin() { + default CharSequence begin(InspectionPoint point) { return "{"; } - default CharSequence separator() { + default CharSequence separator(InspectionPoint point) { return ", "; } - default CharSequence entryEquals() { + default CharSequence entryEquals(InspectionPoint point) { return ": "; } - default CharSequence end() { + default CharSequence end(InspectionPoint point) { return "}"; } } diff --git a/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/RecursionStyle.java b/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/RecursionStyle.java index ea5c2c0..8197c17 100644 --- a/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/RecursionStyle.java +++ b/src/main/java/pl/wavesoftware/utils/stringify/spi/theme/RecursionStyle.java @@ -16,12 +16,14 @@ package pl.wavesoftware.utils.stringify.spi.theme; +import pl.wavesoftware.utils.stringify.api.InspectionPoint; + /** * @author Krzysztof Suszynski * @since 2.0.0 */ public interface RecursionStyle { - default CharSequence representation(Object target) { + default CharSequence representation(InspectionPoint point) { return "(↻)"; } } diff --git a/src/test/java/pl/wavesoftware/utils/stringify/StringifyIT.java b/src/test/java/pl/wavesoftware/utils/stringify/StringifyIT.java index b0f87e9..6d95c89 100644 --- a/src/test/java/pl/wavesoftware/utils/stringify/StringifyIT.java +++ b/src/test/java/pl/wavesoftware/utils/stringify/StringifyIT.java @@ -16,6 +16,7 @@ package pl.wavesoftware.utils.stringify; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.openjdk.jmh.annotations.Benchmark; @@ -41,6 +42,7 @@ * @author Krzysztof Suszynski * @since 20.04.18 */ +@Tag("perf") public class StringifyIT { private static final int PERCENT = 100; private static final int OPERATIONS = 1000; @@ -51,7 +53,7 @@ public class StringifyIT { static final JmhCleaner cleaner = new JmhCleaner(StringifyIT.class); @Test - void doBenckmarking() throws RunnerException { + void performBenckmark() throws RunnerException { Options opt = new OptionsBuilder() .include(this.getClass().getName() + ".*") .mode(Mode.Throughput) diff --git a/src/test/java/pl/wavesoftware/utils/stringify/StringifyTest.java b/src/test/java/pl/wavesoftware/utils/stringify/StringifyTest.java index a1f683c..fae1a1f 100644 --- a/src/test/java/pl/wavesoftware/utils/stringify/StringifyTest.java +++ b/src/test/java/pl/wavesoftware/utils/stringify/StringifyTest.java @@ -25,15 +25,14 @@ import pl.wavesoftware.utils.stringify.api.Mode; import pl.wavesoftware.utils.stringify.spi.BeanFactory; -import java.lang.reflect.Field; import java.util.AbstractMap; import java.util.HashMap; import java.util.Map; import java.util.function.Predicate; -import java.util.function.Supplier; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; +import static org.mockito.Mockito.mock; /** @@ -122,22 +121,7 @@ void withCustomPredicateAndMasker() { ); assertThat(new AlwaysTruePredicate()).accepts( - new InspectionPoint() { - @Override - public Field getField() { - return null; - } - - @Override - public Object getContainingObject() { - return null; - } - - @Override - public Supplier getValueSupplier() { - return null; - } - } + mock(InspectionPoint.class) ); } @@ -255,7 +239,7 @@ private static boolean inspectionPointValue(final InspectionPoint inspectionPoin final Predicate predicate) { return predicate.test( inspectionPoint - .getValueSupplier() + .getValue() .get() ); } diff --git a/src/test/java/pl/wavesoftware/utils/stringify/TestingConfigurator.java b/src/test/java/pl/wavesoftware/utils/stringify/TestingConfigurator.java index 19d1071..9f0ce31 100644 --- a/src/test/java/pl/wavesoftware/utils/stringify/TestingConfigurator.java +++ b/src/test/java/pl/wavesoftware/utils/stringify/TestingConfigurator.java @@ -2,6 +2,7 @@ import pl.wavesoftware.utils.stringify.api.Configuration; import pl.wavesoftware.utils.stringify.api.Mode; +import pl.wavesoftware.utils.stringify.api.Namespace; import pl.wavesoftware.utils.stringify.spi.Configurator; public final class TestingConfigurator implements Configurator { @@ -9,5 +10,6 @@ public final class TestingConfigurator implements Configurator { public void configure(Configuration configuration) { configuration.mode(Mode.DEFAULT_MODE); configuration.beanFactory(new TestBeanFactory()); + configuration.store(Namespace.GLOBAL, store -> store.put("indentation", "\t")); } } diff --git a/src/test/java/pl/wavesoftware/utils/stringify/UnlessContainsSecret.java b/src/test/java/pl/wavesoftware/utils/stringify/UnlessContainsSecret.java index c6b7c3f..e74c7c9 100644 --- a/src/test/java/pl/wavesoftware/utils/stringify/UnlessContainsSecret.java +++ b/src/test/java/pl/wavesoftware/utils/stringify/UnlessContainsSecret.java @@ -7,7 +7,7 @@ final class UnlessContainsSecret implements Predicate { @Override public boolean test(InspectionPoint inspectionPoint) { - Object value = inspectionPoint.getValueSupplier().get(); + Object value = inspectionPoint.getValue().get(); return !value.toString().contains("secret"); } } diff --git a/src/test/java/pl/wavesoftware/utils/stringify/api/NamespaceTest.java b/src/test/java/pl/wavesoftware/utils/stringify/api/NamespaceTest.java new file mode 100644 index 0000000..c7b7327 --- /dev/null +++ b/src/test/java/pl/wavesoftware/utils/stringify/api/NamespaceTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 2018-2019 Wave Software + * + * 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 pl.wavesoftware.utils.stringify.api; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.*; + +/** + * @author Krzysztof Suszynski + * @since 2.0.0 + */ +class NamespaceTest { + + @Test + void equalsToItself() { + // given + Namespace ns = Namespace.GLOBAL; + + // then + assertThat(ns).isEqualTo(Namespace.GLOBAL); + } + + @Test + void doNotEqualNull() { + // then + assertThat(Namespace.GLOBAL).isNotEqualTo(null); + } + + @Test + void doNotEqualOtherType() { + // then + assertThat(Namespace.GLOBAL).isNotEqualTo("test"); + } + + @Test + void equalsByParts() { + // when + Namespace ns1 = Namespace.create("alice", true, "bob"); + Namespace ns2 = Namespace.create("alice", true, "bob"); + + // then + assertThat(ns1).isEqualTo(ns2); + } +} diff --git a/src/test/java/pl/wavesoftware/utils/stringify/impl/InspectionPointImplTest.java b/src/test/java/pl/wavesoftware/utils/stringify/impl/FieldInspectionPointImplTest.java similarity index 56% rename from src/test/java/pl/wavesoftware/utils/stringify/impl/InspectionPointImplTest.java rename to src/test/java/pl/wavesoftware/utils/stringify/impl/FieldInspectionPointImplTest.java index 53381b4..949b64b 100644 --- a/src/test/java/pl/wavesoftware/utils/stringify/impl/InspectionPointImplTest.java +++ b/src/test/java/pl/wavesoftware/utils/stringify/impl/FieldInspectionPointImplTest.java @@ -17,7 +17,12 @@ package pl.wavesoftware.utils.stringify.impl; import org.junit.jupiter.api.Test; +import pl.wavesoftware.utils.stringify.api.InspectionContext; import pl.wavesoftware.utils.stringify.api.InspectionPoint; +import pl.wavesoftware.utils.stringify.api.Mode; +import pl.wavesoftware.utils.stringify.api.Namespace; +import pl.wavesoftware.utils.stringify.api.Store; +import pl.wavesoftware.utils.stringify.impl.inspector.InspectorModule; import java.lang.reflect.Field; @@ -27,7 +32,7 @@ * @author Krzysztof Suszynski * @since 30.04.18 */ -class InspectionPointImplTest { +class FieldInspectionPointImplTest { @Test void testGetValueSupplier() throws NoSuchFieldException { @@ -35,16 +40,30 @@ void testGetValueSupplier() throws NoSuchFieldException { Sample sample = new Sample(); sample.ala = "ma"; Field alaField = Sample.class.getDeclaredField("ala"); - alaField.setAccessible(true); - InspectionPoint inspectionPoint = new InspectionPointImpl(alaField, sample); + InspectionContext ctx = InspectorModule.INSTANCE + .inspectionContext(this::constantStore); + InspectingFieldFactory inspectingFieldFactory = + new InspectingFieldFactory(() -> Mode.DEFAULT_MODE); + InspectingField inspectingField = inspectingFieldFactory.create( + alaField, sample, this::beanFactory + ); + InspectionPoint inspectionPoint = new FieldInspectionPointImpl(inspectingField, ctx); // when - Object value = inspectionPoint.getValueSupplier().get(); + Object value = inspectionPoint.getValue().get(); // then assertThat(value).isEqualTo("ma"); } + private T beanFactory(Class contract) { + throw new UnsupportedOperationException("Not yet implemented"); + } + + private Store constantStore(Namespace namespace) { + return new StoreImpl(); + } + private static final class Sample { private String ala; } diff --git a/src/test/java/pl/wavesoftware/utils/stringify/spi/theme/CustomTheme.java b/src/test/java/pl/wavesoftware/utils/stringify/spi/theme/CustomTheme.java deleted file mode 100644 index a618939..0000000 --- a/src/test/java/pl/wavesoftware/utils/stringify/spi/theme/CustomTheme.java +++ /dev/null @@ -1,64 +0,0 @@ -package pl.wavesoftware.utils.stringify.spi.theme; - -final class CustomTheme implements Theme { - private static CharSequence name(Object target) { - return target.getClass().getSimpleName() - + "#" - + Integer.toUnsignedString(System.identityHashCode(target), 36); - } - - @Override - public ComplexObjectStyle complexObject() { - return new ComplexObjectStyle() { - @Override - public CharSequence begin() { - return "("; - } - - @Override - public CharSequence name(Object target) { - return CustomTheme.name(target); - } - - @Override - public CharSequence end() { - return ")"; - } - - @Override - public CharSequence propertySeparator() { - return ", "; - } - }; - } - - @Override - public MapStyle map() { - return new MapStyle() { - @Override - public CharSequence entryEquals() { - return " => "; - } - }; - } - - @Override - public JpaLazyStyle jpaLazy() { - return new JpaLazyStyle() { - @Override - public CharSequence representation(Object target) { - return "❄️"; - } - }; - } - - @Override - public RecursionStyle recursion() { - return new RecursionStyle() { - @Override - public CharSequence representation(Object target) { - return "(↻️️ " + CustomTheme.name(target) + ")"; - } - }; - } -} diff --git a/src/test/java/pl/wavesoftware/utils/stringify/spi/theme/PrettyPrintTheme.java b/src/test/java/pl/wavesoftware/utils/stringify/spi/theme/PrettyPrintTheme.java new file mode 100644 index 0000000..46cfd9e --- /dev/null +++ b/src/test/java/pl/wavesoftware/utils/stringify/spi/theme/PrettyPrintTheme.java @@ -0,0 +1,119 @@ +package pl.wavesoftware.utils.stringify.spi.theme; + +import pl.wavesoftware.utils.stringify.api.InspectionPoint; +import pl.wavesoftware.utils.stringify.api.Namespace; +import pl.wavesoftware.utils.stringify.api.Store; + +final class PrettyPrintTheme implements Theme { + + private static final String DEFAULT_INDENT = " "; + + private static CharSequence indent(InspectionPoint point) { + Store store = point.getContext().store(Namespace.GLOBAL); + CharSequence indentRepr = store + .get("indentation", CharSequence.class) + .orElse(DEFAULT_INDENT); + return point.getContext().indentationControl().indent(indentRepr); + } + + private static CharSequence name(Object target) { + return target.getClass().getSimpleName() + + "#" + + Integer.toUnsignedString(System.identityHashCode(target), 36); + } + + @Override + public ComplexObjectStyle complexObject() { + return new ComplexObjectStyle() { + @Override + public CharSequence begin(InspectionPoint point) { + point.getContext().indentationControl().increment(); + return "("; + } + + @Override + public CharSequence name(InspectionPoint point) { + return PrettyPrintTheme.name(point.getValue().get()) + + "\n" + indent(point); + } + + @Override + public CharSequence end(InspectionPoint point) { + point.getContext().indentationControl().decrement(); + return "\n" + indent(point) + ")"; + } + + @Override + public CharSequence propertySeparator(InspectionPoint point) { + return ",\n" + indent(point); + } + + @Override + public CharSequence nameSeparator(InspectionPoint point) { + return ""; + } + + @Override + public CharSequence propertyEquals(InspectionPoint point) { + return " = "; + } + }; + } + + @Override + public MapStyle map() { + return new MapStyle() { + @Override + public CharSequence begin(InspectionPoint point) { + point.getContext().indentationControl().increment(); + return "{\n" + indent(point); + } + + @Override + public CharSequence separator(InspectionPoint point) { + return ",\n" + indent(point); + } + + @Override + public CharSequence entryEquals(InspectionPoint point) { + return " => "; + } + + @Override + public CharSequence end(InspectionPoint point) { + point.getContext().indentationControl().decrement(); + return "\n" + indent(point) + "}"; + } + }; + } + + @Override + public JpaLazyStyle jpaLazy() { + return new JpaLazyStyle() { + @Override + public CharSequence representation(InspectionPoint point) { + return "❄️"; + } + }; + } + + @Override + public RecursionStyle recursion() { + return new RecursionStyle() { + @Override + public CharSequence representation(InspectionPoint point) { + return "(↻️️ " + PrettyPrintTheme.name(point.getValue().get()) + ")"; + } + }; + } + + @Override + public IterableStyle iterable() { + return new IterableStyle() { + @Override + public CharSequence separator(InspectionPoint point) { + return ", "; + } + }; + } +} diff --git a/src/test/java/pl/wavesoftware/utils/stringify/spi/theme/ThemeTest.java b/src/test/java/pl/wavesoftware/utils/stringify/spi/theme/ThemeTest.java index c1807ab..14346c0 100644 --- a/src/test/java/pl/wavesoftware/utils/stringify/spi/theme/ThemeTest.java +++ b/src/test/java/pl/wavesoftware/utils/stringify/spi/theme/ThemeTest.java @@ -5,6 +5,7 @@ import pl.wavesoftware.utils.stringify.Earth; import pl.wavesoftware.utils.stringify.Stringify; import pl.wavesoftware.utils.stringify.TestRepository; +import pl.wavesoftware.utils.stringify.api.Namespace; import static org.assertj.core.api.Assertions.assertThat; @@ -12,11 +13,12 @@ class ThemeTest { private final TestRepository testRepository = new TestRepository(); @Test - void customTheme() { + void prettyPrintTheme() { // given Earth planet = (Earth) testRepository.createTestPlanet(); Stringify stringifier = Stringify.of(planet); - stringifier.theme(new CustomTheme()); + stringifier.theme(new PrettyPrintTheme()); + stringifier.store(Namespace.GLOBAL, store -> store.put("indentation", " ")); CharSequence earthHash = hash(planet); CharSequence planetSystemHash = hash(planet.getPlanetSystem()); CharSequence moonHash = hash(planet.getMoon()); @@ -26,15 +28,26 @@ void customTheme() { // then String expected = format( - "(Earth#{0} name=\"Earth\", rocky=true, " + - "planetSystem=(PlanetSystem#{1} planets=❄️), " + - "moon=(Moon#{2} name=\"Moon\", rocky=null, " + - "planetSystem=(↻️️ PlanetSystem#{1}), phase=FULL_MOON, " + - "visits={" + - "\"1969\" => [\"Apollo 11\",\"Apollo 12\"], " + - "\"1971\" => [\"Apollo 14\",\"Apollo 15\"], " + - "\"1972\" => [\"Apollo 16\",\"Apollo 17\"]" + - "}), dayOfYear=14, type='A')", + "(Earth#{0}\n" + + " name = \"Earth\",\n" + + " rocky = true,\n" + + " planetSystem = (PlanetSystem#{1}\n" + + " planets = ❄️\n" + + " ),\n" + + " moon = (Moon#{2}\n" + + " name = \"Moon\",\n" + + " rocky = null,\n" + + " planetSystem = (↻️️ PlanetSystem#{1}),\n" + + " phase = FULL_MOON,\n" + + " visits = {\n" + + " \"1969\" => [\"Apollo 11\", \"Apollo 12\"],\n" + + " \"1971\" => [\"Apollo 14\", \"Apollo 15\"],\n" + + " \"1972\" => [\"Apollo 16\", \"Apollo 17\"]\n" + + " }\n" + + " ),\n" + + " dayOfYear = 14,\n" + + " type = 'A'\n" + + ")", earthHash, planetSystemHash, moonHash ); assertThat(result).isEqualTo(expected);