/*
* (c) Copyright 2010-2011 AgileBirds
*
* This file is part of OpenFlexo.
*
* OpenFlexo 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 3 of the License, or
* (at your option) any later version.
*
* OpenFlexo 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 OpenFlexo. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.netbeans.lib.cvsclient.command.add;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.netbeans.lib.cvsclient.ClientServices;
import org.netbeans.lib.cvsclient.admin.Entry;
import org.netbeans.lib.cvsclient.command.BuildableCommand;
import org.netbeans.lib.cvsclient.command.Builder;
import org.netbeans.lib.cvsclient.command.CommandException;
import org.netbeans.lib.cvsclient.command.KeywordSubstitutionOptions;
import org.netbeans.lib.cvsclient.command.WrapperUtils;
import org.netbeans.lib.cvsclient.connection.AuthenticationException;
import org.netbeans.lib.cvsclient.event.EventManager;
import org.netbeans.lib.cvsclient.event.MessageEvent;
import org.netbeans.lib.cvsclient.request.ArgumentRequest;
import org.netbeans.lib.cvsclient.request.ArgumentxRequest;
import org.netbeans.lib.cvsclient.request.CommandRequest;
import org.netbeans.lib.cvsclient.request.DirectoryRequest;
import org.netbeans.lib.cvsclient.request.EntryRequest;
import org.netbeans.lib.cvsclient.request.IsModifiedRequest;
import org.netbeans.lib.cvsclient.request.KoptRequest;
import org.netbeans.lib.cvsclient.request.RootRequest;
import org.netbeans.lib.cvsclient.request.StickyRequest;
import org.netbeans.lib.cvsclient.util.SimpleStringPattern;
/**
* Adds a file or directory.
*
* @author Robert Greig
*/
public class AddCommand extends BuildableCommand {
/**
* Constants that identify a message that creates a directory in repository.
*/
private static final String DIR_ADDED = " added to the repository"; // NOI18N
private static final String DIRECTORY = "Directory "; // NOI18N
/**
* The requests that are sent and processed.
*/
private List requests;
/**
* The argument requests that are collected and sent in the end just before the add request.
*/
private final List argumentRequests = new LinkedList();
/**
* The list of new directories.
*/
/*
private HashMap newDirList;
*/
private final List newDirList = new LinkedList();
/**
* The client services that are provided to this command.
*/
private ClientServices clientServices;
/**
* The files and/or directories to operate on.
*/
private File[] files;
/**
* Holds value of property message, (add's switch -m).
*/
private String message;
/**
* Holds value of property keywordSubst.
*/
private KeywordSubstitutionOptions keywordSubst;
/**
* This is the merged wrapper map that contains has both server side and client side wrapper settings merged except for the .cvswrappers
* in the individual directories
*/
private Map wrapperMap;
/**
* Holds the cvswrappers map for each directory, keyed by directory name
*/
private HashMap dir2WrapperMap = new HashMap(16);
private static final Map EMPTYWRAPPER = new HashMap(1);
/**
* Constructor.
*/
public AddCommand() {
resetCVSCommand();
}
/**
* Set the files and/or directories on which to execute the command. Sorts the paameter so that directories are first and files follow.
* That way a directory and it's content will be passed correctly. The user of the library has to specify all the files+dirs being added
* though. This is just a sanity check, so that no unnessesary errors occur.
*/
public void setFiles(File[] files) {
this.files = files;
if (files == null) {
return;
}
// sort array: directories first, files follow
this.files = new File[files.length];
int dirCount = 0;
int fileCount = 0;
int totalCount = files.length;
for (int index = 0; index < totalCount; index++) {
File currentFile = files[index];
if (currentFile.isDirectory()) {
this.files[dirCount] = currentFile;
dirCount++;
} else {
this.files[totalCount - (1 + fileCount)] = currentFile;
fileCount++;
}
}
}
/**
* Get the files and/or directories specified for this command to operate on.
*
* @return the array of Files
*/
public File[] getFiles() {
return files;
}
/**
* @param ending
* - the ending part of the file's pathname.. path separator is cvs's default '/'
*/
public File getFileEndingWith(String ending) {
String locEnding = ending.replace('\\', '/');
String localDir = getLocalDirectory().replace('\\', '/');
int index = 0;
for (index = 0; index < files.length; index++) {
String path = files[index].getAbsolutePath();
String parentPath = files[index].getParentFile().getAbsolutePath().replace('\\', '/');
path = path.replace('\\', '/');
if (path.endsWith(locEnding) && locEnding.indexOf('/') >= 0 || files[index].getName().equals(locEnding)
&& parentPath.equals(localDir)) {
return files[index];
}
}
return null;
}
/**
* Getter for property message.
*
* @return Value of property message.
*/
public String getMessage() {
return message;
}
/**
* Setter for property message.
*
* @param message
* New value of property message.
*/
public void setMessage(String message) {
this.message = message;
}
/**
* Getter for property keywordSubst.
*
* @return Value of property keywordSubst.
*/
public KeywordSubstitutionOptions getKeywordSubst() {
return keywordSubst;
}
/**
* Setter for property keywordSubst.
*
* @param keywordSubst
* New value of property keywordSubst.
*/
public void setKeywordSubst(KeywordSubstitutionOptions keywordSubst) {
this.keywordSubst = keywordSubst;
}
/**
* Add requests for a particular file or directory to be added.
*/
protected void addRequests(File file) throws IOException, CommandException {
if (file.isDirectory()) {
addRequestsForDirectory(file, false);
} else {
addRequestsForFile(file);
}
}
/**
* Add requests for a particular directory.
*
* @param directory
* the directory to add
* @param adding
* - for the directory to be added, set to true. used internally to recurse Directory requests.
* @throws IOException
* if an error occurs
*/
private void addRequestsForDirectory(File directory, boolean recursion) throws IOException {
File parentDirectory = directory.getParentFile();
String dir = recursion ? getRelativeToLocalPathInUnixStyle(directory) : getRelativeToLocalPathInUnixStyle(parentDirectory);
String partPath;
if (dir.equals(".")) { // NOI18N
partPath = directory.getName();
} else {
// trim the leading slash from the pathname we end up with
// (e.g. we end up with something like \banana\foo
// and this gives us banana\foo). Also replace backslashes with
// forward slashes. The standard CVS server doesn't like
// backslashes very much.
partPath = dir + "/" + directory.getName(); // NOI18N
// recursively scroll back to the localPath..
addRequestsForDirectory(parentDirectory, true);
}
if (recursion) {
partPath = dir;
}
// Note that the repository file for the directory being added has not
// been created yet, so we are forced to read the repository for
// the parent directory and build the appropriate entry by tagging
// on the directory name (called partPath here)
String repository;
String tag;
if (recursion) {
repository = clientServices.getRepositoryForDirectory(directory.getAbsolutePath());
tag = clientServices.getStickyTagForDirectory(directory);
} else {
repository = clientServices.getRepositoryForDirectory(parentDirectory.getAbsolutePath());
if (repository.endsWith(".")) {
repository = repository.substring(0, repository.length() - 1) + directory.getName();
} else {
repository = repository + "/" + directory.getName(); // NOI18N
}
tag = clientServices.getStickyTagForDirectory(parentDirectory);
}
requests.add(new DirectoryRequest(partPath, repository));
if (tag != null) {
requests.add(new StickyRequest(tag));
}
if (!recursion) {
argumentRequests.add(new ArgumentRequest(partPath));
/*
newDirList.put(partPath, repository);
*/
newDirList.add(new Paths(partPath, repository));
}
// MK argument after Dir request.. also with the rel path from the current working dir
}
/**
* Add requests for a particular file.
*/
protected void addRequestsForFile(File file) throws IOException, CommandException {
File directory = file.getParentFile();
String dir = getRelativeToLocalPathInUnixStyle(directory);
String repository = clientServices.getRepositoryForDirectory(directory.getAbsolutePath());
requests.add(new DirectoryRequest(dir, repository));
String tag = clientServices.getStickyTagForDirectory(directory);
if (tag != null) {
requests.add(new StickyRequest(tag));
}
Entry entry = clientServices.getEntry(file);
if (entry != null) {
requests.add(new EntryRequest(entry));
} else {
Map directoryLevelWrapper = (Map) dir2WrapperMap.get(dir);
if (directoryLevelWrapper == null) {
// we have not parsed the cvs wrappers for this directory
// read the wrappers for this directory
File wrapperFile = new File(directory, ".cvswrappers"); // NOI18N
if (wrapperFile.exists()) {
directoryLevelWrapper = new HashMap(5);
WrapperUtils.readWrappersFromFile(wrapperFile, directoryLevelWrapper);
} else {
directoryLevelWrapper = EMPTYWRAPPER;
}
// store the wrapper map indexed by directory name
dir2WrapperMap.put(dir, directoryLevelWrapper);
}
boolean isBinary = isBinary(clientServices, file.getName(), directoryLevelWrapper);
if (isBinary) {
requests.add(new KoptRequest("-kb")); // NOI18N
}
requests.add(new IsModifiedRequest(file));
}
if (dir.equals(".")) { // NOI18N
argumentRequests.add(new ArgumentRequest(file.getName()));
} else {
argumentRequests.add(new ArgumentRequest(dir + "/" + file.getName())); // NOI18N
}
}
/**
* Returns true, if the file for the specified filename should be treated as a binary file.
*
* The information comes from the wrapper map and the set keywordsubstitution.
*/
private boolean isBinary(ClientServices client, String filename, Map directoryLevelWrappers) throws CommandException {
KeywordSubstitutionOptions keywordSubstitutionOptions = getKeywordSubst();
if (keywordSubstitutionOptions == KeywordSubstitutionOptions.BINARY) {
return true;
}
// The keyWordSubstitutions was set based on MIME-types by
// CVSAdd which had no notion of cvswrappers. Therefore some
// filetypes returned as text may actually be binary within CVS
// We check for those files here
boolean wrapperFound = false;
if (wrapperMap == null) {
// process the wrapper settings as we have not done it before.
wrapperMap = WrapperUtils.mergeWrapperMap(client);
}
for (Iterator it = wrapperMap.keySet().iterator(); it.hasNext();) {
SimpleStringPattern pattern = (SimpleStringPattern) it.next();
if (pattern.doesMatch(filename)) {
keywordSubstitutionOptions = (KeywordSubstitutionOptions) wrapperMap.get(pattern);
wrapperFound = true;
break;
}
}
// if no wrappers are found to match the server and local settings, try
// the wrappers for this local directory
if (!wrapperFound && directoryLevelWrappers != null && directoryLevelWrappers != EMPTYWRAPPER) {
for (Iterator it = directoryLevelWrappers.keySet().iterator(); it.hasNext();) {
SimpleStringPattern pattern = (SimpleStringPattern) it.next();
if (pattern.doesMatch(filename)) {
keywordSubstitutionOptions = (KeywordSubstitutionOptions) directoryLevelWrappers.get(pattern);
wrapperFound = true;
break;
}
}
}
return keywordSubstitutionOptions == KeywordSubstitutionOptions.BINARY;
}
/**
* Execute a command.
*
* @param client
* the client services object that provides any necessary services to this command, including the ability to actually process
* all the requests
*/
@Override
public void execute(ClientServices client, EventManager em) throws CommandException, AuthenticationException {
if (files == null || files.length == 0) {
throw new CommandException("No files have been specified for " + // NOI18N
"adding.", CommandException.getLocalMessage("AddCommand.noFilesSpecified", null)); // NOI18N
}
client.ensureConnection();
clientServices = client;
setLocalDirectory(client.getLocalPath());
String directory = client.getLocalPath();
File cvsfolder = new File(directory, "CVS");
if (!cvsfolder.isDirectory()) {
// setFailed();
MessageEvent event = new MessageEvent(this, "cvs [add aborted]: there is no version here; do 'cvs checkout' first", true);
messageSent(event);
em.fireCVSEvent(event);
return;
}
/*
newDirList = new HashMap();
*/
newDirList.clear();
super.execute(client, em);
requests = new LinkedList();
if (client.isFirstCommand()) {
requests.add(new RootRequest(client.getRepository()));
}
// sets the message argument -m .. one for all files being sent..
String message = getMessage();
if (message != null) {
message = message.trim();
}
if (message != null && message.length() > 0) {
addMessageRequest(message);
}
if (getKeywordSubst() != null && !getKeywordSubst().equals("")) { // NOI18N
requests.add(new ArgumentRequest("-k" + getKeywordSubst())); // NOI18N
}
try {
// current dir sent to server BEFORE and AFTER - kinda hack??
for (int i = 0; i < files.length; i++) {
addRequests(files[i]);
}
// now add the request that indicates the working directory for the
// command
requests.add(new DirectoryRequest(".", // NOI18N
client.getRepositoryForDirectory(getLocalDirectory())));
requests.addAll(argumentRequests);
argumentRequests.clear(); // MK sanity check.
requests.add(CommandRequest.ADD);
client.processRequests(requests);
} catch (CommandException ex) {
throw ex;
} catch (Exception ex) {
throw new CommandException(ex, ex.getLocalizedMessage());
} finally {
requests.clear();
}
}
private void addMessageRequest(String message) {
requests.add(new ArgumentRequest("-m")); // NOI18N
StringTokenizer token = new StringTokenizer(message, "\n", false); // NOI18N
boolean first = true;
while (token.hasMoreTokens()) {
if (first) {
requests.add(new ArgumentRequest(token.nextToken()));
first = false;
} else {
requests.add(new ArgumentxRequest(token.nextToken()));
}
}
}
/**
* This method returns how the command would look like when typed on the command line. Each command is responsible for constructing this
* information.
*
* @returns <command's name> [<parameters>] files/dirs. Example: checkout -p CvsCommand.java
*/
@Override
public String getCVSCommand() {
StringBuffer toReturn = new StringBuffer("add "); // NOI18N
toReturn.append(getCVSArguments());
File[] files = getFiles();
if (files != null) {
for (int index = 0; index < files.length; index++) {
toReturn.append(files[index].getName());
toReturn.append(' ');
}
}
return toReturn.toString();
}
/**
* Method that is called while the command is being executed. Descendants can override this method to return a Builder instance that
* will parse the server's output and create data structures.
*/
@Override
public Builder createBuilder(EventManager eventManager) {
return new AddBuilder(eventManager, this);
}
/**
* Takes the arguments and sets the command. To be mainly used for automatic settings (like parsing the .cvsrc file)
*
* @return true if the option (switch) was recognized and set
*/
@Override
public boolean setCVSCommand(char opt, String optArg) {
if (opt == 'm') {
setMessage(optArg);
} else if (opt == 'k') {
KeywordSubstitutionOptions keywordSubst = KeywordSubstitutionOptions.findKeywordSubstOption(optArg);
setKeywordSubst(keywordSubst);
} else {
return false;
}
return true;
}
/**
* Returns a string indicating the available options.
*/
@Override
public String getOptString() {
return "m:k:"; // NOI18N
}
/**
* Listens for output of the command. If new directory is added, executes the createCvsFiles() method.
*/
@Override
public void messageSent(MessageEvent e) {
String str = e.getMessage();
if (str.endsWith(DIR_ADDED)) {
str = str.substring(DIRECTORY.length(), str.indexOf(DIR_ADDED)).trim();
createCvsFiles(str);
}
super.messageSent(e);
}
/**
* For new directory that was added to the repository, creates the admin files in CVS subdir.
*/
private void createCvsFiles(String newDirInRepository) {
String repository = newDirInRepository;
String dirName = repository;
if (dirName.lastIndexOf('/') >= 0) {
dirName = dirName.substring(dirName.lastIndexOf('/') + 1, dirName.length());
}
if (newDirList.size() == 0) {
System.err.println("JavaCVS: Bug in AddCommand|createCvsFiles"); // NOI18N
System.err.println(" newDirInRepository = " + newDirInRepository); // NOI18N
return;
}
Paths paths = null;
for (Iterator i = newDirList.iterator(); i.hasNext();) {
paths = (Paths) i.next();
if (paths.getRepositoryPath().equals(newDirInRepository)) {
i.remove();
break;
}
}
String local = paths.getPartPath();
String part = paths.getRepositoryPath();
repository = paths.getRepositoryPath();
String tempDirName = part;
if (part.lastIndexOf('/') >= 0) {
tempDirName = part.substring(part.lastIndexOf('/') + 1, part.length());
}
if (!tempDirName.equalsIgnoreCase(dirName)) {
System.err.println("JavaCVS: Bug in AddCommand|createCvsFiles"); // NOI18N
System.err.println(" newDirInRepository = " + newDirInRepository); // NOI18N
System.err.println(" tempDirName = " + tempDirName); // NOI18N
System.err.println(" dirName = " + dirName); // NOI18N
return;
}
try {
if (repository.startsWith(".")) { // NOI18N
repository = repository.substring(1);
}
clientServices.updateAdminData(local, repository, null);
createCvsTagFile(local, repository);
} catch (IOException ex) {
System.err.println("TODO: couldn't create/update Cvs admin files"); // NOI18N
}
/*
Iterator it = newDirList.keySet().iterator();
while (it.hasNext())
{
String local = (String)it.next();
String part = (String)newDirList.get(local);
String tempDirName = part;
if (part.lastIndexOf('/') >= 0)
{
tempDirName = part.substring(part.lastIndexOf('/') + 1,
part.length());
}
if (tempDirName.equalsIgnoreCase(dirName))
{
try
{
clientServices.updateAdminData(local, repository, null);
createCvsTagFile(local, repository);
it.remove(); // hack.. in case 2 dirs being added have the same name??
break;
}
catch (IOException exc)
{
System.out.println("TODO: couldn't create/update Cvs admin files");
}
}
}
*/
}
private void createCvsTagFile(String local, String repository) throws IOException {
File current = new File(getLocalDirectory(), local);
File parent = current.getParentFile();
String tag = clientServices.getStickyTagForDirectory(parent);
if (tag != null) {
File tagFile = new File(current, "CVS/Tag"); // NOI18N
tagFile.createNewFile();
PrintWriter w = new PrintWriter(new BufferedWriter(new FileWriter(tagFile)));
w.println(tag);
w.close();
}
}
/**
* resets all switches in the command. After calling this method, the command should have no switches defined and should behave
* defaultly.
*/
@Override
public void resetCVSCommand() {
setMessage(null);
setKeywordSubst(null);
}
/**
* Returns the arguments of the command in the command-line style. Similar to getCVSCommand() however without the files and command's
* name
*/
@Override
public String getCVSArguments() {
StringBuffer toReturn = new StringBuffer();
if (getMessage() != null) {
toReturn.append("-m \""); // NOI18N
toReturn.append(getMessage());
toReturn.append("\" "); // NOI18N
}
if (getKeywordSubst() != null) {
toReturn.append("-k"); // NOI18N
toReturn.append(getKeywordSubst().toString());
toReturn.append(" "); // NOI18N
}
return toReturn.toString();
}
private static class Paths {
private final String partPath;
private final String repositoryPath;
public Paths(String partPath, String repositoryPath) {
this.partPath = partPath;
this.repositoryPath = repositoryPath;
}
public String getPartPath() {
return partPath;
}
public String getRepositoryPath() {
return repositoryPath;
}
}
}