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