BindUtils.java
001 /*
002  * SPDX-License-Identifier: Apache-2.0
003  *
004  * Copyright 2008-2017 the original author or authors.
005  *
006  * Licensed under the Apache License, Version 2.0 (the "License");
007  * you may not use this file except in compliance with the License.
008  * You may obtain a copy of the License at
009  *
010  *     http://www.apache.org/licenses/LICENSE-2.0
011  *
012  * Unless required by applicable law or agreed to in writing, software
013  * distributed under the License is distributed on an "AS IS" BASIS,
014  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015  * See the License for the specific language governing permissions and
016  * limitations under the License.
017  */
018 package griffon.util;
019 
020 import griffon.core.CallableWithArgs;
021 import groovy.lang.Closure;
022 import groovy.util.FactoryBuilderSupport;
023 
024 import javax.annotation.Nonnull;
025 import javax.annotation.Nullable;
026 import java.util.LinkedHashMap;
027 import java.util.Map;
028 
029 import static griffon.util.GriffonNameUtils.isBlank;
030 import static java.util.Objects.requireNonNull;
031 
032 /**
033  * Helper class for constructing bindings between two objects.
034  *
035  @author Andres Almiray
036  @since 2.0.0
037  */
038 public final class BindUtils {
039     private BindUtils() {
040         // prevent instantiation
041     }
042 
043     /**
044      * Create a new Binding using a builder.
045      */
046     @Nonnull
047     public static BindingBuilder binding() {
048         return new BindingBuilder();
049     }
050 
051     public static class BindingBuilder {
052         private Object source;
053         private Object target;
054         private String sourceProperty;
055         private String targetProperty;
056         private CallableWithArgs<?> converter;
057         private CallableWithArgs<?> validator;
058         private boolean mutual;
059 
060         @Nonnull
061         public BindingBuilder withSource(@Nullable Object source) {
062             this.source = source;
063             return this;
064         }
065 
066         @Nonnull
067         public BindingBuilder withTarget(@Nullable Object target) {
068             this.target = target;
069             return this;
070         }
071 
072         @Nonnull
073         public BindingBuilder withSourceProperty(@Nullable String sourceProperty) {
074             this.sourceProperty = sourceProperty;
075             return this;
076         }
077 
078         @Nonnull
079         public BindingBuilder withTargetProperty(@Nullable String targetProperty) {
080             this.targetProperty = targetProperty;
081             return this;
082         }
083 
084         @Nonnull
085         public BindingBuilder withConverter(@Nullable CallableWithArgs<?> converter) {
086             this.converter = converter;
087             return this;
088         }
089 
090         @Nonnull
091         public BindingBuilder withValidator(@Nullable CallableWithArgs<?> validator) {
092             this.validator = validator;
093             return this;
094         }
095 
096         @Nonnull
097         public BindingBuilder withMutual(boolean mutual) {
098             this.mutual = mutual;
099             return this;
100         }
101 
102         public void make(@Nonnull FactoryBuilderSupport builder) {
103             requireNonNull(builder, "Cannot make binding with a null builder!");
104             requireNonNull(source, "Unspecified value for: source");
105             requireNonNull(target, "Unspecified value for: target");
106 
107             Map<String, Object> attributes = new LinkedHashMap<>();
108 
109             if (isBlank(sourceProperty)) sourceProperty = targetProperty;
110             if (isBlank(sourceProperty)) {
111                 throw new IllegalArgumentException("Unspecified values for: sourceProperty, targetProperty");
112             }
113             if (isBlank(targetProperty)) targetProperty = sourceProperty;
114 
115             attributes.put("source", source);
116             attributes.put("target", target);
117             attributes.put("sourceProperty", sourceProperty);
118             attributes.put("targetProperty", targetProperty);
119             attributes.put("mutual", mutual);
120 
121             if (converter != null) {
122                 attributes.put("converter", makeClosure(builder, converter));
123             }
124             if (validator != null) {
125                 attributes.put("validator", makeClosure(builder, validator));
126             }
127 
128             builder.invokeMethod("bind", attributes);
129         }
130 
131         private Closure<?> makeClosure(@Nonnull FactoryBuilderSupport builder, @Nonnull final CallableWithArgs<?> callback) {
132             if (callback instanceof Closure) {
133                 return (Closure<?>callback;
134             }
135             return new Closure<Object>(builder) {
136                 private static final long serialVersionUID = -4108869890482462552L;
137 
138                 @Override
139                 public Object call(Object... args) {
140                     return callback.call(args);
141                 }
142 
143                 @Override
144                 public Object call(Object args) {
145                     return callback.call(args);
146                 }
147             };
148         }
149     }
150 }