diff --git a/build.xml b/build.xml index 5c61cbe13de..7db866e4eb5 100644 --- a/build.xml +++ b/build.xml @@ -68,6 +68,7 @@ + diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index f9247518a4e..66ccb35f549 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -137,6 +137,9 @@ Build * LUCENE-5249: All Lucene/Solr modules should use the same dependency versions (Steve Rowe) + +* LUCENE-5257: Lock down centralized versioning of ivy dependencies + (Steve Rowe) ======================= Lucene 4.5.0 ======================= diff --git a/lucene/build.xml b/lucene/build.xml index 0e50f6f5cda..3f6fe0ecba6 100644 --- a/lucene/build.xml +++ b/lucene/build.xml @@ -169,6 +169,11 @@ + + + + diff --git a/lucene/ivy-versions.properties b/lucene/ivy-versions.properties index 2d5782f9a2c..5cb40c2c022 100644 --- a/lucene/ivy-versions.properties +++ b/lucene/ivy-versions.properties @@ -1,6 +1,13 @@ +# The /org/name keys in this file must be kept lexically sorted. +# Blank lines, comment lines, and keys that aren't in /org/name format are ignored +# when the lexical sort check is performed by the ant check-lib-versions target. + /cglib/cglib-nodep = 2.2 -/com.carrotsearch.randomizedtesting/junit4-ant = 2.0.10 -/com.carrotsearch.randomizedtesting/randomizedtesting-runner = 2.0.10 + +com.carrotsearch.randomizedtesting.version = 2.0.10 +/com.carrotsearch.randomizedtesting/junit4-ant = ${com.carrotsearch.randomizedtesting.version} +/com.carrotsearch.randomizedtesting/randomizedtesting-runner = ${com.carrotsearch.randomizedtesting.version} + /com.carrotsearch/hppc = 0.5.2 /com.cybozu.labs/langdetect = 1.1-20120112 /com.drewnoakes/metadata-extractor = 2.6.2 @@ -43,80 +50,116 @@ /org.apache.ant/ant = 1.8.2 /org.apache.commons/commons-compress = 1.4.1 /org.apache.derby/derby = 10.9.1.0 -hadoop.version = 2.0.5-alpha -/org.apache.hadoop/hadoop-annotations = ${hadoop.version} -/org.apache.hadoop/hadoop-auth = ${hadoop.version} -/org.apache.hadoop/hadoop-common = ${hadoop.version} -/org.apache.hadoop/hadoop-hdfs = ${hadoop.version} -/org.apache.httpcomponents/httpclient = 4.2.3 + +org.apache.hadoop.version = 2.0.5-alpha +/org.apache.hadoop/hadoop-annotations = ${org.apache.hadoop.version} +/org.apache.hadoop/hadoop-auth = ${org.apache.hadoop.version} +/org.apache.hadoop/hadoop-common = ${org.apache.hadoop.version} +/org.apache.hadoop/hadoop-hdfs = ${org.apache.hadoop.version} + +# The httpcore version is often different from the httpclient and httpmime versions, +# so the httpcore version value should not share the same symbolic name with them. /org.apache.httpcomponents/httpclient = 4.2.6 -/org.apache.httpcomponents/httpcore = 4.2.2 /org.apache.httpcomponents/httpcore = 4.2.5 /org.apache.httpcomponents/httpmime = 4.2.6 -/org.apache.james/apache-mime4j-core = 0.7.2 -/org.apache.james/apache-mime4j-dom = 0.7.2 + +org.apache.james.apache.mime4j = 0.7.2 +/org.apache.james/apache-mime4j-core = ${org.apache.james.apache.mime4j} +/org.apache.james/apache-mime4j-dom = ${org.apache.james.apache.mime4j} + /org.apache.mahout/mahout-collections = 1.0 /org.apache.mahout/mahout-math = 0.6 -/org.apache.pdfbox/fontbox = 1.8.1 -/org.apache.pdfbox/jempbox = 1.8.1 -/org.apache.pdfbox/pdfbox = 1.8.1 -/org.apache.poi/poi = 3.9 -/org.apache.poi/poi-ooxml = 3.9 -/org.apache.poi/poi-ooxml-schemas = 3.9 -/org.apache.poi/poi-scratchpad = 3.9 -/org.apache.tika/tika-core = 1.4 -/org.apache.tika/tika-parsers = 1.4 -/org.apache.uima/AlchemyAPIAnnotator = 2.3.1 -/org.apache.uima/OpenCalaisAnnotator = 2.3.1 -/org.apache.uima/Tagger = 2.3.1 -/org.apache.uima/WhitespaceTokenizer = 2.3.1 -/org.apache.uima/uimaj-core = 2.3.1 + +org.apache.pdfbox.version = 1.8.1 +/org.apache.pdfbox/fontbox = ${org.apache.pdfbox.version} +/org.apache.pdfbox/jempbox = ${org.apache.pdfbox.version} +/org.apache.pdfbox/pdfbox = ${org.apache.pdfbox.version} + +org.apache.poi.version = 3.9 +/org.apache.poi/poi = ${org.apache.poi.version} +/org.apache.poi/poi-ooxml = ${org.apache.poi.version} +/org.apache.poi/poi-ooxml-schemas = ${org.apache.poi.version} +/org.apache.poi/poi-scratchpad = ${org.apache.poi.version} + +org.apache.tika.version = 1.4 +/org.apache.tika/tika-core = ${org.apache.tika.version} +/org.apache.tika/tika-parsers = ${org.apache.tika.version} + +org.apache.uima.version = 2.3.1 +/org.apache.uima/AlchemyAPIAnnotator = ${org.apache.uima.version} +/org.apache.uima/OpenCalaisAnnotator = ${org.apache.uima.version} +/org.apache.uima/Tagger = ${org.apache.uima.version} +/org.apache.uima/WhitespaceTokenizer = ${org.apache.uima.version} +/org.apache.uima/uimaj-core = ${org.apache.uima.version} + /org.apache.velocity/velocity = 1.7 /org.apache.velocity/velocity-tools = 2.0 /org.apache.xmlbeans/xmlbeans = 2.3.0 /org.apache.zookeeper/zookeeper = 3.4.5 -/org.bouncycastle/bcmail-jdk15 = 1.45 -/org.bouncycastle/bcprov-jdk15 = 1.45 + +org.bouncycastle.version = 1.45 +/org.bouncycastle/bcmail-jdk15 = ${org.bouncycastle.version} +/org.bouncycastle/bcprov-jdk15 = ${org.bouncycastle.version} + /org.carrot2.attributes/attributes-binder = 1.2.0 /org.carrot2/carrot2-mini = 3.8.0 -/org.carrot2/morfologik-fsa = 1.7.1 -/org.carrot2/morfologik-polish = 1.7.1 -/org.carrot2/morfologik-stemming = 1.7.1 + +org.carrot2.morfologik.version = 1.7.1 +/org.carrot2/morfologik-fsa = ${org.carrot2.morfologik.version} +/org.carrot2/morfologik-polish = ${org.carrot2.morfologik.version} +/org.carrot2/morfologik-stemming = ${org.carrot2.morfologik.version} + /org.ccil.cowan.tagsoup/tagsoup = 1.2.1 -/org.codehaus.jackson/jackson-core-asl = 1.7.4 -/org.codehaus.jackson/jackson-mapper-asl = 1.7.4 + +org.codehaus.jackson.version = 1.7.4 +/org.codehaus.jackson/jackson-core-asl = ${org.codehaus.jackson.version} +/org.codehaus.jackson/jackson-mapper-asl = ${org.codehaus.jackson.version} + /org.codehaus.woodstox/wstx-asl = 3.2.7 /org.easymock/easymock = 3.0 /org.eclipse.jetty.orbit/javax.servlet = 3.0.0.v201112011016 -jetty.version = 8.1.10.v20130312 -/org.eclipse.jetty/jetty-continuation = ${jetty.version} -/org.eclipse.jetty/jetty-deploy = ${jetty.version} -/org.eclipse.jetty/jetty-http = ${jetty.version} -/org.eclipse.jetty/jetty-io = ${jetty.version} -/org.eclipse.jetty/jetty-jmx = ${jetty.version} -/org.eclipse.jetty/jetty-security = ${jetty.version} -/org.eclipse.jetty/jetty-server = ${jetty.version} -/org.eclipse.jetty/jetty-servlet = ${jetty.version} -/org.eclipse.jetty/jetty-start = ${jetty.version} -/org.eclipse.jetty/jetty-util = ${jetty.version} -/org.eclipse.jetty/jetty-webapp = ${jetty.version} -/org.eclipse.jetty/jetty-xml = ${jetty.version} -/org.gagravarr/vorbis-java-core = 0.1 -/org.gagravarr/vorbis-java-tika = 0.1 -/org.mortbay.jetty/jetty = 6.1.26 -/org.mortbay.jetty/jetty-util = 6.1.26 + +org.eclipse.jetty.version = 8.1.10.v20130312 +/org.eclipse.jetty/jetty-continuation = ${org.eclipse.jetty.version} +/org.eclipse.jetty/jetty-deploy = ${org.eclipse.jetty.version} +/org.eclipse.jetty/jetty-http = ${org.eclipse.jetty.version} +/org.eclipse.jetty/jetty-io = ${org.eclipse.jetty.version} +/org.eclipse.jetty/jetty-jmx = ${org.eclipse.jetty.version} +/org.eclipse.jetty/jetty-security = ${org.eclipse.jetty.version} +/org.eclipse.jetty/jetty-server = ${org.eclipse.jetty.version} +/org.eclipse.jetty/jetty-servlet = ${org.eclipse.jetty.version} +/org.eclipse.jetty/jetty-start = ${org.eclipse.jetty.version} +/org.eclipse.jetty/jetty-util = ${org.eclipse.jetty.version} +/org.eclipse.jetty/jetty-webapp = ${org.eclipse.jetty.version} +/org.eclipse.jetty/jetty-xml = ${org.eclipse.jetty.version} + +org.gagravarr.vorbis.java.version = 0.1 +/org.gagravarr/vorbis-java-core = ${org.gagravarr.vorbis.java.version} +/org.gagravarr/vorbis-java-tika = ${org.gagravarr.vorbis.java.version} + +org.mortbay.jetty.version = 6.1.26 +/org.mortbay.jetty/jetty = ${org.mortbay.jetty.version} +/org.mortbay.jetty/jetty-util = ${org.mortbay.jetty.version} + /org.noggit/noggit = 0.5 /org.objenesis/objenesis = 1.2 -/org.ow2.asm/asm = 4.1 -/org.ow2.asm/asm-commons = 4.1 -/org.restlet.jee/org.restlet = 2.1.1 -/org.restlet.jee/org.restlet.ext.servlet = 2.1.1 + +org.ow2.asm.version = 4.1 +/org.ow2.asm/asm = ${org.ow2.asm.version} +/org.ow2.asm/asm-commons = ${org.ow2.asm.version} + +org.restlet.jee.version = 2.1.1 +/org.restlet.jee/org.restlet = ${org.restlet.jee.version} +/org.restlet.jee/org.restlet.ext.servlet = ${org.restlet.jee.version} + /org.simpleframework/simple-xml = 2.7 -/org.slf4j/jcl-over-slf4j = 1.6.6 -/org.slf4j/jul-to-slf4j = 1.6.6 -/org.slf4j/slf4j-api = 1.6.6 -/org.slf4j/slf4j-log4j12 = 1.6.6 + +org.slf4j.version = 1.6.6 +/org.slf4j/jcl-over-slf4j = ${org.slf4j.version} +/org.slf4j/jul-to-slf4j = ${org.slf4j.version} +/org.slf4j/slf4j-api = ${org.slf4j.version} +/org.slf4j/slf4j-log4j12 = ${org.slf4j.version} + /org.tukaani/xz = 1.0 /rome/rome = 0.9 /xerces/xercesImpl = 2.9.1 - diff --git a/lucene/tools/custom-tasks.xml b/lucene/tools/custom-tasks.xml index e230bb39da3..4fc1645beae 100644 --- a/lucene/tools/custom-tasks.xml +++ b/lucene/tools/custom-tasks.xml @@ -84,4 +84,29 @@ + + + + + + + Lib versions check under: @{dir} + + + + + + + + + + + + + + diff --git a/lucene/tools/src/java/lucene-solr.antlib.xml b/lucene/tools/src/java/lucene-solr.antlib.xml index 6ab57c6695a..0b6bef92363 100644 --- a/lucene/tools/src/java/lucene-solr.antlib.xml +++ b/lucene/tools/src/java/lucene-solr.antlib.xml @@ -18,4 +18,7 @@ + diff --git a/lucene/tools/src/java/org/apache/lucene/validation/LibVersionsCheckTask.java b/lucene/tools/src/java/org/apache/lucene/validation/LibVersionsCheckTask.java new file mode 100644 index 00000000000..2b29fc194c0 --- /dev/null +++ b/lucene/tools/src/java/org/apache/lucene/validation/LibVersionsCheckTask.java @@ -0,0 +1,385 @@ +package org.apache.lucene.validation; + +/* + * 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.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Resource; +import org.apache.tools.ant.types.ResourceCollection; +import org.apache.tools.ant.types.resources.FileResource; +import org.apache.tools.ant.types.resources.Resources; +import org.apache.tools.ant.util.FileNameMapper; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; +import org.xml.sax.helpers.XMLReaderFactory; + +import javax.xml.parsers.ParserConfigurationException; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CodingErrorAction; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Stack; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * An Ant task to verify that the '/org/name' keys in ivy-versions.properties + * are sorted lexically and are neither duplicates nor orphans, and that all + * dependencies in all ivy.xml files use rev="${/org/name}" format. + */ +public class LibVersionsCheckTask extends Task { + + private static final String IVY_XML_FILENAME = "ivy.xml"; + private static final Pattern COORDINATE_KEY_PATTERN = Pattern.compile("(/[^/ \t\f]+/[^=:/ \t\f]+).*"); + private static final Pattern BLANK_OR_COMMENT_LINE_PATTERN = Pattern.compile("[ \t\f]*(?:[#!].*)?"); + private static final Pattern TRAILING_BACKSLASH_PATTERN = Pattern.compile("[^\\\\]*(\\\\+)$"); + private static final Pattern LEADING_WHITESPACE_PATTERN = Pattern.compile("[ \t\f]+(.*)"); + private static final Pattern WHITESPACE_GOODSTUFF_WHITESPACE_BACKSLASH_PATTERN + = Pattern.compile("[ \t\f]*(.*?)(?:(? referencedCoordinateKeys = new LinkedHashMap(); + + /** + * Adds a set of ivy.xml resources to check. + */ + public void add(ResourceCollection rc) { + ivyXmlResources.add(rc); + } + + public void setVerbose(boolean verbose) { + verboseLevel = (verbose ? Project.MSG_INFO : Project.MSG_VERBOSE); + } + + public void setCentralizedVersionsFile(File file) { + centralizedVersionsFile = file; + } + + /** + * Execute the task. + */ + @Override + public void execute() throws BuildException { + log("Starting scan.", verboseLevel); + long start = System.currentTimeMillis(); + + int errors = verifySortedCentralizedVersionsFile() ? 0 : 1; + int checked = 0; + + @SuppressWarnings("unchecked") + Iterator iter = (Iterator)ivyXmlResources.iterator(); + while (iter.hasNext()) { + final Resource resource = iter.next(); + if ( ! resource.isExists()) { + throw new BuildException("Resource does not exist: " + resource.getName()); + } + if ( ! (resource instanceof FileResource)) { + throw new BuildException("Only filesystem resources are supported: " + + resource.getName() + ", was: " + resource.getClass().getName()); + } + + File ivyXmlFile = ((FileResource)resource).getFile(); + try { + if ( ! checkIvyXmlFile(ivyXmlFile) ) { + failures = true; + errors++; + } + } catch (Exception e) { + throw new BuildException("Exception reading file " + ivyXmlFile.getPath(), e); + } + checked++; + } + log("Checking for orphans in " + centralizedVersionsFile.getName(), verboseLevel); + for (Map.Entry entry : referencedCoordinateKeys.entrySet()) { + String coordinateKey = entry.getKey(); + boolean isReferenced = entry.getValue(); + if ( ! isReferenced) { + log("ORPHAN coordinate key '" + coordinateKey + "' in " + centralizedVersionsFile.getName() + + " is not found in any " + IVY_XML_FILENAME + " file.", + Project.MSG_ERR); + failures = true; + errors++; + } + } + + log(String.format(Locale.ROOT, "Checked that %s has lexically sorted " + + "'/org/name' keys and no duplicates or orphans, and scanned %d %s " + + "file(s) for rev=\"${/org/name}\" format (in %.2fs.), %d error(s).", + centralizedVersionsFile.getName(), checked, IVY_XML_FILENAME, + (System.currentTimeMillis() - start) / 1000.0, errors), + errors > 0 ? Project.MSG_ERR : Project.MSG_INFO); + + if (failures) { + throw new BuildException("Lib versions check failed. Check the logs."); + } + } + + /** + * Returns true if the "/org/name" coordinate keys in ivy-versions.properties + * are lexically sorted and are not duplicates. + */ + private boolean verifySortedCentralizedVersionsFile() { + log("Checking for lexically sorted non-duplicated '/org/name' keys in: " + centralizedVersionsFile, verboseLevel); + final InputStream stream; + try { + stream = new FileInputStream(centralizedVersionsFile); + } catch (FileNotFoundException e) { + throw new BuildException("Centralized versions file does not exist: " + + centralizedVersionsFile.getPath()); + } + // Properties files are encoded as Latin-1 + final Reader reader = new InputStreamReader(stream, Charset.forName("ISO-8859-1")); + final BufferedReader bufferedReader = new BufferedReader(reader); + + String line = null; + String currentKey = null; + String previousKey = null; + try { + while (null != (line = readLogicalPropertiesLine(bufferedReader))) { + final Matcher keyMatcher = COORDINATE_KEY_PATTERN.matcher(line); + if ( ! keyMatcher.matches()) { + continue; // Ignore keys that don't look like "/org/name" + } + currentKey = keyMatcher.group(1); + if (null != previousKey) { + int comparison = currentKey.compareTo(previousKey); + if (0 == comparison) { + log("DUPLICATE coordinate key '" + currentKey + "' in " + centralizedVersionsFile.getName(), + Project.MSG_ERR); + failures = true; + } else if (comparison < 0) { + log("OUT-OF-ORDER coordinate key '" + currentKey + "' in " + centralizedVersionsFile.getName(), + Project.MSG_ERR); + failures = true; + } + } + referencedCoordinateKeys.put(currentKey, false); + previousKey = currentKey; + } + } catch (IOException e) { + throw new BuildException("Exception reading centralized versions file: " + + centralizedVersionsFile.getPath(), e); + } finally { + try { reader.close(); } catch (IOException e) { } + } + return ! failures; + } + + /** + * Builds up logical {@link java.util.Properties} lines, composed of one non-blank, + * non-comment initial line, either: + * + * 1. without a non-escaped trailing slash; or + * 2. with a non-escaped trailing slash, followed by + * zero or more lines with a non-escaped trailing slash, followed by + * one or more lines without a non-escaped trailing slash + * + * All leading non-escaped whitespace and trailing non-escaped whitespace + + * non-escaped slash are trimmed from each line before concatenating. + * + * After composing the logical line, escaped characters are un-escaped. + * + * null is returned if there are no lines left to read. + */ + private String readLogicalPropertiesLine(BufferedReader reader) throws IOException { + final StringBuilder logicalLine = new StringBuilder(); + String line; + do { + line = reader.readLine(); + if (null == line) { + return null; + } + } while (BLANK_OR_COMMENT_LINE_PATTERN.matcher(line).matches()); + + Matcher backslashMatcher = TRAILING_BACKSLASH_PATTERN.matcher(line); + // Check for a non-escaped backslash + if (backslashMatcher.find() && 1 == (backslashMatcher.group(1).length() % 2)) { + final Matcher firstLineMatcher = TRAILING_WHITESPACE_BACKSLASH_PATTERN.matcher(line); + if (firstLineMatcher.matches()) { + logicalLine.append(firstLineMatcher.group(1)); // trim trailing backslash and any preceding whitespace + } + line = reader.readLine(); + while (null != line + && (backslashMatcher = TRAILING_BACKSLASH_PATTERN.matcher(line)).find() + && 1 == (backslashMatcher.group(1).length() % 2)) { + // Trim leading whitespace, the trailing backslash and any preceding whitespace + final Matcher goodStuffMatcher = WHITESPACE_GOODSTUFF_WHITESPACE_BACKSLASH_PATTERN.matcher(line); + if (goodStuffMatcher.matches()) { + logicalLine.append(goodStuffMatcher.group(1)); + } + line = reader.readLine(); + } + if (null != line) { + // line can't have a non-escaped trailing backslash + final Matcher leadingWhitespaceMatcher = LEADING_WHITESPACE_PATTERN.matcher(line); + if (leadingWhitespaceMatcher.matches()) { + line = leadingWhitespaceMatcher.group(1); // trim leading whitespace + } + logicalLine.append(line); + } + } else { + logicalLine.append(line); + } + // trim non-escaped leading whitespace + final Matcher leadingWhitespaceMatcher = LEADING_WHITESPACE_PATTERN.matcher(logicalLine); + final CharSequence leadingWhitespaceStripped = leadingWhitespaceMatcher.matches() + ? leadingWhitespaceMatcher.group(1) + : logicalLine; + + // unescape all chars in the logical line + StringBuilder output = new StringBuilder(); + final int numChars = leadingWhitespaceStripped.length(); + for (int pos = 0 ; pos < numChars - 1 ; ++pos) { + char ch = leadingWhitespaceStripped.charAt(pos); + if (ch == '\\') { + ch = leadingWhitespaceStripped.charAt(++pos); + } + output.append(ch); + } + if (numChars > 0) { + output.append(leadingWhitespaceStripped.charAt(numChars - 1)); + } + + return output.toString(); + } + + /** + * Check a single ivy.xml file for dependencies' versions in rev="${/org/name}" + * format. Returns false if problems are found, true otherwise. + */ + private boolean checkIvyXmlFile(File ivyXmlFile) + throws ParserConfigurationException, SAXException, IOException { + log("Scanning: " + ivyXmlFile.getPath(), verboseLevel); + XMLReader xmlReader = XMLReaderFactory.createXMLReader(); + DependencyRevChecker revChecker = new DependencyRevChecker(ivyXmlFile); + xmlReader.setContentHandler(revChecker); + xmlReader.setErrorHandler(revChecker); + // To work around a bug in XERCES (XERCESJ-1257), we assume the XML is always UTF8, so we simply provide reader. + CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder() + .onMalformedInput(CodingErrorAction.REPORT) + .onUnmappableCharacter(CodingErrorAction.REPORT); + InputStream stream = new FileInputStream(ivyXmlFile); + xmlReader.parse(new InputSource(new BufferedReader(new InputStreamReader(stream, decoder)))); + return ! revChecker.fail; + } + + private class DependencyRevChecker extends DefaultHandler { + private final File ivyXmlFile; + private final Stack tags = new Stack(); + + public boolean fail = false; + + public DependencyRevChecker(File ivyXmlFile) { + this.ivyXmlFile = ivyXmlFile; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + if (localName.equals("dependency") && insideDependenciesTag()) { + String org = attributes.getValue("org"); + boolean foundAllAttributes = true; + if (null == org) { + log("MISSING 'org' attribute on in " + ivyXmlFile.getPath(), Project.MSG_ERR); + fail = true; + foundAllAttributes = false; + } + String name = attributes.getValue("name"); + if (null == name) { + log("MISSING 'name' attribute on in " + ivyXmlFile.getPath(), Project.MSG_ERR); + fail = true; + foundAllAttributes = false; + } + String rev = attributes.getValue("rev"); + if (null == rev) { + log("MISSING 'rev' attribute on in " + ivyXmlFile.getPath(), Project.MSG_ERR); + fail = true; + foundAllAttributes = false; + } + if (foundAllAttributes) { + String coordinateKey = "/" + org + '/' + name; + String expectedRev = "${" + coordinateKey + '}'; + if ( ! rev.equals(expectedRev)) { + log("BAD 'rev' attribute value '" + rev + "' - expected '" + expectedRev + "'" + + " in " + ivyXmlFile.getPath(), Project.MSG_ERR); + fail = true; + } + if ( ! referencedCoordinateKeys.containsKey(coordinateKey)) { + log("MISSING key '" + coordinateKey + "' in " + centralizedVersionsFile.getPath(), Project.MSG_ERR); + fail = true; + } + referencedCoordinateKeys.put(coordinateKey, true); + } + } + tags.push(localName); + } + + @Override + public void endElement (String uri, String localName, String qName) throws SAXException { + tags.pop(); + } + + private boolean insideDependenciesTag() { + return tags.size() == 2 && tags.get(0).equals("ivy-module") && tags.get(1).equals("dependencies"); + } + } +}