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