Debugging notation parsers


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')
}

How does files() become a Dependency??


TypeFilteringNotationConverter
NotationConverter
DependencyFilesNotationConverter

NotationParserBuilder
DependencyNotationParser
DependencyHandler
SelfResolvingDependency
DefaultSelfResolvingDependency
FileCollectionDependency

DependencyHandler is documented here

Start with DependencyNotationParser source code

TypeInfo class is here


/**
 * 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.


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();
    }
}

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);
}

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);
}

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;
        }
    }
}

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);
    }
}

public interface NotationConverter<N, T> {

    //Attempt to convert the given notation.

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

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));
    }
}

In the base class it is generic

In the derived class it becomes specific

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


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

FileCollectionDependency is an interface


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

See how the file collection is passed in

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

NotationParserBuilder


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);
        }
    }
}

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

Code returns as soon as the result is secured


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

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!!

So DependencyNotationParser is a localized factory registry!!!


//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))