package org.codehaus.mojo.jaxb2.shared.version; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import org.codehaus.mojo.jaxb2.shared.Validate; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.URL; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.SortedMap; import java.util.StringTokenizer; import java.util.TreeMap; /** * Trivial parser to handle depends-plugin-style files. * * @author <a href="mailto:lj@jguru.se">Lennart Jörelid</a>, jGuru Europe AB * @since 2.0 */ public final class DependsFileParser { /** * String indicating that a line in a dependencies.properties file contains a version definition. */ private static final String VERSION_LINE_INDICATOR = "/version"; /** * String indicating that a line in a dependencies.properties file contains a type definition. */ private static final String TYPE_LINE_INDICATOR = "/type"; /** * String indicating that a line in a dependencies.properties file contains a scope definition. */ private static final String SCOPE_LINE_INDICATOR = "/scope"; // Internal state private static final String GROUP_ARTIFACT_SEPARATOR = "/"; private static final String KEY_VALUE_SEPARATOR = "="; private static final String DEPENDENCIES_PROPERTIES_FILE = "META-INF/maven/dependencies.properties"; private static final String GENERATION_PREFIX = "# Generated at: "; /** * The key where the build time as found within the dependencies.properties file is found. */ public static final String BUILDTIME_KEY = "buildtime"; /** * The key holding the artifactId of this plugin (within the dependencies.properties file). */ public static final String OWN_ARTIFACTID_KEY = "artifactId"; /** * The key holding the groupId of this plugin (within the dependencies.properties file). */ public static final String OWN_GROUPID_KEY = "groupId"; /** * The key holding the version of this plugin (within the dependencies.properties file). */ public static final String OWN_VERSION_KEY = "version"; /** * Hide constructors for utility classes */ private DependsFileParser() { } /** * Extracts all build-time dependency information from a dependencies.properties file * embedded in this plugin's JAR. * * @param artifactId This plugin's artifactId. * @return A SortedMap relating [groupId]/[artifactId] keys to DependencyInfo values. * @throws java.lang.IllegalStateException if no artifact in the current Thread's context ClassLoader * contained the supplied artifactNamePart. */ public static SortedMap<String, String> getVersionMap(final String artifactId) { // Check sanity Validate.notEmpty(artifactId, "artifactNamePart"); Exception extractionException = null; try { // Get the ClassLoader used final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); final List<URL> manifestURLs = Collections.list( contextClassLoader.getResources(DEPENDENCIES_PROPERTIES_FILE)); // Find the latest of the URLs matching, to cope with test-scope dependencies. URL matching = null; for (URL current : manifestURLs) { if (current.toString().contains(artifactId)) { matching = current; } } if (matching != null) { return getVersionMap(matching); } } catch (Exception e) { extractionException = e; } // We should never wind up here ... if (extractionException != null) { throw new IllegalStateException("Could not read data from manifest.", extractionException); } else { throw new IllegalStateException("Found no manifest corresponding to artifact name snippet '" + artifactId + "'."); } } /** * Extracts all build-time dependency information from a dependencies.properties file * embedded in this plugin's JAR. * * @param anURL The non-empty URL to a dependencies.properties file. * @return A SortedMap holding all entries in the dependencies.properties file, plus its build * time which is found under the {@code buildtime} key. * @throws java.lang.IllegalStateException if no artifact in the current Thread's context ClassLoader * contained the supplied artifactNamePart. */ public static SortedMap<String, String> getVersionMap(final URL anURL) { // Check sanity Validate.notNull(anURL, "anURL"); final SortedMap<String, String> toReturn = new TreeMap<String, String>(); try { final BufferedReader in = new BufferedReader(new InputStreamReader(anURL.openStream())); String aLine = null; while ((aLine = in.readLine()) != null) { final String trimmedLine = aLine.trim(); if (trimmedLine.contains(GENERATION_PREFIX)) { toReturn.put(BUILDTIME_KEY, aLine.substring(GENERATION_PREFIX.length())); } else if ("".equals(trimmedLine) || trimmedLine.startsWith("#")) { // Empty lines and comments should be ignored. continue; } else if (trimmedLine.contains("=")) { // Stash this for later use. StringTokenizer tok = new StringTokenizer(trimmedLine, KEY_VALUE_SEPARATOR, false); Validate.isTrue(tok.countTokens() == 2, "Found incorrect dependency.properties line [" + aLine + "]"); final String key = tok.nextToken().trim(); final String value = tok.nextToken().trim(); toReturn.put(key, value); } } } catch (IOException e) { throw new IllegalStateException("Could not parse dependency properties '" + anURL.toString() + "'", e); } // All done. return toReturn; } /** * Converts a SortedMap received from a {@code getVersionMap} call to hold DependencyInfo values, * and keys on the form {@code groupId/artifactId}. * * @param versionMap A non-null Map, as received from a call to {@code getVersionMap}. * @return a SortedMap received from a {@code getVersionMap} call to hold DependencyInfo values, * and keys on the form {@code groupId/artifactId}. */ public static SortedMap<String, DependencyInfo> createDependencyInfoMap( final SortedMap<String, String> versionMap) { // Check sanity Validate.notNull(versionMap, "anURL"); final SortedMap<String, DependencyInfo> toReturn = new TreeMap<String, DependencyInfo>(); // First, only find the version lines. for (Map.Entry<String, String> current : versionMap.entrySet()) { final String currentKey = current.getKey().trim(); if (currentKey.contains(VERSION_LINE_INDICATOR)) { final StringTokenizer tok = new StringTokenizer(currentKey, GROUP_ARTIFACT_SEPARATOR, false); Validate.isTrue(tok.countTokens() == 3, "Expected key on the form [groupId]" + GROUP_ARTIFACT_SEPARATOR + "[artifactId]" + VERSION_LINE_INDICATOR + ", but got [" + currentKey + "]"); final String groupId = tok.nextToken(); final String artifactId = tok.nextToken(); final DependencyInfo di = new DependencyInfo(groupId, artifactId, current.getValue()); toReturn.put(di.getGroupArtifactKey(), di); } } for (Map.Entry<String, DependencyInfo> current : toReturn.entrySet()) { final String currentKey = current.getKey(); final DependencyInfo di = current.getValue(); final String scope = versionMap.get(currentKey + SCOPE_LINE_INDICATOR); final String type = versionMap.get(currentKey + TYPE_LINE_INDICATOR); if (scope != null) { di.setScope(scope); } if (type != null) { di.setType(type); } } // All done. return toReturn; } }