com.foreach.across.modules.web.ui.ViewElementBuilder Maven / Gradle / Ivy
/*
* Copyright 2019 the original author or authors
*
* 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 com.foreach.across.modules.web.ui;
import lombok.NonNull;
import java.util.Collection;
import java.util.Collections;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* Base interface to create a single {@link ViewElement} instance.
* Usually used to build an entire hierarchy of elements by calling {@link #build(ViewElementBuilderContext)} on the
* top-most element. Building a {@link ViewElement} requires a {@link ViewElementBuilderContext} and often a
* single builder context is used to build many elements.
*
* If you call {@link #build()} without manually specifying a builder context, a global context will be retrieved
* using {@link ViewElementBuilderContext#retrieveGlobalBuilderContext()} and if none is available, a new
* {@link DefaultViewElementBuilderContext} will be used instead.
*
* For performance it is often best to manage the lifecycle of a {@link ViewElementBuilderContext} yourself,
* so you don't have unnecessary creation and can optimize contextual data sharing.
*
* @author Arne Vandamme
*/
@FunctionalInterface
public interface ViewElementBuilder
{
/**
* Build the {@link ViewElement} using the globally available {@link ViewElementBuilderContext},
* this will use {@link ViewElementBuilderContext#retrieveGlobalBuilderContext()} to get the global context.
*
* If none is returned, a new {@link DefaultViewElementBuilderContext} will be used instead.
*
* Use this method sparingly, usually only for the single top-level build of a {@link ViewElement}.
* The creation of a {@link ViewElementBuilderContext} can be relatively costly, performance-wise it is usually
* better if you call {@link #build(ViewElementBuilderContext)} with a predefined or {@link ViewElementBuilderContext},
* or at least ensure you have a global context available.
*
* @return view element
* @see ViewElementBuilderContext#retrieveGlobalBuilderContext()
*/
default T build() {
ViewElementBuilderContext globalBuilderContext = ViewElementBuilderContext
.retrieveGlobalBuilderContext()
.orElseGet( DefaultViewElementBuilderContext::new );
return build( globalBuilderContext );
}
/**
* Builds the actual element.
*
* @param builderContext provides the context for this build event
* @return instance to render the element.
*/
T build( ViewElementBuilderContext builderContext );
/**
* Chain a {@link ViewElementPostProcessor} to the result of this builder.
* This will return a new builder instance that applies the post processor to the element generated.
*
* Explicitly supports {@code null} argument values, which mean do nothing and will actually
* return the same builder.
*
* @param postProcessor to apply on the builder result
* @return new builder with the post processor chained to it
*/
default ViewElementBuilder andThen( ViewElementPostProcessor postProcessor ) {
if ( postProcessor != null ) {
ViewElementBuilder self = this;
return builderContext -> {
T element = self.build( builderContext );
postProcessor.postProcess( builderContext, element );
return element;
};
}
return this;
}
/**
* Map the {@link ViewElement} that this builder returns to another type.
* Creates a new builder returning the resulting element.
*
* If you need access to the {@link ViewElementBuilderContext} use {@link #map(BiFunction)} instead.
*
* @param mappingFunction to apply to the generated element
* @param type of the new element returned
* @return new builder instance
* @see #map(BiFunction)
*/
default ViewElementBuilder map( @NonNull Function mappingFunction ) {
return builderContext -> {
T element = this.build( builderContext );
return mappingFunction.apply( element );
};
}
/**
* Map the {@link ViewElement} that this builder returns to another type.
* Creates a new builder returning the resulting element.
*
* @param mappingFunction to apply to the generated element
* @param type of the new element returned
* @return new builder instance
* @see #map(Function)
*/
default ViewElementBuilder map( @NonNull BiFunction mappingFunction ) {
return builderContext -> {
T element = this.build( builderContext );
return mappingFunction.apply( builderContext, element );
};
}
// don't use yet - work in progress
@Deprecated
default ViewElementBuilder doWith( Wither... operations ) {
return null;
}
default ViewElementBuilder postProcess( ViewElementPostProcessor postProcessors ) {
return postProcess( Collections.singleton( postProcessors ) );
}
default ViewElementBuilder postProcess( Collection> postProcessors ) {
return builderContext -> {
T element = build( builderContext );
postProcessors.forEach( processor -> processor.postProcess( builderContext, element ) );
return element;
};
}
static ViewElementBuilder of( Supplier supplier ) {
return ( builderContext ) -> supplier.get();
}
static ViewElementBuilder of( Function supplier ) {
return supplier::apply;
}
@FunctionalInterface
interface Wither
{
void applyTo( T builder );
// div( children(), postProcess() )
// ViewElementBuilder.of( () -> new NodeViewElement("div" ).with( text( "hello" ), attribute( "hm" ) )
}
}