BUG-7568: Use YangTextSchemaSource to emit schema files
[yangtools.git] / yang / yang-maven-plugin / src / main / java / org / opendaylight / yangtools / yang2sources / plugin / Util.java
1 /*
2  * Copyright (c) 2013 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.yangtools.yang2sources.plugin;
9
10 import static org.opendaylight.yangtools.yang.common.YangConstants.RFC6020_YANG_FILE_EXTENSION;
11 import static org.opendaylight.yangtools.yang2sources.plugin.YangToSourcesProcessor.LOG_PREFIX;
12 import static org.opendaylight.yangtools.yang2sources.plugin.YangToSourcesProcessor.META_INF_YANG_STRING;
13 import static org.opendaylight.yangtools.yang2sources.plugin.YangToSourcesProcessor.META_INF_YANG_STRING_JAR;
14
15 import com.google.common.io.ByteSource;
16 import com.google.common.io.ByteStreams;
17 import java.io.File;
18 import java.io.FileNotFoundException;
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.Date;
24 import java.util.Enumeration;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.zip.ZipEntry;
30 import java.util.zip.ZipFile;
31 import org.apache.commons.io.FileUtils;
32 import org.apache.maven.artifact.Artifact;
33 import org.apache.maven.artifact.repository.ArtifactRepository;
34 import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
35 import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
36 import org.apache.maven.model.Dependency;
37 import org.apache.maven.model.Plugin;
38 import org.apache.maven.plugin.MojoFailureException;
39 import org.apache.maven.project.MavenProject;
40 import org.apache.maven.repository.RepositorySystem;
41 import org.opendaylight.yangtools.yang.common.QNameModule;
42 import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
43 import org.opendaylight.yangtools.yang.model.api.Module;
44 import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier;
45 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
46 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49
50 final class Util {
51
52     /**
53      * It isn't desirable to create instances of this class.
54      */
55     private Util() {
56     }
57
58     static final String YANG_SUFFIX = "yang";
59
60     private static final Logger LOG = LoggerFactory.getLogger(Util.class);
61
62     static Collection<File> listFiles(final File root, final Collection<File> excludedFiles)
63             throws FileNotFoundException {
64         if (!root.exists()) {
65             LOG.warn("{} YANG source directory {} not found. No code will be generated.", LOG_PREFIX, root);
66
67             return Collections.emptyList();
68         }
69         Collection<File> result = new ArrayList<>();
70         Collection<File> yangFiles = FileUtils.listFiles(root, new String[] { YANG_SUFFIX }, true);
71         for (File f : yangFiles) {
72             if (excludedFiles.contains(f)) {
73                 LOG.info("{} {} file excluded {}", LOG_PREFIX, Util.YANG_SUFFIX.toUpperCase(), f);
74             } else {
75                 result.add(f);
76             }
77         }
78
79         return result;
80     }
81
82     static List<File> getClassPath(final MavenProject project) {
83         List<File> dependencies = new ArrayList<>();
84         for (Artifact element : project.getArtifacts()) {
85             File asFile = element.getFile();
86             if (isJar(asFile) || asFile.isDirectory()) {
87                 dependencies.add(asFile);
88             }
89         }
90         return dependencies;
91     }
92
93     /**
94      * Read current project dependencies and check if it don't grab incorrect
95      * artifacts versions which could be in conflict with plugin dependencies.
96      *
97      * @param project
98      *            current project
99      * @param repoSystem
100      *            repository system
101      * @param localRepo
102      *            local repository
103      * @param remoteRepos
104      *            remote repositories
105      */
106     static void checkClasspath(final MavenProject project, final RepositorySystem repoSystem,
107             final ArtifactRepository localRepo, final List<ArtifactRepository> remoteRepos) {
108         Plugin plugin = project.getPlugin(YangToSourcesMojo.PLUGIN_NAME);
109         if (plugin == null) {
110             LOG.warn("{} {} not found, dependencies version check skipped", LOG_PREFIX, YangToSourcesMojo.PLUGIN_NAME);
111         } else {
112             Map<Artifact, Collection<Artifact>> pluginDependencies = new HashMap<>();
113             getPluginTransitiveDependencies(plugin, pluginDependencies, repoSystem, localRepo, remoteRepos);
114
115             Set<Artifact> projectDependencies = project.getDependencyArtifacts();
116             for (Map.Entry<Artifact, Collection<Artifact>> entry : pluginDependencies.entrySet()) {
117                 checkArtifact(entry.getKey(), projectDependencies);
118                 for (Artifact dependency : entry.getValue()) {
119                     checkArtifact(dependency, projectDependencies);
120                 }
121             }
122         }
123     }
124
125     /**
126      * Read transitive dependencies of given plugin and store them in map.
127      *
128      * @param plugin
129      *            plugin to read
130      * @param map
131      *            map, where founded transitive dependencies will be stored
132      * @param repoSystem
133      *            repository system
134      * @param localRepository
135      *            local repository
136      * @param remoteRepos
137      *            list of remote repositories
138      */
139     private static void getPluginTransitiveDependencies(final Plugin plugin,
140             final Map<Artifact, Collection<Artifact>> map, final RepositorySystem repoSystem,
141             final ArtifactRepository localRepository, final List<ArtifactRepository> remoteRepos) {
142
143         List<Dependency> pluginDependencies = plugin.getDependencies();
144         for (Dependency dep : pluginDependencies) {
145             Artifact artifact = repoSystem.createDependencyArtifact(dep);
146
147             ArtifactResolutionRequest request = new ArtifactResolutionRequest();
148             request.setArtifact(artifact);
149             request.setResolveTransitively(true);
150             request.setLocalRepository(localRepository);
151             request.setRemoteRepositories(remoteRepos);
152
153             ArtifactResolutionResult result = repoSystem.resolve(request);
154             Set<Artifact> pluginDependencyDependencies = result.getArtifacts();
155             map.put(artifact, pluginDependencyDependencies);
156         }
157     }
158
159     /**
160      * Check artifact against collection of dependencies. If collection contains
161      * artifact with same groupId and artifactId, but different version, logs a
162      * warning.
163      *
164      * @param artifact
165      *            artifact to check
166      * @param dependencies
167      *            collection of dependencies
168      */
169     private static void checkArtifact(final Artifact artifact, final Collection<Artifact> dependencies) {
170         for (org.apache.maven.artifact.Artifact d : dependencies) {
171             if (artifact.getGroupId().equals(d.getGroupId()) && artifact.getArtifactId().equals(d.getArtifactId())) {
172                 if (!(artifact.getVersion().equals(d.getVersion()))) {
173                     LOG.warn("{} Dependency resolution conflict:", LOG_PREFIX);
174                     LOG.warn("{} '{}' dependency [{}] has different version than one declared in current project [{}]"
175                             + ". It is recommended to fix this problem because it may cause compilation errors.",
176                             LOG_PREFIX, YangToSourcesMojo.PLUGIN_NAME, artifact, d);
177                 }
178             }
179         }
180     }
181
182     private static boolean isJar(final File element) {
183         return element.isFile() && element.getName().endsWith(".jar");
184     }
185
186     @SuppressWarnings("checkstyle:illegalCatch")
187     static List<YangTextSchemaSource> findYangFilesInDependenciesAsStream(final MavenProject project)
188             throws MojoFailureException {
189         try {
190             final List<File> filesOnCp = Util.getClassPath(project);
191             LOG.info("{} Searching for yang files in following dependencies: {}", LOG_PREFIX, filesOnCp);
192
193             final List<YangTextSchemaSource> yangsFromDependencies = new ArrayList<>();
194             for (File file : filesOnCp) {
195                 final List<String> foundFilesForReporting = new ArrayList<>();
196                 // is it jar file or directory?
197                 if (file.isDirectory()) {
198                     //FIXME: code duplicate
199                     File yangDir = new File(file, META_INF_YANG_STRING);
200                     if (yangDir.exists() && yangDir.isDirectory()) {
201                         File[] yangFiles = yangDir.listFiles(
202                             (dir, name) -> name.endsWith(RFC6020_YANG_FILE_EXTENSION) && new File(dir, name).isFile());
203                         for (final File yangFile : yangFiles) {
204                             foundFilesForReporting.add(yangFile.getName());
205                             yangsFromDependencies.add(YangTextSchemaSource.forFile(yangFile));
206                         }
207                     }
208                 } else {
209                     try (ZipFile zip = new ZipFile(file)) {
210                         final Enumeration<? extends ZipEntry> entries = zip.entries();
211                         while (entries.hasMoreElements()) {
212                             final ZipEntry entry = entries.nextElement();
213                             final String entryName = entry.getName();
214
215                             if (entryName.startsWith(META_INF_YANG_STRING_JAR) && !entry.isDirectory()
216                                     && entryName.endsWith(RFC6020_YANG_FILE_EXTENSION)) {
217                                 foundFilesForReporting.add(entryName);
218
219                                 yangsFromDependencies.add(YangTextSchemaSource.delegateForByteSource(
220                                     entryName.substring(entryName.lastIndexOf('/') + 1),
221                                     ByteSource.wrap(ByteStreams.toByteArray(zip.getInputStream(entry)))));
222                             }
223                         }
224                     }
225                 }
226                 if (foundFilesForReporting.size() > 0) {
227                     LOG.info("{} Found {} yang files in {}: {}", LOG_PREFIX, foundFilesForReporting.size(), file,
228                         foundFilesForReporting);
229                 }
230
231             }
232
233             return yangsFromDependencies;
234         } catch (Exception e) {
235             throw new MojoFailureException(e.getMessage(), e);
236         }
237     }
238
239     /**
240      * Find all dependencies which contains yang sources.
241      * Returns collection of YANG files and Zip files which contains YANG files.
242      *
243      */
244     //  FIXME: Rename to what class is actually doing.
245     @SuppressWarnings("checkstyle:illegalCatch")
246     static Collection<File> findYangFilesInDependencies(final MavenProject project) throws MojoFailureException {
247         final List<File> yangsFilesFromDependencies = new ArrayList<>();
248
249         final List<File> filesOnCp;
250         try {
251             filesOnCp = Util.getClassPath(project);
252         } catch (Exception e) {
253             throw new MojoFailureException("Failed to scan for YANG files in dependencies", e);
254         }
255         LOG.info("{} Searching for yang files in following dependencies: {}", LOG_PREFIX, filesOnCp);
256
257         for (File file : filesOnCp) {
258             try {
259                 // is it jar file or directory?
260                 if (file.isDirectory()) {
261                     //FIXME: code duplicate
262                     File yangDir = new File(file, META_INF_YANG_STRING);
263                     if (yangDir.exists() && yangDir.isDirectory()) {
264                         File[] yangFiles = yangDir.listFiles(
265                             (dir, name) -> name.endsWith(RFC6020_YANG_FILE_EXTENSION) && new File(dir, name).isFile());
266
267                         yangsFilesFromDependencies.addAll(Arrays.asList(yangFiles));
268                     }
269                 } else {
270                     try (ZipFile zip = new ZipFile(file)) {
271
272                         final Enumeration<? extends ZipEntry> entries = zip.entries();
273                         while (entries.hasMoreElements()) {
274                             ZipEntry entry = entries.nextElement();
275                             String entryName = entry.getName();
276
277                             if (entryName.startsWith(META_INF_YANG_STRING_JAR)
278                                     && !entry.isDirectory() && entryName.endsWith(RFC6020_YANG_FILE_EXTENSION)) {
279                                 LOG.debug("{} Found a YANG file in {}: {}", LOG_PREFIX, file, entryName);
280                                 yangsFilesFromDependencies.add(file);
281                                 break;
282                             }
283                         }
284                     }
285                 }
286             } catch (Exception e) {
287                 throw new MojoFailureException("Failed to scan for YANG files in dependency: " + file.toString(), e);
288             }
289         }
290         return yangsFilesFromDependencies;
291     }
292
293     static SourceIdentifier moduleToIdentifier(final Module module) {
294         final QNameModule mod = module.getQNameModule();
295         final Date rev = mod.getRevision();
296         final com.google.common.base.Optional<String> optRev;
297         if (SimpleDateFormatUtil.DEFAULT_DATE_REV.equals(rev)) {
298             optRev = com.google.common.base.Optional.absent();
299         } else {
300             optRev = com.google.common.base.Optional.of(mod.getFormattedRevision());
301         }
302
303         return RevisionSourceIdentifier.create(module.getName(), optRev);
304     }
305 }