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.implementation;
020
021import org.apache.reef.tang.*;
022import org.apache.reef.tang.exceptions.BindException;
023import org.apache.reef.tang.exceptions.NameResolutionException;
024import org.apache.reef.tang.exceptions.ParseException;
025import org.apache.reef.tang.implementation.java.ClassHierarchyImpl;
026import org.apache.reef.tang.types.*;
027import org.apache.reef.tang.util.MonotonicMultiMap;
028import org.apache.reef.tang.util.TracingMonotonicMap;
029import org.apache.reef.tang.util.TracingMonotonicTreeMap;
030
031import java.net.URL;
032import java.util.List;
033import java.util.Map;
034import java.util.Map.Entry;
035import java.util.Set;
036
037public class ConfigurationBuilderImpl implements ConfigurationBuilder {
038  public static final String IMPORT = "import";
039  public static final String INIT = "<init>";
040  protected final TracingMonotonicMap<ClassNode<?>, ClassNode<?>> boundImpls = new TracingMonotonicTreeMap<>();
041  protected final TracingMonotonicMap<ClassNode<?>, ClassNode<? extends ExternalConstructor<?>>> boundConstructors =
042      new TracingMonotonicTreeMap<>();
043  protected final Map<NamedParameterNode<?>, String> namedParameters = new TracingMonotonicTreeMap<>();
044  protected final Map<ClassNode<?>, ConstructorDef<?>> legacyConstructors = new TracingMonotonicTreeMap<>();
045  protected final MonotonicMultiMap<NamedParameterNode<Set<?>>, Object> boundSetEntries = new MonotonicMultiMap<>();
046  protected final TracingMonotonicMap<NamedParameterNode<List<?>>, List<Object>> boundLists =
047      new TracingMonotonicTreeMap<>();
048  // TODO: None of these should be public! - Move to configurationBuilder. Have
049  // that wrap itself
050  // in a sane Configuration interface...
051  // TODO: Should be final again!
052  private ClassHierarchy namespace;
053
054  protected ConfigurationBuilderImpl() {
055    this.namespace = Tang.Factory.getTang().getDefaultClassHierarchy();
056  }
057
058  protected ConfigurationBuilderImpl(final ClassHierarchy namespace) {
059    this.namespace = namespace;
060  }
061
062  protected ConfigurationBuilderImpl(final URL[] jars,
063                                     final Configuration[] confs,
064                                     final Class<? extends ExternalConstructor<?>>[] parsers)
065      throws BindException {
066    this.namespace = Tang.Factory.getTang().getDefaultClassHierarchy(jars, parsers);
067    for (final Configuration tc : confs) {
068      addConfiguration((ConfigurationImpl) tc);
069    }
070  }
071
072  protected ConfigurationBuilderImpl(final ConfigurationBuilderImpl t) {
073    this.namespace = t.getClassHierarchy();
074    try {
075      addConfiguration(t.getClassHierarchy(), t);
076    } catch (final BindException e) {
077      throw new IllegalStateException("Could not copy builder", e);
078    }
079  }
080
081  @SuppressWarnings("unchecked")
082  protected ConfigurationBuilderImpl(final URL... jars) throws BindException {
083    this(jars, new Configuration[0], new Class[0]);
084  }
085
086  @SuppressWarnings("unchecked")
087  protected ConfigurationBuilderImpl(final Configuration... confs) throws BindException {
088    this(new URL[0], confs, new Class[0]);
089  }
090
091  @Override
092  public void addConfiguration(final Configuration conf) throws BindException {
093    // XXX remove cast!
094    addConfiguration(conf.getClassHierarchy(), ((ConfigurationImpl) conf).getBuilder());
095  }
096
097  @SuppressWarnings("unchecked")
098  private <T> void addConfiguration(final ClassHierarchy ns, final ConfigurationBuilderImpl builder)
099      throws BindException {
100    namespace = namespace.merge(ns);
101    if (namespace instanceof ClassHierarchyImpl || builder.namespace instanceof ClassHierarchyImpl) {
102      if (namespace instanceof ClassHierarchyImpl && builder.namespace instanceof ClassHierarchyImpl) {
103        ((ClassHierarchyImpl) namespace).getParameterParser()
104            .mergeIn(((ClassHierarchyImpl) builder.namespace).getParameterParser());
105      } else {
106        throw new IllegalArgumentException("Attempt to merge Java and non-Java class hierarchy!  Not supported.");
107      }
108    }
109
110    for (final ClassNode<?> cn : builder.boundImpls.keySet()) {
111      bind(cn.getFullName(), builder.boundImpls.get(cn).getFullName());
112    }
113    for (final ClassNode<?> cn : builder.boundConstructors.keySet()) {
114      bind(cn.getFullName(), builder.boundConstructors.get(cn).getFullName());
115    }
116    // The namedParameters set contains the strings that can be used to
117    // instantiate new
118    // named parameter instances. Create new ones where we can.
119    for (final NamedParameterNode<?> np : builder.namedParameters.keySet()) {
120      bind(np.getFullName(), builder.namedParameters.get(np));
121    }
122    for (final ClassNode<?> cn : builder.legacyConstructors.keySet()) {
123      registerLegacyConstructor(cn, builder.legacyConstructors.get(cn)
124          .getArgs());
125    }
126    for (final Entry<NamedParameterNode<Set<?>>, Object> e : builder.boundSetEntries) {
127      final String name = ((NamedParameterNode<Set<T>>) (NamedParameterNode<?>) e.getKey()).getFullName();
128      if (e.getValue() instanceof Node) {
129        bindSetEntry(name, (Node) e.getValue());
130      } else if (e.getValue() instanceof String) {
131        bindSetEntry(name, (String) e.getValue());
132      } else {
133        throw new IllegalStateException("The value of the named parameter node in boundSetEntries"
134                + " is neither String nor Node. The actual type is  " + e.getValue().getClass());
135      }
136    }
137    // The boundLists set contains bound lists with their target NamedParameters
138    for (final NamedParameterNode<List<?>> np : builder.boundLists.keySet()) {
139      bindList(np.getFullName(), builder.boundLists.get(np));
140    }
141  }
142
143  @Override
144  public ClassHierarchy getClassHierarchy() {
145    return namespace;
146  }
147
148  @Override
149  public void registerLegacyConstructor(final ClassNode<?> c,
150                                        final ConstructorArg... args) throws BindException {
151    final String[] cn = new String[args.length];
152    for (int i = 0; i < args.length; i++) {
153      cn[i] = args[i].getType();
154    }
155    registerLegacyConstructor(c.getFullName(), cn);
156  }
157
158  @Override
159  public void registerLegacyConstructor(final String s, final String... args)
160      throws BindException {
161    final ClassNode<?> cn = (ClassNode<?>) namespace.getNode(s);
162    final ClassNode<?>[] cnArgs = new ClassNode[args.length];
163    for (int i = 0; i < args.length; i++) {
164      cnArgs[i] = (ClassNode<?>) namespace.getNode(args[i]);
165    }
166    registerLegacyConstructor(cn, cnArgs);
167  }
168
169  @Override
170  public void registerLegacyConstructor(final ClassNode<?> cn,
171                                        final ClassNode<?>... args) throws BindException {
172    legacyConstructors.put(cn, cn.getConstructorDef(args));
173  }
174
175  @Override
176  public <T> void bind(final String key, final String value) throws BindException {
177    final Node n = namespace.getNode(key);
178    if (n instanceof NamedParameterNode) {
179      bindParameter((NamedParameterNode<?>) n, value);
180    } else if (n instanceof ClassNode) {
181      final Node m = namespace.getNode(value);
182      bind((ClassNode<?>) n, (ClassNode<?>) m);
183    } else {
184      throw new IllegalStateException("getNode() returned " + n +
185          " which is neither a ClassNode nor a NamedParameterNode");
186    }
187  }
188
189  @SuppressWarnings({"unchecked", "rawtypes"})
190  public void bind(final Node key, final Node value) throws BindException {
191    if (key instanceof NamedParameterNode) {
192      bindParameter((NamedParameterNode<?>) key, value.getFullName());
193    } else if (key instanceof ClassNode) {
194      final ClassNode<?> k = (ClassNode<?>) key;
195      if (value instanceof ClassNode) {
196        final ClassNode<?> val = (ClassNode<?>) value;
197        if (val.isExternalConstructor() && !k.isExternalConstructor()) {
198          bindConstructor(k, (ClassNode) val);
199        } else {
200          bindImplementation(k, (ClassNode) val);
201        }
202      }
203    }
204  }
205
206  public <T> void bindImplementation(final ClassNode<T> n, final ClassNode<? extends T> m)
207      throws BindException {
208    if (namespace.isImplementation(n, m)) {
209      boundImpls.put(n, m);
210    } else {
211      throw new IllegalArgumentException("Class" + m + " does not extend " + n);
212    }
213  }
214
215  @SuppressWarnings({"unchecked", "rawtypes"})
216  public <T> void bindParameter(final NamedParameterNode<T> name, final String value)
217      throws BindException {
218    /* Parse and discard value; this is just for type checking */
219    if (namespace instanceof JavaClassHierarchy) {
220      ((JavaClassHierarchy) namespace).parse(name, value);
221    }
222    if (name.isSet()) {
223      bindSetEntry((NamedParameterNode) name, value);
224    } else {
225      namedParameters.put(name, value);
226    }
227  }
228
229  @SuppressWarnings("unchecked")
230  @Override
231  public void bindSetEntry(final String iface, final String impl)
232      throws BindException {
233    boundSetEntries.put((NamedParameterNode<Set<?>>) namespace.getNode(iface), impl);
234  }
235
236  @SuppressWarnings("unchecked")
237  @Override
238  public void bindSetEntry(final String iface, final Node impl)
239      throws BindException {
240    boundSetEntries.put((NamedParameterNode<Set<?>>) namespace.getNode(iface), impl);
241  }
242
243  @SuppressWarnings("unchecked")
244  @Override
245  public <T> void bindSetEntry(final NamedParameterNode<Set<T>> iface, final String impl)
246      throws BindException {
247    if (namespace instanceof ClassHierarchyImpl) {
248      final JavaClassHierarchy javanamespace = (ClassHierarchyImpl) namespace;
249      try {
250        javanamespace.parse(iface, impl);
251      } catch (final ParseException e) {
252        throw new IllegalStateException("Could not parse " + impl + " which was passed to " + iface, e);
253      }
254    }
255    boundSetEntries.put((NamedParameterNode<Set<?>>) (NamedParameterNode<?>) iface, impl);
256  }
257
258  @SuppressWarnings("unchecked")
259  @Override
260  public <T> void bindSetEntry(final NamedParameterNode<Set<T>> iface, final Node impl)
261      throws BindException {
262    boundSetEntries.put((NamedParameterNode<Set<?>>) (NamedParameterNode<?>) iface, impl);
263  }
264
265  @SuppressWarnings("unchecked")
266  @Override
267  public <T> void bindList(final NamedParameterNode<List<T>> iface, final List implList) {
268    // Check parsability of list items
269    for (final Object item : implList) {
270      if (item instanceof String) {
271        final JavaClassHierarchy javanamespace = (ClassHierarchyImpl) namespace;
272        try {
273          // Just for parsability checking.
274          javanamespace.parse(iface, (String) item);
275        } catch (final ParseException e) {
276          throw new IllegalStateException("Could not parse " + item + " which was passed to " + iface, e);
277        }
278      }
279    }
280    boundLists.put((NamedParameterNode<List<?>>) (NamedParameterNode<?>) iface, implList);
281  }
282
283  @SuppressWarnings("unchecked")
284  @Override
285  public void bindList(final String iface, final List implList) {
286    final NamedParameterNode<List<?>> ifaceNode = (NamedParameterNode<List<?>>) namespace.getNode(iface);
287    // Check parsability of list items
288    for (final Object item : implList) {
289      if (item instanceof String) {
290        final JavaClassHierarchy javanamespace = (ClassHierarchyImpl) namespace;
291        try {
292          // Just for parsability checking.
293          javanamespace.parse(ifaceNode, (String) item);
294        } catch (final ParseException e) {
295          throw new IllegalStateException("Could not parse " + item + " which was passed to " + iface, e);
296        }
297      }
298    }
299    boundLists.put(ifaceNode, implList);
300  }
301
302  @Override
303  public <T> void bindConstructor(final ClassNode<T> k,
304                                  final ClassNode<? extends ExternalConstructor<? extends T>> v) {
305    boundConstructors.put(k, v);
306  }
307
308  @Override
309  public ConfigurationImpl build() {
310    return new ConfigurationImpl(new ConfigurationBuilderImpl(this));
311  }
312
313  @Override
314  public String classPrettyDefaultString(final String longName) throws NameResolutionException {
315    final NamedParameterNode<?> param = (NamedParameterNode<?>) namespace
316        .getNode(longName);
317    return param.getSimpleArgName() + "=" + join(",", param.getDefaultInstanceAsStrings());
318  }
319
320  private String join(final String sep, final String[] s) {
321    if (s.length == 0) {
322      return null;
323    } else {
324      final StringBuilder sb = new StringBuilder(s[0]);
325      for (int i = 1; i < s.length; i++) {
326        sb.append(sep);
327        sb.append(s[i]);
328      }
329      return sb.toString();
330    }
331  }
332
333  @Override
334  public String classPrettyDescriptionString(final String fullName)
335      throws NameResolutionException {
336    final NamedParameterNode<?> param = (NamedParameterNode<?>) namespace
337        .getNode(fullName);
338    return param.getDocumentation() + "\n" + param.getFullName();
339  }
340
341  @Override
342  public boolean equals(final Object o) {
343    if (this == o) {
344      return true;
345    }
346    if (o == null || getClass() != o.getClass()) {
347      return false;
348    }
349
350    final ConfigurationBuilderImpl that = (ConfigurationBuilderImpl) o;
351
352    if (boundConstructors != null ?
353        !boundConstructors.equals(that.boundConstructors) : that.boundConstructors != null) {
354      return false;
355    }
356    if (boundImpls != null ? !boundImpls.equals(that.boundImpls) : that.boundImpls != null) {
357      return false;
358    }
359    if (boundSetEntries != null ? !boundSetEntries.equals(that.boundSetEntries) : that.boundSetEntries != null) {
360      return false;
361    }
362    if (boundLists != null ? !boundLists.equals(that.boundLists) : that.boundLists != null) {
363      return false;
364    }
365    if (legacyConstructors != null ?
366        !legacyConstructors.equals(that.legacyConstructors) : that.legacyConstructors != null) {
367      return false;
368    }
369    if (namedParameters != null ? !namedParameters.equals(that.namedParameters) : that.namedParameters != null) {
370      return false;
371    }
372
373    return true;
374  }
375
376  @Override
377  public int hashCode() {
378    int result = boundImpls != null ? boundImpls.hashCode() : 0;
379    result = 31 * result + (boundConstructors != null ? boundConstructors.hashCode() : 0);
380    result = 31 * result + (namedParameters != null ? namedParameters.hashCode() : 0);
381    result = 31 * result + (legacyConstructors != null ? legacyConstructors.hashCode() : 0);
382    result = 31 * result + (boundSetEntries != null ? boundSetEntries.hashCode() : 0);
383    result = 31 * result + (boundLists != null ? boundLists.hashCode() : 0);
384    return result;
385  }
386}