This project has retired. For details please refer to its Attic page.
Source code
001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  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,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.reef.tang.formats;
020
021import org.apache.reef.tang.ExternalConstructor;
022import org.apache.reef.tang.JavaConfigurationBuilder;
023import org.apache.reef.tang.Tang;
024import org.apache.reef.tang.annotations.Name;
025import org.apache.reef.tang.exceptions.BindException;
026import org.apache.reef.tang.exceptions.ClassHierarchyException;
027import org.apache.reef.tang.exceptions.NameResolutionException;
028import org.apache.reef.tang.util.MonotonicHashMap;
029import org.apache.reef.tang.util.MonotonicHashSet;
030import org.apache.reef.tang.util.ReflectionUtilities;
031
032import java.lang.reflect.Field;
033import java.lang.reflect.Modifier;
034import java.util.List;
035import java.util.Map;
036import java.util.Set;
037
038public abstract class ConfigurationModuleBuilder {
039
040  private static final Set<Class<?>> PARAM_BLACKLIST = new MonotonicHashSet<>(
041      Param.class, Impl.class);
042  private static final Set<Class<?>> PARAM_TYPES = new MonotonicHashSet<>(
043      RequiredImpl.class, OptionalImpl.class, RequiredParameter.class,
044      OptionalParameter.class);
045  protected final JavaConfigurationBuilder b = Tang.Factory.getTang()
046      .newConfigurationBuilder();
047  // Sets of things that have been declared
048  protected final Set<Field> reqDecl = new MonotonicHashSet<>();
049  protected final Set<Object> setOpts = new MonotonicHashSet<>();
050  // Maps from field instance variables to the fields that they
051  // are assigned to. These better be unique!
052  protected Map<Object, Field> map = new MonotonicHashMap<>();
053  protected final Map<Class<?>, Impl<?>> freeImpls = new MonotonicHashMap<>();
054  protected final Map<Class<? extends Name<?>>, Param<?>> freeParams = new MonotonicHashMap<>();
055  private final Set<Field> optDecl = new MonotonicHashSet<>();
056  // Set of things that have been used in a bind. These must be equal
057  // to the decl counterparts before build() is called.
058  private final Set<Field> reqUsed = new MonotonicHashSet<>();
059  private final Set<Field> optUsed = new MonotonicHashSet<>();
060  private final Map<Class<?>, String> lateBindClazz = new MonotonicHashMap<>();
061
062  protected ConfigurationModuleBuilder() {
063    for (final Field f : getClass().getDeclaredFields()) {
064      final Class<?> t = f.getType();
065      if (PARAM_BLACKLIST.contains(t)) {
066        throw new ClassHierarchyException(
067            "Found a field of type " + t + " which should be a Required/Optional Parameter/Implementation instead"
068        );
069      }
070      if (PARAM_TYPES.contains(t)) {
071        if (!Modifier.isPublic(f.getModifiers())) {
072          throw new ClassHierarchyException(
073              "Found a non-public configuration option in " + getClass() + ": "
074                  + f);
075        }
076        if (!Modifier.isStatic(f.getModifiers())) {
077          throw new ClassHierarchyException(
078              "Found a non-static configuration option in " + getClass() + ": "
079                  + f);
080        }
081        if (!Modifier.isFinal(f.getModifiers())) {
082          throw new ClassHierarchyException(
083              "Found a non-final configuration option in " + getClass() + ": "
084                  + f);
085        }
086        final Object o;
087        try {
088          o = f.get(null);
089        } catch (IllegalArgumentException | IllegalAccessException e) {
090          throw new ClassHierarchyException(
091              "Could not look up field instance in " + getClass() + " field: "
092                  + f, e);
093        }
094        if (map.containsKey(o)) {
095          throw new ClassHierarchyException(
096              "Detected aliased instances in class " + getClass()
097                  + " for fields " + map.get(o) + " and " + f);
098        }
099        if (t == RequiredImpl.class || t == RequiredParameter.class) {
100          reqDecl.add(f);
101        } else {
102          optDecl.add(f);
103        }
104        map.put(o, f);
105      }
106    }
107  }
108
109  private ConfigurationModuleBuilder(final ConfigurationModuleBuilder c) {
110    try {
111      b.addConfiguration(c.b.build());
112    } catch (final BindException e) {
113      throw new ClassHierarchyException(e);
114    }
115    reqDecl.addAll(c.reqDecl);
116    optDecl.addAll(c.optDecl);
117    reqUsed.addAll(c.reqUsed);
118    optUsed.addAll(c.optUsed);
119    setOpts.addAll(c.setOpts);
120    map.putAll(c.map);
121    freeImpls.putAll(c.freeImpls);
122    freeParams.putAll(c.freeParams);
123    lateBindClazz.putAll(c.lateBindClazz);
124
125  }
126
127  /**
128   * TODO: It would be nice if this incorporated d by reference so that static analysis / documentation tools
129   * could document the dependency between c and d.
130   *
131   * @param d a configuration module to merge
132   * @return the merged configuration module builder
133   */
134  public final ConfigurationModuleBuilder merge(final ConfigurationModule d) {
135    if (d == null) {
136      throw new NullPointerException("If merge() was passed a static final field that is initialized to non-null, " +
137          "then this is almost certainly caused by a circular class dependency.");
138    }
139    try {
140      d.assertStaticClean();
141    } catch (final ClassHierarchyException e) {
142      throw new ClassHierarchyException(ReflectionUtilities.getFullName(getClass()) +
143          ": detected attempt to merge with ConfigurationModule that has had set() called on it", e);
144    }
145    final ConfigurationModuleBuilder c = deepCopy();
146    final ConfigurationModuleBuilder builder = d.getBuilder();
147    try {
148      c.b.addConfiguration(builder.b.build());
149    } catch (final BindException e) {
150      throw new ClassHierarchyException(e);
151    }
152    c.reqDecl.addAll(builder.reqDecl);
153    c.optDecl.addAll(builder.optDecl);
154    c.reqUsed.addAll(builder.reqUsed);
155    c.optUsed.addAll(builder.optUsed);
156    c.setOpts.addAll(builder.setOpts);
157    c.map.putAll(builder.map);
158    c.freeImpls.putAll(builder.freeImpls);
159    c.freeParams.putAll(builder.freeParams);
160    c.lateBindClazz.putAll(builder.lateBindClazz);
161
162    return c;
163  }
164
165  public final <T> ConfigurationModuleBuilder bind(final Class<?> iface, final Impl<?> opt) {
166    final ConfigurationModuleBuilder c = deepCopy();
167    c.processUse(opt);
168    c.freeImpls.put(iface, opt);
169    return c;
170  }
171
172  public final <T> ConfigurationModuleBuilder bindSetEntry(
173      final Class<? extends Name<Set<T>>> iface, final String impl) {
174    final ConfigurationModuleBuilder c = deepCopy();
175    try {
176      c.b.bindSetEntry(iface, impl);
177    } catch (final BindException e) {
178      throw new ClassHierarchyException(e);
179    }
180    return c;
181  }
182
183  public final <T> ConfigurationModuleBuilder bindSetEntry(final Class<? extends Name<Set<T>>> iface,
184                                                           final Class<? extends T> impl) {
185    final ConfigurationModuleBuilder c = deepCopy();
186    try {
187      c.b.bindSetEntry(iface, impl);
188    } catch (final BindException e) {
189      throw new ClassHierarchyException(e);
190    }
191    return c;
192  }
193
194  public final <T> ConfigurationModuleBuilder bindSetEntry(final Class<? extends Name<Set<T>>> iface,
195                                                           final Impl<? extends T> opt) {
196    final ConfigurationModuleBuilder c = deepCopy();
197    c.processUse(opt);
198    c.freeImpls.put(iface, opt);
199    if (!setOpts.contains(opt)) {
200      c.setOpts.add(opt);
201    }
202    return c;
203  }
204
205  public final <T> ConfigurationModuleBuilder bindSetEntry(final Class<? extends Name<Set<T>>> iface,
206                                                           final Param<? extends T> opt) {
207    final ConfigurationModuleBuilder c = deepCopy();
208    c.processUse(opt);
209    c.freeParams.put(iface, opt);
210    if (!setOpts.contains(opt)) {
211      c.setOpts.add(opt);
212    }
213    return c;
214  }
215
216
217  public final <T> ConfigurationModuleBuilder bindImplementation(final Class<T> iface,
218                                                                 final Class<? extends T> impl) {
219    final ConfigurationModuleBuilder c = deepCopy();
220    try {
221      c.b.bindImplementation(iface, impl);
222    } catch (final BindException e) {
223      throw new ClassHierarchyException(e);
224    }
225    return c;
226  }
227
228  public final <T> ConfigurationModuleBuilder bindImplementation(final Class<T> iface,
229                                                                 final String impl) {
230    final ConfigurationModuleBuilder c = deepCopy();
231    c.lateBindClazz.put(iface, impl);
232    return c;
233  }
234
235  public final <T> ConfigurationModuleBuilder bindImplementation(final Class<T> iface,
236                                                                 final Impl<? extends T> opt) {
237    final ConfigurationModuleBuilder c = deepCopy();
238    c.processUse(opt);
239    c.freeImpls.put(iface, opt);
240    return c;
241  }
242
243  public final <T> ConfigurationModuleBuilder bindNamedParameter(
244      final Class<? extends Name<T>> name, final String value) {
245    final ConfigurationModuleBuilder c = deepCopy();
246    try {
247      c.b.bindNamedParameter(name, value);
248    } catch (final BindException e) {
249      throw new ClassHierarchyException(e);
250    }
251    return c;
252  }
253
254  public final <T> ConfigurationModuleBuilder bindNamedParameter(
255      final Class<? extends Name<T>> name, final Param<T> opt) {
256    final ConfigurationModuleBuilder c = deepCopy();
257    c.processUse(opt);
258    c.freeParams.put(name, opt);
259    return c;
260  }
261
262  public final <T> ConfigurationModuleBuilder bindNamedParameter(
263      final Class<? extends Name<T>> iface, final Class<? extends T> impl) {
264    final ConfigurationModuleBuilder c = deepCopy();
265    try {
266      c.b.bindNamedParameter(iface, impl);
267    } catch (final BindException e) {
268      throw new ClassHierarchyException(e);
269    }
270    return c;
271  }
272
273  public final <T> ConfigurationModuleBuilder bindNamedParameter(
274      final Class<? extends Name<T>> iface, final Impl<? extends T> opt) {
275    final ConfigurationModuleBuilder c = deepCopy();
276    c.processUse(opt);
277    c.freeImpls.put(iface, opt);
278    return c;
279  }
280
281  public final <T> ConfigurationModuleBuilder bindConstructor(final Class<T> clazz,
282                                                              final Class<? extends ExternalConstructor<? extends T>>
283                                                              constructor) {
284    final ConfigurationModuleBuilder c = deepCopy();
285    try {
286      c.b.bindConstructor(clazz, constructor);
287    } catch (final BindException e) {
288      throw new ClassHierarchyException(e);
289    }
290    return c;
291  }
292
293  public final <T> ConfigurationModuleBuilder bindConstructor(
294      final Class<T> cons, final Impl<? extends ExternalConstructor<? extends T>> v) {
295    final ConfigurationModuleBuilder c = deepCopy();
296    c.processUse(v);
297    c.freeImpls.put(cons, v);
298    return c;
299  }
300
301  public final <T> ConfigurationModuleBuilder bindList(final Class<? extends Name<List<T>>> iface,
302                                                       final Impl<List> opt) {
303    final ConfigurationModuleBuilder c = deepCopy();
304    c.processUse(opt);
305    c.freeImpls.put(iface, opt);
306    return c;
307  }
308
309  public final <T> ConfigurationModuleBuilder bindList(final Class<? extends Name<List<T>>> iface,
310                                                       final Param<List> opt) {
311    final ConfigurationModuleBuilder c = deepCopy();
312    c.processUse(opt);
313    c.freeParams.put(iface, opt);
314    return c;
315  }
316
317  public final <T> ConfigurationModuleBuilder bindList(final Class<? extends Name<List<T>>> iface, final List list) {
318    final ConfigurationModuleBuilder c = deepCopy();
319    c.b.bindList(iface, list);
320    return c;
321  }
322
323  private <T> void processUse(final Object impl) {
324    final Field f = map.get(impl);
325    if (f == null) { /* throw */
326      throw new ClassHierarchyException("Unknown Impl/Param when binding " +
327          ReflectionUtilities.getSimpleName(impl.getClass()) + ".  Did you pass in a field from some other module?");
328    }
329    if (!reqUsed.contains(f)) {
330      reqUsed.add(f);
331    }
332    if (!optUsed.contains(f)) {
333      optUsed.add(f);
334    }
335  }
336
337  public final ConfigurationModule build() throws ClassHierarchyException {
338    final ConfigurationModuleBuilder c = deepCopy();
339
340    if (!(c.reqUsed.containsAll(c.reqDecl) && c.optUsed.containsAll(c.optDecl))) {
341      final Set<Field> fset = new MonotonicHashSet<>();
342      for (final Field f : c.reqDecl) {
343        if (!c.reqUsed.contains(f)) {
344          fset.add(f);
345        }
346      }
347      for (final Field f : c.optDecl) {
348        if (!c.optUsed.contains(f)) {
349          fset.add(f);
350        }
351      }
352      throw new ClassHierarchyException(
353          "Found declared options that were not used in binds: "
354              + toString(fset));
355    }
356    for (final Class<?> clz : c.lateBindClazz.keySet()) {
357      try {
358        c.b.bind(ReflectionUtilities.getFullName(clz), c.lateBindClazz.get(clz));
359      } catch (final NameResolutionException e) {
360        throw new ClassHierarchyException("ConfigurationModule refers to unknown class: " +
361            c.lateBindClazz.get(clz), e);
362      } catch (final BindException e) {
363        throw new ClassHierarchyException("bind failed while initializing ConfigurationModuleBuilder", e);
364      }
365    }
366    return new ConfigurationModule(c);
367  }
368
369/*  public final <T> ConfigurationModuleBuilder bind(Class<T> iface, Class<?> impl) {
370    ConfigurationModuleBuilder c = deepCopy();
371    try {
372      c.b.bind(iface, impl);
373    } catch (BindException e) {
374      throw new ClassHierarchyException(e);
375    }
376    return c;
377  } */
378
379  final ConfigurationModuleBuilder deepCopy() {
380    // ooh... this is a dirty trick --- we strip this's type off here,
381    // fortunately, we've all ready looked at the root object's class's
382    // fields, and we copy the information we extracted from them, so
383    // everything works out OK w.r.t. field detection.
384    return new ConfigurationModuleBuilder(this) {
385    };
386  }
387
388  final String toString(final Set<Field> s) {
389    final StringBuilder sb = new StringBuilder("{");
390    boolean first = true;
391    for (final Field f : s) {
392      sb.append((first ? " " : ", ") + f.getName());
393      first = false;
394    }
395    sb.append(" }");
396    return sb.toString();
397  }
398}