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.Configuration;
022import org.apache.reef.tang.annotations.Name;
023import org.apache.reef.tang.exceptions.BindException;
024import org.apache.reef.tang.exceptions.ClassHierarchyException;
025import org.apache.reef.tang.exceptions.NameResolutionException;
026import org.apache.reef.tang.implementation.ConfigurationBuilderImpl;
027import org.apache.reef.tang.implementation.ConfigurationImpl;
028import org.apache.reef.tang.types.ClassNode;
029import org.apache.reef.tang.types.ConstructorArg;
030import org.apache.reef.tang.types.NamedParameterNode;
031import org.apache.reef.tang.types.Node;
032import org.apache.reef.tang.util.*;
033
034import java.lang.reflect.Field;
035import java.util.ArrayList;
036import java.util.List;
037import java.util.Map;
038import java.util.Map.Entry;
039import java.util.Set;
040import java.util.logging.Level;
041import java.util.logging.Logger;
042
043/**
044 * Allows applications to bundle sets of configuration options together into
045 * discrete packages.  Unlike more conventional approaches,
046 * ConfigurationModules store such information in static data structures that
047 * can be statically discovered and sanity-checked.
048 *
049 * See org.apache.reef.tang.formats.TestConfigurationModule for more information and examples.
050 */
051public class ConfigurationModule {
052
053  private static final Logger LOG = Logger.getLogger(ConfigurationModule.class.getName());
054
055  private final ConfigurationModuleBuilder builder;
056  // Set of required unset parameters. Must be empty before build.
057  private final Set<Field> reqSet = new MonotonicHashSet<>();
058  private final Map<Impl<?>, Class<?>> setImpls = new MonotonicHashMap<>();
059  private final MonotonicMultiHashMap<Impl<?>, Class<?>> setImplSets = new MonotonicMultiHashMap<>();
060  private final MonotonicMultiHashMap<Impl<?>, String> setLateImplSets = new MonotonicMultiHashMap<>();
061  private final MonotonicMultiHashMap<Param<?>, String> setParamSets = new MonotonicMultiHashMap<>();
062  private final Map<Impl<?>, String> setLateImpls = new MonotonicHashMap<>();
063  private final Map<Param<?>, String> setParams = new MonotonicHashMap<>();
064  private final Map<Impl<List>, List<?>> setImplLists = new MonotonicHashMap<>();
065  private final Map<Param<List>, List<?>> setParamLists = new MonotonicHashMap<>();
066
067  protected ConfigurationModule(final ConfigurationModuleBuilder builder) {
068    this.builder = builder.deepCopy();
069  }
070
071  private ConfigurationModule deepCopy() {
072    final ConfigurationModule cm = new ConfigurationModule(builder.deepCopy());
073    cm.setImpls.putAll(setImpls);
074    cm.setImplSets.addAll(setImplSets);
075    cm.setLateImplSets.addAll(setLateImplSets);
076    cm.setParamSets.addAll(setParamSets);
077    cm.setLateImpls.putAll(setLateImpls);
078    cm.setParams.putAll(setParams);
079    cm.reqSet.addAll(reqSet);
080    cm.setImplLists.putAll(setImplLists);
081    cm.setParamLists.putAll(setParamLists);
082    return cm;
083  }
084
085  private <T> void processSet(final Object impl) {
086    final Field f = builder.map.get(impl);
087    if (f == null) { /* throw */
088      throw new ClassHierarchyException("Unknown Impl/Param when setting " +
089          ReflectionUtilities.getSimpleName(impl.getClass()) + ".  Did you pass in a field from some other module?");
090    }
091    if (!reqSet.contains(f)) {
092      reqSet.add(f);
093    }
094  }
095
096  public final <T> ConfigurationModule set(final Impl<T> opt, final Class<? extends T> impl) {
097    final ConfigurationModule c = deepCopy();
098    c.processSet(opt);
099    if (c.builder.setOpts.contains(opt)) {
100      c.setImplSets.put(opt, impl);
101    } else {
102      c.setImpls.put(opt, impl);
103    }
104    return c;
105  }
106
107  public final <T> ConfigurationModule set(final Impl<T> opt, final String impl) {
108    final ConfigurationModule c = deepCopy();
109    c.processSet(opt);
110    if (c.builder.setOpts.contains(opt)) {
111      c.setLateImplSets.put(opt, impl);
112    } else {
113      c.setLateImpls.put(opt, impl);
114    }
115    return c;
116  }
117
118  /**
119   * Binds a list to a specific optional/required Impl using ConfigurationModule.
120   *
121   * @param opt      Target optional/required Impl
122   * @param implList List object to be injected
123   * @param <T> a type
124   * @return the configuration module
125   */
126  public final <T> ConfigurationModule set(final Impl<List> opt, final List implList) {
127    final ConfigurationModule c = deepCopy();
128    c.processSet(opt);
129    c.setImplLists.put(opt, implList);
130    return c;
131  }
132
133  public final <T> ConfigurationModule set(final Param<T> opt, final Class<? extends T> val) {
134    return set(opt, ReflectionUtilities.getFullName(val));
135  }
136
137  public final ConfigurationModule set(final Param<Boolean> opt, final boolean val) {
138    return set(opt, "" + val);
139  }
140
141  public final ConfigurationModule set(final Param<? extends Number> opt, final Number val) {
142    return set(opt, "" + val);
143  }
144
145  public final <T> ConfigurationModule set(final Param<T> opt, final String val) {
146    final ConfigurationModule c = deepCopy();
147    c.processSet(opt);
148    if (c.builder.setOpts.contains(opt)) {
149      c.setParamSets.put(opt, val);
150    } else {
151      c.setParams.put(opt, val);
152    }
153    return c;
154  }
155
156  /**
157   * Binds a set of values to a Param using ConfigurationModule.
158   *
159   * @param opt    Target Param
160   * @param values Values to bind to the Param
161   * @param <T> type
162   * @return the Configuration module
163   */
164  public final <T> ConfigurationModule setMultiple(final Param<T> opt, final Iterable<String> values) {
165    ConfigurationModule c = deepCopy();
166    for (final String val : values) {
167      c = c.set(opt, val);
168    }
169    return c;
170  }
171
172  /**
173   * Binds a set of values to a Param using ConfigurationModule.
174   *
175   * @param opt    Target Param
176   * @param values Values to bind to the Param
177   * @param <T> type
178   * @return the Configuration module
179   */
180  public final <T> ConfigurationModule setMultiple(final Param<T> opt, final String... values) {
181    ConfigurationModule c = deepCopy();
182    for (final String val : values) {
183      c = c.set(opt, val);
184    }
185    return c;
186  }
187
188  /**
189   * Binds a list to a specific optional/required Param using ConfigurationModule.
190   *
191   * @param opt      target optional/required Param
192   * @param implList List object to be injected
193   * @param <T>      type
194   * @return the Configuration module
195   */
196  public final <T> ConfigurationModule set(final Param<List> opt, final List implList) {
197    final ConfigurationModule c = deepCopy();
198    c.processSet(opt);
199    c.setParamLists.put(opt, implList);
200    return c;
201  }
202
203  @SuppressWarnings({"unchecked", "rawtypes"})
204  public Configuration build() throws BindException {
205    final ConfigurationModule c = deepCopy();
206
207    //TODO[REEF-968] check that required parameters have not been set to null
208
209    if (!c.reqSet.containsAll(c.builder.reqDecl)) {
210      final Set<Field> missingSet = new MonotonicHashSet<>();
211      for (final Field f : c.builder.reqDecl) {
212        if (!c.reqSet.contains(f)) {
213          missingSet.add(f);
214        }
215      }
216      throw new BindException(
217          "Attempt to build configuration before setting required option(s): "
218              + builder.toString(missingSet));
219    }
220
221    for (final Class<?> clazz : c.builder.freeImpls.keySet()) {
222      final Impl<?> i = c.builder.freeImpls.get(clazz);
223      if (c.setImpls.containsKey(i)) {
224        c.builder.b.bind(clazz, c.setImpls.get(i));
225      } else if (c.setLateImpls.containsKey(i)) {
226        c.builder.b.bind(ReflectionUtilities.getFullName(clazz), c.setLateImpls.get(i));
227      } else if (c.setImplSets.containsKey(i) || c.setLateImplSets.containsKey(i)) {
228        for (final Class<?> clz : c.setImplSets.getValuesForKey(i)) {
229          c.builder.b.bindSetEntry((Class) clazz, (Class) clz);
230        }
231        for (final String s : c.setLateImplSets.getValuesForKey(i)) {
232          c.builder.b.bindSetEntry((Class) clazz, s);
233        }
234      } else if (c.setImplLists.containsKey(i)) {
235        c.builder.b.bindList((Class) clazz, c.setImplLists.get(i));
236      }
237    }
238    for (final Class<? extends Name<?>> clazz : c.builder.freeParams.keySet()) {
239      final Param<?> p = c.builder.freeParams.get(clazz);
240      final String s = c.setParams.get(p);
241      boolean foundOne = false;
242      if (s != null) {
243        c.builder.b.bindNamedParameter(clazz, s);
244        foundOne = true;
245      }
246      // Find the bound list for the NamedParameter
247      final List list = c.setParamLists.get(p);
248      if (list != null) {
249        c.builder.b.bindList((Class) clazz, list);
250        foundOne = true;
251      }
252      for (final String paramStr : c.setParamSets.getValuesForKey(p)) {
253        c.builder.b.bindSetEntry((Class) clazz, paramStr);
254        foundOne = true;
255      }
256
257      if (!foundOne && !(p instanceof OptionalParameter)) {
258        final IllegalStateException e =
259            new IllegalStateException("Cannot find the value for the RequiredParameter of the " + clazz
260                    + ". Check that you don't pass null as the parameter value.");
261        LOG.log(Level.SEVERE, "Failed to build configuration", e);
262        throw e;
263      }
264
265    }
266
267    return c.builder.b.build();
268
269  }
270
271
272  public Set<NamedParameterNode<?>> getBoundNamedParameters() {
273    final Configuration c = this.builder.b.build();
274    final Set<NamedParameterNode<?>> nps = new MonotonicSet<>();
275    nps.addAll(c.getNamedParameters());
276    for (final Class<?> np : this.builder.freeParams.keySet()) {
277      try {
278        nps.add((NamedParameterNode<?>) builder.b.getClassHierarchy().getNode(ReflectionUtilities.getFullName(np)));
279      } catch (final NameResolutionException e) {
280        throw new IllegalStateException(e);
281      }
282    }
283    return nps;
284  }
285
286  /**
287   * Replace any \'s in the input string with \\. and any "'s with \".
288   *
289   * @param in
290   * @return
291   */
292  private static String escape(final String in) {
293    // After regexp escaping \\\\ = 1 slash, \\\\\\\\ = 2 slashes.
294
295    // Also, the second args of replaceAll are neither strings nor regexps, and
296    // are instead a special DSL used by Matcher. Therefore, we need to double
297    // escape slashes (4 slashes) and quotes (3 slashes + ") in those strings.
298    // Since we need to write \\ and \", we end up with 8 and 7 slashes,
299    // respectively.
300    return in.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\\\\\"");
301  }
302
303  private static StringBuilder join(final StringBuilder sb, final String sep, final ConstructorArg[] types) {
304    if (types.length > 0) {
305      sb.append(types[0].getType());
306      for (int i = 1; i < types.length; i++) {
307        sb.append(sep).append(types[i].getType());
308      }
309    }
310    return sb;
311  }
312
313  /**
314   * Convert Configuration to a list of strings formatted as "param=value".
315   *
316   * @param c
317   * @return
318   */
319  private static List<String> toConfigurationStringList(final Configuration c) {
320    final ConfigurationImpl conf = (ConfigurationImpl) c;
321    final List<String> l = new ArrayList<>();
322    for (final ClassNode<?> opt : conf.getBoundImplementations()) {
323      l.add(opt.getFullName()
324          + '='
325          + escape(conf.getBoundImplementation(opt).getFullName()));
326    }
327    for (final ClassNode<?> opt : conf.getBoundConstructors()) {
328      l.add(opt.getFullName()
329          + '='
330          + escape(conf.getBoundConstructor(opt).getFullName()));
331    }
332    for (final NamedParameterNode<?> opt : conf.getNamedParameters()) {
333      l.add(opt.getFullName()
334          + '='
335          + escape(conf.getNamedParameter(opt)));
336    }
337    for (final ClassNode<?> cn : conf.getLegacyConstructors()) {
338      final StringBuilder sb = new StringBuilder();
339      join(sb, "-", conf.getLegacyConstructor(cn).getArgs());
340      l.add(cn.getFullName()
341          + escape('='
342              + ConfigurationBuilderImpl.INIT
343              + '('
344              + sb.toString()
345              + ')'
346      ));
347    }
348    for (final NamedParameterNode<Set<?>> key : conf.getBoundSets()) {
349      for (final Object value : conf.getBoundSet(key)) {
350        final String val;
351        if (value instanceof String) {
352          val = (String) value;
353        } else if (value instanceof Node) {
354          val = ((Node) value).getFullName();
355        } else {
356          throw new IllegalStateException();
357        }
358        l.add(key.getFullName() + '=' + escape(val));
359      }
360    }
361    return l;
362  }
363
364  public List<Entry<String, String>> toStringPairs() {
365    final List<Entry<String, String>> ret = new ArrayList<>();
366    class MyEntry implements Entry<String, String> {
367      private final String k;
368      private final String v;
369
370      MyEntry(final String k, final String v) {
371        this.k = k;
372        this.v = v;
373      }
374
375      @Override
376      public String getKey() {
377        return k;
378      }
379
380      @Override
381      public String getValue() {
382        return v;
383      }
384
385      @Override
386      public String setValue(final String value) {
387        throw new UnsupportedOperationException();
388      }
389
390    }
391    for (final Class<?> c : this.builder.freeParams.keySet()) {
392      ret.add(new MyEntry(ReflectionUtilities.getFullName(c),
393          this.builder.map.get(this.builder.freeParams.get(c)).getName()));
394    }
395    for (final Class<?> c : this.builder.freeImpls.keySet()) {
396      ret.add(new MyEntry(ReflectionUtilities.getFullName(c),
397          this.builder.map.get(this.builder.freeImpls.get(c)).getName()));
398    }
399    for (final String s : toConfigurationStringList(builder.b.build())) {
400      final String[] tok = s.split("=", 2);
401      ret.add(new MyEntry(tok[0], tok[1]));
402    }
403
404    return ret;
405  }
406
407  public String toPrettyString() {
408    final StringBuilder sb = new StringBuilder();
409
410    for (final Entry<String, String> l : toStringPairs()) {
411      sb.append(l.getKey() + "=" + l.getValue() + "\n");
412    }
413    return sb.toString();
414  }
415
416  public void assertStaticClean() throws ClassHierarchyException {
417    if (!(
418        setImpls.isEmpty() &&
419            setImplSets.isEmpty() &&
420            setLateImplSets.isEmpty() &&
421            setParamSets.isEmpty() &&
422            setLateImpls.isEmpty() &&
423            setParams.isEmpty() &&
424            setImplLists.isEmpty() &&
425            setParamLists.isEmpty()
426      )) {
427      throw new ClassHierarchyException("Detected statically set ConfigurationModule Parameter / Implementation.  " +
428          "set() should only be used dynamically.  Use bind...() instead.");
429    }
430  }
431
432  public ConfigurationModuleBuilder getBuilder() {
433    return builder;
434  }
435}