3030
3131import java .lang .reflect .Field ;
3232import java .lang .reflect .Modifier ;
33+ import java .lang .reflect .ParameterizedType ;
3334import java .lang .reflect .Type ;
3435import java .lang .reflect .TypeVariable ;
3536import java .util .ArrayList ;
3637import java .util .Arrays ;
38+ import java .util .Collections ;
3739import java .util .EnumSet ;
3840import java .util .HashMap ;
3941import java .util .Iterator ;
4042import java .util .LinkedList ;
4143import java .util .List ;
4244import java .util .Map ;
4345import java .util .Map .Entry ;
46+ import java .util .function .Function ;
4447import java .util .stream .Collectors ;
4548
4649import org .scijava .InstantiableException ;
4750import org .scijava .log .LogService ;
51+ import org .scijava .ops .adapt .AdaptedOp ;
4852import org .scijava .ops .core .Op ;
4953import org .scijava .ops .core .OpCollection ;
5054import org .scijava .ops .matcher .DefaultOpMatcher ;
55+ import org .scijava .ops .matcher .MatchingUtils ;
5156import org .scijava .ops .matcher .OpCandidate ;
5257import org .scijava .ops .matcher .OpClassInfo ;
5358import org .scijava .ops .matcher .OpFieldInfo ;
5459import org .scijava .ops .matcher .OpInfo ;
5560import org .scijava .ops .matcher .OpMatcher ;
5661import org .scijava .ops .matcher .OpMatchingException ;
5762import org .scijava .ops .matcher .OpRef ;
58- import org .scijava .ops .transform .DefaultOpTransformationMatcher ;
59- import org .scijava .ops .transform .OpTransformationCandidate ;
60- import org .scijava .ops .transform .OpTransformationException ;
61- import org .scijava .ops .transform .OpTransformationMatcher ;
62- import org .scijava .ops .transform .OpTransformer ;
6363import org .scijava .ops .types .Nil ;
6464import org .scijava .ops .types .TypeService ;
6565import org .scijava .ops .util .OpWrapper ;
@@ -93,8 +93,6 @@ public class OpService extends AbstractService implements SciJavaService, OpEnvi
9393 @ Parameter
9494 private LogService log ;
9595
96- private OpTransformationMatcher transformationMatcher ;
97-
9896 @ Parameter
9997 private TypeService typeService ;
10098
@@ -109,8 +107,6 @@ public class OpService extends AbstractService implements SciJavaService, OpEnvi
109107 */
110108 private Map <String , List <OpInfo >> opCache ;
111109
112- private List <OpTransformer > transformerIndex ;
113-
114110 private Map <Class <?>, OpWrapper <?>> wrappers ;
115111
116112 private void initOpCache () {
@@ -171,10 +167,6 @@ private void addToOpIndex(final OpInfo opInfo, final String opNames) {
171167 }
172168 }
173169
174- public synchronized void initTransformerIndex () {
175- transformerIndex = pluginService .createInstancesOfType (OpTransformer .class );
176- }
177-
178170 @ Override
179171 public Iterable <OpInfo > infos () {
180172 if (opCache == null ) {
@@ -204,20 +196,6 @@ private OpMatcher getOpMatcher() {
204196 return opMatcher ;
205197 }
206198
207- private synchronized List <OpTransformer > getTransformerIndex () {
208- if (transformerIndex == null ) {
209- initTransformerIndex ();
210- }
211- return transformerIndex ;
212- }
213-
214- private OpTransformationMatcher getTransformationMatcher () {
215- if (transformationMatcher == null ) {
216- transformationMatcher = new DefaultOpTransformationMatcher (getOpMatcher ());
217- }
218- return transformationMatcher ;
219- }
220-
221199 /**
222200 * Attempts to inject {@link OpDependency} annotated fields of the specified
223201 * object by looking for Ops matching the field type and the name specified in
@@ -242,7 +220,7 @@ private List<Object> resolveOpDependencies(OpCandidate op) throws OpMatchingExce
242220 + "method inputs and outputs of Op dependency field: " + dependency .getKey ());
243221 }
244222 try {
245- resolvedDependencies .add (findOpInstance (dependencyName , inferredRef ));
223+ resolvedDependencies .add (findOpInstance (dependencyName , inferredRef , dependency . isAdaptable () ));
246224 } catch (final Exception e ) {
247225 throw new OpMatchingException ("Could not find Op that matches requested Op dependency:" + "\n Op class: "
248226 + op .opInfo ().implementationName () + //
@@ -258,52 +236,128 @@ public <T> T findOpInstance(final String opName, final Nil<T> specialType, final
258236 final Nil <?> outType ) {
259237 final OpRef ref = OpRef .fromTypes (opName , toTypes (specialType ), outType != null ? outType .getType () : null ,
260238 toTypes (inTypes ));
261- return (T ) findOpInstance (opName , ref );
239+ return (T ) findOpInstance (opName , ref , true );
262240 }
263241
264- public Object findOpInstance (final String opName , final OpRef ref ) {
242+ public Object findOpInstance (final String opName , final OpRef ref , boolean adaptable ) {
265243 Object op = null ;
266244 OpCandidate match = null ;
267- OpTransformationCandidate transformation = null ;
245+ AdaptedOp adaptation = null ;
268246 try {
269247 // Find single match which matches the specified types
270248 match = getOpMatcher ().findSingleMatch (this , ref );
271249 final List <Object > dependencies = resolveOpDependencies (match );
272250 op = match .createOp (dependencies );
273251 } catch (OpMatchingException e ) {
274252 log .debug ("No matching Op for request: " + ref + "\n " );
275- log .debug ("Attempting Op transformation..." );
276-
277- // If we can't find an op matching the original request, we try to find a
278- // transformation
279- transformation = getTransformationMatcher ().findTransformation (this , getTransformerIndex (), ref );
280- if (transformation == null ) {
281- log .debug ("No matching Op transformation found" );
282- throw new IllegalArgumentException (e );
253+ if (!adaptable ) {
254+ throw new IllegalArgumentException (opName + " cannot be adapted (adaptation is disabled)" );
283255 }
284-
285- // If we found one, try to do transformation and return transformed op
286- log .debug ("Matching Op transformation found:\n " + transformation + "\n " );
256+ log .debug ("Attempting Op adaptation..." );
287257 try {
288- final List <Object > dependencies = resolveOpDependencies (transformation .getSourceOp ());
289- op = transformation .exceute (this , dependencies );
290- } catch (OpMatchingException | OpTransformationException e1 ) {
291- throw new IllegalArgumentException ("Execution of Op transformatioon failed:\n " + e1 );
258+ adaptation = adaptOp (ref );
259+ op = adaptation .op ();
260+ } catch (OpMatchingException e1 ) {
261+ log .debug ("No suitable Op adaptation found" );
262+ throw new IllegalArgumentException (e1 );
292263 }
264+
293265 }
294266 try {
295267 // Try to resolve annotated OpDependency fields
268+ // N.B. Adapted Op dependency fields are already matched.
296269 if (match != null )
297270 resolveOpDependencies (match );
298- else if (transformation != null )
299- resolveOpDependencies (transformation .getSourceOp ());
300271 } catch (OpMatchingException e ) {
301272 throw new IllegalArgumentException (e );
302273 }
303- Object wrappedOp = wrapOp (op , match , transformation );
274+ OpInfo adaptedInfo = adaptation == null ? null : adaptation .opInfo ();
275+ Object wrappedOp = wrapOp (op , match , adaptedInfo );
304276 return wrappedOp ;
305277 }
306278
279+ /**
280+ * Adapts an Op with the name of ref into a type that can be SAFELY cast to ref.
281+ *
282+ * @param ref
283+ * - the type of Op that we are looking to adapt to.
284+ * @return {@link AdaptedOp} - an Op that has been adapted to conform the the
285+ * ref type.
286+ * @throws OpMatchingException
287+ */
288+ private AdaptedOp adaptOp (OpRef ref ) throws OpMatchingException {
289+ Type opType = ref .getTypes ()[0 ];
290+ List <OpInfo > adaptors = new ArrayList <>(opCache .get ("adapt" ));
291+ Collections .sort (adaptors , (OpInfo i1 , OpInfo i2 ) -> i1 .priority () < i2 .priority () ? 1 : i1 .priority () == i2 .priority () ? 0 : -1 );
292+
293+ for (final OpInfo adaptor : adaptors ) {
294+ Type adaptTo = adaptor .output ().getType ();
295+ Map <TypeVariable <?>, Type > map = new HashMap <>();
296+ // make sure that the adaptor outputs the correct type
297+ if (opType instanceof ParameterizedType ) {
298+ // TODO: remove try/catch
299+ try {
300+ if (!MatchingUtils .checkGenericAssignability (adaptTo , (ParameterizedType ) opType , map , true ))
301+ continue ;
302+ } catch (IllegalArgumentException e ) {
303+ continue ;
304+ }
305+ } else if (!Types .isAssignable (opType , adaptTo , map )) {
306+ continue ;
307+ }
308+ // make sure that the adaptor is a Function (so we can cast it later)
309+ if (Types .isInstance (adaptor .opType (), Function .class )) {
310+ log .debug (adaptor + " is an illegal adaptor Op: must be a Function" );
311+ continue ;
312+ }
313+ // build the type of fromOp (we know there must be one input because the adaptor
314+ // is a Function)
315+ Type adaptFrom = adaptor .inputs ().get (0 ).getType ();
316+ Type refAdaptTo = Types .substituteTypeVariables (adaptTo , map );
317+ Type refAdaptFrom = Types .substituteTypeVariables (adaptFrom , map );
318+
319+ // build the OpRef of the adaptor.
320+ Type refType = Types .parameterize (Function .class , new Type [] { refAdaptFrom , refAdaptTo });
321+ OpRef adaptorRef = new OpRef ("adapt" , new Type [] { refType }, refAdaptTo , new Type [] { refAdaptFrom });
322+
323+ // make an OpCandidate
324+ OpCandidate candidate = new OpCandidate (this , log , adaptorRef , adaptor , map );
325+
326+ try {
327+ // resolve adaptor dependencies and get the adaptor (as a function)
328+ final List <Object > dependencies = resolveOpDependencies (candidate );
329+ Object adaptorOp = adaptor .createOpInstance (dependencies ).object ();
330+
331+ // grab the first type parameter (from the OpCandidate?) and search for an Op
332+ // that will then be adapted (this will be the first (only) type in the args of
333+ // the adaptor)
334+ Type srcOpType = adaptor .inputs ().get (0 ).getType ();
335+ final OpRef srcOpRef ;
336+ srcOpRef = inferOpRef (srcOpType , ref .getName (), candidate .typeVarAssigns ());
337+ // TODO: export this to another function (also done in findOpInstance).
338+ // We need this here because we need to grab the OpInfo.
339+ // TODO: is there a better way to do this?
340+ final OpCandidate srcCandidate = getOpMatcher ().findSingleMatch (this , srcOpRef );
341+ final List <Object > srcDependencies = resolveOpDependencies (srcCandidate );
342+ final Object fromOp = srcCandidate .opInfo ().createOpInstance (srcDependencies ).object ();
343+
344+ // get adapted Op by applying adaptor on unadapted Op, then return
345+ // TODO: can we make this safer?
346+ @ SuppressWarnings ("unchecked" )
347+ Object toOp = ((Function <Object , Object >) adaptorOp ).apply (fromOp );
348+ // construct type of adapted op
349+ Type adapterOpType = Types .substituteTypeVariables (adaptor .output ().getType (),
350+ srcCandidate .typeVarAssigns ());
351+ return new AdaptedOp (toOp , adapterOpType , srcCandidate .opInfo (), adaptor );
352+ } catch (OpMatchingException e1 ) {
353+ log .trace (e1 );
354+ }
355+ }
356+
357+ // no adaptors available.
358+ throw new OpMatchingException ("Op adaptation failed: no adaptable Ops of type " + ref .getName ());
359+ }
360+
307361 /**
308362 * Wraps the matched op into an {@link Op} that knows its generic typing and
309363 * {@link OpInfo}.
@@ -318,11 +372,11 @@ else if (transformation != null)
318372 * needed.
319373 * @return an {@link Op} wrapping of op.
320374 */
321- private Object wrapOp (Object op , OpCandidate match , OpTransformationCandidate transformation ) {
375+ private Object wrapOp (Object op , OpCandidate match , OpInfo adaptationSrcInfo ) {
322376 if (wrappers == null )
323377 initWrappers ();
324378
325- OpInfo opInfo = match == null ? transformation . getSourceOp (). opInfo () : match .opInfo ();
379+ OpInfo opInfo = match == null ? adaptationSrcInfo : match .opInfo ();
326380 // FIXME: this type is not necessarily Computer, Function, etc. but often
327381 // something more specific (like the class of an Op).
328382 Type type = opInfo .opType ();
0 commit comments