Debugging notation parsers

satya - 8/21/2018, 10:35:09 AM

Consider these dependencies


dependencies {
  //for dependencies found in artifact repositories you can use
  //the group:name:version notation
  compile 'commons-lang:commons-lang:2.6'
  testCompile 'org.mockito:mockito:1.9.0-rc1'

  //map-style notation:
  compile group: 'com.google.code.guice', name: 'guice', version: '1.0'

  //declaring arbitrary files as dependencies
  compile files('hibernate.jar', 'libs/spring.jar')

  //putting all jars from 'libs' onto compile classpath
  compile fileTree('libs')
}

satya - 8/21/2018, 10:35:24 AM

How does files() become a Dependency??

How does files() become a Dependency??

satya - 8/21/2018, 10:37:07 AM

Here is some earlier research


TypeFilteringNotationConverter
NotationConverter
DependencyFilesNotationConverter

NotationParserBuilder
DependencyNotationParser
DependencyHandler
SelfResolvingDependency
DefaultSelfResolvingDependency
FileCollectionDependency

satya - 8/21/2018, 10:39:01 AM

DependencyHandler is documented here

DependencyHandler is documented here

satya - 8/21/2018, 10:40:27 AM

Start with DependencyNotationParser source code

Start with DependencyNotationParser source code

satya - 8/22/2018, 10:30:10 AM

TypeInfo class is here

TypeInfo class is here

satya - 8/22/2018, 10:31:10 AM

TypeInfo code


/**
 * Type literal, useful for nested Generics.
 */
public class TypeInfo<T> {
    private final Class<T> targetType;

    public TypeInfo(Class targetType) {
        assert targetType != null;
        this.targetType = targetType;
    }

    public Class<T> getTargetType() {
        return targetType;
    }
}

Just stores a class reference of a specified type.

satya - 8/22/2018, 10:36:55 AM

Here is how a notationpareserbuilder is used


public class DependencyNotationParser {
    public static NotationParser<Object, Dependency> parser(Instantiator instantiator, 
           DefaultProjectDependencyFactory dependencyFactory, 
           ClassPathRegistry classPathRegistry, FileLookup fileLookup, 
           RuntimeShadedJarFactory runtimeShadedJarFactory, 
           CurrentGradleInstallation currentGradleInstallation, 
           Interner<String> stringInterner) {

        return NotationParserBuilder

            .toType(Dependency.class)

            .fromCharSequence(
                new DependencyStringNotationConverter<DefaultExternalModuleDependency>
                 (instantiator, 
                 DefaultExternalModuleDependency.class, 
                 stringInterner))

            .converter(
                new DependencyMapNotationConverter<DefaultExternalModuleDependency>
                 (instantiator, 
                 DefaultExternalModuleDependency.class))

            .fromType(FileCollection.class, 
                new DependencyFilesNotationConverter(instantiator))

            .fromType(Project.class, 
                new DependencyProjectNotationConverter(dependencyFactory))

            .fromType(DependencyFactory.ClassPathNotation.class, 
                 new DependencyClassPathNotationConverter(
                    instantiator, 
                    classPathRegistry, 
                    fileLookup.getFileResolver(), 
                    runtimeShadedJarFactory, 
                    currentGradleInstallation))

            .invalidNotationMessage(
                  "Comprehensive documentation on dependency 
                  notations is available in DSL reference for 
                  DependencyHandler type.")

            .toComposite();
    }
}

satya - 8/22/2018, 10:57:51 AM

NotationConvertResult


package org.gradle.internal.typeconversion;

public interface NotationConvertResult<T> {
    boolean hasResult();

    /**
     * Invoked when a {@link NotationConverter} is able to convert a notation to a result.
     */
    void converted(T result);
}

satya - 8/22/2018, 11:00:30 AM

NotationParser


public interface NotationParser<N, T> {
    /**
     * @throws UnsupportedNotationException When the supplied notation is not handled by this parser.
     * @throws TypeConversionException When the supplied notation cannot be converted to the target type.
     */
    T parseNotation(N notation) throws TypeConversionException;

    /**
     * Describes the formats and values that the parser accepts.
     */
    void describe(DiagnosticsVisitor visitor);
}

satya - 8/22/2018, 11:10:51 AM

NotationConverterToNotationParserAdapter


public class NotationConverterToNotationParserAdapter<N, T> 
implements NotationParser<N, T> {
    private final NotationConverter<? super N, ? extends T> converter;

    public NotationConverterToNotationParserAdapter
       (NotationConverter<? super N, ? extends T> converter) {
        this.converter = converter;
    }

    public T parseNotation(N notation) throws TypeConversionException {
        ResultImpl<T> result = new ResultImpl<T>();
        converter.convert(notation, result);
        if (!result.hasResult) {
            throw new UnsupportedNotationException(notation);
        }
        return result.result;
    }

    @Override
    public void describe(DiagnosticsVisitor visitor) {
        converter.describe(visitor);
    }

    private static class ResultImpl<T> 
    implements NotationConvertResult<T> {
        private boolean hasResult;
        private T result;

        public boolean hasResult() {
            return hasResult;
        }

        public void converted(T result) {
            hasResult = true;
            this.result = result;
        }
    }
}

satya - 8/22/2018, 1:06:36 PM

TypeFilteringNotationConverter


class TypeFilteringNotationConverter<N, S, T> 
implements NotationConverter<N, T> {
    private final Class<S> type;
    private final NotationConverter<? super S, ? extends T> delegate;

    public TypeFilteringNotationConverter(
        Class<S> type, 
        NotationConverter<? super S, ? extends T> delegate) {
        this.type = type;
        this.delegate = delegate;
    }

    public void convert(N notation, NotationConvertResult<? super T> result) 
    throws TypeConversionException {
        if (type.isInstance(notation)) {
            delegate.convert(type.cast(notation), result);
        }
    }

    @Override
    public void describe(DiagnosticsVisitor visitor) {
        delegate.describe(visitor);
    }
}

satya - 8/22/2018, 1:09:39 PM

NotationConverter


public interface NotationConverter<N, T> {

    //Attempt to convert the given notation.

    void convert(N notation, 
      NotationConvertResult<? super T> result) 
    throws TypeConversionException;
}

satya - 8/22/2018, 1:16:07 PM

DependencyFilesNotationConverter


public class DependencyFilesNotationConverter 
implements NotationConverter<FileCollection, 
           SelfResolvingDependency> {
    private final Instantiator instantiator;

    public DependencyFilesNotationConverter(Instantiator instantiator) {
        this.instantiator = instantiator;
    }

    @Override
    public void describe(DiagnosticsVisitor visitor) {
        visitor.candidate("FileCollections").example("files('some.jar', 'someOther.jar')");
    }

    public void convert(FileCollection notation, 
             NotationConvertResult<? super SelfResolvingDependency> result) 
    throws TypeConversionException 
    {
        result.converted(
           instantiator
             .newInstance(DefaultSelfResolvingDependency.class, 
                   notation));
    }
}

satya - 8/22/2018, 1:18:51 PM

Notice the nature of convert()

In the base class it is generic

In the derived class it becomes specific

For each derived class it carries a different type signature!!

satya - 8/22/2018, 1:26:06 PM

DefaultSelfResolvingDependency


public class DefaultSelfResolvingDependency 
extends AbstractDependency 
implements SelfResolvingDependencyInternal, FileCollectionDependency 
{
    @Override
    public Set<File> resolve() {
        return source.getFiles();
    }
}

satya - 8/22/2018, 1:26:41 PM

Notice

FileCollectionDependency is an interface

satya - 8/22/2018, 1:27:12 PM

Here is the constructor of DefaultSelfResolvingDependency


public DefaultSelfResolvingDependency(FileCollectionInternal source) {
        this.targetComponentId = null;
        this.source = source;
    }

satya - 8/22/2018, 1:27:31 PM

See how the file collection is passed in

See how the file collection is passed in

satya - 8/22/2018, 1:39:06 PM

High level logic

A notation is an object

it can be a string

it can be a file collection

etc.

A series of converters are registered with the NotationParserBuilder.

Each converter will assess to see if an input notation (object) is allowed by any of the converters

DependencyFileNotationConverter is responsible for converting a file collection to a dependency subclass.

This converter simply instantiates a DefaultSelfResolvingDependency with the notation (FileCollection) as an input to that constructor

satya - 8/22/2018, 1:40:54 PM

NotationParserBuilder

NotationParserBuilder

satya - 8/22/2018, 1:44:55 PM

Here is the compositeconverter


public class CompositeNotationConverter<N, T> 
implements NotationConverter<N, T> {
    private final List<NotationConverter<? super N, ? extends T>> converters;

    public CompositeNotationConverter(
      List<NotationConverter<? super N, ? extends T>> converters) {
        this.converters = converters;
    }

    public void convert(N notation, NotationConvertResult<? super T> result) 
   throws TypeConversionException {
        for (int i = 0; !result.hasResult() && i < converters.size(); i++) {
            NotationConverter<? super N, ? extends T> converter = converters.get(i);
            converter.convert(notation, result);
        }
    }

    @Override
    public void describe(DiagnosticsVisitor visitor) {
        for (NotationConverter<? super N, ? extends T> converter : converters) {
            converter.describe(visitor);
        }
    }
}

satya - 8/22/2018, 1:45:28 PM

Notice the decision to convert or not is done by the Converter

Notice the decision to convert or not is done by the Converter

satya - 8/22/2018, 1:45:57 PM

Code returns as soon as the result is secured

Code returns as soon as the result is secured

satya - 8/22/2018, 1:47:18 PM

Notice how the TypeFilteringNotationConverter rejects/selects converter to help the composition


public void convert(N notation, NotationConvertResult<? super T> result) 
    throws TypeConversionException {
        if (type.isInstance(notation)) {
            delegate.convert(type.cast(notation), result);
        }
    }

satya - 8/22/2018, 1:52:13 PM

This appears to be a very round about way to confirm to an expected interface: Dependency

Question has been how to convert an agreeable type to make it look like a dependency

So how to wrap or construct a suitable dependency object

why register converters this way?

isn't there a better way to register on the fly?

Question becomes how to register for type conversion or instantiation at run time. Perhaps an equivalence of replacing property file registrations with code based registrations!!

satya - 8/22/2018, 1:53:46 PM

So DependencyNotationParser is a localized factory registry!!!

So DependencyNotationParser is a localized factory registry!!!

satya - 8/22/2018, 2:06:50 PM

So


//Say given
f(T)

//but you want to allow
f(T1)
f(T2)
....
f(T3)
etc.

//So you do a composite registry
R( 
  a(T1) -> T
  b(T2) -> T
  c(T3) -> T )

//ok
f(R(T1))
f(R(T2))
f(R(T3))