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.implementation.java;
020
021import org.apache.reef.tang.*;
022import org.apache.reef.tang.annotations.Name;
023import org.apache.reef.tang.exceptions.*;
024import org.apache.reef.tang.implementation.*;
025import org.apache.reef.tang.types.*;
026import org.apache.reef.tang.util.MonotonicHashSet;
027import org.apache.reef.tang.util.MonotonicSet;
028import org.apache.reef.tang.util.ReflectionUtilities;
029import org.apache.reef.tang.util.TracingMonotonicTreeMap;
030
031import java.lang.reflect.InvocationTargetException;
032import java.util.*;
033
034public class InjectorImpl implements Injector {
035  static final InjectionPlan<?> BUILDING = new InjectionPlan<Object>(null) {
036    @Override
037    public int getNumAlternatives() {
038      throw new UnsupportedOperationException();
039    }
040
041    @Override
042    public String toString() {
043      return "BUILDING INJECTION PLAN";
044    }
045
046    @Override
047    public boolean isAmbiguous() {
048      throw new UnsupportedOperationException();
049    }
050
051    @Override
052    public boolean isInjectable() {
053      throw new UnsupportedOperationException();
054    }
055
056    @Override
057    protected String toAmbiguousInjectString() {
058      throw new UnsupportedOperationException();
059    }
060
061    @Override
062    protected String toInfeasibleInjectString() {
063      throw new UnsupportedOperationException();
064    }
065
066    @Override
067    protected boolean isInfeasibleLeaf() {
068      throw new UnsupportedOperationException();
069    }
070
071    @Override
072    public String toShallowString() {
073      throw new UnsupportedOperationException();
074    }
075
076  };
077  final Map<ClassNode<?>, Object> instances = new TracingMonotonicTreeMap<>();
078  final Map<NamedParameterNode<?>, Object> namedParameterInstances = new TracingMonotonicTreeMap<>();
079  private final Configuration c;
080  private final ClassHierarchy namespace;
081  private final JavaClassHierarchy javaNamespace;
082  private final Set<InjectionFuture<?>> pendingFutures = new HashSet<>();
083  private boolean concurrentModificationGuard = false;
084  private Aspect aspect;
085
086  public InjectorImpl(Configuration c) throws BindException {
087    this.c = c;
088    this.namespace = c.getClassHierarchy();
089    this.javaNamespace = (ClassHierarchyImpl) this.namespace;
090  }
091
092  private static InjectorImpl copy(InjectorImpl old,
093                                   Configuration... configurations) throws BindException {
094    final InjectorImpl i;
095    try {
096      final ConfigurationBuilder cb = old.c.newBuilder();
097      for (Configuration c : configurations) {
098        cb.addConfiguration(c);
099      }
100      i = new InjectorImpl(cb.build());
101    } catch (BindException e) {
102      throw new IllegalStateException(
103          "Unexpected error copying configuration!", e);
104    }
105    for (ClassNode<?> cn : old.instances.keySet()) {
106      if (cn.getFullName().equals(ReflectionUtilities.getFullName(Injector.class))
107          || cn.getFullName().equals(ReflectionUtilities.getFullName(InjectorImpl.class))) {
108        // This would imply that we're treating injector as a singleton somewhere.  It should be copied fresh each time.
109        throw new IllegalStateException();
110      }
111      try {
112        ClassNode<?> new_cn = (ClassNode<?>) i.namespace.getNode(cn
113            .getFullName());
114        i.instances.put(new_cn, old.instances.get(cn));
115      } catch (BindException e) {
116        throw new IllegalStateException("Could not resolve name "
117            + cn.getFullName() + " when copying injector");
118      }
119    }
120    // Copy references to the remaining (which must have been set with
121    // bindVolatileParameter())
122    for (NamedParameterNode<?> np : old.namedParameterInstances.keySet()) {
123      // if (!builder.namedParameters.containsKey(np)) {
124      Object o = old.namedParameterInstances.get(np);
125      NamedParameterNode<?> new_np = (NamedParameterNode<?>) i.namespace
126          .getNode(np.getFullName());
127      i.namedParameterInstances.put(new_np, o);
128    }
129    // Fork the aspect (if any)
130    if (old.aspect != null) {
131      i.bindAspect(old.aspect.createChildAspect());
132    }
133    return i;
134  }
135
136  private void assertNotConcurrent() {
137    if (concurrentModificationGuard) {
138      throw new ConcurrentModificationException("Detected attempt to use Injector from within an injected constructor!");
139    }
140  }
141
142  @SuppressWarnings("unchecked")
143  private <T> T getCachedInstance(ClassNode<T> cn) {
144    if (cn.getFullName().equals("org.apache.reef.tang.Injector")) {
145      return (T) this;// TODO: We should be insisting on injection futures here! .forkInjector();
146    } else {
147      T t = (T) instances.get(cn);
148      if (t instanceof InjectionFuture) {
149        throw new IllegalStateException("Found an injection future in getCachedInstance: " + cn);
150      }
151      return t;
152    }
153  }
154
155  /**
156   * Produce a list of "interesting" constructors from a set of ClassNodes.
157   * <p/>
158   * Tang Constructors expose a isMoreSpecificThan function that embeds all
159   * a skyline query over the lattices.  Precisely:
160   * <p/>
161   * Let candidateConstructors be the union of all constructors defined by
162   * ClassNodes in candidateImplementations.
163   * <p/>
164   * This function returns a set called filteredImplementations, defined as
165   * follows:
166   * <p/>
167   * For each member f of filteredConstructors, there does not exist
168   * a g in candidateConstructors s.t. g.isMoreSpecificThan(f).
169   */
170  private <T> List<InjectionPlan<T>> filterCandidateConstructors(
171      final List<ClassNode<T>> candidateImplementations,
172      final Map<Node, InjectionPlan<?>> memo) {
173
174    final List<InjectionPlan<T>> sub_ips = new ArrayList<>();
175    for (final ClassNode<T> thisCN : candidateImplementations) {
176      final List<Constructor<T>> constructors = new ArrayList<>();
177      final List<ConstructorDef<T>> constructorList = new ArrayList<>();
178      if (null != c.getLegacyConstructor(thisCN)) {
179        constructorList.add(c.getLegacyConstructor(thisCN));
180      }
181      constructorList
182          .addAll(Arrays.asList(thisCN.getInjectableConstructors()));
183
184      for (ConstructorDef<T> def : constructorList) {
185        final List<InjectionPlan<?>> args = new ArrayList<InjectionPlan<?>>();
186        final ConstructorArg[] defArgs = def.getArgs();
187
188        for (final ConstructorArg arg : defArgs) {
189          if (!arg.isInjectionFuture()) {
190            try {
191              final Node argNode = namespace.getNode(arg.getName());
192              buildInjectionPlan(argNode, memo);
193              args.add(memo.get(argNode));
194            } catch (NameResolutionException e) {
195              throw new IllegalStateException("Detected unresolvable "
196                  + "constructor arg while building injection plan.  "
197                  + "This should have been caught earlier!", e);
198            }
199          } else {
200            try {
201              args.add(new InjectionFuturePlan<>(namespace.getNode(arg
202                  .getName())));
203            } catch (NameResolutionException e) {
204              throw new IllegalStateException("Detected unresolvable "
205                  + "constructor arg while building injection plan.  "
206                  + "This should have been caught earlier!", e);
207            }
208          }
209        }
210        final Constructor<T> constructor = new Constructor<T>(thisCN, def,
211            args.toArray(new InjectionPlan[0]));
212        constructors.add(constructor);
213      }
214      // The constructors are embedded in a lattice defined by
215      // isMoreSpecificThan().  We want to see if, amongst the injectable
216      // plans, there is a unique dominant plan, and select it.
217
218      // First, compute the set of injectable plans.
219      final List<Integer> liveIndices = new ArrayList<>();
220      for (int i = 0; i < constructors.size(); i++) {
221        if (constructors.get(i).getNumAlternatives() > 0) {
222          liveIndices.add(i);
223        }
224      }
225      // Now, do an all-by-all comparison, removing indices that are dominated
226      // by others.
227      for (int i = 0; i < liveIndices.size(); i++) {
228        for (int j = i + 1; j < liveIndices.size(); j++) {
229          final ConstructorDef<T> ci = constructors.get(liveIndices.get(i))
230              .getConstructorDef();
231          final ConstructorDef<T> cj = constructors.get(liveIndices.get(j))
232              .getConstructorDef();
233
234          if (ci.isMoreSpecificThan(cj)) {
235            liveIndices.remove(j);
236            j--;
237          } else if (cj.isMoreSpecificThan(ci)) {
238            liveIndices.remove(i);
239            // Done with this inner loop invocation. Check the new ci.
240            i--;
241            break;
242          }
243        }
244      }
245      if (constructors.size() > 0) {
246        sub_ips.add(wrapInjectionPlans(thisCN, constructors, false,
247            liveIndices.size() == 1 ? liveIndices.get(0) : -1));
248      }
249    }
250    return sub_ips;
251  }
252
253  @SuppressWarnings("unchecked")
254  private <T> InjectionPlan<T> buildClassNodeInjectionPlan(ClassNode<T> cn,
255                                                           T cachedInstance,
256                                                           ClassNode<ExternalConstructor<T>> externalConstructor,
257                                                           ClassNode<T> boundImpl,
258                                                           ClassNode<T> defaultImpl,
259                                                           Map<Node, InjectionPlan<?>> memo) {
260
261    if (cachedInstance != null) {
262      return new JavaInstance<T>(cn, cachedInstance);
263    } else if (externalConstructor != null) {
264      buildInjectionPlan(externalConstructor, memo);
265      return new Subplan<>(cn, 0, (InjectionPlan<T>) memo.get(externalConstructor));
266    } else if (boundImpl != null && !cn.equals(boundImpl)) {
267      // We need to delegate to boundImpl, so recurse.
268      buildInjectionPlan(boundImpl, memo);
269      return new Subplan<>(cn, 0, (InjectionPlan<T>) memo.get(boundImpl));
270    } else if (defaultImpl != null && !cn.equals(defaultImpl)) {
271      buildInjectionPlan(defaultImpl, memo);
272      return new Subplan<>(cn, 0, (InjectionPlan<T>) memo.get(defaultImpl));
273    } else {
274      // if we're here and there isn't a bound impl or a default impl,
275      // then we're bound / defaulted to ourselves, so don't add
276      // other impls to the list of things to consider.
277      final List<ClassNode<T>> candidateImplementations = new ArrayList<>();
278      candidateImplementations.add(cn);
279      final List<InjectionPlan<T>> sub_ips = filterCandidateConstructors(candidateImplementations, memo);
280      if (sub_ips.size() == 1) {
281        return wrapInjectionPlans(cn, sub_ips, false, -1);
282      } else {
283        return wrapInjectionPlans(cn, sub_ips, true, -1);
284      }
285    }
286  }
287
288  @SuppressWarnings("unchecked")
289  private <T> InjectionPlan<T> wrapInjectionPlans(ClassNode<T> infeasibleNode,
290                                                  List<? extends InjectionPlan<T>> list, boolean forceAmbiguous, int selectedIndex) {
291    if (list.size() == 0) {
292      return new Subplan<>(infeasibleNode);
293    } else if ((!forceAmbiguous) && list.size() == 1) {
294      return list.get(0);
295    } else {
296      return new Subplan<>(infeasibleNode, selectedIndex, list.toArray(new InjectionPlan[0]));
297    }
298  }
299
300  /**
301   * Parse the bound value of np.  When possible, this returns a cached instance.
302   *
303   * @return null if np has not been bound.
304   * @throws ParseException
305   */
306  @SuppressWarnings("unchecked")
307  private <T> T parseBoundNamedParameter(NamedParameterNode<T> np) {
308    final T ret;
309
310    @SuppressWarnings("rawtypes")
311    final Set<Object> boundSet = c.getBoundSet((NamedParameterNode) np);
312    if (!boundSet.isEmpty()) {
313      Set<T> ret2 = new MonotonicSet<>();
314      for (Object o : boundSet) {
315        if (o instanceof String) {
316          try {
317            ret2.add(javaNamespace.parse(np, (String) o));
318          } catch (ParseException e) {
319            // Parsability is now pre-checked in bindSet, so it should not be reached!
320            throw new IllegalStateException("Could not parse " + o + " which was passed into " + np + " FIXME: Parsability is not currently checked by bindSetEntry(Node,String)");
321          }
322        } else if (o instanceof Node) {
323          ret2.add((T) o);
324        } else {
325          throw new IllegalStateException("Unexpected object " + o + " in bound set.  Should consist of nodes and strings");
326        }
327      }
328      return (T) ret2;
329    }
330    final List<Object> boundList = c.getBoundList((NamedParameterNode) np);
331    if (boundList != null) {
332      List<T> ret2 = new ArrayList<>();
333      for (Object o : boundList) {
334        if (o instanceof String) {
335          try {
336            ret2.add(javaNamespace.parse(np, (String) o));
337          } catch (ParseException e) {
338            // Parsability is now pre-checked in bindList, so it should not be reached!
339            throw new IllegalStateException("Could not parse " + o + " which was passed into " + np + " FIXME: " +
340                "Parsability is not currently checked by bindList(Node,List)");
341          }
342        } else if (o instanceof Node) {
343          ret2.add((T) o);
344        } else {
345          throw new IllegalStateException("Unexpected object " + o + " in bound list.  Should consist of nodes and " +
346              "strings");
347        }
348      }
349      return (T) ret2;
350    } else if (namedParameterInstances.containsKey(np)) {
351      ret = (T) namedParameterInstances.get(np);
352    } else {
353      final String value = c.getNamedParameter(np);
354      if (value == null) {
355        ret = null;
356      } else {
357        try {
358          ret = javaNamespace.parse(np, value);
359          namedParameterInstances.put(np, ret);
360        } catch (BindException e) {
361          throw new IllegalStateException(
362              "Could not parse pre-validated value", e);
363        }
364      }
365    }
366    return ret;
367  }
368
369  @SuppressWarnings("unchecked")
370  private <T> ClassNode<T> parseDefaultImplementation(ClassNode<T> cn) {
371    if (cn.getDefaultImplementation() != null) {
372      try {
373        return (ClassNode<T>) javaNamespace.getNode(cn.getDefaultImplementation());
374      } catch (ClassCastException | NameResolutionException e) {
375        throw new IllegalStateException("After validation, " + cn + " had a bad default implementation named " + cn.getDefaultImplementation(), e);
376      }
377    } else {
378      return null;
379    }
380  }
381
382  @SuppressWarnings({"unchecked"})
383  private <T> void buildInjectionPlan(final Node n,
384                                      Map<Node, InjectionPlan<?>> memo) {
385    if (memo.containsKey(n)) {
386      if (BUILDING == memo.get(n)) {
387        StringBuilder loopyList = new StringBuilder("[");
388        for (Node node : memo.keySet()) {
389          if (memo.get(node) == BUILDING) {
390            loopyList.append(" " + node.getFullName());
391          }
392        }
393        loopyList.append(" ]");
394        throw new ClassHierarchyException("Detected loopy constructor involving "
395            + loopyList.toString());
396      } else {
397        return;
398      }
399    }
400    memo.put(n, BUILDING);
401    final InjectionPlan<T> ip;
402    if (n instanceof NamedParameterNode) {
403      final NamedParameterNode<T> np = (NamedParameterNode<T>) n;
404
405      final T boundInstance = parseBoundNamedParameter(np);
406      final T defaultInstance = javaNamespace.parseDefaultValue(np);
407      final T instance = boundInstance != null ? boundInstance : defaultInstance;
408
409      if (instance instanceof Node) {
410        buildInjectionPlan((Node) instance, memo);
411        ip = new Subplan<T>(n, 0, (InjectionPlan<T>) memo.get(instance));
412      } else if (instance instanceof Set) {
413        Set<T> entries = (Set<T>) instance;
414        Set<InjectionPlan<T>> plans = new MonotonicHashSet<>();
415        for (T entry : entries) {
416          if (entry instanceof ClassNode) {
417            buildInjectionPlan((ClassNode<?>) entry, memo);
418            plans.add((InjectionPlan<T>) memo.get(entry));
419          } else {
420            plans.add(new JavaInstance<T>(n, entry));
421          }
422
423        }
424        ip = new SetInjectionPlan<T>(n, plans);
425      } else if (instance instanceof List) {
426        List<T> entries = (List<T>) instance;
427        List<InjectionPlan<T>> plans = new ArrayList<>();
428        for (T entry : entries) {
429          if (entry instanceof ClassNode) {
430            buildInjectionPlan((ClassNode<?>) entry, memo);
431            plans.add((InjectionPlan<T>) memo.get(entry));
432          } else {
433            plans.add(new JavaInstance<T>(n, entry));
434          }
435        }
436        ip = new ListInjectionPlan<T>(n, plans);
437      } else {
438        ip = new JavaInstance<T>(np, instance);
439      }
440    } else if (n instanceof ClassNode) {
441      final ClassNode<T> cn = (ClassNode<T>) n;
442
443      // Any (or all) of the next four values might be null; that's fine.
444      final T cached = getCachedInstance(cn);
445      final ClassNode<T> boundImpl = c.getBoundImplementation(cn);
446      final ClassNode<T> defaultImpl = parseDefaultImplementation(cn);
447      final ClassNode<ExternalConstructor<T>> ec = c.getBoundConstructor(cn);
448
449      ip = buildClassNodeInjectionPlan(cn, cached, ec, boundImpl, defaultImpl, memo);
450    } else if (n instanceof PackageNode) {
451      throw new IllegalArgumentException(
452          "Request to instantiate Java package as object");
453    } else {
454      throw new IllegalStateException(
455          "Type hierarchy contained unknown node type!:" + n);
456    }
457    memo.put(n, ip);
458  }
459
460  /**
461   * Return an injection plan for the given class / parameter name.
462   *
463   * @param n The name of an injectable class or interface, or a NamedParameter.
464   * @return
465   * @throws NameResolutionException
466   */
467  public InjectionPlan<?> getInjectionPlan(final Node n) {
468    Map<Node, InjectionPlan<?>> memo = new HashMap<>();
469    buildInjectionPlan(n, memo);
470    return memo.get(n);
471  }
472
473  @Override
474  public InjectionPlan<?> getInjectionPlan(String name) throws NameResolutionException {
475    return getInjectionPlan(namespace.getNode(name));
476  }
477
478  @SuppressWarnings("unchecked")
479  public <T> InjectionPlan<T> getInjectionPlan(Class<T> name) {
480    return (InjectionPlan<T>) getInjectionPlan(javaNamespace.getNode(name));
481  }
482
483  @Override
484  public boolean isInjectable(String name) throws NameResolutionException {
485    return getInjectionPlan(namespace.getNode(name)).isInjectable();
486  }
487
488  @Override
489  public boolean isInjectable(Class<?> clazz) {
490    try {
491      return isInjectable(ReflectionUtilities.getFullName(clazz));
492    } catch (NameResolutionException e) {
493      throw new IllegalStateException("Could not round trip " + clazz + " through ClassHierarchy", e);
494    }
495  }
496
497  @Override
498  public boolean isParameterSet(String name) throws NameResolutionException {
499    InjectionPlan<?> p = getInjectionPlan(namespace.getNode(name));
500    return p.isInjectable();
501  }
502
503  @Override
504  public boolean isParameterSet(Class<? extends Name<?>> name)
505      throws BindException {
506    return isParameterSet(name.getName());
507  }
508
509  private <U> U getInstance(Node n) throws InjectionException {
510    assertNotConcurrent();
511    @SuppressWarnings("unchecked")
512    InjectionPlan<U> plan = (InjectionPlan<U>) getInjectionPlan(n);
513    U u = (U) injectFromPlan(plan);
514
515    while (!pendingFutures.isEmpty()) {
516      Iterator<InjectionFuture<?>> i = pendingFutures.iterator();
517      InjectionFuture<?> f = i.next();
518      pendingFutures.remove(f);
519      f.get();
520    }
521    return u;
522  }
523
524  @Override
525  public <U> U getInstance(Class<U> clazz) throws InjectionException {
526    if (Name.class.isAssignableFrom(clazz)) {
527      throw new InjectionException("getInstance() called on Name "
528          + ReflectionUtilities.getFullName(clazz)
529          + " Did you mean to call getNamedInstance() instead?");
530    }
531    return getInstance(javaNamespace.getNode(clazz));
532  }
533
534  @SuppressWarnings("unchecked")
535  @Override
536  public <U> U getInstance(String clazz) throws InjectionException, NameResolutionException {
537    return (U) getInstance(namespace.getNode(clazz));
538  }
539
540  @Override
541  @SuppressWarnings("unchecked")
542  public <T> T getNamedInstance(Class<? extends Name<T>> clazz)
543      throws InjectionException {
544    return (T) getInstance(javaNamespace.getNode(clazz));
545  }
546
547  public <T> T getNamedParameter(Class<? extends Name<T>> clazz)
548      throws InjectionException {
549    return getNamedInstance(clazz);
550  }
551
552  private <T> java.lang.reflect.Constructor<T> getConstructor(
553      ConstructorDef<T> constructor) throws ClassNotFoundException,
554      NoSuchMethodException, SecurityException {
555    @SuppressWarnings("unchecked")
556    Class<T> clazz = (Class<T>) javaNamespace.classForName(constructor
557        .getClassName());
558    ConstructorArg[] args = constructor.getArgs();
559    Class<?> parameterTypes[] = new Class[args.length];
560    for (int i = 0; i < args.length; i++) {
561      if (args[i].isInjectionFuture()) {
562        parameterTypes[i] = InjectionFuture.class;
563      } else {
564        parameterTypes[i] = javaNamespace.classForName(args[i].getType());
565      }
566    }
567    java.lang.reflect.Constructor<T> cons = clazz
568        .getDeclaredConstructor(parameterTypes);
569    cons.setAccessible(true);
570    return cons;
571  }
572
573  /**
574   * This gets really nasty now that constructors can invoke operations on us.
575   * The upshot is that we should check to see if instances have been
576   * registered by callees after each recursive invocation of injectFromPlan or
577   * constructor invocations. The error handling currently bails if the thing we
578   * just instantiated should be discarded.
579   * <p/>
580   * This could happen if (for instance), a constructor did a
581   * bindVolatileInstance of its own class to an instance, or somehow triggered
582   * an injection of itself with a different plan (an injection of itself with
583   * the same plan would lead to an infinite recursion, so it's not really our
584   * problem).
585   *
586   * @param plan
587   * @return
588   * @throws InjectionException
589   */
590  @SuppressWarnings("unchecked")
591  private <T> T injectFromPlan(InjectionPlan<T> plan) throws InjectionException {
592
593    if (!plan.isFeasible()) {
594      throw new InjectionException("Cannot inject " + plan.getNode().getFullName() + ": "
595          + plan.toCantInjectString());
596    }
597    if (plan.isAmbiguous()) {
598      throw new InjectionException("Cannot inject " + plan.getNode().getFullName() + " "
599          + plan.toCantInjectString());
600    }
601    if (plan instanceof InjectionFuturePlan) {
602      InjectionFuturePlan<T> fut = (InjectionFuturePlan<T>) plan;
603      final String key = fut.getNode().getFullName();
604      try {
605        InjectionFuture<?> ret = new InjectionFuture<>(this, javaNamespace.classForName(fut.getNode().getFullName()));
606        pendingFutures.add(ret);
607        return (T) ret;
608      } catch (ClassNotFoundException e) {
609        throw new InjectionException("Could not get class for " + key);
610      }
611    } else if (plan.getNode() instanceof ClassNode && null != getCachedInstance((ClassNode<T>) plan.getNode())) {
612      return getCachedInstance((ClassNode<T>) plan.getNode());
613    } else if (plan instanceof JavaInstance) {
614      // TODO: Must be named parameter node.  Check.
615//      throw new IllegalStateException("Instance from plan not in Injector's set of instances?!?");
616      return ((JavaInstance<T>) plan).instance;
617    } else if (plan instanceof Constructor) {
618      final Constructor<T> constructor = (Constructor<T>) plan;
619      final Object[] args = new Object[constructor.getArgs().length];
620      final InjectionPlan<?>[] argPlans = constructor.getArgs();
621
622      for (int i = 0; i < argPlans.length; i++) {
623        args[i] = injectFromPlan(argPlans[i]);
624      }
625      try {
626        concurrentModificationGuard = true;
627        T ret;
628        try {
629          ConstructorDef<T> def = (ConstructorDef<T>) constructor.getConstructorDef();
630          java.lang.reflect.Constructor<T> c = getConstructor(def);
631
632          if (aspect != null) {
633            ret = aspect.inject(def, c, args);
634          } else {
635            ret = c.newInstance(args);
636          }
637        } catch (IllegalArgumentException e) {
638          StringBuilder sb = new StringBuilder("Internal Tang error?  Could not call constructor " + constructor.getConstructorDef() + " with arguments [");
639          for (Object o : args) {
640            sb.append("\n\t" + o);
641          }
642          sb.append("]");
643          throw new IllegalStateException(sb.toString(), e);
644        }
645        if (ret instanceof ExternalConstructor) {
646          ret = ((ExternalConstructor<T>) ret).newInstance();
647        }
648        instances.put(constructor.getNode(), ret);
649        return ret;
650      } catch (ReflectiveOperationException e) {
651        throw new InjectionException("Could not invoke constructor: " + plan, e instanceof InvocationTargetException ? e.getCause() : e);
652      } finally {
653        concurrentModificationGuard = false;
654      }
655    } else if (plan instanceof Subplan) {
656      Subplan<T> ambiguous = (Subplan<T>) plan;
657      return injectFromPlan(ambiguous.getDelegatedPlan());
658    } else if (plan instanceof SetInjectionPlan) {
659      SetInjectionPlan<T> setPlan = (SetInjectionPlan<T>) plan;
660      Set<T> ret = new MonotonicHashSet<>();
661      for (InjectionPlan<T> subplan : setPlan.getEntryPlans()) {
662        ret.add(injectFromPlan(subplan));
663      }
664      return (T) ret;
665    } else if (plan instanceof ListInjectionPlan) {
666      ListInjectionPlan<T> listPlan = (ListInjectionPlan<T>) plan;
667      List<T> ret = new ArrayList<>();
668      for (InjectionPlan<T> subplan : listPlan.getEntryPlans()) {
669        ret.add(injectFromPlan(subplan));
670      }
671      return (T) ret;
672    } else {
673      throw new IllegalStateException("Unknown plan type: " + plan);
674    }
675  }
676
677  @Override
678  public <T> void bindVolatileInstance(Class<T> c, T o) throws BindException {
679    bindVolatileInstanceNoCopy(c, o);
680  }
681
682  @Override
683  public <T> void bindVolatileParameter(Class<? extends Name<T>> c, T o)
684      throws BindException {
685    bindVolatileParameterNoCopy(c, o);
686  }
687
688  <T> void bindVolatileInstanceNoCopy(Class<T> c, T o) throws BindException {
689    assertNotConcurrent();
690    Node n = javaNamespace.getNode(c);
691    if (n instanceof ClassNode) {
692      ClassNode<?> cn = (ClassNode<?>) n;
693      Object old = getCachedInstance(cn);
694      if (old != null) {
695        throw new BindException("Attempt to re-bind instance.  Old value was "
696            + old + " new value is " + o);
697      }
698      instances.put(cn, o);
699    } else {
700      throw new IllegalArgumentException("Expected Class but got " + c
701          + " (probably a named parameter).");
702    }
703  }
704
705  <T> void bindVolatileParameterNoCopy(Class<? extends Name<T>> c, T o)
706      throws BindException {
707    Node n = javaNamespace.getNode(c);
708    if (n instanceof NamedParameterNode) {
709      NamedParameterNode<?> np = (NamedParameterNode<?>) n;
710      Object old = this.c.getNamedParameter(np);
711      if (old != null) {
712        // XXX need to get the binding site here!
713        throw new BindException(
714            "Attempt to re-bind named parameter " + ReflectionUtilities.getFullName(c) + ".  Old value was [" + old
715                + "] new value is [" + o + "]");
716      }
717      try {
718        namedParameterInstances.put(np, o);
719      } catch (IllegalArgumentException e) {
720        throw new BindException(
721            "Attempt to re-bind named parameter " + ReflectionUtilities.getFullName(c) + ".  Old value was [" + old
722                + "] new value is [" + o + "]");
723
724      }
725    } else {
726      throw new IllegalArgumentException("Expected Name, got " + c
727          + " (probably a class)");
728    }
729  }
730
731  @Override
732  public Injector forkInjector() {
733    try {
734      return forkInjector(new Configuration[0]);
735    } catch (BindException e) {
736      throw new IllegalStateException(e);
737    }
738  }
739
740  @Override
741  public Injector forkInjector(Configuration... configurations)
742      throws BindException {
743    InjectorImpl ret;
744    ret = copy(this, configurations);
745    return ret;
746  }
747
748  @Override
749  public <T> void bindAspect(Aspect a) throws BindException {
750    if (aspect != null) {
751      throw new BindException("Attempt to re-bind aspect! old=" + aspect + " new=" + a);
752    }
753    aspect = a;
754  }
755
756  @Override
757  public Aspect getAspect() {
758    return aspect;
759  }
760}