View Javadoc
1   /*
2    * Copyright 2013 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  
17  package io.netty.util;
18  
19  import io.netty.util.internal.PlatformDependent;
20  
21  import java.io.InputStream;
22  import java.net.URL;
23  import java.text.ParseException;
24  import java.text.SimpleDateFormat;
25  import java.util.Enumeration;
26  import java.util.HashSet;
27  import java.util.Map;
28  import java.util.Properties;
29  import java.util.Set;
30  import java.util.TreeMap;
31  
32  /**
33   * Retrieves the version information of available Netty artifacts.
34   * <p>
35   * This class retrieves the version information from {@code META-INF/io.netty.versions.properties}, which is
36   * generated in build time.  Note that it may not be possible to retrieve the information completely, depending on
37   * your environment, such as the specified {@link ClassLoader}, the current {@link SecurityManager}.
38   * </p>
39   */
40  public final class Version {
41  
42      private static final String PROP_VERSION = ".version";
43      private static final String PROP_BUILD_DATE = ".buildDate";
44      private static final String PROP_COMMIT_DATE = ".commitDate";
45      private static final String PROP_SHORT_COMMIT_HASH = ".shortCommitHash";
46      private static final String PROP_LONG_COMMIT_HASH = ".longCommitHash";
47      private static final String PROP_REPO_STATUS = ".repoStatus";
48  
49      /**
50       * Retrieves the version information of Netty artifacts using the current
51       * {@linkplain Thread#getContextClassLoader() context class loader}.
52       *
53       * @return A {@link Map} whose keys are Maven artifact IDs and whose values are {@link Version}s
54       */
55      public static Map<String, Version> identify() {
56          return identify(null);
57      }
58  
59      /**
60       * Retrieves the version information of Netty artifacts using the specified {@link ClassLoader}.
61       *
62       * @return A {@link Map} whose keys are Maven artifact IDs and whose values are {@link Version}s
63       */
64      public static Map<String, Version> identify(ClassLoader classLoader) {
65          if (classLoader == null) {
66              classLoader = PlatformDependent.getContextClassLoader();
67          }
68  
69          // Collect all properties.
70          Properties props = new Properties();
71          try {
72              Enumeration<URL> resources = classLoader.getResources("META-INF/io.netty.versions.properties");
73              while (resources.hasMoreElements()) {
74                  URL url = resources.nextElement();
75                  InputStream in = url.openStream();
76                  try {
77                      props.load(in);
78                  } finally {
79                      try {
80                          in.close();
81                      } catch (Exception ignore) {
82                          // Ignore.
83                      }
84                  }
85              }
86          } catch (Exception ignore) {
87              // Not critical. Just ignore.
88          }
89  
90          // Collect all artifactIds.
91          Set<String> artifactIds = new HashSet<String>();
92          for (Object o: props.keySet()) {
93              String k = (String) o;
94  
95              int dotIndex = k.indexOf('.');
96              if (dotIndex <= 0) {
97                  continue;
98              }
99  
100             String artifactId = k.substring(0, dotIndex);
101 
102             // Skip the entries without required information.
103             if (!props.containsKey(artifactId + PROP_VERSION) ||
104                 !props.containsKey(artifactId + PROP_BUILD_DATE) ||
105                 !props.containsKey(artifactId + PROP_COMMIT_DATE) ||
106                 !props.containsKey(artifactId + PROP_SHORT_COMMIT_HASH) ||
107                 !props.containsKey(artifactId + PROP_LONG_COMMIT_HASH) ||
108                 !props.containsKey(artifactId + PROP_REPO_STATUS)) {
109                 continue;
110             }
111 
112             artifactIds.add(artifactId);
113         }
114 
115         Map<String, Version> versions = new TreeMap<String, Version>();
116         for (String artifactId: artifactIds) {
117             versions.put(
118                     artifactId,
119                     new Version(
120                             artifactId,
121                             props.getProperty(artifactId + PROP_VERSION),
122                             parseIso8601(props.getProperty(artifactId + PROP_BUILD_DATE)),
123                             parseIso8601(props.getProperty(artifactId + PROP_COMMIT_DATE)),
124                             props.getProperty(artifactId + PROP_SHORT_COMMIT_HASH),
125                             props.getProperty(artifactId + PROP_LONG_COMMIT_HASH),
126                             props.getProperty(artifactId + PROP_REPO_STATUS)));
127         }
128 
129         return versions;
130     }
131 
132     private static long parseIso8601(String value) {
133         try {
134             return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").parse(value).getTime();
135         } catch (ParseException ignored) {
136             return 0;
137         }
138     }
139 
140     /**
141      * Prints the version information to {@link System#err}.
142      */
143     public static void main(String[] args) {
144         for (Version v: identify().values()) {
145             System.err.println(v);
146         }
147     }
148 
149     private final String artifactId;
150     private final String artifactVersion;
151     private final long buildTimeMillis;
152     private final long commitTimeMillis;
153     private final String shortCommitHash;
154     private final String longCommitHash;
155     private final String repositoryStatus;
156 
157     private Version(
158             String artifactId, String artifactVersion,
159             long buildTimeMillis, long commitTimeMillis,
160             String shortCommitHash, String longCommitHash, String repositoryStatus) {
161         this.artifactId = artifactId;
162         this.artifactVersion = artifactVersion;
163         this.buildTimeMillis = buildTimeMillis;
164         this.commitTimeMillis = commitTimeMillis;
165         this.shortCommitHash = shortCommitHash;
166         this.longCommitHash = longCommitHash;
167         this.repositoryStatus = repositoryStatus;
168     }
169 
170     public String artifactId() {
171         return artifactId;
172     }
173 
174     public String artifactVersion() {
175         return artifactVersion;
176     }
177 
178     public long buildTimeMillis() {
179         return buildTimeMillis;
180     }
181 
182     public long commitTimeMillis() {
183         return commitTimeMillis;
184     }
185 
186     public String shortCommitHash() {
187         return shortCommitHash;
188     }
189 
190     public String longCommitHash() {
191         return longCommitHash;
192     }
193 
194     public String repositoryStatus() {
195         return repositoryStatus;
196     }
197 
198     @Override
199     public String toString() {
200         return artifactId + '-' + artifactVersion + '.' + shortCommitHash +
201                ("clean".equals(repositoryStatus)? "" : " (repository: " + repositoryStatus + ')');
202     }
203 }