BUG-997: Rework URLSchemaContextResolver 24/9524/6
authorRobert Varga <rovarga@cisco.com>
Mon, 28 Jul 2014 10:47:21 +0000 (12:47 +0200)
committerRobert Varga <rovarga@cisco.com>
Thu, 31 Jul 2014 15:52:13 +0000 (17:52 +0200)
This patch introduces an alternative implementation of
URLSchemaContextResolver, which takes advantage of the newly-created
APIs. The core improvement lies in the fact that it pre-validates
registered sources, thus eliding multiple parse actions.

Change-Id: I3311a3f0251868f91c385d0291365934ccfb9ede
Signed-off-by: Robert Varga <rovarga@cisco.com>
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/impl/YangModelBasicValidationListener.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/impl/YangParserImpl.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/DependencyResolver.java [new file with mode: 0644]
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/URLSchemaContextResolver.java [new file with mode: 0644]
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/util/TextToASTTransformer.java

index 5805cd7e8ccf4e5ce575ea003c001b0ea9000147..133c7b18a75d3b0d736607939086ddd5770016eb 100644 (file)
@@ -8,10 +8,12 @@
 package org.opendaylight.yangtools.yang.parser.impl;
 
 import com.google.common.collect.Sets;
+
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.HashSet;
 import java.util.Set;
+
 import org.antlr.v4.runtime.tree.ParseTree;
 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser;
 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Anyxml_stmtContext;
@@ -66,7 +68,7 @@ import org.slf4j.LoggerFactory;
  * This validator expects only one module or submodule per file and performs
  * only basic validation where context from all yang models is not present.
  */
-final class YangModelBasicValidationListener extends YangParserBaseListener {
+public final class YangModelBasicValidationListener extends YangParserBaseListener {
     private static final Logger LOGGER = LoggerFactory.getLogger(YangModelBasicValidationListener.class);
     private final Set<String> uniquePrefixes = new HashSet<>();
     private final Set<String> uniqueImports = new HashSet<>();
index bf163b9ec7a56569bdf0e1e0c68ae5971b7e45a4..1d0d48a7ddf2d7b5c034440d15f477c76b29e8fd 100644 (file)
@@ -22,6 +22,7 @@ import com.google.common.base.Preconditions;
 import com.google.common.base.Splitter;
 import com.google.common.collect.HashBiMap;
 import com.google.common.io.ByteSource;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -37,7 +38,9 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeMap;
+
 import javax.annotation.concurrent.Immutable;
+
 import org.antlr.v4.runtime.ANTLRInputStream;
 import org.antlr.v4.runtime.CommonTokenStream;
 import org.antlr.v4.runtime.tree.ParseTree;
@@ -107,8 +110,7 @@ public final class YangParserImpl implements YangContextParser {
     }
 
     @Override
-    public SchemaContext parseFile(final File yangFile, final File directory) throws IOException,
-    YangSyntaxErrorException {
+    public SchemaContext parseFile(final File yangFile, final File directory) throws IOException, YangSyntaxErrorException {
         Preconditions.checkState(yangFile.exists(), yangFile + " does not exists");
         Preconditions.checkState(directory.exists(), directory + " does not exists");
         Preconditions.checkState(directory.isDirectory(), directory + " is not a directory");
@@ -194,12 +196,8 @@ public final class YangParserImpl implements YangContextParser {
     }
 
     @Override
-    public SchemaContext parseSources(final Collection<ByteSource> sources) throws IOException,
-    YangSyntaxErrorException {
-        Collection<Module> unsorted = parseYangModelSources(sources).values();
-        Set<Module> sorted = new LinkedHashSet<>(
-                ModuleDependencySort.sort(unsorted.toArray(new Module[unsorted.size()])));
-        return resolveSchemaContext(sorted);
+    public SchemaContext parseSources(final Collection<ByteSource> sources) throws IOException,YangSyntaxErrorException {
+        return assembleContext(parseYangModelSources(sources).values());
     }
 
     @Override
@@ -236,7 +234,7 @@ public final class YangParserImpl implements YangContextParser {
         return resolveSchemaContext(result);
     }
 
-    private LinkedHashMap<String, TreeMap<Date, ModuleBuilder>> resolveModulesWithImports(final List<ModuleBuilder> sorted,
+    private static LinkedHashMap<String, TreeMap<Date, ModuleBuilder>> resolveModulesWithImports(final List<ModuleBuilder> sorted,
             final SchemaContext context) {
         final LinkedHashMap<String, TreeMap<Date, ModuleBuilder>> modules = orderModules(sorted);
         for (ModuleBuilder module : sorted) {
@@ -335,8 +333,21 @@ public final class YangParserImpl implements YangContextParser {
         return new SchemaContextImpl(modules, identifiersToSources);
     }
 
-    private Map<ByteSource, Module> parseYangModelSources(final Collection<ByteSource> sources) throws IOException,
-    YangSyntaxErrorException {
+    public Collection<Module> buildModules(final Collection<ModuleBuilder> builders) {
+        List<ModuleBuilder> sorted = ModuleDependencySort.sort(builders);
+        Map<String, TreeMap<Date, ModuleBuilder>> modules = resolveModulesWithImports(sorted, null);
+        Map<ModuleBuilder, Module> builderToModule = build(modules);
+
+        return builderToModule.values();
+    }
+
+    public SchemaContext assembleContext(final Collection<Module> modules) {
+        final Set<Module> sorted = new LinkedHashSet<>(
+                ModuleDependencySort.sort(modules.toArray(new Module[modules.size()])));
+        return resolveSchemaContext(sorted);
+    }
+
+    private Map<ByteSource, Module> parseYangModelSources(final Collection<ByteSource> sources) throws IOException, YangSyntaxErrorException {
         if (sources == null || sources.isEmpty()) {
             return Collections.emptyMap();
         }
@@ -368,8 +379,7 @@ public final class YangParserImpl implements YangContextParser {
      * @throws YangSyntaxErrorException
      */
     // TODO: remove ByteSource result after removing YangModelParser
-    private Map<ByteSource, ModuleBuilder> resolveSources(final Collection<ByteSource> streams) throws IOException,
-    YangSyntaxErrorException {
+    private Map<ByteSource, ModuleBuilder> resolveSources(final Collection<ByteSource> streams) throws IOException, YangSyntaxErrorException {
         Map<ByteSource, ModuleBuilder> builders = parseSourcesToBuilders(streams);
         return resolveSubmodules(builders);
     }
@@ -511,7 +521,7 @@ public final class YangParserImpl implements YangContextParser {
      *            topologically sorted modules
      * @return modules ordered by name and revision
      */
-    private LinkedHashMap<String, TreeMap<Date, ModuleBuilder>> orderModules(final List<ModuleBuilder> modules) {
+    private static LinkedHashMap<String, TreeMap<Date, ModuleBuilder>> orderModules(final List<ModuleBuilder> modules) {
         final LinkedHashMap<String, TreeMap<Date, ModuleBuilder>> result = new LinkedHashMap<>();
         for (final ModuleBuilder builder : modules) {
             if (builder == null) {
@@ -1274,5 +1284,4 @@ public final class YangParserImpl implements YangContextParser {
         }
         dev.setTargetPath(((SchemaNodeBuilder) currentParent).getPath());
     }
-
 }
diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/DependencyResolver.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/DependencyResolver.java
new file mode 100644 (file)
index 0000000..45ad366
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.parser.repo;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.Multimap;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.opendaylight.yangtools.yang.model.api.ModuleImport;
+import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
+import org.opendaylight.yangtools.yang.parser.impl.util.YangModelDependencyInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Inter-module dependency resolved. Given a set of schema source identifiers and their
+ * corresponding dependency information, the {@link #create(Map)} method creates a
+ * a view of how consistent the dependencies are. In particular, this detects whether
+ * any imports are unsatisfied.
+ *
+ * FIXME: improve this class to track and expose how wildcard imports were resolved.
+ *        That information will allow us to track "damage" to dependency resolution
+ *        as new models are added to a schema context.
+ */
+final class DependencyResolver {
+    private static final Logger LOG = LoggerFactory.getLogger(DependencyResolver.class);
+    private final Collection<SourceIdentifier> resolvedSources;
+    private final Collection<SourceIdentifier> unresolvedSources;
+    private final Multimap<SourceIdentifier, ModuleImport> unsatisfiedImports;
+
+    public DependencyResolver(final Collection<SourceIdentifier> resolvedSources,
+            final Collection<SourceIdentifier> unresolvedSources, final Multimap<SourceIdentifier, ModuleImport> unsatisfiedImports) {
+        this.resolvedSources = Preconditions.checkNotNull(resolvedSources);
+        this.unresolvedSources = Preconditions.checkNotNull(unresolvedSources);
+        this.unsatisfiedImports = Preconditions.checkNotNull(unsatisfiedImports);
+    }
+
+    private static SourceIdentifier findWildcard(final Iterable<SourceIdentifier> haystack, final String needle) {
+        for (SourceIdentifier r : haystack) {
+            if (r.getName().equals(needle)) {
+                return r;
+            }
+        }
+
+        return null;
+    }
+
+    private static boolean isKnown(final Collection<SourceIdentifier> haystack, final ModuleImport mi) {
+        final String rev = mi.getRevision() != null ? mi.getRevision().toString() : null;
+        final SourceIdentifier msi = SourceIdentifier.create(mi.getModuleName(), Optional.fromNullable(rev));
+
+        // Quick lookup
+        if (haystack.contains(msi)) {
+            return true;
+        }
+
+        // Slow revision-less walk
+        return rev == null && findWildcard(haystack, mi.getModuleName()) != null;
+    }
+
+    public static final DependencyResolver create(final Map<SourceIdentifier, YangModelDependencyInfo> depInfo) {
+        final Collection<SourceIdentifier> resolved = new ArrayList<>(depInfo.size());
+        final Collection<SourceIdentifier> pending = new ArrayList<>(depInfo.keySet());
+
+        boolean progress;
+        do {
+            progress = false;
+
+            final Iterator<SourceIdentifier> it = pending.iterator();
+            while (it.hasNext()) {
+                final SourceIdentifier id = it.next();
+                final YangModelDependencyInfo dep = depInfo.get(id);
+
+                boolean okay = true;
+                for (ModuleImport mi : dep.getDependencies()) {
+                    if (!isKnown(resolved, mi)) {
+                        LOG.debug("Source {} is missing import {}", id, mi);
+                        okay = false;
+                        break;
+                    }
+                }
+
+                if (okay) {
+                    LOG.debug("Resolved source {}", id);
+                    resolved.add(id);
+                    it.remove();
+                    progress = true;
+                }
+            }
+        } while (progress);
+
+        if (!pending.isEmpty()) {
+            final Multimap<SourceIdentifier, ModuleImport> imports = ArrayListMultimap.create();
+            for (SourceIdentifier id : pending) {
+                final YangModelDependencyInfo dep = depInfo.get(id);
+                for (ModuleImport mi : dep.getDependencies()) {
+                    if (!isKnown(pending, mi) && !isKnown(resolved, mi)) {
+                        imports.put(id, mi);
+                    }
+                }
+            }
+
+            return new DependencyResolver(resolved, pending, imports);
+        } else {
+            return new DependencyResolver(resolved, Collections.<SourceIdentifier>emptyList(), ImmutableMultimap.<SourceIdentifier, ModuleImport>of());
+        }
+    }
+
+    /**
+     * Collection of sources which have been resolved.
+     *
+     * @return
+     */
+    Collection<SourceIdentifier> getResolvedSources() {
+        return resolvedSources;
+    }
+
+    /**
+     * Collection of sources which have not been resolved due to missing dependencies.
+     *
+     * @return
+     */
+    Collection<SourceIdentifier> getUnresolvedSources() {
+        return unresolvedSources;
+    }
+
+    /**
+     * Detailed information about which imports were missing. The key in the map
+     * is the source identifier of module which was issuing an import, the values
+     * are imports which were unsatisfied.
+     *
+     * Note that this map contains only imports which are missing from the reactor,
+     * not transitive failures.
+     *
+     * Examples:
+     *
+     * If A imports B, B imports C, and both A and B are in the reactor, only B->C
+     * will be reported.
+     *
+     * If A imports B and C, B imports C, and both A and B are in the reactor,
+     * A->C and B->C will be reported.
+     *
+     * @return
+     */
+    Multimap<SourceIdentifier, ModuleImport> getUnsatisfiedImports() {
+        return unsatisfiedImports;
+    }
+}
diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/URLSchemaContextResolver.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/URLSchemaContextResolver.java
new file mode 100644 (file)
index 0000000..f036322
--- /dev/null
@@ -0,0 +1,274 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.parser.repo;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.base.Function;
+import com.google.common.base.Objects.ToStringHelper;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Maps.EntryTransformer;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.antlr.v4.runtime.ParserRuleContext;
+import org.antlr.v4.runtime.tree.ParseTreeWalker;
+import org.opendaylight.yangtools.concepts.AbstractObjectRegistration;
+import org.opendaylight.yangtools.concepts.ObjectRegistration;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
+import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
+import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceTransformationException;
+import org.opendaylight.yangtools.yang.parser.builder.impl.ModuleBuilder;
+import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl;
+import org.opendaylight.yangtools.yang.parser.impl.YangParserListenerImpl;
+import org.opendaylight.yangtools.yang.parser.impl.util.YangModelDependencyInfo;
+import org.opendaylight.yangtools.yang.parser.util.ASTSchemaSource;
+import org.opendaylight.yangtools.yang.parser.util.TextToASTTransformer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@ThreadSafe
+public class URLSchemaContextResolver {
+    private static final Logger LOG = LoggerFactory.getLogger(URLSchemaContextResolver.class);
+    private static final Function<ASTSchemaSource, YangModelDependencyInfo> EXTRACT_DEPINFO = new Function<ASTSchemaSource, YangModelDependencyInfo>() {
+        @Override
+        public YangModelDependencyInfo apply(final ASTSchemaSource input) {
+            return input.getDependencyInformation();
+        }
+    };
+    private static final EntryTransformer<SourceIdentifier, Collection<YangModelDependencyInfo>, YangModelDependencyInfo> SQUASH_DEPINFO =
+            new EntryTransformer<SourceIdentifier, Collection<YangModelDependencyInfo>, YangModelDependencyInfo>() {
+        @Override
+        public YangModelDependencyInfo transformEntry(final SourceIdentifier key, final Collection<YangModelDependencyInfo> value) {
+            // FIXME: validate that all the info objects are the same
+            return value.iterator().next();
+        }
+    };
+    private static final Function<ASTSchemaSource, ParserRuleContext> EXTRACT_AST = new Function<ASTSchemaSource, ParserRuleContext>() {
+        @Override
+        public ParserRuleContext apply(final ASTSchemaSource input) {
+            return input.getAST();
+        }
+    };
+    private static final EntryTransformer<SourceIdentifier, Collection<ParserRuleContext>, ParserRuleContext> SQUASH_AST =
+            new EntryTransformer<SourceIdentifier, Collection<ParserRuleContext>, ParserRuleContext>() {
+        @Override
+        public ParserRuleContext transformEntry(final SourceIdentifier key, final Collection<ParserRuleContext> value) {
+            // FIXME: validate that all the info objects are the same
+            return value.iterator().next();
+        }
+    };
+
+    @GuardedBy("this")
+    private final Multimap<SourceIdentifier, ASTSchemaSource> resolvedRegs = ArrayListMultimap.create();
+    private final AtomicReference<Optional<SchemaContext>> currentSchemaContext = new AtomicReference<>(Optional.<SchemaContext>absent());
+    private final Queue<URLRegistration> outstandingRegs = new ConcurrentLinkedQueue<>();
+    private final TextToASTTransformer transformer;
+    @GuardedBy("this")
+    private Object version = new Object();
+    @GuardedBy("this")
+    private Object contextVersion = version;
+
+    private final class URLRegistration extends AbstractObjectRegistration<URL> {
+        @GuardedBy("this")
+        private CheckedFuture<ASTSchemaSource, SchemaSourceTransformationException> future;
+        @GuardedBy("this")
+        private ASTSchemaSource result;
+
+        protected URLRegistration(final URL url, final CheckedFuture<ASTSchemaSource, SchemaSourceTransformationException> future) {
+            super(url);
+            this.future = Preconditions.checkNotNull(future);
+        }
+
+        private synchronized boolean setResult(final ASTSchemaSource result) {
+            if (future != null) {
+                this.result = result;
+                return true;
+            } else {
+                return false;
+            }
+        }
+
+        @Override
+        protected void removeRegistration() {
+            // Cancel the future, but it may already be completing
+            future.cancel(false);
+
+            synchronized (this) {
+                future = null;
+                outstandingRegs.remove(this);
+                if (result != null) {
+                    removeSchemaSource(result);
+                }
+            }
+        }
+    }
+
+    private URLSchemaContextResolver(final TextToASTTransformer transformer) {
+        this.transformer = Preconditions.checkNotNull(transformer);
+    }
+
+    public static URLSchemaContextResolver create(final String name) {
+        final ThreadFactory f = new ThreadFactoryBuilder().setDaemon(true).setNameFormat(name + "yangparser-%d").build();
+        final ListeningExecutorService s = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor(f));
+
+        return new URLSchemaContextResolver(TextToASTTransformer.create(s));
+    }
+
+    /**
+     * Register a URL hosting a YANG Text file.
+     *
+     * @param url URL
+     */
+    public ObjectRegistration<URL> registerSource(final URL url) {
+        checkArgument(url != null, "Supplied URL must not be null");
+
+        final SourceIdentifier id = SourceIdentifier.create(url.getFile().toString(), Optional.<String>absent());
+        final YangTextSchemaSource text = new YangTextSchemaSource(id) {
+            @Override
+            public InputStream openStream() throws IOException {
+                return url.openStream();
+            }
+
+            @Override
+            protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
+                return toStringHelper.add("url", url);
+            }
+        };
+
+        final CheckedFuture<ASTSchemaSource, SchemaSourceTransformationException> ast = transformer.transformSchemaSource(text);
+        final URLRegistration reg = new URLRegistration(url, ast);
+        outstandingRegs.add(reg);
+
+        Futures.addCallback(ast, new FutureCallback<ASTSchemaSource>() {
+            @Override
+            public void onSuccess(final ASTSchemaSource result) {
+                LOG.trace("Resolved URL {} to source {}", url, result);
+
+                outstandingRegs.remove(reg);
+                if (reg.setResult(result)) {
+                    addSchemaSource(result);
+                }
+            }
+
+            @Override
+            public void onFailure(final Throwable t) {
+                LOG.warn("Failed to parse YANG text from {}, ignoring it", url, t);
+                outstandingRegs.remove(reg);
+            }
+        });
+
+        return reg;
+    }
+
+    private synchronized void addSchemaSource(final ASTSchemaSource src) {
+        resolvedRegs.put(src.getIdentifier(), src);
+        version = new Object();
+    }
+
+    private synchronized void removeSchemaSource(final ASTSchemaSource src) {
+        resolvedRegs.put(src.getIdentifier(), src);
+        version = new Object();
+    }
+
+    /**
+     * Try to parse all currently available yang files and build new schema context.
+     * @return new schema context iif there is at least 1 yang file registered and
+     *         new schema context was successfully built.
+     */
+    public Optional<SchemaContext> getSchemaContext() {
+        while (true) {
+            Optional<SchemaContext> result;
+            final Multimap<SourceIdentifier, ASTSchemaSource> sources;
+            final Object v;
+            synchronized (this) {
+                result = currentSchemaContext.get();
+                if (version == contextVersion) {
+                    return result;
+                }
+
+                sources = ImmutableMultimap.copyOf(resolvedRegs);
+                v = version;
+            }
+
+            if (!sources.isEmpty()) {
+                final Map<SourceIdentifier, YangModelDependencyInfo> deps =
+                        Maps.transformEntries(Multimaps.transformValues(sources, EXTRACT_DEPINFO).asMap(), SQUASH_DEPINFO);
+
+                LOG.debug("Resolving dependency reactor {}", deps);
+                final DependencyResolver res = DependencyResolver.create(deps);
+                if (!res.getUnresolvedSources().isEmpty()) {
+                    LOG.debug("Omitting models {} due to unsatisfied imports {}", res.getUnresolvedSources(), res.getUnsatisfiedImports());
+                }
+
+                final Map<SourceIdentifier, ParserRuleContext> asts =
+                        Maps.transformEntries(Multimaps.transformValues(sources, EXTRACT_AST).asMap(), SQUASH_AST);
+
+                final ParseTreeWalker walker = new ParseTreeWalker();
+                final Map<SourceIdentifier, ModuleBuilder> sourceToBuilder = new LinkedHashMap<>();
+
+                for (Entry<SourceIdentifier, ParserRuleContext> entry : asts.entrySet()) {
+                    final YangParserListenerImpl yangModelParser = new YangParserListenerImpl(entry.getKey().getName());
+                    walker.walk(yangModelParser, entry.getValue());
+                    ModuleBuilder moduleBuilder = yangModelParser.getModuleBuilder();
+
+                    // FIXME: do we need to lug this around?
+                    // moduleBuilder.setSource(source);
+                    sourceToBuilder.put(entry.getKey(), moduleBuilder);
+                }
+                LOG.debug("Modules ready for integration");
+
+                final YangParserImpl parser = YangParserImpl.getInstance();
+                final Collection<Module> modules = parser.buildModules(sourceToBuilder.values());
+                LOG.debug("Integrated cross-references modules");
+
+                result = Optional.of(parser.assembleContext(modules));
+            } else {
+                result = Optional.absent();
+            }
+
+            synchronized (this) {
+                if (v == version) {
+                    currentSchemaContext.set(result);
+                    contextVersion = version;
+                    return result;
+                }
+
+                LOG.debug("Context version {} expected {}, retry", version, v);
+            }
+        }
+    }
+}
index 38c0dc314ee9db076d6e5111b17845ca5f967be1..5c43a53bb5ef1d143c524ffa68f1b877a2d05fe6 100644 (file)
@@ -16,13 +16,17 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.util.concurrent.Callable;
 
+import org.antlr.v4.runtime.tree.ParseTreeWalker;
 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.YangContext;
 import org.opendaylight.yangtools.util.concurrent.ExceptionMapper;
 import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException;
 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceTransformationException;
 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceTransformer;
+import org.opendaylight.yangtools.yang.parser.impl.YangModelBasicValidationListener;
 import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * A {@link SchemaSourceTransformer} which handles translation of models from
@@ -34,18 +38,24 @@ import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl;
  * ASTSchemaSource representation.
  */
 public final class TextToASTTransformer implements SchemaSourceTransformer<YangTextSchemaSource, ASTSchemaSource> {
+    private static final Logger LOG = LoggerFactory.getLogger(TextToASTTransformer.class);
     private static final Function<Exception, SchemaSourceTransformationException> MAPPER = new ExceptionMapper<SchemaSourceTransformationException>("Source transformation", SchemaSourceTransformationException.class) {
         @Override
         protected SchemaSourceTransformationException newWithCause(final String message, final Throwable cause) {
             return new SchemaSourceTransformationException(message, cause);
         }
     };
+
     private final ListeningExecutorService executor;
 
-    TextToASTTransformer(final ListeningExecutorService executor) {
+    private TextToASTTransformer(final ListeningExecutorService executor) {
         this.executor = Preconditions.checkNotNull(executor);
     }
 
+    public static final TextToASTTransformer create(final ListeningExecutorService executor) {
+        return new TextToASTTransformer(executor);
+    }
+
     @Override
     public Class<YangTextSchemaSource> getInputRepresentation() {
         return YangTextSchemaSource.class;
@@ -63,6 +73,13 @@ public final class TextToASTTransformer implements SchemaSourceTransformer<YangT
             public ASTSchemaSource call() throws IOException, YangSyntaxErrorException {
                 try (InputStream is = source.openStream()) {
                     final YangContext ctx = YangParserImpl.parseYangSource(is);
+                    LOG.debug("Model {} parsed successfully", source);
+
+                    final ParseTreeWalker walker = new ParseTreeWalker();
+                    final YangModelBasicValidationListener validator = new YangModelBasicValidationListener();
+                    walker.walk(validator, ctx);
+                    LOG.debug("Model {} validated successfully", source);
+
                     return ASTSchemaSource.create(source.getIdentifier().getName(), ctx);
                 }
             }