DTOs for anydata/anyxml are not generated within a list
[mdsal.git] / binding / mdsal-binding-generator-impl / src / main / java / org / opendaylight / mdsal / binding / generator / impl / ModuleInfoBackedContext.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.mdsal.binding.generator.impl;
9
10 import static com.google.common.base.Preconditions.checkState;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.annotations.Beta;
14 import com.google.common.base.MoreObjects;
15 import com.google.common.base.MoreObjects.ToStringHelper;
16 import com.google.common.cache.CacheBuilder;
17 import com.google.common.cache.CacheLoader;
18 import com.google.common.cache.LoadingCache;
19 import com.google.common.collect.ImmutableList;
20 import com.google.common.collect.ImmutableList.Builder;
21 import com.google.common.collect.ImmutableSet;
22 import com.google.common.collect.ListMultimap;
23 import com.google.common.collect.MultimapBuilder;
24 import com.google.common.util.concurrent.ListenableFuture;
25 import java.io.IOException;
26 import java.util.ArrayList;
27 import java.util.Collections;
28 import java.util.LinkedHashSet;
29 import java.util.List;
30 import java.util.Optional;
31 import java.util.Set;
32 import org.checkerframework.checker.lock.qual.GuardedBy;
33 import org.checkerframework.checker.lock.qual.Holding;
34 import org.eclipse.jdt.annotation.NonNull;
35 import org.opendaylight.mdsal.binding.generator.api.ClassLoadingStrategy;
36 import org.opendaylight.mdsal.binding.generator.api.ModuleInfoRegistry;
37 import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
38 import org.opendaylight.yangtools.concepts.AbstractObjectRegistration;
39 import org.opendaylight.yangtools.concepts.ObjectRegistration;
40 import org.opendaylight.yangtools.util.ClassLoaderUtils;
41 import org.opendaylight.yangtools.yang.binding.YangModuleInfo;
42 import org.opendaylight.yangtools.yang.common.QName;
43 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
44 import org.opendaylight.yangtools.yang.model.api.SchemaContextProvider;
45 import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException;
46 import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier;
47 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException;
48 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
49 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
50 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceProvider;
51 import org.opendaylight.yangtools.yang.parser.repo.YangTextSchemaContextResolver;
52 import org.opendaylight.yangtools.yang.parser.repo.YangTextSchemaSourceRegistration;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 public final class ModuleInfoBackedContext extends GeneratedClassLoadingStrategy
57         implements ModuleInfoRegistry, SchemaContextProvider, SchemaSourceProvider<YangTextSchemaSource> {
58     private abstract static class AbstractRegisteredModuleInfo {
59         final YangTextSchemaSourceRegistration reg;
60         final YangModuleInfo info;
61         final ClassLoader loader;
62
63         AbstractRegisteredModuleInfo(final YangModuleInfo info, final YangTextSchemaSourceRegistration reg,
64             final ClassLoader loader) {
65             this.info = requireNonNull(info);
66             this.reg = requireNonNull(reg);
67             this.loader = requireNonNull(loader);
68         }
69
70         @Override
71         public final String toString() {
72             return addToStringAttributes(MoreObjects.toStringHelper(this)).toString();
73         }
74
75         ToStringHelper addToStringAttributes(final ToStringHelper helper) {
76             return helper.add("info", info).add("registration", reg).add("classLoader", loader);
77         }
78     }
79
80     private static final class ExplicitRegisteredModuleInfo extends AbstractRegisteredModuleInfo {
81         private int refcount = 1;
82
83         ExplicitRegisteredModuleInfo(final YangModuleInfo info, final YangTextSchemaSourceRegistration reg,
84                 final ClassLoader loader) {
85             super(info, reg, loader);
86         }
87
88         void incRef() {
89             ++refcount;
90         }
91
92         boolean decRef() {
93             return --refcount == 0;
94         }
95
96         @Override
97         ToStringHelper addToStringAttributes(final ToStringHelper helper) {
98             return super.addToStringAttributes(helper).add("refCount", refcount);
99         }
100     }
101
102     private static final class ImplicitRegisteredModuleInfo extends AbstractRegisteredModuleInfo {
103         ImplicitRegisteredModuleInfo(final YangModuleInfo info, final YangTextSchemaSourceRegistration reg,
104                 final ClassLoader loader) {
105             super(info, reg, loader);
106         }
107     }
108
109     private static final Logger LOG = LoggerFactory.getLogger(ModuleInfoBackedContext.class);
110
111     private static final LoadingCache<ClassLoadingStrategy,
112         LoadingCache<ImmutableSet<YangModuleInfo>, ModuleInfoBackedContext>> CONTEXT_CACHES = CacheBuilder.newBuilder()
113             .weakKeys().build(new CacheLoader<ClassLoadingStrategy,
114                 LoadingCache<ImmutableSet<YangModuleInfo>, ModuleInfoBackedContext>>() {
115                     @Override
116                     public LoadingCache<ImmutableSet<YangModuleInfo>, ModuleInfoBackedContext> load(
117                             final ClassLoadingStrategy strategy) {
118                         return CacheBuilder.newBuilder().weakValues().build(
119                             new CacheLoader<Set<YangModuleInfo>, ModuleInfoBackedContext>() {
120                                 @Override
121                                 public ModuleInfoBackedContext load(final Set<YangModuleInfo> key) {
122                                     final ModuleInfoBackedContext context = ModuleInfoBackedContext.create(strategy);
123                                     context.addModuleInfos(key);
124                                     return context;
125                                 }
126                             });
127                     }
128             });
129
130     private final YangTextSchemaContextResolver ctxResolver = YangTextSchemaContextResolver.create("binding-context");
131
132     @GuardedBy("this")
133     private final ListMultimap<String, AbstractRegisteredModuleInfo> packageToInfoReg =
134             MultimapBuilder.hashKeys().arrayListValues().build();
135     @GuardedBy("this")
136     private final ListMultimap<SourceIdentifier, AbstractRegisteredModuleInfo> sourceToInfoReg =
137             MultimapBuilder.hashKeys().arrayListValues().build();
138
139     private final ClassLoadingStrategy backingLoadingStrategy;
140
141     private ModuleInfoBackedContext(final ClassLoadingStrategy loadingStrategy) {
142         this.backingLoadingStrategy = loadingStrategy;
143     }
144
145     @Beta
146     public static ModuleInfoBackedContext cacheContext(final ClassLoadingStrategy loadingStrategy,
147             final ImmutableSet<YangModuleInfo> infos) {
148         return CONTEXT_CACHES.getUnchecked(loadingStrategy).getUnchecked(infos);
149     }
150
151     public static ModuleInfoBackedContext create() {
152         return new ModuleInfoBackedContext(getTCCLClassLoadingStrategy());
153     }
154
155     public static ModuleInfoBackedContext create(final ClassLoadingStrategy loadingStrategy) {
156         return new ModuleInfoBackedContext(loadingStrategy);
157     }
158
159     @Override
160     public SchemaContext getSchemaContext() {
161         final Optional<SchemaContext> contextOptional = tryToCreateSchemaContext();
162         checkState(contextOptional.isPresent(), "Unable to recreate SchemaContext, error while parsing");
163         return contextOptional.get();
164     }
165
166     @Override
167     @SuppressWarnings("checkstyle:illegalCatch")
168     public Class<?> loadClass(final String fullyQualifiedName) throws ClassNotFoundException {
169         // This performs an explicit check for binding classes
170         final String modulePackageName = BindingReflections.getModelRootPackageName(fullyQualifiedName);
171
172         synchronized (this) {
173             // Try to find a loaded class loader
174             // FIXME: two-step process, try explicit registrations first
175             for (AbstractRegisteredModuleInfo reg : packageToInfoReg.get(modulePackageName)) {
176                 return ClassLoaderUtils.loadClass(reg.loader, fullyQualifiedName);
177             }
178
179             // We have not found a matching registration, consult the backing strategy
180             if (backingLoadingStrategy == null) {
181                 throw new ClassNotFoundException(fullyQualifiedName);
182             }
183
184             final Class<?> cls = backingLoadingStrategy.loadClass(fullyQualifiedName);
185             final YangModuleInfo moduleInfo;
186             try {
187                 moduleInfo = BindingReflections.getModuleInfo(cls);
188             } catch (Exception e) {
189                 throw new IllegalStateException("Failed to resolve module information for class " + cls, e);
190             }
191
192             registerImplicitModuleInfo(requireNonNull(moduleInfo));
193             return cls;
194         }
195     }
196
197     @Override
198     public synchronized ObjectRegistration<YangModuleInfo> registerModuleInfo(final YangModuleInfo yangModuleInfo) {
199         return register(requireNonNull(yangModuleInfo));
200     }
201
202     @Override
203     public ListenableFuture<? extends YangTextSchemaSource> getSource(final SourceIdentifier sourceIdentifier) {
204         return ctxResolver.getSource(sourceIdentifier);
205     }
206
207     public synchronized void addModuleInfos(final Iterable<? extends YangModuleInfo> moduleInfos) {
208         for (YangModuleInfo yangModuleInfo : moduleInfos) {
209             register(requireNonNull(yangModuleInfo));
210         }
211     }
212
213     // TODO finish schema parsing and expose as SchemaService
214     // Unite with current SchemaService
215
216     public Optional<SchemaContext> tryToCreateSchemaContext() {
217         return ctxResolver.getSchemaContext();
218     }
219
220     @Holding("this")
221     private ObjectRegistration<YangModuleInfo> register(final @NonNull YangModuleInfo moduleInfo) {
222         final Builder<ExplicitRegisteredModuleInfo> regBuilder = ImmutableList.builder();
223         for (YangModuleInfo info : flatDependencies(moduleInfo)) {
224             regBuilder.add(registerExplicitModuleInfo(info));
225         }
226         final ImmutableList<ExplicitRegisteredModuleInfo> regInfos = regBuilder.build();
227
228         return new AbstractObjectRegistration<YangModuleInfo>(moduleInfo) {
229             @Override
230             protected void removeRegistration() {
231                 unregister(regInfos);
232             }
233         };
234     }
235
236     /*
237      * Perform implicit registration of a YangModuleInfo and any of its dependencies. If there is a registration for
238      * a particular source, we do not create a duplicate registration.
239      */
240     @Holding("this")
241     private void registerImplicitModuleInfo(final @NonNull YangModuleInfo moduleInfo) {
242         for (YangModuleInfo info : flatDependencies(moduleInfo)) {
243             final Class<?> infoClass = info.getClass();
244             final SourceIdentifier sourceId = sourceIdentifierFrom(info);
245             if (sourceToInfoReg.containsKey(sourceId)) {
246                 LOG.debug("Skipping implicit registration of {} as source {} is already registered", info, sourceId);
247                 continue;
248             }
249
250             final YangTextSchemaSourceRegistration reg;
251             try {
252                 reg = ctxResolver.registerSource(toYangTextSource(sourceId, info));
253             } catch (YangSyntaxErrorException | SchemaSourceException | IOException e) {
254                 LOG.warn("Failed to register info {} source {}, ignoring it", info, sourceId, e);
255                 continue;
256             }
257
258             final ImplicitRegisteredModuleInfo regInfo = new ImplicitRegisteredModuleInfo(info, reg,
259                 infoClass.getClassLoader());
260             sourceToInfoReg.put(sourceId, regInfo);
261             packageToInfoReg.put(BindingReflections.getModelRootPackageName(infoClass.getPackage()), regInfo);
262         }
263     }
264
265     /*
266      * Perform explicit registration of a YangModuleInfo. This always results in a new explicit registration. In case
267      * there is a pre-existing implicit registration, it is removed just after the explicit registration is made.
268      */
269     @Holding("this")
270     private ExplicitRegisteredModuleInfo registerExplicitModuleInfo(final @NonNull YangModuleInfo info) {
271         // First search for an existing explicit registration
272         final SourceIdentifier sourceId = sourceIdentifierFrom(info);
273         for (AbstractRegisteredModuleInfo reg : sourceToInfoReg.get(sourceId)) {
274             if (reg instanceof ExplicitRegisteredModuleInfo && info.equals(reg.info)) {
275                 final ExplicitRegisteredModuleInfo explicit = (ExplicitRegisteredModuleInfo) reg;
276                 explicit.incRef();
277                 LOG.debug("Reusing explicit registration {}", explicit);
278                 return explicit;
279             }
280         }
281
282         // Create an explicit registration
283         final YangTextSchemaSourceRegistration reg;
284         try {
285             reg = ctxResolver.registerSource(toYangTextSource(sourceId, info));
286         } catch (YangSyntaxErrorException | SchemaSourceException | IOException e) {
287             throw new IllegalStateException("Failed to register info " + info, e);
288         }
289
290         final Class<?> infoClass = info.getClass();
291         final String packageName = BindingReflections.getModelRootPackageName(infoClass.getPackage());
292         final ExplicitRegisteredModuleInfo regInfo = new ExplicitRegisteredModuleInfo(info, reg,
293             infoClass.getClassLoader());
294         LOG.debug("Created new explicit registration {}", regInfo);
295
296         sourceToInfoReg.put(sourceId, regInfo);
297         removeImplicit(sourceToInfoReg.get(sourceId));
298         packageToInfoReg.put(packageName, regInfo);
299         removeImplicit(packageToInfoReg.get(packageName));
300
301         return regInfo;
302     }
303
304     synchronized void unregister(final ImmutableList<ExplicitRegisteredModuleInfo> regInfos) {
305         for (ExplicitRegisteredModuleInfo regInfo : regInfos) {
306             if (!regInfo.decRef()) {
307                 LOG.debug("Registration {} has references, not removing it", regInfo);
308                 continue;
309             }
310
311             final SourceIdentifier sourceId = sourceIdentifierFrom(regInfo.info);
312             if (!sourceToInfoReg.remove(sourceId, regInfo)) {
313                 LOG.warn("Failed to find {} registered under {}", regInfo, sourceId);
314             }
315
316             final String packageName = BindingReflections.getModelRootPackageName(regInfo.info.getClass().getPackage());
317             if (!packageToInfoReg.remove(packageName, regInfo)) {
318                 LOG.warn("Failed to find {} registered under {}", regInfo, packageName);
319             }
320
321             regInfo.reg.close();
322         }
323     }
324
325     @Holding("this")
326     private static void removeImplicit(final List<AbstractRegisteredModuleInfo> regs) {
327         /*
328          * Search for implicit registration for a sourceId/packageName.
329          *
330          * Since we are called while an explicit registration is being created (and has already been inserted, we know
331          * there is at least one entry in the maps. We also know registrations retain the order in which they were
332          * created and that implicit registrations are not created if there already is a registration.
333          *
334          * This means that if an implicit registration exists, it will be the first entry in the list.
335          */
336         final AbstractRegisteredModuleInfo reg = regs.get(0);
337         if (reg instanceof ImplicitRegisteredModuleInfo) {
338             LOG.debug("Removing implicit registration {}", reg);
339             regs.remove(0);
340             reg.reg.close();
341         }
342     }
343
344     private static @NonNull YangTextSchemaSource toYangTextSource(final SourceIdentifier identifier,
345             final YangModuleInfo moduleInfo) {
346         return YangTextSchemaSource.delegateForByteSource(identifier, moduleInfo.getYangTextByteSource());
347     }
348
349     private static SourceIdentifier sourceIdentifierFrom(final YangModuleInfo moduleInfo) {
350         final QName name = moduleInfo.getName();
351         return RevisionSourceIdentifier.create(name.getLocalName(), name.getRevision());
352     }
353
354     private static List<YangModuleInfo> flatDependencies(final YangModuleInfo moduleInfo) {
355         // Flatten the modules being registered, with the triggering module being first...
356         final Set<YangModuleInfo> requiredInfos = new LinkedHashSet<>();
357         flatDependencies(requiredInfos, moduleInfo);
358
359         // ... now reverse the order in an effort to register dependencies first (triggering module last)
360         final List<YangModuleInfo> intendedOrder = new ArrayList<>(requiredInfos);
361         Collections.reverse(intendedOrder);
362
363         return intendedOrder;
364     }
365
366     private static void flatDependencies(final Set<YangModuleInfo> set, final YangModuleInfo moduleInfo) {
367         if (set.add(moduleInfo)) {
368             for (YangModuleInfo dep : moduleInfo.getImportedModules()) {
369                 flatDependencies(set, dep);
370             }
371         }
372     }
373 }