View Javadoc
1   package org.codehaus.mojo.jaxb2.shared.version;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.codehaus.mojo.jaxb2.shared.Validate;
23  
24  import java.io.BufferedReader;
25  import java.io.IOException;
26  import java.io.InputStreamReader;
27  import java.net.URL;
28  import java.util.Collections;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.SortedMap;
32  import java.util.StringTokenizer;
33  import java.util.TreeMap;
34  
35  /**
36   * Trivial parser to handle depends-plugin-style files.
37   *
38   * @author <a href="mailto:lj@jguru.se">Lennart J&ouml;relid</a>, jGuru Europe AB
39   * @since 2.0
40   */
41  public final class DependsFileParser {
42  
43      /**
44       * String indicating that a line in a dependencies.properties file contains a version definition.
45       */
46      private static final String VERSION_LINE_INDICATOR = "/version";
47  
48      /**
49       * String indicating that a line in a dependencies.properties file contains a type definition.
50       */
51      private static final String TYPE_LINE_INDICATOR = "/type";
52  
53      /**
54       * String indicating that a line in a dependencies.properties file contains a scope definition.
55       */
56      private static final String SCOPE_LINE_INDICATOR = "/scope";
57  
58      // Internal state
59      private static final String GROUP_ARTIFACT_SEPARATOR = "/";
60      private static final String KEY_VALUE_SEPARATOR = "=";
61      private static final String DEPENDENCIES_PROPERTIES_FILE = "META-INF/maven/dependencies.properties";
62      private static final String GENERATION_PREFIX = "# Generated at: ";
63  
64      /**
65       * The key where the build time as found within the dependencies.properties file is found.
66       */
67      public static final String BUILDTIME_KEY = "buildtime";
68  
69      /**
70       * The key holding the artifactId of this plugin (within the dependencies.properties file).
71       */
72      public static final String OWN_ARTIFACTID_KEY = "artifactId";
73  
74      /**
75       * The key holding the groupId of this plugin (within the dependencies.properties file).
76       */
77      public static final String OWN_GROUPID_KEY = "groupId";
78  
79      /**
80       * The key holding the version of this plugin (within the dependencies.properties file).
81       */
82      public static final String OWN_VERSION_KEY = "version";
83  
84      /**
85       * Hide constructors for utility classes
86       */
87      private DependsFileParser() {
88      }
89  
90      /**
91       * Extracts all build-time dependency information from a dependencies.properties file
92       * embedded in this plugin's JAR.
93       *
94       * @param artifactId This plugin's artifactId.
95       * @return A SortedMap relating [groupId]/[artifactId] keys to DependencyInfo values.
96       * @throws java.lang.IllegalStateException if no artifact in the current Thread's context ClassLoader
97       *                                         contained the supplied artifactNamePart.
98       */
99      public static SortedMap<String, String> getVersionMap(final String artifactId) {
100 
101         // Check sanity
102         Validate.notEmpty(artifactId, "artifactNamePart");
103 
104         Exception extractionException = null;
105 
106         try {
107             // Get the ClassLoader used
108             final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
109             final List<URL> manifestURLs = Collections.list(
110                     contextClassLoader.getResources(DEPENDENCIES_PROPERTIES_FILE));
111 
112             // Find the latest of the URLs matching, to cope with test-scope dependencies.
113             URL matching = null;
114             for (URL current : manifestURLs) {
115                 if (current.toString().contains(artifactId)) {
116                     matching = current;
117                 }
118             }
119 
120             if(matching != null) {
121                 return getVersionMap(matching);
122             }
123 
124         } catch (Exception e) {
125             extractionException = e;
126         }
127 
128         // We should never wind up here ...
129         if (extractionException != null) {
130             throw new IllegalStateException("Could not read data from manifest.", extractionException);
131         } else {
132             throw new IllegalStateException("Found no manifest corresponding to artifact name snippet '"
133                     + artifactId + "'.");
134         }
135     }
136 
137     /**
138      * Extracts all build-time dependency information from a dependencies.properties file
139      * embedded in this plugin's JAR.
140      *
141      * @param anURL The non-empty URL to a dependencies.properties file.
142      * @return A SortedMap holding all entries in the dependencies.properties file, plus its build
143      * time which is found under the {@code buildtime} key.
144      * @throws java.lang.IllegalStateException if no artifact in the current Thread's context ClassLoader
145      *                                         contained the supplied artifactNamePart.
146      */
147     public static SortedMap<String, String> getVersionMap(final URL anURL) {
148 
149         // Check sanity
150         Validate.notNull(anURL, "anURL");
151 
152         final SortedMap<String, String> toReturn = new TreeMap<String, String>();
153 
154         try {
155             final BufferedReader in = new BufferedReader(new InputStreamReader(anURL.openStream()));
156             String aLine = null;
157 
158             while ((aLine = in.readLine()) != null) {
159 
160                 final String trimmedLine = aLine.trim();
161 
162                 if (trimmedLine.contains(GENERATION_PREFIX)) {
163                     toReturn.put(BUILDTIME_KEY, aLine.substring(GENERATION_PREFIX.length()));
164                 } else if ("".equals(trimmedLine) || trimmedLine.startsWith("#")) {
165                     // Empty lines and comments should be ignored.
166                     continue;
167                 } else if (trimmedLine.contains("=")) {
168 
169                     // Stash this for later use.
170                     StringTokenizer tok = new StringTokenizer(trimmedLine, KEY_VALUE_SEPARATOR, false);
171                     Validate.isTrue(tok.countTokens() == 2, "Found incorrect dependency.properties line ["
172                             + aLine + "]");
173 
174                     final String key = tok.nextToken().trim();
175                     final String value = tok.nextToken().trim();
176 
177                     toReturn.put(key, value);
178                 }
179             }
180         } catch (IOException e) {
181             throw new IllegalStateException("Could not parse dependency properties '" + anURL.toString() + "'", e);
182         }
183 
184         // All done.
185         return toReturn;
186     }
187 
188     /**
189      * Converts a SortedMap received from a {@code getVersionMap} call to hold DependencyInfo values,
190      * and keys on the form {@code groupId/artifactId}.
191      *
192      * @param versionMap A non-null Map, as received from a call to {@code getVersionMap}.
193      * @return a SortedMap received from a {@code getVersionMap} call to hold DependencyInfo values,
194      * and keys on the form {@code groupId/artifactId}.
195      */
196     public static SortedMap<String, DependencyInfo> createDependencyInfoMap(
197             final SortedMap<String, String> versionMap) {
198 
199         // Check sanity
200         Validate.notNull(versionMap, "anURL");
201 
202         final SortedMap<String, DependencyInfo> toReturn = new TreeMap<String, DependencyInfo>();
203 
204         // First, only find the version lines.
205         for (Map.Entry<String, String> current : versionMap.entrySet()) {
206 
207             final String currentKey = current.getKey().trim();
208             if (currentKey.contains(VERSION_LINE_INDICATOR)) {
209 
210                 final StringTokenizer tok = new StringTokenizer(currentKey, GROUP_ARTIFACT_SEPARATOR, false);
211                 Validate.isTrue(tok.countTokens() == 3, "Expected key on the form [groupId]"
212                         + GROUP_ARTIFACT_SEPARATOR + "[artifactId]" + VERSION_LINE_INDICATOR + ", but got ["
213                         + currentKey + "]");
214 
215                 final String groupId = tok.nextToken();
216                 final String artifactId = tok.nextToken();
217 
218                 final DependencyInfo di = new DependencyInfo(groupId, artifactId, current.getValue());
219                 toReturn.put(di.getGroupArtifactKey(), di);
220             }
221         }
222 
223         for (Map.Entry<String, DependencyInfo> current : toReturn.entrySet()) {
224 
225             final String currentKey = current.getKey();
226             final DependencyInfo di = current.getValue();
227 
228             final String scope = versionMap.get(currentKey + SCOPE_LINE_INDICATOR);
229             final String type = versionMap.get(currentKey + TYPE_LINE_INDICATOR);
230 
231             if (scope != null) {
232                 di.setScope(scope);
233             }
234             if (type != null) {
235                 di.setType(type);
236             }
237         }
238 
239         // All done.
240         return toReturn;
241     }
242 }