001: /*****************************************************************************
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is the CVS Client Library.
027: * The Initial Developer of the Original Software is Robert Greig.
028: * Portions created by Robert Greig are Copyright (C) 2000.
029: * All Rights Reserved.
030: *
031: * If you wish your version of this file to be governed by only the CDDL
032: * or only the GPL Version 2, indicate your decision by adding
033: * "[Contributor] elects to include this software in this distribution
034: * under the [CDDL or GPL Version 2] license." If you do not indicate a
035: * single choice of license, a recipient has the option to distribute
036: * your version of this file under either the CDDL, the GPL Version 2 or
037: * to extend the choice of license to its licensees as provided above.
038: * However, if you add GPL Version 2 code and therefore, elected the GPL
039: * Version 2 license, then the option applies only if the new code is
040: * made subject to such option by the copyright holder.
041: *
042: * Contributor(s): Robert Greig.
043: *****************************************************************************/package org.netbeans.lib.cvsclient.command.add;
044:
045: import java.io.*;
046: import java.util.*;
047:
048: import org.netbeans.lib.cvsclient.*;
049: import org.netbeans.lib.cvsclient.util.SimpleStringPattern;
050: import org.netbeans.lib.cvsclient.admin.*;
051: import org.netbeans.lib.cvsclient.command.*;
052: import org.netbeans.lib.cvsclient.connection.*;
053: import org.netbeans.lib.cvsclient.event.*;
054: import org.netbeans.lib.cvsclient.request.*;
055:
056: /**
057: * Adds a file or directory.
058: * @author Robert Greig
059: */
060: public class AddCommand extends BuildableCommand {
061: /**
062: * Constants that identify a message that creates a directory in
063: * repository.
064: */
065: private static final String DIR_ADDED = " added to the repository"; //NOI18N
066: private static final String DIRECTORY = "Directory "; //NOI18N
067:
068: /**
069: * The requests that are sent and processed.
070: */
071: private List requests;
072:
073: /**
074: * The argument requests that are collected and sent in the end just before the
075: * add request.
076: */
077: private final List argumentRequests = new LinkedList();
078:
079: /**
080: * The list of new directories.
081: */
082: /*
083: private HashMap newDirList;
084: */
085: private final List newDirList = new LinkedList();
086:
087: /**
088: * The client services that are provided to this command.
089: */
090: private ClientServices clientServices;
091:
092: /**
093: * The files and/or directories to operate on.
094: */
095: private File[] files;
096:
097: /**
098: * Holds value of property message, (add's switch -m).
099: */
100: private String message;
101:
102: /**
103: * Holds value of property keywordSubst.
104: */
105: private KeywordSubstitutionOptions keywordSubst;
106:
107: /**
108: *This is the merged wrapper map that contains has both server side
109: *and client side wrapper settings merged except for the .cvswrappers
110: *in the individual directories
111: */
112: private Map wrapperMap;
113:
114: /**
115: * Holds the cvswrappers map for each directory, keyed
116: * by directory name
117: */
118: private HashMap dir2WrapperMap = new HashMap(16);
119:
120: private static final Map EMPTYWRAPPER = new HashMap(1);
121:
122: /**
123: * Constructor.
124: */
125: public AddCommand() {
126: resetCVSCommand();
127: }
128:
129: /**
130: * Set the files and/or directories on which to execute the command.
131: * Sorts the paameter so that directories are first and files follow.
132: * That way a directory and it's content will be passed correctly.
133: * The user of the library has to specify all the files+dirs being added though.
134: * This is just a sanity check, so that no unnessesary errors occur.
135: */
136: public void setFiles(File[] files) {
137: this .files = files;
138: if (files == null) {
139: return;
140: }
141:
142: // sort array: directories first, files follow
143: this .files = new File[files.length];
144: int dirCount = 0;
145: int fileCount = 0;
146: int totalCount = files.length;
147: for (int index = 0; index < totalCount; index++) {
148: File currentFile = files[index];
149: if (currentFile.isDirectory()) {
150: this .files[dirCount] = currentFile;
151: dirCount++;
152: } else {
153: this .files[totalCount - (1 + fileCount)] = currentFile;
154: fileCount++;
155: }
156: }
157: }
158:
159: /**
160: * Get the files and/or directories specified for this command to operate
161: * on.
162: * @return the array of Files
163: */
164: public File[] getFiles() {
165: return files;
166: }
167:
168: /**
169: * @param ending - the ending part of the file's pathname.. path separator is cvs's default '/'
170: */
171: public File getFileEndingWith(String ending) {
172: String locEnding = ending.replace('\\', '/');
173: String localDir = getLocalDirectory().replace('\\', '/');
174: int index = 0;
175: for (index = 0; index < files.length; index++) {
176: String path = files[index].getAbsolutePath();
177: String parentPath = files[index].getParentFile()
178: .getAbsolutePath().replace('\\', '/');
179: path = path.replace('\\', '/');
180: if ((path.endsWith(locEnding) && locEnding.indexOf('/') >= 0)
181: || (files[index].getName().equals(locEnding) && parentPath
182: .equals(localDir))) {
183: return files[index];
184: }
185: }
186: return null;
187: }
188:
189: /**
190: * Getter for property message.
191: * @return Value of property message.
192: */
193: public String getMessage() {
194: return message;
195: }
196:
197: /**
198: * Setter for property message.
199: * @param message New value of property message.
200: */
201: public void setMessage(String message) {
202: this .message = message;
203: }
204:
205: /**
206: * Getter for property keywordSubst.
207: * @return Value of property keywordSubst.
208: */
209: public KeywordSubstitutionOptions getKeywordSubst() {
210: return keywordSubst;
211: }
212:
213: /**
214: * Setter for property keywordSubst.
215: * @param keywordSubst New value of property keywordSubst.
216: */
217: public void setKeywordSubst(KeywordSubstitutionOptions keywordSubst) {
218: this .keywordSubst = keywordSubst;
219: }
220:
221: /**
222: * Add requests for a particular file or directory to be added.
223: */
224: protected void addRequests(File file) throws IOException,
225: CommandException {
226: if (file.isDirectory()) {
227: addRequestsForDirectory(file, false);
228: } else {
229: addRequestsForFile(file);
230: }
231: }
232:
233: /**
234: * Add requests for a particular directory.
235: * @param directory the directory to add
236: * @param adding - for the directory to be added, set to true.
237: * used internally to recurse Directory requests.
238: * @throws IOException if an error occurs
239: */
240: private void addRequestsForDirectory(File directory,
241: boolean recursion) throws IOException {
242:
243: File parentDirectory = directory.getParentFile();
244: String dir = recursion ? getRelativeToLocalPathInUnixStyle(directory)
245: : getRelativeToLocalPathInUnixStyle(parentDirectory);
246:
247: String partPath;
248: if (dir.equals(".")) { //NOI18N
249: partPath = directory.getName();
250: } else {
251: // trim the leading slash from the pathname we end up with
252: // (e.g. we end up with something like \banana\foo
253: // and this gives us banana\foo). Also replace backslashes with
254: // forward slashes. The standard CVS server doesn't like
255: // backslashes very much.
256: partPath = dir + "/" + directory.getName(); //NOI18N
257: // recursively scroll back to the localPath..
258: addRequestsForDirectory(parentDirectory, true);
259: }
260:
261: if (recursion) {
262: partPath = dir;
263: }
264:
265: // Note that the repository file for the directory being added has not
266: // been created yet, so we are forced to read the repository for
267: // the parent directory and build the appropriate entry by tagging
268: // on the directory name (called partPath here)
269: String repository;
270: String tag;
271:
272: if (recursion) {
273: repository = clientServices
274: .getRepositoryForDirectory(directory
275: .getAbsolutePath());
276: tag = clientServices.getStickyTagForDirectory(directory);
277: } else {
278: repository = clientServices
279: .getRepositoryForDirectory(parentDirectory
280: .getAbsolutePath());
281: if (repository.endsWith(".")) {
282: repository = repository.substring(0, repository
283: .length() - 1)
284: + directory.getName();
285: } else {
286: repository = repository + "/" + directory.getName(); //NOI18N
287: }
288: tag = clientServices
289: .getStickyTagForDirectory(parentDirectory);
290: }
291:
292: requests.add(new DirectoryRequest(partPath, repository));
293: if (tag != null) {
294: requests.add(new StickyRequest(tag));
295: }
296:
297: if (!recursion) {
298: argumentRequests.add(new ArgumentRequest(partPath));
299: /*
300: newDirList.put(partPath, repository);
301: */
302: newDirList.add(new Paths(partPath, repository));
303: }
304: // MK argument after Dir request.. also with the rel path from the current working dir
305: }
306:
307: /**
308: * Add requests for a particular file.
309: */
310: protected void addRequestsForFile(File file) throws IOException,
311: CommandException {
312: File directory = file.getParentFile();
313: String dir = getRelativeToLocalPathInUnixStyle(directory);
314:
315: String repository = clientServices
316: .getRepositoryForDirectory(directory.getAbsolutePath());
317: requests.add(new DirectoryRequest(dir, repository));
318: String tag = clientServices.getStickyTagForDirectory(directory);
319: if (tag != null) {
320: requests.add(new StickyRequest(tag));
321: }
322:
323: Entry entry = clientServices.getEntry(file);
324:
325: if (entry != null) {
326: requests.add(new EntryRequest(entry));
327: } else {
328:
329: Map directoryLevelWrapper = (Map) dir2WrapperMap.get(dir);
330: if (directoryLevelWrapper == null) {
331:
332: // we have not parsed the cvs wrappers for this directory
333: // read the wrappers for this directory
334:
335: File wrapperFile = new File(directory, ".cvswrappers"); // NOI18N
336: if (wrapperFile.exists()) {
337: directoryLevelWrapper = new HashMap(5);
338: WrapperUtils.readWrappersFromFile(wrapperFile,
339: directoryLevelWrapper);
340: } else {
341: directoryLevelWrapper = EMPTYWRAPPER;
342: }
343:
344: // store the wrapper map indexed by directory name
345: dir2WrapperMap.put(dir, directoryLevelWrapper);
346: }
347:
348: boolean isBinary = isBinary(clientServices, file.getName(),
349: directoryLevelWrapper);
350:
351: if (isBinary) {
352: requests.add(new KoptRequest("-kb")); // NOI18N
353: }
354: requests.add(new IsModifiedRequest(file));
355: }
356:
357: if (dir.equals(".")) { //NOI18N
358: argumentRequests.add(new ArgumentRequest(file.getName(),
359: true));
360: } else {
361: argumentRequests.add(new ArgumentRequest(dir + "/"
362: + file.getName())); //NOI18N
363: }
364: }
365:
366: /**
367: * Returns true, if the file for the specified filename should be treated as
368: * a binary file.
369: *
370: * The information comes from the wrapper map and the set keywordsubstitution.
371: */
372: private boolean isBinary(ClientServices client, String filename,
373: Map directoryLevelWrappers) throws CommandException {
374: KeywordSubstitutionOptions keywordSubstitutionOptions = getKeywordSubst();
375:
376: if (keywordSubstitutionOptions == KeywordSubstitutionOptions.BINARY) {
377: return true;
378: }
379:
380: // The keyWordSubstitutions was set based on MIME-types by
381: // CVSAdd which had no notion of cvswrappers. Therefore some
382: // filetypes returned as text may actually be binary within CVS
383: // We check for those files here
384:
385: boolean wrapperFound = false;
386:
387: if (wrapperMap == null) {
388: // process the wrapper settings as we have not done it before.
389: wrapperMap = WrapperUtils.mergeWrapperMap(client);
390: }
391:
392: for (Iterator it = wrapperMap.keySet().iterator(); it.hasNext();) {
393: SimpleStringPattern pattern = (SimpleStringPattern) it
394: .next();
395: if (pattern.doesMatch(filename)) {
396: keywordSubstitutionOptions = (KeywordSubstitutionOptions) wrapperMap
397: .get(pattern);
398: wrapperFound = true;
399: break;
400: }
401: }
402:
403: // if no wrappers are found to match the server and local settings, try
404: // the wrappers for this local directory
405: if (!wrapperFound && (directoryLevelWrappers != null)
406: && (directoryLevelWrappers != EMPTYWRAPPER)) {
407: for (Iterator it = directoryLevelWrappers.keySet()
408: .iterator(); it.hasNext();) {
409: SimpleStringPattern pattern = (SimpleStringPattern) it
410: .next();
411: if (pattern.doesMatch(filename)) {
412: keywordSubstitutionOptions = (KeywordSubstitutionOptions) directoryLevelWrappers
413: .get(pattern);
414: wrapperFound = true;
415: break;
416: }
417: }
418: }
419:
420: return keywordSubstitutionOptions == KeywordSubstitutionOptions.BINARY;
421: }
422:
423: /**
424: * Execute a command.
425: * @param client the client services object that provides any necessary
426: * services to this command, including the ability to actually process
427: * all the requests
428: */
429: public void execute(ClientServices client, EventManager em)
430: throws CommandException, AuthenticationException {
431: if (files == null || files.length == 0) {
432: throw new CommandException(
433: "No files have been specified for " + //NOI18N
434: "adding.",
435: CommandException.getLocalMessage(
436: "AddCommand.noFilesSpecified", null)); //NOI18N
437: }
438:
439: client.ensureConnection();
440:
441: clientServices = client;
442: setLocalDirectory(client.getLocalPath());
443:
444: String directory = client.getLocalPath();
445: File cvsfolder = new File(directory, "CVS");
446: if (!cvsfolder.isDirectory()) {
447: //setFailed();
448: MessageEvent event = new MessageEvent(
449: this ,
450: "cvs [add aborted]: there is no version here; do 'cvs checkout' first",
451: true);
452: messageSent(event);
453: em.fireCVSEvent(event);
454: return;
455: }
456: /*
457: newDirList = new HashMap();
458: */
459: newDirList.clear();
460:
461: super .execute(client, em);
462:
463: requests = new LinkedList();
464:
465: if (client.isFirstCommand()) {
466: requests.add(new RootRequest(client.getRepository()));
467: }
468:
469: // sets the message argument -m .. one for all files being sent..
470: String message = getMessage();
471: if (message != null) {
472: message = message.trim();
473: }
474: if (message != null && message.length() > 0) {
475: addMessageRequest(message);
476: }
477:
478: if (getKeywordSubst() != null && !getKeywordSubst().equals("")) { //NOI18N
479: requests.add(new ArgumentRequest("-k" + getKeywordSubst())); //NOI18N
480: }
481:
482: try {
483: // current dir sent to server BEFORE and AFTER - kinda hack??
484: for (int i = 0; i < files.length; i++) {
485: addRequests(files[i]);
486: }
487:
488: // now add the request that indicates the working directory for the
489: // command
490: requests
491: .add(new DirectoryRequest(
492: ".", //NOI18N
493: client
494: .getRepositoryForDirectory(getLocalDirectory())));
495:
496: requests.addAll(argumentRequests);
497: argumentRequests.clear(); // MK sanity check.
498: requests.add(CommandRequest.ADD);
499: client.processRequests(requests);
500: } catch (CommandException ex) {
501: throw ex;
502: } catch (Exception ex) {
503: throw new CommandException(ex, ex.getLocalizedMessage());
504: } finally {
505: requests.clear();
506: }
507: }
508:
509: private void addMessageRequest(String message) {
510: requests.add(new ArgumentRequest("-m")); //NOI18N
511: StringTokenizer token = new StringTokenizer(message, "\n",
512: false); //NOI18N
513: boolean first = true;
514: while (token.hasMoreTokens()) {
515: if (first) {
516: requests.add(new ArgumentRequest(token.nextToken()));
517: first = false;
518: } else {
519: requests.add(new ArgumentxRequest(token.nextToken()));
520: }
521: }
522: }
523:
524: /**
525: * This method returns how the command would look like when typed on the
526: * command line.
527: * Each command is responsible for constructing this information.
528: * @returns <command's name> [<parameters>] files/dirs. Example: checkout -p CvsCommand.java
529: */
530: public String getCVSCommand() {
531: StringBuffer toReturn = new StringBuffer("add "); //NOI18N
532: toReturn.append(getCVSArguments());
533: File[] files = getFiles();
534: if (files != null) {
535: for (int index = 0; index < files.length; index++) {
536: toReturn.append(files[index].getName());
537: toReturn.append(' ');
538: }
539: }
540: return toReturn.toString();
541: }
542:
543: /**
544: * Method that is called while the command is being executed.
545: * Descendants can override this method to return a Builder instance
546: * that will parse the server's output and create data structures.
547: */
548: public Builder createBuilder(EventManager eventManager) {
549: return new AddBuilder(eventManager, this );
550: }
551:
552: /**
553: * Takes the arguments and sets the command.
554: * To be mainly used for automatic settings (like parsing the .cvsrc file)
555: * @return true if the option (switch) was recognized and set
556: */
557: public boolean setCVSCommand(char opt, String optArg) {
558: if (opt == 'm') {
559: setMessage(optArg);
560: } else if (opt == 'k') {
561: KeywordSubstitutionOptions keywordSubst = KeywordSubstitutionOptions
562: .findKeywordSubstOption(optArg);
563: setKeywordSubst(keywordSubst);
564: } else {
565: return false;
566: }
567: return true;
568: }
569:
570: /**
571: * Returns a string indicating the available options.
572: */
573: public String getOptString() {
574: return "m:k:"; //NOI18N
575: }
576:
577: /**
578: * Listens for output of the command.
579: * If new directory is added, executes the createCvsFiles() method.
580: */
581: public void messageSent(MessageEvent e) {
582: String str = e.getMessage();
583: if (str.endsWith(DIR_ADDED)) {
584: str = str.substring(DIRECTORY.length(),
585: str.indexOf(DIR_ADDED)).trim();
586: createCvsFiles(str);
587: }
588: super .messageSent(e);
589: }
590:
591: /**
592: * For new directory that was added to the repository, creates the admin
593: * files in CVS subdir.
594: */
595: private void createCvsFiles(String newDirInRepository) {
596: String repository = newDirInRepository;
597: String dirName = repository;
598: if (dirName.lastIndexOf('/') >= 0) {
599: dirName = dirName.substring(dirName.lastIndexOf('/') + 1,
600: dirName.length());
601: }
602:
603: if (newDirList.size() == 0) {
604: System.err
605: .println("JavaCVS: Bug in AddCommand|createCvsFiles"); // NOI18N
606: System.err.println(" newDirInRepository = "
607: + newDirInRepository); // NOI18N
608: return;
609: }
610:
611: Paths paths = null;
612: for (Iterator i = newDirList.iterator(); i.hasNext();) {
613: paths = (Paths) i.next();
614: if (paths.getRepositoryPath().equals(newDirInRepository)) {
615: i.remove();
616: break;
617: }
618: }
619:
620: String local = paths.getPartPath();
621: String part = paths.getRepositoryPath();
622: repository = paths.getRepositoryPath();
623:
624: String tempDirName = part;
625: if (part.lastIndexOf('/') >= 0) {
626: tempDirName = part.substring(part.lastIndexOf('/') + 1,
627: part.length());
628: }
629:
630: if (!tempDirName.equalsIgnoreCase(dirName)) {
631: System.err
632: .println("JavaCVS: Bug in AddCommand|createCvsFiles"); // NOI18N
633: System.err.println(" newDirInRepository = "
634: + newDirInRepository); // NOI18N
635: System.err.println(" tempDirName = " + tempDirName); // NOI18N
636: System.err.println(" dirName = " + dirName); // NOI18N
637: return;
638: }
639:
640: try {
641: if (repository.startsWith(".")) { //NOI18N
642: repository = repository.substring(1);
643: }
644: clientServices.updateAdminData(local, repository, null);
645: createCvsTagFile(local, repository);
646: } catch (IOException ex) {
647: System.err
648: .println("TODO: couldn't create/update Cvs admin files"); // NOI18N
649: }
650: /*
651: Iterator it = newDirList.keySet().iterator();
652: while (it.hasNext())
653: {
654: String local = (String)it.next();
655: String part = (String)newDirList.get(local);
656: String tempDirName = part;
657: if (part.lastIndexOf('/') >= 0)
658: {
659: tempDirName = part.substring(part.lastIndexOf('/') + 1,
660: part.length());
661: }
662:
663: if (tempDirName.equalsIgnoreCase(dirName))
664: {
665: try
666: {
667: clientServices.updateAdminData(local, repository, null);
668: createCvsTagFile(local, repository);
669: it.remove(); // hack.. in case 2 dirs being added have the same name??
670: break;
671: }
672: catch (IOException exc)
673: {
674: System.out.println("TODO: couldn't create/update Cvs admin files");
675: }
676: }
677: }
678: */
679: }
680:
681: private void createCvsTagFile(String local, String repository)
682: throws IOException {
683: File current = new File(getLocalDirectory(), local);
684: File parent = current.getParentFile();
685: String tag = clientServices.getStickyTagForDirectory(parent);
686: if (tag != null) {
687: File tagFile = new File(current, "CVS/Tag"); // NOI18N
688: tagFile.createNewFile();
689: PrintWriter w = new PrintWriter(new BufferedWriter(
690: new FileWriter(tagFile)));
691: w.println(tag);
692: w.close();
693: }
694: }
695:
696: /**
697: * resets all switches in the command. After calling this method,
698: * the command should have no switches defined and should behave defaultly.
699: */
700: public void resetCVSCommand() {
701: setMessage(null);
702: setKeywordSubst(null);
703: }
704:
705: /**
706: * Returns the arguments of the command in the command-line style.
707: * Similar to getCVSCommand() however without the files and command's name
708: */
709: public String getCVSArguments() {
710: StringBuffer toReturn = new StringBuffer();
711: if (getMessage() != null) {
712: toReturn.append("-m \""); //NOI18N
713: toReturn.append(getMessage());
714: toReturn.append("\" "); //NOI18N
715: }
716: if (getKeywordSubst() != null) {
717: toReturn.append("-k"); //NOI18N
718: toReturn.append(getKeywordSubst().toString());
719: toReturn.append(" "); //NOI18N
720: }
721: return toReturn.toString();
722: }
723:
724: private static class Paths {
725: private final String partPath;
726: private final String repositoryPath;
727:
728: public Paths(String partPath, String repositoryPath) {
729: this .partPath = partPath;
730: this .repositoryPath = repositoryPath;
731: }
732:
733: public String getPartPath() {
734: return partPath;
735: }
736:
737: public String getRepositoryPath() {
738: return repositoryPath;
739: }
740: }
741: }
|