/*
* 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.
*
*/
package org.apache.ivyde.common.ivyfile;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.ivy.core.module.id.ModuleId;
import org.apache.ivy.core.module.id.ModuleRevisionId;
import org.apache.ivy.util.FileUtil;
public class IvyFileUpdater {
private static final String NL = System.getProperty("line.separator");
private static final class UpdateInfo {
private int insertFromIndex = 0;
private int insertToIndex = 0;
private String prefix = "";
private String suffix = "";
private String insert = "";
private UpdateInfo() {
// nothing to do
}
}
public void addDependency(File ivyFile, String org, String name, String revision,
String confMapping) throws IOException {
ModuleRevisionId depId = new ModuleRevisionId(new ModuleId(org, name), revision);
addDependency(ivyFile, depId, confMapping);
}
public void addDependency(File ivyFile, ModuleRevisionId depId, String confMapping)
throws IOException {
String content = FileUtil.readEntirely(ivyFile);
UpdateInfo info = findUpdateInfoToAddDependency(content, depId, confMapping);
update(ivyFile, content, info);
}
/**
* Removes a direct dependency in the given Ivy file, or excludes it if it isn't a direct
* dependency, unless the Ivy file declares no dependency at all, in which case the file isn't
* changed.
*
* @param ivyFile
* the file pointing to the Ivy file to update
* @param depId
* the module id of the dependency to remove or exclude
*/
public void removeOrExcludeDependency(File ivyFile, ModuleId depId) throws IOException {
String content = FileUtil.readEntirely(ivyFile);
UpdateInfo info = findUpdateInfoToRemoveDependency(content, depId);
if (info != null) {
update(ivyFile, content, info);
}
}
private void update(File ivyFile, String content, UpdateInfo info)
throws FileNotFoundException {
PrintWriter w = new PrintWriter(new FileOutputStream(ivyFile));
try {
w.print(content.substring(0, info.insertFromIndex));
w.print(info.prefix);
w.print(info.insert);
w.print(info.suffix);
w.print(content.substring(info.insertToIndex));
w.flush();
} finally {
w.close();
}
}
private UpdateInfo findUpdateInfoToAddDependency(String content, ModuleRevisionId depId,
String confMapping) {
UpdateInfo info = new UpdateInfo();
info.insert = getDependencyToAdd(depId, confMapping);
Pattern dependenciesClose = Pattern.compile("<\\s*/dependencies");
Matcher depsCloseMatcher = dependenciesClose.matcher(content);
if (depsCloseMatcher.find()) {
info.insertFromIndex = findLastDependencyEnd(content, depsCloseMatcher.start());
if (info.insertFromIndex == -1) {
info.insertFromIndex = getLastMatchIndex(Pattern.compile("<\\s*dependencies.*?>"),
content, depsCloseMatcher.start());
if (info.insertFromIndex == -1) {
info.insertFromIndex = depsCloseMatcher.start();
} else {
info.prefix = NL;
}
} else {
info.prefix = NL;
}
info.insertToIndex = info.insertFromIndex;
return info;
}
Pattern depsOpenClose = Pattern.compile("<\\s*dependencies\\s*/>");
Matcher depsOpenCloseMatcher = depsOpenClose.matcher(content);
if (depsOpenCloseMatcher.find()) {
info.insertFromIndex = depsOpenCloseMatcher.start();
info.insertToIndex = depsOpenCloseMatcher.end();
info.prefix = "<dependencies>" + NL;
info.suffix = NL + " </dependencies>";
return info;
}
Pattern moduleClose = Pattern.compile("</\\s*ivy-module\\s*>");
Matcher moduleCloseMatcher = moduleClose.matcher(content);
if (moduleCloseMatcher.find()) {
info.insertFromIndex = moduleCloseMatcher.start();
info.insertToIndex = info.insertFromIndex;
info.prefix = " <dependencies>" + NL;
info.suffix = NL + " </dependencies>" + NL;
return info;
}
return info;
}
private UpdateInfo findUpdateInfoToRemoveDependency(String content, ModuleId depId) {
UpdateInfo info = new UpdateInfo();
Matcher depsMatcher = Pattern.compile("<\\s*dependencies").matcher(content);
if (!depsMatcher.find()) {
// no dependencies at all, nothing to do
return null;
}
Matcher depMatcher = Pattern.compile("<\\s*dependency\\s+.*name=[\"']([^\"']+)[\"']")
.matcher(content);
int start = depsMatcher.start();
while (depMatcher.find(start)) {
if (depId.getName().equals(depMatcher.group(1))) {
// we have found the dependency to remove: let's remove it
info.insertFromIndex = depMatcher.start();
Matcher m = Pattern.compile("</\\s*dependency\\s*>").matcher(content);
if (m.find(info.insertFromIndex)) {
info.insertToIndex = m.end();
}
m = Pattern.compile("<\\s*dependency[^<]*?\\/\\>").matcher(content);
if (m.find(info.insertFromIndex)) {
info.insertToIndex = info.insertToIndex > 0 ? Math.min(info.insertToIndex, m
.end()) : m.end();
}
info.insertFromIndex = findStartOfBlock(content, info.insertFromIndex);
info.insertToIndex = findEndOfBlock(content, info.insertToIndex);
return info;
}
start = depMatcher.end();
}
// we haven't found the dependency in the list of declared dependencies
if (start == depsMatcher.start()) {
// no dependencies at all, nothing to do
return null;
}
// there is at least one direct dependency, but not the one to remove, so we must exclude it
Matcher depsCloseMatcher = Pattern.compile("<\\s*/dependencies").matcher(content);
if (!depsCloseMatcher.find()) {
// no closing tag for dependencies, probably malformed xml, nothing to do
return null;
}
info.insertFromIndex = findLastDependencyEnd(content, depsCloseMatcher.start());
info.insertToIndex = info.insertFromIndex;
info.prefix = NL;
info.insert = getDependencyToExclude(depId);
return info;
}
private int findLastDependencyEnd(String content, int end) {
int depCloseIndex = getLastMatchIndex(Pattern.compile("</\\s*dependency\\s*>"), content,
end);
int depOpCloseIndex = getLastMatchIndex(Pattern.compile("\\<\\s*dependency.*?\\/\\>"),
content, end);
return Math.max(depCloseIndex, depOpCloseIndex);
}
private String getDependencyToAdd(ModuleRevisionId depId, String confMapping) {
String dep = " <dependency org=\"" + depId.getOrganisation() + "\"";
dep += " name=\"" + depId.getName() + "\"";
dep += " rev=\"" + depId.getRevision() + "\"";
if (confMapping != null) {
dep += " conf=\"" + confMapping + "\"";
}
dep += " />";
return dep;
}
private String getDependencyToExclude(ModuleId depId) {
String dep = " <exclude org=\"" + depId.getOrganisation() + "\"";
dep += " module=\"" + depId.getName() + "\"";
dep += " />";
return dep;
}
private int getLastMatchIndex(Pattern pattern, String content, int end) {
Matcher matcher = pattern.matcher(content);
int index = -1;
while (matcher.find(index + 1)) {
if (matcher.end() > end) {
return index;
} else {
index = matcher.end();
}
}
return index;
}
private int findStartOfBlock(String content, int index) {
for (index--; index >= 0; index--) {
char c = content.charAt(index);
if (c != ' ' && c != '\t') {
return index + 1;
}
}
return 0;
}
private int findEndOfBlock(String content, int index) {
for (; index < content.length(); index++) {
char c = content.charAt(index);
if (c != ' ' && c != '\t') {
if (c == '\n' || c == '\r') {
return index + 1;
}
return index;
}
}
return index - 1;
}
}