/*
* Update
* Copyright (C) 2009 John Pritchard
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.channels.FileChannel;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
/**
* Accept source and target path expressions to update. One or more
* files in the source path are copied to one or more locations in the
* target path.
*
* Perform GIT or SVN file management as available and found at the
* target location.
*
* Perform version management for <code>"name-VERSION.jar"</code>
* files. Old versions are deleted, new versions are added on git and
* subversion repositories -- for a numeric VERSION. The VERSION
* pattern is <code>[0-9.]+</code>. The managed file name pattern is
* very highly defined in order to prevent unintended consequences of
* the feature under various applications.
*
* @author jdp
*/
public class Update
extends Object
implements java.io.FileFilter
{
public static void usage(){
System.err.println("Usage");
System.err.println();
System.err.println(" Update source[':'source]* target[':'target]* [update.properties]");
System.err.println();
System.err.println("Description");
System.err.println();
System.err.println(" Copy one or more source files into the");
System.err.println(" target directories. Delete old versions.");
System.err.println();
System.err.println(" Targets may employ system properties via ");
System.err.println(" ${property.name}");
System.err.println(" syntax. For example");
System.err.println(" ${user.home}");
System.err.println(" for the home directory, or");
System.err.println(" ${user.dir}");
System.err.println(" for the process current working directory.");
System.err.println();
System.err.println(" For no targets found on the cmd line, exit");
System.err.println(" silently.");
System.err.println();
System.err.println(" Optionally specify the update properties file,");
System.err.println(" default");
System.err.println(" ${user.home}/update.properties");
System.err.println(" for properties beyond the system properties.");
System.err.println();
System.err.println(" Optionally employ special source 'eval' to");
System.err.println(" do no more than echo the interpreted targets. ");
System.err.println();
}
public static void main(String[] argv){
try {
if (2 <= argv.length){
if (3 <= argv.length){
PropertySource(argv[2]);
}
else {
PropertySource("${user.home}/update.properties");
}
if ("eval".equals(argv[0])){
File[] targets = PropertyFiles(argv[1]);
if (null != targets){
for (File tgt: targets){
System.out.println(tgt);
}
System.exit(0);
}
else {
System.err.println("No targets found.");
System.exit(1);
}
}
else {
File[] sources = Source(argv[0]);
if (null != sources){
for (File src: sources){
File[] targets = Target(src,argv[1]);
if (null != targets){
if (Debug){
try {
for (File tgt: targets){
/*
* Copy source to target
*/
System.out.printf("+ copy '%s' to '%s' in '%s'%n",src.getPath(),tgt.getName(),tgt.getParentFile().getPath());
/*
* Delete old versions
*/
File[] deletes = ListDeletes(src,tgt);
if (null != deletes){
for (File del: deletes){
if (DeleteFile(del))
System.out.printf("Deleted %s\n",del.getPath());
else
System.out.printf("Failed to delete %s\n",del.getPath());
}
}
/*
* Add new versions
*/
if (AddFile(tgt))
System.out.printf("Added %s\n",tgt.getPath());
else
System.out.printf("Modified %s\n",tgt.getPath());
}
}
catch (Exception exc){
exc.printStackTrace();
System.exit(1);
}
}
else {
final long srclen = src.length();
try {
FileChannel source = new FileInputStream(src).getChannel();
try {
for (File tgt: targets){
/*
* Copy source to target
*/
try {
FileChannel target = new FileOutputStream(tgt).getChannel();
try {
source.transferTo(0L,srclen,target);
}
finally {
target.close();
}
}
catch (java.io.FileNotFoundException exc){
System.err.printf("File not found: %s%n",tgt.getAbsolutePath());
exc.printStackTrace();
System.exit(1);
}
/*
* Delete old versions
*/
File[] deletes = ListDeletes(src,tgt);
if (null != deletes){
for (File del: deletes){
if (DeleteFile(del))
System.out.printf("Deleted %s\n",del.getPath());
else
System.out.printf("Failed to delete %s\n",del.getPath());
}
}
/*
* Add new versions
*/
if (AddFile(tgt))
System.out.printf("Added %s\n",tgt.getPath());
else
System.out.printf("Modified %s\n",tgt.getPath());
}
}
finally {
source.close();
}
}
catch (java.io.FileNotFoundException exc){
System.err.printf("File not found: %s%n",src.getAbsolutePath());
exc.printStackTrace();
System.exit(1);
}
catch (Exception exc){
exc.printStackTrace();
System.exit(1);
}
}
}
}
System.exit(0);
}
else {
System.err.printf("Source file(s) not found in '%s'\n",argv[0]);
System.exit(1);
}
}
}
else {
usage();
System.exit(1);
}
}
catch (RuntimeException exc){
exc.printStackTrace();
System.exit(0);// continue build
}
}
private final static java.util.Properties Properties = new java.util.Properties(java.lang.System.getProperties());
/**
* Initialize properties file
*/
private final static void PropertySource(String value){
File file = PropertyFile(value);
if (file.isFile()){
try {
InputStream fin = new FileInputStream(file);
try {
Properties.load(fin);
}
finally {
fin.close();
}
}
catch (IOException exc){
System.err.printf("File: %s%n",file);
exc.printStackTrace();
System.exit(1);
}
}
else {
System.err.printf("Properties file not found '%s' in value '%s'",file.getAbsolutePath(),value);
}
}
/**
* Parse and evaluate target value string
*/
private final static String PropertyEval(String request){
int start = request.indexOf('$');
if (-1 < start){
String q = request;
StringBuilder string = new StringBuilder();
while (true){
if ('{' == q.charAt(start+1)){
if (0 < start){
string.append(q.substring(0,start));
}
int end = q.indexOf('}',start);
if (start < end){
String name = q.substring((start+2),end);
String value = Properties.getProperty(name);
if (null != value)
string.append(value);
else
throw new IllegalArgumentException(String.format("Property not found '%s' in '%s'",name,request));
}
else
throw new IllegalArgumentException(String.format("Syntax error found at '%s' in '%s'",q.substring(start),request));
q = q.substring(end+1);
start = q.indexOf('$');
if (0 > start){
string.append(q);
break;
}
}
else
throw new IllegalArgumentException(String.format("Syntax error found at '%s' in '%s'",q.substring(start),request));
}
return PropertyEval(string.toString());
}
else
return request;
}
private final static File PropertyFile(String request){
String value = PropertyEval(request);
if (null != value)
return new File(value);
else
throw new IllegalArgumentException(String.format("File not found in '%s' from '%s'",value,request));
}
private final static File[] PropertyFiles(String request){
String value = PropertyEval(request);
if (null != value){
String[] list = value.split(":");
int count = list.length;
File[] re = new File[count];
for (int cc = 0; cc < count; cc++){
re[cc] = new File(list[cc]);
}
return re;
}
else
throw new IllegalArgumentException(String.format("File not found in '%s' from '%s'",value,request));
}
private final static boolean Debug = (null != System.getProperty("Debug"));
private final static String Svn, Git;
static {
String svn = null, git = null, rm = null;
try {
StringTokenizer strtok = new StringTokenizer(System.getenv("PATH"),File.pathSeparator);
File chk;
while (strtok.hasMoreTokens()){
String pel = strtok.nextToken();
chk = new File(pel,"svn");
if (chk.isFile() && chk.canExecute()){
svn = chk.getPath();
if (null != git && null != rm)
break;
}
else {
chk = new File(pel,"svn.exe");
if (chk.isFile() && chk.canExecute()){
svn = chk.getPath();
if (null != git && null != rm)
break;
}
}
chk = new File(pel,"git");
if (chk.isFile() && chk.canExecute()){
git = chk.getPath();
if (null != svn && null != rm)
break;
}
else {
chk = new File(pel,"git.exe");
if (chk.isFile() && chk.canExecute()){
git = chk.getPath();
if (null != svn && null != rm)
break;
}
}
}
}
catch (Exception exc){
throw new InternalError();
}
Svn = svn;
Git = git;
}
public final static boolean HaveSvn = (null != Svn);
public final static boolean HaveGit = (null != Git);
static {
if (HaveSvn){
if (HaveGit){
if (Debug)
System.err.println("Using git and subversion. Using Debug (Dry Run).");
else
System.err.println("Using git and subversion.");
}
else {
if (Debug)
System.err.println("Using subversion but not git. Using Debug (Dry Run).");
else
System.err.println("Using subversion but not git.");
}
}
else if (HaveGit){
if (Debug)
System.err.println("Using git but not subversion. Using Debug (Dry Run).");
else
System.err.println("Using git but not subversion.");
}
else {
if (Debug)
System.err.println("Not using either of subversion or git. Using Debug (Dry Run).");
else
System.err.println("Not using either of subversion or git.");
}
}
private final static Runtime RT = Runtime.getRuntime();
private final static boolean DeleteFile(File file)
throws java.io.IOException,
java.lang.InterruptedException
{
if (file.isFile()){
if (IsSvnRepo(file)){
if (SvnContains(file))
return SvnDelete(file);
else if (Debug){
System.out.printf("+ delete %s in %s%n",file.getName(),file.getParentFile().getPath());
return true;
}
else
return file.delete();
}
else if (GitContains(file))
return GitDelete(file);
else if (Debug){
System.out.printf("+ delete %s in %s%n",file.getName(),file.getParentFile().getPath());
return true;
}
else
return file.delete();
}
else
return false;
}
private final static boolean SvnDelete(File file)
throws java.io.IOException,
java.lang.InterruptedException
{
if (Update.HaveSvn){
String[] cmd = new String[]{
Update.Svn, "delete", "--force", file.getName()
};
if (Debug){
System.out.printf("+ svn delete --force %s in %s%n",file.getName(),file.getParentFile().getPath());
return true;
}
else {
Process p = RT.exec(cmd,ENV,file.getParentFile());
if (0 == p.waitFor()){
return true;
}
else {
System.out.printf("| svn delete --force %s in %s%n",file.getName(),file.getParentFile().getPath());
Copy(p.getInputStream(),System.out);
Copy(p.getErrorStream(),System.err);
return false;
}
}
}
else
return false;
}
private final static boolean GitDelete(File file)
throws java.io.IOException,
java.lang.InterruptedException
{
if (Update.HaveGit){
String[] cmd = new String[]{
Update.Git, "rm", "-f", file.getName()
};
if (Debug){
System.out.printf("+ git rm -f %s in %s%n",file.getName(),file.getParentFile().getPath());
return true;
}
else {
Process p = RT.exec(cmd,ENV,file.getParentFile());
if (0 == p.waitFor()){
return true;
}
else {
System.out.printf("| git rm -f %s in %s%n",file.getName(),file.getParentFile().getPath());
Copy(p.getInputStream(),System.out);
Copy(p.getErrorStream(),System.err);
return false;
}
}
}
else
return false;
}
private final static boolean AddFile(File file)
throws java.io.IOException,
java.lang.InterruptedException
{
if (Update.HaveSvn && IsSvnRepo(file)){
if (SvnContains(file))
return false;
else
return SvnAdd(file);
}
else if (Update.HaveGit && IsGitRepo(file)){
if (GitContains(file))
return false;
else
return GitAdd(file);
}
else
return false;
}
private final static boolean SvnAdd(File file)
throws java.io.IOException,
java.lang.InterruptedException
{
if (Update.HaveSvn){
String[] cmd = new String[]{
Update.Svn, "add", file.getName()
};
if (Debug){
System.out.printf("+ svn add %s in %s%n",file.getName(),file.getParentFile().getPath());
return true;
}
else {
Process p = RT.exec(cmd,ENV,file.getParentFile());
if (0 == p.waitFor()){
return true;
}
else {
System.out.printf("| svn add %s in %s%n",file.getName(),file.getParentFile().getPath());
Copy(p.getInputStream(),System.out);
Copy(p.getErrorStream(),System.err);
return false;
}
}
}
else
return false;
}
private final static boolean GitAdd(File file)
throws java.io.IOException,
java.lang.InterruptedException
{
if (Update.HaveGit){
String[] cmd = new String[]{
Update.Git, "add", file.getName()
};
if (Debug){
System.out.printf("+ git add %s in %s%n",file.getName(),file.getParentFile().getPath());
return true;
}
else {
Process p = RT.exec(cmd,ENV,file.getParentFile());
if (0 == p.waitFor()){
return true;
}
else {
System.out.printf("| git add %s in %s%n",file.getName(),file.getParentFile().getPath());
Copy(p.getInputStream(),System.out);
Copy(p.getErrorStream(),System.err);
return false;
}
}
}
else
return false;
}
private final static boolean IsSvnRepo(File file){
File dir = new File(file.getParentFile(),".svn");
return (dir.isDirectory());
}
private final static boolean IsGitRepo(File file){
File parent = file.getParentFile();
while (null != parent){
File dir = new File(parent,".git");
if (dir.isDirectory())
return true;
else {
parent = parent.getParentFile();
}
}
return false;
}
private final static boolean SvnContains(File file)
throws java.io.IOException,
java.lang.InterruptedException
{
if (Update.HaveSvn){
String[] cmd = new String[]{
Update.Svn, "diff", file.getName()
};
Process p = RT.exec(cmd,ENV,file.getParentFile());
if (0 == p.waitFor()){
return true;
}
else {
return false;
}
}
else
return false;
}
private final static boolean GitContains(File file)
throws java.io.IOException,
java.lang.InterruptedException
{
if (Update.HaveGit){
String[] cmd = new String[]{
Update.Git, "ls-files", "--others"
};
Process p = RT.exec(cmd,ENV,file.getParentFile());
if (0 == p.waitFor()){
/*
* Is a git repo
*/
ByteArrayOutputStream buf = new ByteArrayOutputStream();
Copy(p.getInputStream(),buf);
if (Contains(buf,file.getName()))
return false;
else
return true;
}
else {
/*
* Not a git repo
*/
return false;
}
}
else
return false;
}
private final static File[] Source(String path){
File[] list = null;
String[] files = path.split(":");
for (int ccc = 0, ccz = files.length; ccc < ccz; ccc++){
File tgt = new File(files[ccc]);
if (tgt.isFile()){
list = Add(list,tgt);
}
}
return list;
}
private final static File[] Target(File src, String tgts){
File[] list = null;
final String name = src.getName();
if (null != name){
for (File tgt : PropertyFiles(tgts)){
if (tgt.isDirectory()){
tgt = new File(tgt,name);
if (!tgt.getAbsolutePath().equals(src.getAbsolutePath())){
list = Add(list,tgt);
}
}
}
}
return list;
}
private final static Pattern DeletesBasenameRe = Pattern.compile("-([kK][0-9]{2}[A-L][0-9]{2}|[0-9.]+)\\.jar");
private final static File[] ListDeletes(File src, File tgt){
File tgtd = tgt.getParentFile();
if (null != tgtd){
String name = src.getName();
String basename = Basename(name);
if (name.equals(basename)){
if (Debug)
System.out.printf("+ listing deletes, basename (%s) == name (%s)%n",basename,name);
return null;
}
else
return tgtd.listFiles(new Update(basename,name));
}
return null;
}
private final static String Basename(String name){
String[] s = DeletesBasenameRe.split(name,0);
if (null != s && 0 < s.length)
return s[0];
else
throw new IllegalArgumentException(name);
}
private final static void Copy(InputStream in, OutputStream out){
try {
try {
byte[] iob = new byte[128];
int read;
while (0 < (read = in.read(iob,0,128)))
out.write(iob,0,read);
}
finally {
in.close();
}
}
catch (IOException exc){
}
}
public final static File[] Add(File[] list, File item){
if (null == item)
return list;
else if (null == list)
return new File[]{item};
else {
int len = list.length;
File[] copier = new File[len+1];
System.arraycopy(list,0,copier,0,len);
copier[len] = item;
return copier;
}
}
private final static boolean Contains(ByteArrayOutputStream stdout, String name){
return Contains(stdout.toByteArray(),name);
}
private final static boolean Contains(byte[] stdout, String name){
if (null != stdout && 0 < stdout.length)
return Contains(new String(stdout,0,0,stdout.length),name);
else
return false;
}
private final static boolean Contains(String stdout, String name){
StringTokenizer strtok = new StringTokenizer(stdout,"\r\n");
while (strtok.hasMoreTokens()){
if (name.equals(strtok.nextToken()))
return true;
}
return false;
}
private final static String[] ENV = new String[0];
/**
* Delete files filter
*/
public final String include, exclude;
public Update(String include, String exclude){
super();
this.include = include;
this.exclude = exclude;
if (Debug)
System.out.printf("+ list deletes filter include (%s) exclude (%s)%n",include,exclude);
}
public boolean accept(File file){
if (file.isFile()){
final String name = file.getName();
/*
* Files to delete
*/
if (name.startsWith(this.include)){
/*
*/
if (DeletesBasenameRe.matcher(name.substring(this.include.length())).matches()){
/*
*/
if (!name.equals(this.exclude)){
if (Debug)
System.out.printf("+ list deletes filter (%s)%n",file.getPath());
return true;
}
}
}
}
return false;
}
}